diff --git a/include/mgos_ccs811.h b/include/mgos_ccs811.h new file mode 100644 index 0000000..092bf8a --- /dev/null +++ b/include/mgos_ccs811.h @@ -0,0 +1,102 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "mgos.h" +#include "mgos_i2c.h" + +#define MGOS_CCS811_READ_DELAY (2) + +#ifdef __cplusplus +extern "C" { + #endif + +struct mgos_ccs811; +struct mgos_ccs811_stats { + double last_read_time; // value of mg_time() upon last call to _read() + uint32_t read; // calls to _read() + uint32_t read_success; // successful _read() + uint32_t read_success_cached; // calls to _read() which were cached + // Note: read_errors := read - read_success - read_success_cached + double read_success_usecs; // time spent in successful uncached _read() +}; + +// Drive modes +enum mgos_ccs811_drive_mode_t { + CCS811_DRIVE_MODE_IDLE = 0x00, + CCS811_DRIVE_MODE_1SEC = 0x01, + CCS811_DRIVE_MODE_10SEC = 0x02, + CCS811_DRIVE_MODE_60SEC = 0x03, + CCS811_DRIVE_MODE_250MS = 0x04, +}; + +/* + * Initialize a CCS811 on the I2C bus `i2c` at address specified in `i2caddr` + * parameter (default CCS811 is on address 0x5A). The sensor will be polled for + * validity, upon success a new `struct mgos_ccs811` is allocated and + * returned. If the device could not be found, NULL is returned. + */ +struct mgos_ccs811 *mgos_ccs811_create(struct mgos_i2c *i2c, uint8_t i2caddr); + +/* + * Destroy the data structure associated with a CCS811 device. The reference + * to the pointer of the `struct mgos_ccs811` has to be provided, and upon + * successful destruction, its associated memory will be freed and the pointer + * set to NULL. + */ +void mgos_ccs811_destroy(struct mgos_ccs811 **sensor); + +/* + * The sensor will be polled for its temperature and humidity data. If the poll + * has occured in the last `MGOS_CCS811_READ_DELAY` seconds, the cached data is + * used (so as not to repeatedly poll the bus upon subsequent calls). + */ +bool mgos_ccs811_read(struct mgos_ccs811 *sensor); + +/* + * Set the drive mode of the CCS811 sensor based on the `mode` argument + * Returns true on success, false otherwise. + */ +bool mgos_ccs811_setDriveMode(struct mgos_ccs811 *sensor, enum mgos_ccs811_drive_mode_t mode); + +/* + * Retrieve the current drive mode (which will be one of `enum mgos_ccs811_drive_mode_t` + * values into the byte pointed to by `mode`. + * Returns true on success, false otherwise. + */ +bool mgos_ccs811_getDriveMode(struct mgos_ccs811 *sensor, uint8_t *mode); + +/* + * + * Returns a value on success, NAN otherwise. + */ +float mgos_ccs811_get_eco2(struct mgos_ccs811 *sensor); + +/* + * + * Returns a value on success, NAN otherwise. + */ +float mgos_ccs811_get_tvoc(struct mgos_ccs811 *sensor); + +/* + * Initialization function for MGOS -- currently a noop. + */ +bool mgos_ccs811_i2c_init(void); + + #ifdef __cplusplus +} +#endif diff --git a/src/main.c b/src/main.c index 6ba1bf8..33445a3 100644 --- a/src/main.c +++ b/src/main.c @@ -20,103 +20,124 @@ #include "mgos_si7021.h" #include "mgos_htu21df.h" #include "mgos_mcp9808.h" +#include "mgos_ccs811.h" #include #include -#define I2CBUSNR 5 +#define I2CBUSNR 5 void i2c_scanner(struct mgos_i2c *i2c) { int i; + if (!i2c) { LOG(LL_ERROR, ("No global I2C bus configured")); return; } - for(i=0x3; i<0x77; i++) { + for (i = 0x3; i < 0x77; i++) { bool ret; - ret=mgos_i2c_read(i2c, i, NULL, 0, true); - if (ret) - LOG(LL_INFO, ("I2C Address 0x%02x %s", i, ret?"true":"false")); + ret = mgos_i2c_read(i2c, i, NULL, 0, true); + if (ret) { + LOG(LL_INFO, ("I2C Address 0x%02x %s", i, ret ? "true" : "false")); + } } } - bool i2c_dumpregs(struct mgos_i2c *i2c, uint8_t i2caddr) { uint16_t reg; - int value; + int value; - for(reg=0; reg<256; reg++) { - value=mgos_i2c_read_reg_b(i2c, i2caddr, reg); - if (value<0) { + for (reg = 0; reg < 256; reg++) { + value = mgos_i2c_read_reg_b(i2c, i2caddr, reg); + if (value < 0) { printf(" XX"); - } - else { + }else { printf(" %02x", value); } - if (reg%16==15) printf("\n"); + if (reg % 16 == 15) { + printf("\n"); + } } printf("\n"); return true; } +bool do_ccs811(struct mgos_ccs811 *sensor) { + float eco2, tvoc; + + if (!sensor) { + return false; + } + + eco2 = mgos_ccs811_get_eco2(sensor); + tvoc = mgos_ccs811_get_tvoc(sensor); + LOG(LL_INFO, ("eCO2=%.0fppm TVOC=%.0fppb", eco2, tvoc)); + + return true; +} bool do_sht31(struct mgos_sht31 *sensor) { float temp, humid; - if (!sensor) return false; + if (!sensor) { + return false; + } - temp=mgos_sht31_getTemperature(sensor); - humid=mgos_sht31_getHumidity(sensor); + temp = mgos_sht31_getTemperature(sensor); + humid = mgos_sht31_getHumidity(sensor); LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid)); return true; } - bool do_si7021(struct mgos_si7021 *sensor) { float temp, humid; - if (!sensor) return false; + if (!sensor) { + return false; + } - temp=mgos_si7021_getTemperature(sensor); - humid=mgos_si7021_getHumidity(sensor); + temp = mgos_si7021_getTemperature(sensor); + humid = mgos_si7021_getHumidity(sensor); LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid)); return true; } - bool do_htu21df(struct mgos_htu21df *sensor) { float temp, humid; - if (!sensor) return false; + if (!sensor) { + return false; + } - temp=mgos_htu21df_getTemperature(sensor); - humid=mgos_htu21df_getHumidity(sensor); + temp = mgos_htu21df_getTemperature(sensor); + humid = mgos_htu21df_getHumidity(sensor); LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid)); return true; } - bool do_mcp9808(struct mgos_mcp9808 *sensor) { float temp; - if (!sensor) return false; + if (!sensor) { + return false; + } - temp=mgos_mcp9808_getTemperature(sensor); + temp = mgos_mcp9808_getTemperature(sensor); LOG(LL_INFO, ("temperature=%.2fC", temp)); return true; } - int main() { - struct mgos_i2c *i2c; - struct mgos_si7021 *si7021; - struct mgos_sht31 *sht31; + struct mgos_i2c * i2c; + struct mgos_si7021 * si7021; + struct mgos_sht31 * sht31; struct mgos_htu21df *htu21df; struct mgos_mcp9808 *mcp9808; + struct mgos_ccs811 * ccs811; if (!mgos_i2c_open(I2CBUSNR)) { LOG(LL_ERROR, ("Cannot open I2C bus %u", I2CBUSNR)); @@ -129,23 +150,32 @@ int main() { i2c_scanner(i2c); - if (!(sht31 = mgos_sht31_create(i2c, 0x44))) - LOG(LL_ERROR, ("Cannot create SHT31 device")); + /* + * if (!(sht31 = mgos_sht31_create(i2c, 0x44))) + * LOG(LL_ERROR, ("Cannot create SHT31 device")); + * + * if (!(si7021 = mgos_si7021_create(i2c, 0x40))) + * LOG(LL_ERROR, ("Cannot create SI7021 device")); + * + * if (!(htu21df = mgos_htu21df_create(i2c, 0x40))) + * LOG(LL_ERROR, ("Cannot create HTU21DF device")); + * + * if (!(mcp9808 = mgos_mcp9808_create(i2c, 0x18))) + * LOG(LL_ERROR, ("Cannot create MCP9808 device")); + */ - if (!(si7021 = mgos_si7021_create(i2c, 0x40))) - LOG(LL_ERROR, ("Cannot create SI7021 device")); - - if (!(htu21df = mgos_htu21df_create(i2c, 0x40))) - LOG(LL_ERROR, ("Cannot create HTU21DF device")); - - if (!(mcp9808 = mgos_mcp9808_create(i2c, 0x18))) - LOG(LL_ERROR, ("Cannot create MCP9808 device")); + if (!(ccs811 = mgos_ccs811_create(i2c, 0x5A))) { + LOG(LL_ERROR, ("Cannot create CCS811 device")); + } for (;;) { - do_sht31(sht31); - do_si7021(si7021); - do_htu21df(htu21df); - do_mcp9808(mcp9808); + /* + * do_sht31(sht31); + * do_si7021(si7021); + * do_htu21df(htu21df); + * do_mcp9808(mcp9808); + */ + do_ccs811(ccs811); sleep(5); } @@ -153,6 +183,7 @@ int main() { mgos_si7021_destroy(&si7021); mgos_htu21df_destroy(&htu21df); mgos_mcp9808_destroy(&mcp9808); + mgos_ccs811_destroy(&ccs811); return 0; } diff --git a/src/mgos_ccs811.c b/src/mgos_ccs811.c new file mode 100644 index 0000000..fa2fcfa --- /dev/null +++ b/src/mgos_ccs811.c @@ -0,0 +1,238 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mgos.h" +#include "mgos_ccs811_internal.h" +#include "mgos_i2c.h" + +// Datasheet: +// https://cdn-shop.adafruit.com/product-files/2857/Sensirion_Humidity_SHT3x_Datasheet_digital-767294.pdf + +// Private functions follow +static bool mgos_ccs811_getStatus(struct mgos_ccs811 *sensor, uint8_t *status) { + int ret; + + if (!sensor) { + return false; + } + ret = mgos_i2c_read_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_STATUS); + if (ret < 0) { + return false; + } + *status = (uint8_t)ret; + return true; +} + +static bool mgos_ccs811_getMeasMode(struct mgos_ccs811 *sensor, uint8_t *meas_mode) { + int ret; + + if (!sensor) { + return false; + } + ret = mgos_i2c_read_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_MEAS_MODE); + if (ret < 0) { + return false; + } + *meas_mode = (uint8_t)ret; + return true; +} + +// Return true if sensor status register has error-bit set (or upon read failure) + +/* + * static bool mgos_ccs811_error(struct mgos_ccs811 *sensor) { + * uint8_t status; + * + * if (!mgos_ccs811_getStatus(sensor, &status)) + * return true; + * // bits -- 7:FW_MODE 6:APP_ERASE 5:APP_VERIFY 4:APP_VALID 3:DATA_READY 0:ERROR + * return (status & MGOS_CCS811_STATUS_ERR); + * } + */ + +// Return true if sensor status register has data-ready set (false upon read failure) +static bool mgos_ccs811_dataready(struct mgos_ccs811 *sensor) { + uint8_t status; + + if (!mgos_ccs811_getStatus(sensor, &status)) { + return false; + } + return status & MGOS_CCS811_STATUS_DATA_READY; +} + +static bool mgos_ccs811_reset(struct mgos_ccs811 *sensor) { + uint8_t data[5] = { MGOS_CCS811_REG_SW_RESET, 0x11, 0xE5, 0x72, 0x8A }; + + if (!sensor) { + return false; + } + return mgos_i2c_write(sensor->i2c, sensor->i2caddr, data, 5, true); +} + +// Private functions end + +// Public functions follow +struct mgos_ccs811 *mgos_ccs811_create(struct mgos_i2c *i2c, uint8_t i2caddr) { + struct mgos_ccs811 *sensor; + int success = 0; + int ret; + + if (!i2c) { + return NULL; + } + + ret = mgos_i2c_read_reg_b(i2c, i2caddr, MGOS_CCS811_REG_HW_ID); + if (ret != MGOS_CCS811_HW_ID_CODE) { + LOG(LL_ERROR, ("Failed to detect CCS811 at I2C 0x%02x", i2caddr)); + return NULL; + } + + sensor = calloc(1, sizeof(struct mgos_ccs811)); + if (!sensor) { + return NULL; + } + + memset(sensor, 0, sizeof(struct mgos_ccs811)); + sensor->i2caddr = i2caddr; + sensor->i2c = i2c; + sensor->eco2 = 400; + + // Boot the application on CCS811. + mgos_ccs811_reset(sensor); + mgos_usleep(2000); + + uint8_t cmd = MGOS_CCS811_BOOTLOADER_REG_APP_START; + uint8_t status = MGOS_CCS811_STATUS_ERR; + uint8_t drive_mode = CCS811_DRIVE_MODE_IDLE; + mgos_i2c_write(sensor->i2c, sensor->i2caddr, &cmd, 1, true); + mgos_usleep(2000); + + // Read status (expecting FW_MODE to be set and ERR to be clear) + mgos_ccs811_getStatus(sensor, &status); + if (!(status & MGOS_CCS811_STATUS_FW_MODE) || (status & MGOS_CCS811_STATUS_ERR)) { + LOG(LL_ERROR, ("CCS811 invalid firmware mode, and/or status error")); + goto exit; + } + + mgos_ccs811_setDriveMode(sensor, CCS811_DRIVE_MODE_1SEC); + mgos_ccs811_getDriveMode(sensor, &drive_mode); + if (drive_mode != CCS811_DRIVE_MODE_1SEC) { + LOG(LL_ERROR, ("CCS811 failed to set drive mode")); + goto exit; + } + + success = 1; + LOG(LL_INFO, ("CCS811 created at I2C 0x%02x", i2caddr)); +exit: + if (!success) { + free(sensor); + sensor = NULL; + } + return sensor; +} + +bool mgos_ccs811_getDriveMode(struct mgos_ccs811 *sensor, uint8_t *mode) { + uint8_t meas_mode; + + if (!mgos_ccs811_getMeasMode(sensor, &meas_mode)) { + return false; + } + // bits -- 6:4 DRIVE_MOCE 3: Interrupt enable 2: Int on Threshhold + meas_mode >>= 4; + meas_mode &= 0x07; + *mode = meas_mode; + + return true; +} + +bool mgos_ccs811_setDriveMode(struct mgos_ccs811 *sensor, enum mgos_ccs811_drive_mode_t mode) { + uint8_t meas_mode; + + // bits -- 6:4 DRIVE_MOCE 3: Interrupt enable 2: Int on Threshhold + meas_mode = (mode << 4); + return mgos_i2c_write_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_MEAS_MODE, meas_mode); +} + +void mgos_ccs811_destroy(struct mgos_ccs811 **sensor) { + if (!*sensor) { + return; + } + free(*sensor); + *sensor = NULL; + return; +} + +bool mgos_ccs811_read(struct mgos_ccs811 *sensor) { + double start = mg_time(); + + if (!sensor || !sensor->i2c) { + return false; + } + + sensor->stats.read++; + + if (start - sensor->stats.last_read_time < MGOS_CCS811_READ_DELAY) { + sensor->stats.read_success_cached++; + return true; + } + // Read out sensor data here + // + if (!mgos_ccs811_dataready(sensor)) { + sensor->stats.read_success_cached++; + return true; + } + + uint8_t data[8]; + uint8_t cmd = MGOS_CCS811_REG_ALG_RESULT_DATA; + + data[4] = MGOS_CCS811_STATUS_ERR; + mgos_i2c_write(sensor->i2c, sensor->i2caddr, &cmd, 1, false); + mgos_i2c_read(sensor->i2c, sensor->i2caddr, data, 8, true); + + // bytes 0-1:eco2 2-3:tvoc 4:status 5:error 6-7: raw_data + if (data[4] & MGOS_CCS811_STATUS_ERR) { + LOG(LL_ERROR, ("Read error 0x%02x", data[5])); + return false; + } + sensor->eco2 = ((uint16_t)data[0] << 8) | ((uint16_t)data[1]); + sensor->tvoc = ((uint16_t)data[2] << 8) | ((uint16_t)data[3]); + LOG(LL_DEBUG, ("eCO2=%u TVOC=%u", sensor->eco2, sensor->tvoc)); + + sensor->stats.read_success++; + sensor->stats.read_success_usecs += 1000000 * (mg_time() - start); + sensor->stats.last_read_time = start; + return true; +} + +float mgos_ccs811_get_eco2(struct mgos_ccs811 *sensor) { + if (!mgos_ccs811_read(sensor)) { + return NAN; + } + return (float)sensor->eco2; +} + +float mgos_ccs811_get_tvoc(struct mgos_ccs811 *sensor) { + if (!mgos_ccs811_read(sensor)) { + return NAN; + } + return (float)sensor->tvoc; +} + +bool mgos_ccs811_i2c_init(void) { + return true; +} + +// Public functions end diff --git a/src/mgos_ccs811_internal.h b/src/mgos_ccs811_internal.h new file mode 100644 index 0000000..27ee1fa --- /dev/null +++ b/src/mgos_ccs811_internal.h @@ -0,0 +1,74 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "mgos.h" +#include "mgos_i2c.h" +#include "mgos_ccs811.h" +#include + +#define MGOS_SHT31_DEFAULT_I2CADDR (0x5A) + +// Registers +#define MGOS_CCS811_REG_STATUS (0x00) +#define MGOS_CCS811_REG_MEAS_MODE (0x01) +#define MGOS_CCS811_REG_ALG_RESULT_DATA (0x02) +#define MGOS_CCS811_REG_RAW_DATA (0x03) +#define MGOS_CCS811_REG_ENV_DATA (0x05) +#define MGOS_CCS811_REG_NTC (0x06) +#define MGOS_CCS811_REG_THRESHOLDS (0x10) +#define MGOS_CCS811_REG_BASELINE (0x11) +#define MGOS_CCS811_REG_HW_ID (0x20) +#define MGOS_CCS811_REG_HW_VERSION (0x21) +#define MGOS_CCS811_REG_FW_BOOT_VERSION (0x23) +#define MGOS_CCS811_REG_FW_APP_VERSION (0x24) +#define MGOS_CCS811_REG_ERROR_ID (0xE0) +#define MGOS_CCS811_REG_SW_RESET (0xFF) + +// Bootloader registers +#define MGOS_CCS811_BOOTLOADER_REG_APP_ERASE (0xF1) +#define MGOS_CCS811_BOOTLOADER_REG_APP_DATA (0xF2) +#define MGOS_CCS811_BOOTLOADER_REG_APP_VERIFY (0xF3) +#define MGOS_CCS811_BOOTLOADER_REG_APP_START (0xF4) + +// Status register bits +#define MGOS_CCS811_STATUS_ERR (0x01) +#define MGOS_CCS811_STATUS_DATA_READY (0x08) +#define MGOS_CCS811_STATUS_APP_VALID (0x10) +#define MGOS_CCS811_STATUS_FW_MODE (0x80) + +// Other defines +#define MGOS_CCS811_HW_ID_CODE (0x81) +#define MGOS_CCS811_REF_RESISTOR (100000) + +#ifdef __cplusplus +extern "C" { + #endif + +struct mgos_ccs811 { + struct mgos_i2c * i2c; + uint8_t i2caddr; + struct mgos_ccs811_stats stats; + + float temperature_offset; + uint16_t tvoc; + uint16_t eco2; +}; + + #ifdef __cplusplus +} +#endif