simple CCS811 driver

Dependencies:   AMS_ENS210_temp_humid_sensor

Dependents:   TBSense2_Sensor_Demo

Fork of AMS_CCS811_gas_sensor by Marcus Lee

AMS_CCS811.cpp

Committer:
Steven Cooreman
Date:
10 months ago
Revision:
15:cf658680c53f
Parent:
14:0e8f5bf68b50

File content as of revision 15:cf658680c53f:


#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;
}

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;
    delete _int_data;
    delete _ens210;
}

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) {
            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();
        }
    }

    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;
    }
    update_ens210_timer();
    return _ens210_enabled;
}

bool AMS_CCS811::ens210_is_enabled() {
    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
}

int AMS_CCS811::ens210_poll_interval() {
    return _ens210_poll_split;
}

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) {
    _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();
    }

    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;
}

bool AMS_CCS811::n_wake_pin(PinName pin) {
    _n_wake_out = _n_wake_out == NULL ? new (std::nothrow) DigitalOut(pin) : new (_n_wake_out) DigitalOut(pin);
    return _n_wake_out != NULL;
}

bool AMS_CCS811::env_data(float humid, float temp) {
    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;
}


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];
                    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 (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;
}

uint16_t AMS_CCS811::co2_read() {
    return 0 | (_alg_result_data[0] << 8) | _alg_result_data[1];
}

uint16_t AMS_CCS811::tvoc_read() {
    return 0 | (_alg_result_data[2] << 8) | _alg_result_data[3];
}

uint16_t AMS_CCS811::raw_read() {
    return 0 | (_alg_result_data[6] << 8) | _alg_result_data[7];
}

float AMS_CCS811::temp_read() {
    return temp_reading;
}

float AMS_CCS811::humid_read() {
    return humid_reading;
}

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++) {
            if ((byte[0] << i) & 1) {
                error_result.codes[error_result.count++] = i;
            }
        }
    }
    for(int i = 0; i < CCS811_LIB_ERR_NUM; i++) {
        if (_errors[i]) {
            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)
        strcpy(result, _error_strings[err_code]);
    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;
}




/** Private **/

void AMS_CCS811::set_defaults() {
    if (_mode == NULL)
        _mode = CONFIG_OP_MODE;
    if (_addr_dir == NULL)
        _addr_dir = CONFIG_ADDR_DIR;
    if (_int_data_enabled == NULL)
        _int_data_enabled = CONFIG_INTR;
    if (_ens210_poll_split == NULL)
        _ens210_poll_split = CONFIG_ENS210_POLL;

    update_slave_addr();
}

void AMS_CCS811::_init_errors() {
    clear_errors();
    /* Sensor errors */
    strcpy(_error_strings[0], CCS811_WRITE_REG_INVALID);
    strcpy(_error_strings[1], CCS811_READ_REG_INVALID);
    strcpy(_error_strings[2], CCS811_MEASMODE_INVALID);
    strcpy(_error_strings[3], CCS811_MAX_RESISTANCE);
    strcpy(_error_strings[4], CCS811_HEATER_FAULT);
    strcpy(_error_strings[5], CCS811_HEATER_SUPPLY);
    strcpy(_error_strings[6], CCS811_RESERVED);
    strcpy(_error_strings[7], CCS811_RESERVED);
    /* Library errors */
    strcpy(_error_strings[CCS811_LIB_N_WAKE_ID+CCS811_ERR_NUM], CCS811_LIB_N_WAKE);
    strcpy(_error_strings[CCS811_LIB_I2C_ID+CCS811_ERR_NUM], CCS811_LIB_I2C);
    strcpy(_error_strings[CCS811_LIB_SLAVE_W_ID+CCS811_ERR_NUM], CCS811_LIB_SLAVE_W);
    strcpy(_error_strings[CCS811_LIB_REG_ADDR_ID+CCS811_ERR_NUM], CCS811_LIB_REG_ADDR);
    strcpy(_error_strings[CCS811_LIB_I2CWRITE_ID+CCS811_ERR_NUM], CCS811_LIB_I2CWRITE);
    strcpy(_error_strings[CCS811_LIB_SLAVE_R_ID+CCS811_ERR_NUM], CCS811_LIB_SLAVE_R);
    strcpy(_error_strings[CCS811_LIB_INV_MODE_ID+CCS811_ERR_NUM], CCS811_LIB_INV_MODE);
    strcpy(_error_strings[CCS811_LIB_ENS210_INIT_ID+CCS811_ERR_NUM], CCS811_LIB_ENS210_INIT);
}

void AMS_CCS811::clear_errors() {
    _error_count = 0;
    for (int i = 0; i < CCS811_LIB_ERR_NUM; i++) {
        _errors[i] = false;
    }
}

void AMS_CCS811::new_error(int error_id) {
    if (!_errors[error_id]) {
        _errors[error_id] = true;
        _error_count++;
    }
}

void AMS_CCS811::update_ens210_timer() {
    _ens210_poll_t.detach();
    if (_ens210_enabled)
        _ens210_poll_t.attach_us(callback(this, &AMS_CCS811::ens210_isr), _ens210_poll_split*1000);
}

void AMS_CCS811::ens210_isr() {
    temp_reading = ((float)_ens210->temp_read() / 64) - - 273.15;
    humid_reading = (float)_ens210->humid_read()/512;
    env_data(humid_reading, temp_reading);
}

void AMS_CCS811::_init_fractions() {

    fractions[0] = 0.5;
    fractions[1] = 0.25;
    fractions[2] = 0.125;
    fractions[3] = 0.0625;
    fractions[4] = 0.03125;
    fractions[5] = 0.015625;
    fractions[6] = 0.0078125;
    fractions[7] = 0.00390625;
    fractions[8] = 0.001953125;

}

void AMS_CCS811::float_to_short(float in, char * output) {

    uint8_t int_part = (uint8_t)in;
    float dec_part = in - int_part;

    uint16_t _short = 0;
    for (int i = 0; i < 9; i++) {
        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;
}

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();
}

bool AMS_CCS811::write_config() {
    char cmd[1] = {0 | (_int_data_enabled ? 1 << 3 : 0) | (_mode << 4)};
    return i2c_write(MEAS_MODE, cmd, 1) == 1;
}

AMS_CCS811::read_byte_result AMS_CCS811::read_config() {
    read_byte_result result;
    char byte[1];
    if (i2c_read(MEAS_MODE, byte, 1) == 1) {
        result.success = true;
        result.byte = byte[0];
    }
    return result;
}

AMS_CCS811::read_byte_result AMS_CCS811::read_status() {
    read_byte_result result;
    char byte[1];
    if (i2c_read(STATUS, byte, 1) == 1) {
        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
        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
            if(_i2c->write(CCS811_SLAVE_ADDR_W) == 1) {                 // write slave address with write bit
                if(_i2c->write(reg_addr) == 1) {                        // write register address
                    _i2c->start();                                      // send another start condition for read
                    if(_i2c->write(CCS811_SLAVE_ADDR_R) == 1) {         // write slave address with read bit
                        for (int i = 0; i < len; i++) {                 // read len bytes
                            output[i] = _i2c->read(i < len-1 ? 1 : 0);  // ack all reads aside from the final one (i == len-1)
                            read_count++;
                        }
                    } 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
        } 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
        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
            if(_i2c->write(CCS811_SLAVE_ADDR_W) == 1) {                 // write slave address
                if(_i2c->write(reg_addr) == 1) {                        // write register address
                    write_count = 0;
                    for (int i = 0; i < len; i++) {                     // write len bytes
                        if(_i2c->write(input[i]) == 1) write_count++;   // write each byte, if successful increment 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();                                               // 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;
}