Library for the AMS CC811 digitial gas sensor

Dependencies:   AMS_ENS210_temp_humid_sensor

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers AMS_CCS811.cpp Source File

AMS_CCS811.cpp

00001 
00002 #include "AMS_CCS811.h"
00003 
00004 AMS_CCS811::AMS_CCS811(I2C * i2c, PinName n_wake_pin) : _n_wake_out(), _addr_out(), _int_data(), _ens210(), _i2c() {
00005     _n_wake_out = new (std::nothrow) DigitalOut(n_wake_pin, 1);
00006     _i2c = i2c; 
00007 }
00008             
00009 AMS_CCS811::AMS_CCS811(I2C * i2c, PinName n_wake_pin, I2C * ens210_i2c) : _n_wake_out(), _addr_out(), _int_data(), _ens210(), _i2c() {
00010     _n_wake_out = new (std::nothrow) DigitalOut(n_wake_pin, 1);
00011     _i2c = i2c;
00012     ens210_i2c_interface(ens210_i2c);
00013 }
00014               
00015 AMS_CCS811::~AMS_CCS811() {
00016     delete _n_wake_out;
00017     delete _addr_out;
00018     delete _int_data;
00019     delete _ens210;
00020 }
00021 
00022 bool AMS_CCS811::init() {
00023    
00024     bool success = false;
00025     
00026     
00027     _init_errors();
00028     _init_fractions();
00029     set_defaults();
00030     
00031     temp_reading = 0;
00032     humid_reading = 0;
00033     
00034     if (_n_wake_out) {
00035     
00036         int fw_mode = firmware_mode();
00037        
00038         if (fw_mode == 1) {  
00039             success = write_config();
00040             enable_ens210(true);
00041     
00042         } else if (fw_mode == 0) {  // is in boot mode, needs to be loaded into app mode
00043             if (boot_app_start())   // if succesfully writes to app_start, retry init
00044                 success = init();
00045         }
00046     }
00047 
00048     return success;
00049 }
00050 
00051 void AMS_CCS811::i2c_interface(I2C * i2c) {
00052     _i2c = i2c;
00053 }
00054         
00055 bool AMS_CCS811::ens210_i2c_interface(I2C * i2c) {
00056 
00057     bool success;
00058    
00059     if (_ens210 == NULL) {
00060         _ens210 = new (std::nothrow) AMS_ENS210(i2c, true, true);
00061         if (_ens210 != NULL) {
00062             if (_ens210->init()) {
00063                 success = _ens210->start();
00064             }
00065         } 
00066     } else {
00067         _ens210->i2c_interface(i2c);
00068         success = true;
00069     }
00070     
00071     if (!success) new_error(CCS811_LIB_ENS210_INIT_ID);
00072     
00073     return success;
00074 }
00075 
00076 bool AMS_CCS811::enable_ens210(bool enable) {
00077     
00078     _ens210_enabled = false;
00079     if (_ens210 != NULL) {
00080         if (_ens210->i2c_interface() != NULL) _ens210_enabled = enable;
00081     }
00082     update_ens210_timer();
00083     return _ens210_enabled;
00084 }
00085 
00086 bool AMS_CCS811::ens210_is_enabled() {
00087     enable_ens210(_ens210_enabled);     // Make sure the state is representive
00088     return _ens210_enabled;
00089 }
00090         
00091 void AMS_CCS811::ens210_poll_interval(int poll_ms) {
00092     _ens210_poll_split = poll_ms;
00093     enable_ens210(_ens210_enabled);     // makes sure the state is representive, and will also update the timer
00094 }
00095 
00096 int AMS_CCS811::ens210_poll_interval() {
00097     return _ens210_poll_split;
00098 }
00099 
00100 int AMS_CCS811::firmware_mode() {
00101     int firmware_result = -1;
00102     
00103     clear_errors();
00104     
00105     read_byte_result read_result = read_status();
00106     if (read_result.success) {
00107         firmware_result = (read_result.byte >> 7) & 1;
00108     }
00109     
00110     return firmware_result;
00111 }
00112 
00113 bool AMS_CCS811::mode(OP_MODES mode) {
00114     clear_errors();
00115     
00116     OP_MODES old = _mode;               // incase the write fails, to roll back
00117     _mode = mode;
00118     
00119     bool success = write_config();
00120     if (!success)
00121         _mode = old;
00122     
00123     return success;
00124 }
00125 
00126 AMS_CCS811::OP_MODES AMS_CCS811::mode() {
00127     clear_errors();
00128     
00129     OP_MODES result = INVALID;
00130     
00131     read_byte_result read_result = read_config();
00132     if (read_result.success) {
00133         int mode = (read_result.byte >> 4) & 0b111;
00134         result = mode > 4 ? INVALID : (OP_MODES)mode;
00135     }
00136     
00137     return result;
00138 }
00139 
00140 bool AMS_CCS811::addr_mode(bool high) { 
00141     _addr_dir = high;
00142     if (_addr_out != NULL) _addr_out->write(_addr_dir);
00143     
00144     update_slave_addr();
00145     
00146     return addr_mode() == high;
00147 }
00148 
00149 bool AMS_CCS811::addr_mode() {
00150     _addr_dir = false;
00151     if (_addr_out != NULL) {
00152         _addr_dir = _addr_out->read(); 
00153     }
00154     
00155     return _addr_dir;
00156 }
00157 
00158 bool AMS_CCS811::addr_pin(PinName pin) {
00159     _addr_out = _addr_out == NULL ? new (std::nothrow) DigitalOut(pin) : new (_addr_out) DigitalOut(pin);
00160     addr_mode(_addr_dir);
00161     
00162     return _addr_out != NULL;
00163 }
00164 
00165 bool AMS_CCS811::n_wake_pin(PinName pin) {
00166     _n_wake_out = _n_wake_out == NULL ? new (std::nothrow) DigitalOut(pin) : new (_n_wake_out) DigitalOut(pin);
00167     return _n_wake_out != NULL;
00168 }
00169 
00170 bool AMS_CCS811::env_data(float humid, float temp) {
00171     char bytes[4];
00172     if (humid > CCS811_MAX_HUMID) humid = CCS811_MAX_HUMID;
00173     if (humid < 0) humid = 0;
00174         
00175     temp += 25;
00176     if (temp > CCS811_MAX_TEMP) humid = CCS811_MAX_TEMP;
00177     if (temp < 0) temp = 0;
00178     
00179     float_to_short(humid, bytes);
00180     float_to_short(temp, bytes+2);
00181     
00182     return i2c_write(ENV_DATA, bytes, 4) == 4;
00183 }
00184 
00185 
00186 int AMS_CCS811::has_new_data() {
00187 
00188     clear_errors();
00189     
00190     int result = -1;
00191     
00192     char meas_mode[1];
00193     if(i2c_read(MEAS_MODE, meas_mode, 1) == 1) {                                // one read here is quicker than calling read_config() twice
00194     
00195         int curr_mode = (meas_mode[0] >> 4) & 0b111;
00196         if (curr_mode < 5) {
00197             if (curr_mode > 0) {                                                // check for all valid modes other than idle
00198                 if (((meas_mode[0] >> 3) & 1) == 0) {                           // check if interrupts are disabled
00199                     char status[1];                     
00200                     if (i2c_read(STATUS, status, 1) == 1)                       // for some reason the status register in ALG_RESULT_DATA is not updated after reading data, however the STATUS register is
00201                         result = (status[0] >> 3) & 1;
00202                 
00203                 } else result = 1;
00204                 
00205                 if (result == 1) 
00206                     if (i2c_read(ALG_RESULT_DATA, _alg_result_data, 8) != 8) result = -1;
00207                 
00208   
00209             } else result = 0;                                                  // return 0 when in idle
00210         } else new_error(CCS811_LIB_INV_MODE_ID);
00211     }
00212     
00213     return result;
00214 }
00215 
00216 uint16_t AMS_CCS811::co2_read() {
00217     return 0 | (_alg_result_data[0] << 8) | _alg_result_data[1];
00218 }
00219 
00220 uint16_t AMS_CCS811::tvoc_read() {
00221     return 0 | (_alg_result_data[2] << 8) | _alg_result_data[3];
00222 }
00223 
00224 uint16_t AMS_CCS811::raw_read() {
00225     return 0 | (_alg_result_data[6] << 8) | _alg_result_data[7];
00226 }
00227 
00228 float AMS_CCS811::temp_read() {
00229     return temp_reading;
00230 }
00231 
00232 float AMS_CCS811::humid_read() {
00233     return humid_reading;
00234 }
00235 
00236 bool AMS_CCS811::error_status() {
00237     bool result = false;
00238     
00239     read_byte_result read_result = read_status();
00240     if (read_result.success) {
00241         result = read_result.byte & 1;
00242     }
00243     
00244     result = result || (_error_count > 0);
00245     
00246     return result;
00247 }
00248       
00249 AMS_CCS811::ccs811_errors AMS_CCS811::errors() {
00250     ccs811_errors error_result;
00251     
00252     char byte[1];
00253     if (i2c_read(ERROR_ID, byte, 1) == 1) {
00254         for(int i = 0; i < CCS811_ERR_NUM; i++) {
00255             if ((byte[0] << i) & 1) {
00256                 error_result.codes[error_result.count++] = i;
00257             }
00258         }
00259     }
00260     for(int i = 0; i < CCS811_LIB_ERR_NUM; i++) {
00261         if (_errors[i]) {
00262             error_result.codes[error_result.count++] = i + CCS811_ERR_NUM;
00263         }
00264     }
00265     
00266     return error_result;
00267     
00268 }
00269 
00270 const char * AMS_CCS811::error_string(int err_code){
00271     static char result[255];
00272     result[0] = 0;
00273     if (err_code < CCS811_TOTAL_ERR_NUM && err_code > -1) 
00274         strcpy(result, _error_strings[err_code]);
00275     else 
00276         sprintf(result, "Invalid Code: %d is out of range (0 - %d)", err_code, CCS811_TOTAL_ERR_NUM-1);
00277     
00278     return result;
00279 }
00280 
00281 bool AMS_CCS811::enable_interupt(bool enable) {
00282     bool old = _int_data_enabled;   // incase the write fails, to roll back
00283     _int_data_enabled = enable;
00284     
00285     bool success = write_config();
00286     if (!success)
00287         _int_data_enabled = old;
00288     
00289     return success;
00290 
00291 }
00292 
00293 int AMS_CCS811::interupt_enabled() {
00294     int enabled = -1;
00295     
00296     read_byte_result read_result = read_config();
00297     if (read_result.success) {
00298         enabled = (read_result.byte >> 3) & 1;
00299     }
00300     
00301     return enabled;
00302 }
00303 
00304 bool AMS_CCS811::interrupt_pin(PinName pin) {
00305     bool success = false;
00306     
00307     _int_data = _int_data == NULL ? new (std::nothrow) InterruptIn(pin) : new (_int_data) InterruptIn(pin);
00308     if (_int_data != NULL) {
00309         _int_data->fall(callback(this, &AMS_CCS811::_isr_data));
00310         success = true;
00311     }
00312     
00313     return success;
00314 }
00315 
00316 
00317 
00318 
00319 /** Private **/
00320 
00321 void AMS_CCS811::set_defaults() {
00322     if (_mode == NULL) 
00323         _mode = CONFIG_OP_MODE;
00324     if (_addr_dir == NULL)
00325         _addr_dir = CONFIG_ADDR_DIR;
00326     if (_int_data_enabled == NULL)
00327         _int_data_enabled = CONFIG_INTR;
00328     if (_ens210_poll_split == NULL)
00329         _ens210_poll_split = CONFIG_ENS210_POLL;
00330         
00331     update_slave_addr();
00332 }
00333 
00334 void AMS_CCS811::_init_errors() {
00335     clear_errors();
00336     /* Sensor errors */
00337     strcpy(_error_strings[0], CCS811_WRITE_REG_INVALID);
00338     strcpy(_error_strings[1], CCS811_READ_REG_INVALID);
00339     strcpy(_error_strings[2], CCS811_MEASMODE_INVALID);
00340     strcpy(_error_strings[3], CCS811_MAX_RESISTANCE);
00341     strcpy(_error_strings[4], CCS811_HEATER_FAULT);
00342     strcpy(_error_strings[5], CCS811_HEATER_SUPPLY);
00343     strcpy(_error_strings[6], CCS811_RESERVED);
00344     strcpy(_error_strings[7], CCS811_RESERVED);
00345     /* Library errors */
00346     strcpy(_error_strings[CCS811_LIB_N_WAKE_ID+CCS811_ERR_NUM], CCS811_LIB_N_WAKE);
00347     strcpy(_error_strings[CCS811_LIB_I2C_ID+CCS811_ERR_NUM], CCS811_LIB_I2C);
00348     strcpy(_error_strings[CCS811_LIB_SLAVE_W_ID+CCS811_ERR_NUM], CCS811_LIB_SLAVE_W);
00349     strcpy(_error_strings[CCS811_LIB_REG_ADDR_ID+CCS811_ERR_NUM], CCS811_LIB_REG_ADDR);
00350     strcpy(_error_strings[CCS811_LIB_I2CWRITE_ID+CCS811_ERR_NUM], CCS811_LIB_I2CWRITE);
00351     strcpy(_error_strings[CCS811_LIB_SLAVE_R_ID+CCS811_ERR_NUM], CCS811_LIB_SLAVE_R);
00352     strcpy(_error_strings[CCS811_LIB_INV_MODE_ID+CCS811_ERR_NUM], CCS811_LIB_INV_MODE);
00353     strcpy(_error_strings[CCS811_LIB_ENS210_INIT_ID+CCS811_ERR_NUM], CCS811_LIB_ENS210_INIT);
00354 }
00355 
00356 void AMS_CCS811::clear_errors() {
00357     _error_count = 0;
00358     for (int i = 0; i < CCS811_LIB_ERR_NUM; i++) {
00359         _errors[i] = false;
00360     }
00361 }
00362 
00363 void AMS_CCS811::new_error(int error_id) {
00364     if (!_errors[error_id]) {
00365         _errors[error_id] = true;
00366         _error_count++;
00367     }
00368 }
00369 
00370 void AMS_CCS811::update_ens210_timer() {
00371     _ens210_poll_t.detach();
00372     if (_ens210_enabled)
00373         _ens210_poll_t.attach_us(callback(this, &AMS_CCS811::ens210_isr), _ens210_poll_split*1000);
00374 }
00375 
00376 void AMS_CCS811::ens210_isr() {
00377     temp_reading = ((float)_ens210->temp_read() / 64) - - 273.15;
00378     humid_reading = (float)_ens210->humid_read()/512;
00379     env_data(humid_reading, temp_reading);
00380 }
00381 
00382 void AMS_CCS811::_init_fractions() {
00383     
00384     fractions[0] = 0.5;
00385     fractions[1] = 0.25;
00386     fractions[2] = 0.125;
00387     fractions[3] = 0.0625;
00388     fractions[4] = 0.03125;
00389     fractions[5] = 0.015625;
00390     fractions[6] = 0.0078125;
00391     fractions[7] = 0.00390625;
00392     fractions[8] = 0.001953125;
00393     
00394 }
00395 
00396 void AMS_CCS811::float_to_short(float in, char * output) {
00397 
00398     uint8_t int_part = (uint8_t)in;
00399     float dec_part = in - int_part;
00400 
00401     uint16_t _short = 0;
00402     for (int i = 0; i < 9; i++) {
00403         if (dec_part == 0) break;  
00404         if (dec_part >= fractions[i]) {
00405             dec_part -= fractions[i];
00406             _short |= 256 >> i;
00407         }
00408     }
00409     
00410     _short |= int_part << 9;
00411     
00412     output[0] = _short >> 8;
00413     output[1] = _short;
00414 }
00415 
00416 void AMS_CCS811::update_slave_addr() {
00417     _slave_addr = addr_mode() ? CCS811_SLAVE_ADDR_RAW_H : CCS811_SLAVE_ADDR_RAW_L;
00418 }
00419         
00420 void AMS_CCS811::_isr_data() {
00421     has_new_data();             // populate the data array
00422     _isr_data_fp.call();
00423 }
00424 
00425 bool AMS_CCS811::write_config() {
00426     char cmd[1] = {0 | (_int_data_enabled << 3) | (_mode << 4)};
00427     return i2c_write(MEAS_MODE, cmd, 1) == 1;
00428 }
00429 
00430 AMS_CCS811::read_byte_result AMS_CCS811::read_config() {
00431     read_byte_result result;
00432     char byte[1];
00433     if (i2c_read(MEAS_MODE, byte, 1) == 1) {
00434         result.success = true;
00435         result.byte = byte[0];
00436     }
00437     return result;
00438 }
00439 
00440 AMS_CCS811::read_byte_result AMS_CCS811::read_status() {
00441     read_byte_result result;
00442     char byte[1];
00443     if (i2c_read(STATUS, byte, 1) == 1) {
00444         result.success = true;
00445         result.byte = byte[0];
00446     }
00447     
00448     return result;
00449 }
00450 
00451 bool AMS_CCS811::boot_app_start() {
00452     bool success = false;
00453     
00454     if (i2c_write(APP_START, NULL, 0) == 0) {
00455         wait_ms(70);
00456         success = true;
00457     }
00458     
00459     return success;
00460 }
00461 
00462 int AMS_CCS811::i2c_read(char reg_addr, char* output, int len) {
00463 
00464     int read_count = 0;
00465     if (_n_wake_out != NULL) {                                          // check nWAKE pin is set 
00466         _n_wake_out->write(0);                                          // Hold low 
00467         wait_us(CCS811_T_AWAKE);                                        // tAWAKE time to allow sensor I2C to wake up
00468         if (_i2c != NULL) {                                             // check I2C interface is set
00469             _i2c->start();                                              // send start condition for write
00470             if(_i2c->write(CCS811_SLAVE_ADDR_W) == 1) {                 // write slave address with write bit
00471                 if(_i2c->write(reg_addr) == 1) {                        // write register address
00472                     _i2c->start();                                      // send another start condition for read
00473                     if(_i2c->write(CCS811_SLAVE_ADDR_R) == 1) {         // write slave address with read bit
00474                         for (int i = 0; i < len; i++) {                 // read len bytes
00475                             output[i] = _i2c->read(i < len-1 ? 1 : 0);  // ack all reads aside from the final one (i == len-1)
00476                             read_count++;
00477                         }
00478                     } else new_error(CCS811_LIB_SLAVE_R_ID);
00479                 } else new_error(CCS811_LIB_REG_ADDR_ID);
00480             } else new_error(CCS811_LIB_SLAVE_W_ID);
00481             _i2c->stop();                                               // send stop condition 
00482         } else new_error(CCS811_LIB_I2C_ID);
00483         _n_wake_out->write(1);                                          // Set back to high
00484         wait_us(CCS811_T_DWAKE);                                        // tDWAKE time to allow sensor I2C to sleep
00485     } else new_error(CCS811_LIB_N_WAKE_ID);
00486     
00487     return read_count;
00488 }
00489 
00490 int AMS_CCS811::i2c_write(char reg_addr, char* input, int len) {
00491     
00492     int write_count = -1;
00493     if (_n_wake_out != NULL) {                                          // check nWAKE pin is set
00494         _n_wake_out->write(0);                                          // Hold low
00495         wait_us(CCS811_T_AWAKE);                                        // tAWAKE time to allow sensor I2C to wake up
00496         if (_i2c != NULL) {                                             // check I2C interface is set
00497             _i2c->start();                                              // send start condition for write
00498             if(_i2c->write(CCS811_SLAVE_ADDR_W) == 1) {                 // write slave address
00499                 if(_i2c->write(reg_addr) == 1) {                        // write register address
00500                     write_count = 0;
00501                     for (int i = 0; i < len; i++) {                     // write len bytes
00502                         if(_i2c->write(input[i]) == 1) write_count++;   // write each byte, if successful increment count
00503                         else new_error(CCS811_LIB_I2CWRITE_ID);
00504                     }
00505                 } else new_error(CCS811_LIB_REG_ADDR_ID);
00506             } else new_error(CCS811_LIB_SLAVE_W_ID);
00507             _i2c->stop();                                               // send stop condition 
00508         } else new_error(CCS811_LIB_I2C_ID);
00509         _n_wake_out->write(1);                                          // set back to high
00510         wait_us(CCS811_T_DWAKE);                                        // tDWAKE time to allow sensor I2C to sleep
00511     }else new_error(CCS811_LIB_N_WAKE_ID);
00512     
00513     return write_count;
00514 }