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:
UHSLMarcus
Date:
2017-01-24
Revision:
13:a74306788df7
Parent:
12:45065badca0f
Child:
14:0e8f5bf68b50

File content as of revision 13:a74306788df7:


#include "AMS_CCS811.h"

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

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