simple CCS811 driver

Dependencies:   AMS_ENS210_temp_humid_sensor

Dependents:   TBSense2_Sensor_Demo

Fork of AMS_CCS811_gas_sensor by Marcus Lee

Revision:
15:cf658680c53f
Parent:
14:0e8f5bf68b50
--- a/AMS_CCS811.cpp	Thu May 04 13:08:54 2017 +0000
+++ b/AMS_CCS811.cpp	Tue Mar 05 20:18:27 2019 +0100
@@ -1,17 +1,21 @@
 
 #include "AMS_CCS811.h"
+#include "AMS_CCS811_fw_2_0_0.h"
+#include "mbed_trace.h"
+
+#define TRACE_GROUP "CCS"
 
 AMS_CCS811::AMS_CCS811(I2C * i2c, PinName n_wake_pin) : _n_wake_out(), _addr_out(), _int_data(), _ens210(), _i2c() {
     _n_wake_out = new (std::nothrow) DigitalOut(n_wake_pin, 1);
-    _i2c = i2c; 
+    _i2c = i2c;
 }
-            
+
 AMS_CCS811::AMS_CCS811(I2C * i2c, PinName n_wake_pin, I2C * ens210_i2c) : _n_wake_out(), _addr_out(), _int_data(), _ens210(), _i2c() {
     _n_wake_out = new (std::nothrow) DigitalOut(n_wake_pin, 1);
     _i2c = i2c;
     ens210_i2c_interface(ens210_i2c);
 }
-              
+
 AMS_CCS811::~AMS_CCS811() {
     delete _n_wake_out;
     delete _addr_out;
@@ -20,25 +24,54 @@
 }
 
 bool AMS_CCS811::init() {
-   
+
     bool success = false;
-    
-    
+
     _init_errors();
     _init_fractions();
     set_defaults();
-    
+
     temp_reading = 0;
     humid_reading = 0;
-    
+
     if (_n_wake_out) {
-    
+        char buffer[2];
+
+        if(i2c_read(HW_ID, buffer, 1) == 1) {
+            if(buffer[0] != 0x81) {
+                // not a CCS811
+                tr_err("Not a CCS: %02x", buffer[0]);
+                return false;
+            }
+        } else {
+            return false;
+        }
+
+        if(i2c_read(STATUS, buffer, 1) == 1) {
+            if((buffer[0] & 0x10) == 0) {
+                // does not have valid FW
+                flash_firmware();
+            }
+        } else {
+            return false;
+        }
+
+        if(i2c_read(FW_APP_VERSION, buffer, 2) == 2) {
+            tr_warn("CCS version %02x %02x", buffer[0], buffer[1]);
+            if (buffer[0] < CCS_FW_UPGRADE_VERSION) {
+                tr_warn("Flashing Firmware");
+                flash_firmware();
+            }
+        } else {
+            return false;
+        }
+
         int fw_mode = firmware_mode();
-       
-        if (fw_mode == 1) {  
+
+        if (fw_mode == 1) {
             success = write_config();
             enable_ens210(true);
-    
+
         } else if (fw_mode == 0) {  // is in boot mode, needs to be loaded into app mode
             if (boot_app_start())   // if succesfully writes to app_start, retry init
                 success = init();
@@ -48,33 +81,163 @@
     return success;
 }
 
+bool AMS_CCS811::flash_firmware() {
+    // kick into bootloader mode
+    int fw_mode = firmware_mode();
+    if (fw_mode == 1) {
+        const char reset_sequence[] = {0x11, 0xE5, 0x72, 0x8A};
+        if(i2c_write(0xFF, (char*)reset_sequence, 4) == 4) {
+            fw_mode = firmware_mode();
+            if(fw_mode == 1) {
+                tr_err("Couldn't exit app mode");
+                return false;
+            }
+        } else {
+            tr_err("Couldn't issue reset command");
+            return false;
+        }
+    }
+
+    // assert nwake
+    if (_n_wake_out != NULL) {
+        int write_count;
+
+        _n_wake_out->write(0);
+        wait_ms(100);
+
+        // erase current FW
+        static const char erase_sequence[4] = {0xE7, 0xA7, 0xE6, 0x09};
+        write_count = 0;
+        _i2c->start();
+        if(_i2c->write(CCS811_SLAVE_ADDR_W) == 1) {
+            if(_i2c->write(FW_ERASE) == 1) {
+                for (size_t i = 0; i < 4; i++) {
+                    if(_i2c->write(erase_sequence[i]) == 1) write_count++;
+                    else new_error(CCS811_LIB_I2CWRITE_ID);
+                }
+            } else new_error(CCS811_LIB_REG_ADDR_ID);
+        } else new_error(CCS811_LIB_SLAVE_W_ID);
+        _i2c->stop();
+
+        if (write_count != 4) {
+            _n_wake_out->write(1);
+            return false;
+        }
+        tr_info("CCS FW erased");
+        wait_ms(500);
+
+        // upload new FW
+        tr_info("uploading new FW");
+        char payload[8];
+        for(size_t offset = 0; offset < 5120; offset += 8) {
+            for(size_t j = 0; j < 8; j++) {
+                payload[j] = ams_fw_image[offset + j];
+            }
+
+            write_count = 0;
+            _i2c->start();
+            if(_i2c->write(CCS811_SLAVE_ADDR_W) == 1) {
+                if(_i2c->write(FW_FLASH) == 1) {
+                    for (size_t j = 0; j < 8; j++) {
+                        if(_i2c->write(payload[j]) == 1) write_count++;
+                        else new_error(CCS811_LIB_I2CWRITE_ID);
+                    }
+                } else new_error(CCS811_LIB_REG_ADDR_ID);
+            } else new_error(CCS811_LIB_SLAVE_W_ID);
+            _i2c->stop();
+
+            wait_ms(50);
+
+            if (write_count != 8) {
+                _n_wake_out->write(1);
+                tr_err("flash error");
+                return false;
+            }
+
+            tr_debug("Flashed byte %d of 5120", offset);
+        }
+        tr_info("CCS FW uploaded");
+
+        // verify new FW
+        write_count = 0;
+        _i2c->start();
+        if(_i2c->write(CCS811_SLAVE_ADDR_W) == 1) {
+            if(_i2c->write(FW_VERIFY) == 1) {
+                write_count = 1;
+            } else new_error(CCS811_LIB_REG_ADDR_ID);
+        } else new_error(CCS811_LIB_SLAVE_W_ID);
+        _i2c->stop();
+
+        wait_ms(500);
+
+        if (write_count != 1) {
+            _n_wake_out->write(1);
+            tr_err("Failed to issue verify");
+            return false;
+        }
+
+        char status = 0;
+        _i2c->start();
+        if(_i2c->write(CCS811_SLAVE_ADDR_W) == 1) {
+            if(_i2c->write(STATUS) == 1) {
+                _i2c->start();
+                if(_i2c->write(CCS811_SLAVE_ADDR_R) == 1) {
+                    status = _i2c->read(0);
+                } else new_error(CCS811_LIB_SLAVE_R_ID);
+            } else new_error(CCS811_LIB_REG_ADDR_ID);
+        } else new_error(CCS811_LIB_SLAVE_W_ID);
+        _i2c->stop();
+
+        if (status == 0) {
+            tr_err("Failed update");
+            _n_wake_out->write(1);
+            return false;
+        }
+
+        if ((status & 0x30) != 0x30) {
+            tr_err("Failed verify");
+            _n_wake_out->write(1);
+            return false;
+        }
+
+        // boot into new FW
+        tr_info("upgraded");
+        _n_wake_out->write(1);
+        wait_ms(50);
+        return boot_app_start();
+    } else {
+        tr_err("No nWAKE available");
+        return false;
+    }
+}
+
 void AMS_CCS811::i2c_interface(I2C * i2c) {
     _i2c = i2c;
 }
-        
+
 bool AMS_CCS811::ens210_i2c_interface(I2C * i2c) {
 
     bool success;
-   
+
     if (_ens210 == NULL) {
         _ens210 = new (std::nothrow) AMS_ENS210(i2c, true, true);
         if (_ens210 != NULL) {
             if (_ens210->init()) {
                 success = _ens210->start();
             }
-        } 
+        }
     } else {
         _ens210->i2c_interface(i2c);
         success = true;
     }
-    
+
     if (!success) new_error(CCS811_LIB_ENS210_INIT_ID);
-    
+
     return success;
 }
 
 bool AMS_CCS811::enable_ens210(bool enable) {
-    
+
     _ens210_enabled = false;
     if (_ens210 != NULL) {
         if (_ens210->i2c_interface() != NULL) _ens210_enabled = enable;
@@ -87,7 +250,7 @@
     enable_ens210(_ens210_enabled);     // Make sure the state is representive
     return _ens210_enabled;
 }
-        
+
 void AMS_CCS811::ens210_poll_interval(int poll_ms) {
     _ens210_poll_split = poll_ms;
     enable_ens210(_ens210_enabled);     // makes sure the state is representive, and will also update the timer
@@ -99,66 +262,66 @@
 
 int AMS_CCS811::firmware_mode() {
     int firmware_result = -1;
-    
+
     clear_errors();
-    
+
     read_byte_result read_result = read_status();
     if (read_result.success) {
         firmware_result = (read_result.byte >> 7) & 1;
     }
-    
+
     return firmware_result;
 }
 
 bool AMS_CCS811::mode(OP_MODES mode) {
     clear_errors();
-    
+
     OP_MODES old = _mode;               // incase the write fails, to roll back
     _mode = mode;
-    
+
     bool success = write_config();
     if (!success)
         _mode = old;
-    
+
     return success;
 }
 
 AMS_CCS811::OP_MODES AMS_CCS811::mode() {
     clear_errors();
-    
+
     OP_MODES result = INVALID;
-    
+
     read_byte_result read_result = read_config();
     if (read_result.success) {
         int mode = (read_result.byte >> 4) & 0b111;
         result = mode > 4 ? INVALID : (OP_MODES)mode;
     }
-    
+
     return result;
 }
 
-bool AMS_CCS811::addr_mode(bool high) { 
+bool AMS_CCS811::addr_mode(bool high) {
     _addr_dir = high;
     if (_addr_out != NULL) _addr_out->write(_addr_dir);
-    
+
     update_slave_addr();
-    
+
     return addr_mode() == high;
 }
 
 bool AMS_CCS811::addr_mode() {
     _addr_dir = false;
     if (_addr_out != NULL) {
-        _addr_dir = _addr_out->read(); 
+        _addr_dir = _addr_out->read();
     }
-    
+
     return _addr_dir;
 }
 
 bool AMS_CCS811::addr_pin(PinName pin) {
     _addr_out = _addr_out == NULL ? new (std::nothrow) DigitalOut(pin) : new (_addr_out) DigitalOut(pin);
     addr_mode(_addr_dir);
-    
+
     return _addr_out != NULL;
 }
 
@@ -171,14 +334,14 @@
     char bytes[4];
     if (humid > CCS811_MAX_HUMID) humid = CCS811_MAX_HUMID;
     if (humid < 0) humid = 0;
-        
+
     temp += 25;
     if (temp > CCS811_MAX_TEMP) humid = CCS811_MAX_TEMP;
     if (temp < 0) temp = 0;
-    
+
     float_to_short(humid, bytes);
     float_to_short(temp, bytes+2);
-    
+
     return i2c_write(ENV_DATA, bytes, 4) == 4;
 }
 
@@ -186,30 +349,30 @@
 int AMS_CCS811::has_new_data() {
 
     clear_errors();
-    
+
     int result = -1;
-    
+
     char meas_mode[1];
     if(i2c_read(MEAS_MODE, meas_mode, 1) == 1) {                                // one read here is quicker than calling read_config() twice
-    
+
         int curr_mode = (meas_mode[0] >> 4) & 0b111;
         if (curr_mode < 5) {
             if (curr_mode > 0) {                                                // check for all valid modes other than idle
                 if (((meas_mode[0] >> 3) & 1) == 0) {                           // check if interrupts are disabled
-                    char status[1];                     
+                    char status[1];
                     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
                         result = (status[0] >> 3) & 1;
-                
+
                 } else result = 1;
-                
-                if (result == 1) 
+
+                if (result == 1)
                     if (i2c_read(ALG_RESULT_DATA, _alg_result_data, 8) != 8) result = -1;
-                
-  
+
+
             } else result = 0;                                                  // return 0 when in idle
         } else new_error(CCS811_LIB_INV_MODE_ID);
     }
-    
+
     return result;
 }
 
@@ -235,20 +398,20 @@
 
 bool AMS_CCS811::error_status() {
     bool result = false;
-    
+
     read_byte_result read_result = read_status();
     if (read_result.success) {
         result = read_result.byte & 1;
     }
-    
+
     result = result || (_error_count > 0);
-    
+
     return result;
 }
-      
+
 AMS_CCS811::ccs811_errors AMS_CCS811::errors() {
     ccs811_errors error_result;
-    
+
     char byte[1];
     if (i2c_read(ERROR_ID, byte, 1) == 1) {
         for(int i = 0; i < CCS811_ERR_NUM; i++) {
@@ -262,54 +425,54 @@
             error_result.codes[error_result.count++] = i + CCS811_ERR_NUM;
         }
     }
-    
+
     return error_result;
-    
+
 }
 
 const char * AMS_CCS811::error_string(int err_code){
     static char result[255];
     result[0] = 0;
-    if (err_code < CCS811_TOTAL_ERR_NUM && err_code > -1) 
+    if (err_code < CCS811_TOTAL_ERR_NUM && err_code > -1)
         strcpy(result, _error_strings[err_code]);
-    else 
+    else
         sprintf(result, "Invalid Code: %d is out of range (0 - %d)", err_code, CCS811_TOTAL_ERR_NUM-1);
-    
+
     return result;
 }
 
 bool AMS_CCS811::enable_interupt(bool enable) {
     bool old = _int_data_enabled;   // incase the write fails, to roll back
     _int_data_enabled = enable;
-    
+
     bool success = write_config();
     if (!success)
         _int_data_enabled = old;
-    
+
     return success;
 
 }
 
 int AMS_CCS811::interupt_enabled() {
     int enabled = -1;
-    
+
     read_byte_result read_result = read_config();
     if (read_result.success) {
         enabled = (read_result.byte >> 3) & 1;
     }
-    
+
     return enabled;
 }
 
 bool AMS_CCS811::interrupt_pin(PinName pin) {
     bool success = false;
-    
+
     _int_data = _int_data == NULL ? new (std::nothrow) InterruptIn(pin) : new (_int_data) InterruptIn(pin);
     if (_int_data != NULL) {
         _int_data->fall(callback(this, &AMS_CCS811::_isr_data));
         success = true;
     }
-    
+
     return success;
 }
 
@@ -319,7 +482,7 @@
 /** Private **/
 
 void AMS_CCS811::set_defaults() {
-    if (_mode == NULL) 
+    if (_mode == NULL)
         _mode = CONFIG_OP_MODE;
     if (_addr_dir == NULL)
         _addr_dir = CONFIG_ADDR_DIR;
@@ -327,7 +490,7 @@
         _int_data_enabled = CONFIG_INTR;
     if (_ens210_poll_split == NULL)
         _ens210_poll_split = CONFIG_ENS210_POLL;
-        
+
     update_slave_addr();
 }
 
@@ -380,7 +543,7 @@
 }
 
 void AMS_CCS811::_init_fractions() {
-    
+
     fractions[0] = 0.5;
     fractions[1] = 0.25;
     fractions[2] = 0.125;
@@ -390,7 +553,7 @@
     fractions[6] = 0.0078125;
     fractions[7] = 0.00390625;
     fractions[8] = 0.001953125;
-    
+
 }
 
 void AMS_CCS811::float_to_short(float in, char * output) {
@@ -400,15 +563,15 @@
 
     uint16_t _short = 0;
     for (int i = 0; i < 9; i++) {
-        if (dec_part == 0) break;  
+        if (dec_part == 0) break;
         if (dec_part >= fractions[i]) {
             dec_part -= fractions[i];
             _short |= 256 >> i;
         }
     }
-    
+
     _short |= int_part << 9;
-    
+
     output[0] = _short >> 8;
     output[1] = _short;
 }
@@ -416,7 +579,7 @@
 void AMS_CCS811::update_slave_addr() {
     _slave_addr = addr_mode() ? CCS811_SLAVE_ADDR_RAW_H : CCS811_SLAVE_ADDR_RAW_L;
 }
-        
+
 void AMS_CCS811::_isr_data() {
     has_new_data();             // populate the data array
     _isr_data_fp.call();
@@ -444,26 +607,26 @@
         result.success = true;
         result.byte = byte[0];
     }
-    
+
     return result;
 }
 
 bool AMS_CCS811::boot_app_start() {
     bool success = false;
-    
+
     if (i2c_write(APP_START, NULL, 0) == 0) {
         wait_ms(70);
         success = true;
     }
-    
+
     return success;
 }
 
 int AMS_CCS811::i2c_read(char reg_addr, char* output, int len) {
 
     int read_count = 0;
-    if (_n_wake_out != NULL) {                                          // check nWAKE pin is set 
-        _n_wake_out->write(0);                                          // Hold low 
+    if (_n_wake_out != NULL) {                                          // check nWAKE pin is set
+        _n_wake_out->write(0);                                          // Hold low
         wait_us(CCS811_T_AWAKE);                                        // tAWAKE time to allow sensor I2C to wake up
         if (_i2c != NULL) {                                             // check I2C interface is set
             _i2c->start();                                              // send start condition for write
@@ -478,17 +641,17 @@
                     } else new_error(CCS811_LIB_SLAVE_R_ID);
                 } else new_error(CCS811_LIB_REG_ADDR_ID);
             } else new_error(CCS811_LIB_SLAVE_W_ID);
-            _i2c->stop();                                               // send stop condition 
+            _i2c->stop();                                               // send stop condition
         } else new_error(CCS811_LIB_I2C_ID);
         _n_wake_out->write(1);                                          // Set back to high
         wait_us(CCS811_T_DWAKE);                                        // tDWAKE time to allow sensor I2C to sleep
     } else new_error(CCS811_LIB_N_WAKE_ID);
-    
+
     return read_count;
 }
 
 int AMS_CCS811::i2c_write(char reg_addr, char* input, int len) {
-    
+
     int write_count = -1;
     if (_n_wake_out != NULL) {                                          // check nWAKE pin is set
         _n_wake_out->write(0);                                          // Hold low
@@ -504,11 +667,11 @@
                     }
                 } else new_error(CCS811_LIB_REG_ADDR_ID);
             } else new_error(CCS811_LIB_SLAVE_W_ID);
-            _i2c->stop();                                               // send stop condition 
+            _i2c->stop();                                               // send stop condition
         } else new_error(CCS811_LIB_I2C_ID);
         _n_wake_out->write(1);                                          // set back to high
         wait_us(CCS811_T_DWAKE);                                        // tDWAKE time to allow sensor I2C to sleep
     }else new_error(CCS811_LIB_N_WAKE_ID);
-    
+
     return write_count;
 }
\ No newline at end of file