1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 10:12:45 +01:00

drivers: add driver for CCS811 gas sensor

This commit is contained in:
Schorcht 2018-09-28 23:48:47 +02:00 committed by Alexandre Abadie
parent fa38f05574
commit 74e1ab220c
14 changed files with 2061 additions and 0 deletions

View File

@ -100,6 +100,17 @@ ifneq (,$(filter cc2420,$(USEMODULE)))
FEATURES_REQUIRED += periph_spi
endif
ifneq (,$(filter ccs811_full,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio_irq
USEMODULE += ccs811
endif
ifneq (,$(filter ccs811,$(USEMODULE)))
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_i2c
USEMODULE += xtimer
endif
ifneq (,$(filter dht,$(USEMODULE)))
USEMODULE += xtimer
FEATURES_REQUIRED += periph_gpio

View File

@ -46,6 +46,10 @@ ifneq (,$(filter cc2420,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/cc2420/include
endif
ifneq (,$(filter ccs811,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/ccs811/include
endif
ifneq (,$(filter dht,$(USEMODULE)))
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/dht/include
endif

1
drivers/ccs811/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

684
drivers/ccs811/ccs811.c Normal file
View File

@ -0,0 +1,684 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_ccs811
* @brief Device Driver for AMS CCS811 digital gas sensor
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "log.h"
#include "xtimer.h"
#include "ccs811_regs.h"
#include "ccs811.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/**
* Internal macro definitions
*/
#define ASSERT_PARAM(cond) \
if (!(cond)) { \
DEBUG("[ccs811] %s: %s\n", \
__func__, "parameter condition (" # cond ") not fulfilled"); \
assert(cond); \
}
#define DEBUG_DEV(f, d, ...) \
DEBUG("[ccs811] %s dev=%d addr=%02x: " f "\n", \
__func__, d->params.i2c_dev, d->params.i2c_addr, ## __VA_ARGS__)
#define ERROR_DEV(f, d, ...) \
LOG_ERROR("[ccs811] dev=%d addr=%x: " f "\n", \
d->params.i2c_dev, d->params.i2c_addr, ## __VA_ARGS__)
/**
* Internal type declarations
*/
typedef struct {
uint8_t reserved_1 : 2;
uint8_t int_thresh : 1; /**< interrupt if new ALG_RESULT_DAT crosses on of the thresholds */
uint8_t int_datardy: 1; /**< interrupt if new sample is ready in ALG_RESULT_DAT */
uint8_t drive_mode : 3; /**< mode number binary coded */
} ccs811_meas_mode_reg_t;
/**
* forward declaration of functions for internal use only
*/
static int _reg_read(const ccs811_t *dev, uint8_t reg, uint8_t *data, uint32_t len);
static int _reg_write(const ccs811_t *dev, uint8_t reg, uint8_t *data, uint32_t len);
static int _check_error_status(const ccs811_t *dev);
static int _error_code(const ccs811_t *dev, uint8_t err_reg);
static int _is_available(const ccs811_t *dev);
int ccs811_init(ccs811_t *dev, const ccs811_params_t *params)
{
ASSERT_PARAM(dev != NULL);
ASSERT_PARAM(params != NULL);
/* init sensor data structure */
dev->params = *params;
int res = CCS811_OK;
#if MODULE_CCS811_FULL
if (dev->params.reset_pin != GPIO_UNDEF &&
gpio_init(dev->params.reset_pin, GPIO_OUT) == 0) {
DEBUG_DEV("nRESET pin configured", dev);
/* enable low active reset signal */
gpio_clear(dev->params.reset_pin);
/* t_RESET (reset impuls) has to be at least 20 us, we wait 1 ms */
xtimer_usleep(1000);
/* disable low active reset signal */
gpio_set(dev->params.reset_pin);
/* t_START after reset is 1 ms, we wait 1 further ms */
xtimer_usleep(1000);
}
else {
dev->params.reset_pin = GPIO_UNDEF;
DEBUG_DEV("nRESET pin not configured or could not be used", dev);
}
if (dev->params.wake_pin != GPIO_UNDEF &&
gpio_init(dev->params.wake_pin, GPIO_OUT) == 0) {
DEBUG_DEV("nWAKE pin configured", dev);
}
else {
dev->params.wake_pin = GPIO_UNDEF;
DEBUG_DEV("nWAKE pin not configured or could not be used", dev);
}
#endif /* MODULE_CCS811_FULL */
/* check whether sensor is available including the check of the hardware id */
if ((res = _is_available(dev)) != CCS811_OK) {
return res;
}
static const uint8_t sw_reset[4] = { 0x11, 0xe5, 0x72, 0x8a };
/* doing a software reset first */
if (_reg_write(dev, CCS811_REG_SW_RESET, (uint8_t *)sw_reset, 4) != CCS811_OK) {
DEBUG_DEV("could not write software reset command "
"to register CCS811_REG_SW_RESET", dev);
return -CCS811_ERROR_I2C;
}
uint8_t status;
/* wait 100 ms after the reset */
xtimer_usleep(100000);
/* get the status to check whether sensor is in bootloader mode */
if (_reg_read(dev, CCS811_REG_STATUS, &status, 1) != CCS811_OK) {
DEBUG_DEV("could not read register CCS811_REG_STATUS", dev);
return -CCS811_ERROR_I2C;
}
/*
* if sensor is in bootloader mode (FW_MODE == 0), it has to switch
* to the application mode first
*/
if (!(status & CCS811_STATUS_FW_MODE)) {
/* check whether valid application firmware is loaded */
if (!(status & CCS811_STATUS_APP_VALID)) {
DEBUG_DEV("sensor is in boot mode, but has no app", dev);
return -CCS811_ERROR_NO_APP;
}
/* switch to application mode */
if (_reg_write(dev, CCS811_REG_APP_START, 0, 0) != CCS811_OK) {
DEBUG_DEV("could not write app start command "
"to register CCS811_REG_APP_START", dev);
return -CCS811_ERROR_I2C;
}
/* wait 100 ms after starting the app */
xtimer_usleep(100000);
/* get the status to check whether sensor switched to application mode */
if (_reg_read(dev, CCS811_REG_STATUS, &status, 1) != CCS811_OK) {
DEBUG_DEV("could not read register CCS811_REG_STATUS", dev);
return -CCS811_ERROR_I2C;
}
if ((status & CCS811_STATUS_FW_MODE) == 0) {
DEBUG_DEV("could not start application", dev);
return -CCS811_ERROR_NO_APP;
}
}
#if MODULE_CCS811_FULL
/* try to set interrupt mode */
if (dev->params.int_mode != CCS811_INT_NONE &&
(res = ccs811_set_int_mode (dev, dev->params.int_mode)) != CCS811_OK) {
return res;
}
#endif /* MODULE_CCS811_FULL */
/* try to set default measurement mode */
return ccs811_set_mode(dev, dev->params.mode);
}
int ccs811_set_mode(ccs811_t *dev, ccs811_mode_t mode)
{
ASSERT_PARAM(dev != NULL);
ccs811_meas_mode_reg_t reg;
/* read measurement mode register value */
if (_reg_read(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1) != CCS811_OK) {
DEBUG_DEV("could not read current measurement mode "
"from register CCS811_REG_MEAS_MODE", dev);
return -CCS811_ERROR_I2C;
}
reg.drive_mode = mode;
/* write back measurement mode register */
if (_reg_write(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1) != CCS811_OK) {
DEBUG_DEV("could not write new measurement mode "
"to register CCS811_REG_MEAS_MODE", dev);
return -CCS811_ERROR_I2C;
}
/* check whether setting measurement mode were succesfull */
if (_reg_read(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1) != CCS811_OK) {
DEBUG_DEV("could not read new measurement mode "
"from register CCS811_REG_MEAS_MODE", dev);
return -CCS811_ERROR_I2C;
}
if (reg.drive_mode != mode) {
DEBUG_DEV("could not set measurement mode to %d", dev, mode);
return -CCS811_ERROR_MEASMODE_INV;
}
dev->params.mode = mode;
return CCS811_OK;
}
#if MODULE_CCS811_FULL
int ccs811_set_int_mode(ccs811_t *dev, ccs811_int_mode_t mode)
{
ASSERT_PARAM(dev != NULL);
if (dev->params.int_pin == GPIO_UNDEF) {
DEBUG_DEV("nINT pin not configured", dev);
return CCS811_ERROR_NO_INT_PIN;
}
ccs811_meas_mode_reg_t reg;
/* read measurement mode register value */
if (_reg_read(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1) != CCS811_OK) {
DEBUG_DEV("could not set interrupt mode, could not read register "
"CCS811_REG_MEAS_MODE", dev);
return CCS811_ERROR_I2C;
}
reg.int_datardy = mode != CCS811_INT_NONE;
reg.int_thresh = mode == CCS811_INT_THRESHOLD;
/* write back measurement mode register */
if (_reg_write(dev, CCS811_REG_MEAS_MODE, (uint8_t *)&reg, 1) != CCS811_OK) {
DEBUG_DEV("could not set interrupt mode, could not write register "
"CCS811_REG_MEAS_MODE", dev);
return CCS811_ERROR_I2C;
}
dev->params.int_mode = mode;
return CCS811_OK;
}
#endif /* MODULE_CCS811_FULL */
int ccs811_data_ready(const ccs811_t *dev)
{
uint8_t status;
/* check status register */
if (_reg_read(dev, CCS811_REG_STATUS, &status, 1) != CCS811_OK) {
DEBUG_DEV("could not read CCS811_REG_STATUS", dev);
return -CCS811_ERROR_I2C;
}
if ((status & CCS811_STATUS_DATA_RDY)) {
/* new data available */
return CCS811_OK;
}
return CCS811_ERROR_NO_NEW_DATA;
}
#define CCS811_ALG_DATA_ECO2_HB 0
#define CCS811_ALG_DATA_ECO2_LB 1
#define CCS811_ALG_DATA_TVOC_HB 2
#define CCS811_ALG_DATA_TVOC_LB 3
#define CCS811_ALG_DATA_STATUS 4
#define CCS811_ALG_DATA_ERROR_ID 5
#define CCS811_ALG_DATA_RAW_HB 6
#define CCS811_ALG_DATA_RAW_LB 7
int ccs811_read_iaq(const ccs811_t *dev,
uint16_t *iaq_tvoc, uint16_t *iaq_eco2,
uint16_t *raw_i, uint16_t *raw_v)
{
ASSERT_PARAM(dev != NULL);
int res = CCS811_OK;
if (dev->params.mode == CCS811_MODE_IDLE) {
DEBUG_DEV("sensor is in idle mode and not performing "
"measurements", dev);
return -CCS811_ERROR_MEASMODE_INV;
}
if (dev->params.mode == CCS811_MODE_250MS && (iaq_tvoc || iaq_eco2)) {
DEBUG_DEV("sensor is in constant power mode, only raw data "
"are available every 250ms", dev);
return -CCS811_ERROR_NO_IAQ_DATA;
}
uint8_t data[8];
/* read IAQ sensor values and RAW sensor data including status and error id */
if (_reg_read(dev, CCS811_REG_ALG_RESULT_DATA, data, 8) != CCS811_OK) {
DEBUG_DEV("could not read sensor data from "
"register CCS811_REG_ALG_RESULT_DATA", dev);
return -CCS811_ERROR_I2C;
}
/* check for errors */
if (data[CCS811_ALG_DATA_STATUS] & CCS811_STATUS_ERROR) {
return _error_code(dev, data[CCS811_ALG_DATA_ERROR_ID]);
}
/*
* check whether new data are ready to read; if not, latest values read
* from sensor are used and error code CCS811_ERROR_NO_NEW_DATA is returned
*/
if (!(data[CCS811_ALG_DATA_STATUS] & CCS811_STATUS_DATA_RDY)) {
DEBUG_DEV("no new data", dev);
res = -CCS811_ERROR_NO_NEW_DATA;
}
/* if *iaq* is not NULL return IAQ sensor values */
if (iaq_tvoc) {
*iaq_tvoc = data[CCS811_ALG_DATA_TVOC_HB] << 8;
*iaq_tvoc |= data[CCS811_ALG_DATA_TVOC_LB];
}
if (iaq_eco2) {
*iaq_eco2 = data[CCS811_ALG_DATA_ECO2_HB] << 8;
*iaq_eco2 |= data[CCS811_ALG_DATA_ECO2_LB];
}
/* if *raw* is not NULL return RAW sensor data */
if (raw_i) {
*raw_i = data[CCS811_ALG_DATA_RAW_HB] >> 2;
}
if (raw_v) {
*raw_v = (data[CCS811_ALG_DATA_RAW_HB] & 0x03) << 8;
*raw_v |= data[CCS811_ALG_DATA_RAW_LB];
}
return res;
}
#if MODULE_CCS811_FULL
int ccs811_read_ntc(const ccs811_t *dev, uint32_t r_ref, uint32_t *r_ntc)
{
ASSERT_PARAM(dev != NULL);
ASSERT_PARAM(r_ntc != NULL);
uint8_t data[4];
/* read baseline register */
if (_reg_read(dev, CCS811_REG_NTC, data, 4) != CCS811_OK) {
DEBUG_DEV("could not read the V_REF and V_NTC "
"from register CCS811_REG_NTC", dev);
return -CCS811_ERROR_I2C;
}
/* calculation from application note ams AN000372 */
uint32_t v_ref = (uint16_t)(data[0]) << 8 | data[1];
uint32_t v_ntc = (uint16_t)(data[2]) << 8 | data[3];
*r_ntc = v_ntc * r_ref / v_ref;
return CCS811_OK;
}
#endif /* MODULE_CCS811_FULL */
int ccs811_power_down (ccs811_t *dev)
{
ASSERT_PARAM(dev != NULL);
if (dev->params.wake_pin == GPIO_UNDEF) {
DEBUG_DEV("nWAKE signal pin not configured", dev);
return CCS811_ERROR_NO_WAKE_PIN;
}
ccs811_mode_t tmp_mode = dev->params.mode;
int res = ccs811_set_mode(dev, CCS811_MODE_IDLE);
dev->params.mode = tmp_mode;
return res;
}
int ccs811_power_up (ccs811_t *dev)
{
ASSERT_PARAM(dev != NULL);
if (dev->params.wake_pin == GPIO_UNDEF) {
DEBUG_DEV("nWAKE signal pin not configured", dev);
return CCS811_ERROR_NO_WAKE_PIN;
}
return ccs811_set_mode(dev, dev->params.mode);
}
#if MODULE_CCS811_FULL
int ccs811_set_environmental_data(const ccs811_t *dev,
int16_t temp, int16_t hum)
{
ASSERT_PARAM(dev != NULL);
temp = (((uint32_t)temp + 2500) << 9) / 100; /* -25 °C maps to 0 */
hum = ((uint32_t)hum << 9) / 100;
/* fill environmental data */
uint8_t data[4] = { temp >> 8, temp & 0xff,
hum >> 8, hum & 0xff };
/* send environmental data to the sensor */
if (_reg_write(dev, CCS811_REG_ENV_DATA, data, 4) != CCS811_OK) {
DEBUG_DEV("could not write environmental data "
"to register CCS811_REG_ENV_DATA", dev);
return CCS811_ERROR_I2C;
}
return CCS811_OK;
}
int ccs811_set_eco2_thresholds(const ccs811_t *dev,
uint16_t low, uint16_t high, uint8_t hyst)
{
ASSERT_PARAM(dev != NULL);
/* check parameters */
if (low < CCS811_ECO2_RANGE_MIN ||
high > CCS811_ECO2_RANGE_MAX || low > high || !hyst) {
DEBUG_DEV("wrong threshold parameters", dev);
return CCS811_ERROR_THRESH_INV;
}
/* fill the threshold data */
uint8_t data[5] = { low >> 8, low & 0xff,
high >> 8, high & 0xff,
hyst };
/* write threshold data to the sensor */
if (_reg_write(dev, CCS811_REG_THRESHOLDS, data, 5) != CCS811_OK) {
DEBUG_DEV("could not set threshold interrupt parameters, "
"could not write register CCS811_REG_THRESHOLDS", dev);
return CCS811_ERROR_I2C;
}
return CCS811_OK;
}
int ccs811_get_baseline(const ccs811_t *dev, uint16_t *base)
{
ASSERT_PARAM(dev != NULL);
ASSERT_PARAM(base != NULL);
uint8_t data[2];
/* read baseline register */
if (_reg_read(dev, CCS811_REG_BASELINE, data, 2) != CCS811_OK) {
DEBUG_DEV("could not get current baseline value, "
"could not read register CCS811_REG_BASELINE", dev);
return CCS811_ERROR_I2C;
}
*base = (uint16_t)(data[0]) << 8 | data[1];
return CCS811_OK;
}
int ccs811_set_baseline(const ccs811_t *dev, uint16_t baseline)
{
ASSERT_PARAM(dev != NULL);
uint8_t data[2] = { baseline >> 8, baseline & 0xff };
/* write baseline register */
if (_reg_write(dev, CCS811_REG_THRESHOLDS, data, 5) != CCS811_OK) {
DEBUG_DEV("could not set baseline value, "
"could not write register CCS811_REG_BASELINE", dev);
return CCS811_ERROR_I2C;
}
return CCS811_OK;
}
#endif /* MODULE_CCS811_FULL */
/**
* function for internal use only
*/
static int _reg_read(const ccs811_t *dev, uint8_t reg, uint8_t *data, uint32_t len)
{
DEBUG_DEV("read %d bytes from sensor registers starting at addr %02x",
dev, len, reg);
int res = CCS811_OK;
if (i2c_acquire(dev->params.i2c_dev) != CCS811_OK) {
DEBUG_DEV("could not aquire I2C bus", dev);
return -CCS811_ERROR_I2C;
}
#if MODULE_CCS811_FULL
if (dev->params.wake_pin != GPIO_UNDEF) {
/* wake the sensor with low active WAKE signal */
gpio_clear(dev->params.wake_pin);
/* t_WAKE is 50 us */
xtimer_usleep(50);
}
#endif
res = i2c_read_regs(dev->params.i2c_dev, dev->params.i2c_addr, reg, data, len, 0);
i2c_release(dev->params.i2c_dev);
#if MODULE_CCS811_FULL
if (dev->params.wake_pin != GPIO_UNDEF) {
/* let the sensor enter to sleep mode */
gpio_set(dev->params.wake_pin);
/* minimum t_DWAKE is 20 us */
xtimer_usleep(20);
}
#endif
if (res == CCS811_OK) {
if (ENABLE_DEBUG) {
printf("[ccs811] %s dev=%d addr=%02x: read following bytes: ",
__func__, dev->params.i2c_dev, dev->params.i2c_addr);
for (unsigned i = 0; i < len; i++) {
printf("%02x ", data[i]);
}
printf("\n");
}
}
else {
DEBUG_DEV("could not read %d bytes from sensor registers "
"starting at addr %02x, reason %d (%s)",
dev, len, reg, res, strerror(res * -1));
return -CCS811_ERROR_I2C;
}
return CCS811_OK;
}
static int _reg_write(const ccs811_t *dev, uint8_t reg, uint8_t *data, uint32_t len)
{
DEBUG_DEV("write %d bytes to sensor registers starting at addr %02x",
dev, len, reg);
int res = CCS811_OK;
if (ENABLE_DEBUG && data && len) {
printf("[css811] %s dev=%d addr=%02x: write following bytes: ",
__func__, dev->params.i2c_dev, dev->params.i2c_addr);
for (unsigned i = 0; i < len; i++) {
printf("%02x ", data[i]);
}
printf("\n");
}
if (i2c_acquire(dev->params.i2c_dev)) {
DEBUG_DEV("could not aquire I2C bus", dev);
return -CCS811_ERROR_I2C;
}
#if MODULE_CCS811_FULL
if (dev->params.wake_pin != GPIO_UNDEF) {
/* wake the sensor with low active WAKE signal */
gpio_clear(dev->params.wake_pin);
/* t_WAKE is 50 us */
xtimer_usleep(50);
}
#endif
if (!data || !len) {
res = i2c_write_byte(dev->params.i2c_dev, dev->params.i2c_addr, reg, 0);
}
else {
res = i2c_write_regs(dev->params.i2c_dev, dev->params.i2c_addr, reg, data, len, 0);
}
i2c_release(dev->params.i2c_dev);
#if MODULE_CCS811_FULL
if (dev->params.wake_pin != GPIO_UNDEF) {
/* let the sensor enter to sleep mode */
gpio_set(dev->params.wake_pin);
/* minimum t_DWAKE is 20 us */
xtimer_usleep(20);
}
#endif
if (res != CCS811_OK) {
DEBUG_DEV("could not write %d bytes to sensor registers "
"starting at addr %02x, reason %d (%s)",
dev, len, reg, res, strerror(res * -1));
return -CCS811_ERROR_I2C;
}
return CCS811_OK;
}
static int _error_code(const ccs811_t *dev, uint8_t err_reg)
{
if (err_reg & CCS811_ERR_WRITE_REG_INV) {
DEBUG_DEV("invalid register address on write", dev);
return -CCS811_ERROR_WRITE_REG_INV;
}
if (err_reg & CCS811_ERR_READ_REG_INV) {
DEBUG_DEV("invalid register address on read", dev);
return -CCS811_ERROR_READ_REG_INV;
}
if (err_reg & CCS811_ERR_MEASMODE_INV) {
DEBUG_DEV("invalid requested measurement mode", dev);
return -CCS811_ERROR_MEASMODE_INV;
}
if (err_reg & CCS811_ERR_MAX_RESISTANCE) {
DEBUG_DEV("sensor resistance measurement has reached "
"or exceeded the maximum range", dev);
return -CCS811_ERROR_MAX_RESISTANCE;
}
if (err_reg & CCS811_ERR_HEATER_FAULT) {
DEBUG_DEV("heater current not in range", dev);
return -CCS811_ERROR_HEATER_FAULT;
}
if (err_reg & CCS811_ERR_HEATER_SUPPLY) {
DEBUG_DEV("heater voltage is not being applied correctly", dev);
return -CCS811_ERROR_HEATER_SUPPLY;
}
return CCS811_OK;
}
static int _check_error_status(const ccs811_t *dev)
{
uint8_t status;
uint8_t err_reg;
/* check status register */
if (_reg_read(dev, CCS811_REG_STATUS, &status, 1) != CCS811_OK) {
DEBUG_DEV("could not read CCS811_REG_STATUS", dev);
return -CCS811_ERROR_I2C;
}
if (!(status & CCS811_STATUS_ERROR)) {
/* everything is OK */
return CCS811_OK;
}
/* Check the error id register */
if (_reg_read(dev, CCS811_REG_ERROR_ID, &err_reg, 1) != CCS811_OK) {
DEBUG_DEV("could not read CCS811_REG_ERROR_ID", dev);
return -CCS811_ERROR_I2C;
}
if (err_reg != 0) {
return _error_code(dev, err_reg);
}
return CCS811_OK;
}
static int _is_available(const ccs811_t *dev)
{
uint8_t reg_data[5];
/* check hardware id (register 0x20) and hardware version (register 0x21) */
if (_reg_read(dev, CCS811_REG_HW_ID, reg_data, 5) != CCS811_OK) {
DEBUG_DEV("could not read CCS811_REG_HW_ID", dev);
return -CCS811_ERROR_I2C;
}
if (reg_data[0] != CCS811_HW_ID) {
DEBUG_DEV("wrong hardware ID %02x, should be %02x",
dev, reg_data[0], CCS811_HW_ID);
return -CCS811_ERROR_NO_DEV;
}
DEBUG_DEV("hardware version: %02x", dev, reg_data[1]);
DEBUG_DEV("firmware boot version: %02x", dev, reg_data[3]);
DEBUG_DEV("firmware app version: %02x", dev, reg_data[4]);
return _check_error_status(dev);
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_ccs811
* @brief SAUL adaption for AMS CCS811 digital gas sensor devices
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "phydat.h"
#include "saul.h"
#include "ccs811.h"
static uint16_t _iaq_tvoc = 0;
static uint16_t _iaq_eco2 = 0;
static int read(const ccs811_t *dev)
{
/* check whether new data can be read */
int res = ccs811_data_ready((ccs811_t *)dev);
if (res != CCS811_OK) {
return res;
}
/* read new data and save them to local storage */
return ccs811_read_iaq((ccs811_t *)dev, &_iaq_tvoc, &_iaq_eco2, 0, 0);
}
static int read_tvoc(const void *dev, phydat_t *res)
{
/* read new data if available */
read(dev);
/* fill data from local storage */
res->val[0] = _iaq_tvoc;
res->unit = UNIT_PPB;
res->scale = 0;
return 1;
}
static int read_eco2(const void *dev, phydat_t *res)
{
/* read new data if available */
read(dev);
/* fill data from local storage */
res->val[0] = _iaq_eco2;
res->unit = UNIT_PPM;
res->scale = 0;
return 1;
}
const saul_driver_t ccs811_saul_driver_eco2 = {
.read = read_eco2,
.write = saul_notsup,
.type = SAUL_SENSE_CO2
};
const saul_driver_t ccs811_saul_driver_tvoc = {
.read = read_tvoc,
.write = saul_notsup,
.type = SAUL_SENSE_TVOC
};

459
drivers/ccs811/doc.txt Normal file
View File

@ -0,0 +1,459 @@
/**
@defgroup drivers_ccs811 CCS 811 digital gas sensor
@ingroup drivers_sensors
@ingroup drivers_saul
@brief Device Driver for AMS CCS 811 digital gas sensor for monitoring
Indoor Air Quality (IAQ)
# Driver for the ams CCS811 digital gas sensor for monitoring indoor air quality.
The driver is for the usage with [RIOT-OS](https://github.com/RIOT-OS/RIOT).
## <a name="toc"> Table of contets </a>
1. [Overview](#overview)
1. [About the sensor](#about)
2. [Supported features](#supported)
2. [Measurement Process](#measurement_process)
1. [Sensor modes](#sensor_modes)
2. [Measurement results](#measurement_results)
3. [Compensation](#compensation)
4. [Negative Thermal Coefficient Thermistor (NTC)](#ntc)
5. [Interrupts](#interrupts)
1. [Data ready interrupt](#data_ready_interrupt)
2. [Threshold interrupt](#threshold interrupt)
6. [Power Saving](#power saving)
7. [Baseline](#baseline)
8. [Error Handling](#error_handling)
19. [Configuration](#configuration)
1. [Hardware Configurations](#hardware_configuration)
2. [Driver Configuration Parameters](#driver_configuration)
### <a name="overview"> Overview </a> &nbsp;&nbsp; [[TOC](#toc)]
### <a name="about"> About the sensor </a> &nbsp;&nbsp; [[TOC](#toc)]
The CCS811 is an ultra-low power digital sensor which detects **Volatile
Organic Compounds (VOC)** for **Indoor Air Quality (IAQ)** monitoring that.
The sensor allows to
- convert raw sensor data to Total Volatile Organic Compound (TVOC)
and equivalent CO2 (eCO2),
- compensate gas readings due to temperature and humidity using an
external sensor,
- trigger interrupts when new measurement results are available or
eCO2 value exceeds thresholds,
- correct baseline automatically or manually
- connect a NTC thermistor to provide means of calculating the local
ambient temperature,
- power-save using a sleep mode and wakup feature.
@note The sensor is connected to I2C interface and uses clock stretching.
The I2C implementation of the MCU has to support clock stretching
to get CCS811 working.
### <a name="supported"> Supported Features </a> &nbsp;&nbsp; [[TOC](#toc)]
@note There are two driver module versions, the ```ccs811``` module
wich provides only basic functionality and the ```ccs811_full```
module with additional functionality.
The ```ccs811_full``` module includes the ```ccs811``` module
automatically. If code size is critical, the ```ccs811``` module can
be used, otherwise using the ```ccs811_full``` module is recommended.
The driver supports the following features when modules ```ccs811```
and ```ccs811_full``` are used.
Feature | Module
--------|-------
read raw and converted gas sensor data (eCO2, TVOC) | ```ccs811```
test for new sensor gas data | ```ccs811```
data ready and threshold interrupt handling | ```ccs811_full```
power saving using sleep mode with wakeup | ```ccs811_full```
ambient temperatur calculation with NTC | ```ccs811_full```
compensate gas readings using an external sensor | ```ccs811_full```
manual baseline handling | ```ccs811_full```
## <a name="measurement_process"> Measurement Process </a> &nbsp;&nbsp; [[TOC](#toc)]
### <a name="sensor_modes"> Sensor modes </a> &nbsp;&nbsp; [[TOC](#toc)]
After power up, the sensor starts automatically in *Idle, Low Current
Mode* (#CCS811_MODE_IDLE). To start periodic measurements, the mode of
the sensor has to be changed to any measurement mode. Measurement modes
with different output data rates are available:
Mode | Driver symbol | Period | RAW data | IAQ values
-----------------------------| ------------------ | ------ |:--------:|:----------:
Idle, Low Current Mode | #CCS811_MODE_IDLE | - | - | -
Constant Power Mode | #CCS811_MODE_1S | 1 s | X | X
Pulse Heating Mode | #CCS811_MODE_10S | 10 s | X | X
Low Power Pulse Heating Mode | #CCS811_MODE_60S | 60 s | X | X
Constant Power Mode | #CCS811_MODE_250MS | 250 ms | X | -
In *Constant Power Mode* with measurements every 250 ms (#CCS811_MODE_250MS)
only raw data are available. In all other measurement modes, the Indoor
Air Quality (IAQ) values are available additionally. The *Constant Power
Mode* with measurements every 250 ms (#CCS811_MODE_250MS) is only intended
for systems where an external host system wants to run an algorithm with
raw data.
@note
- After setting the mode, the sensor is in conditioning period that needs
up to 20 minutes, before accurate readings are generated, see the
[data sheet](https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf)
for more details.
- During the early-live (burn-in) period, the CCS811 sensor should run
for 48 hours in the selected mode of operation to ensure sensor
performance is stable, see the data sheet for more details.
- When the sensor operating mode is changed to a new mode with a lower
sample rate, e.g., from *Pulse Heating Mode* (#CCS811_MODE_10S) to
*Low Power Pulse Heating Mode* (#CCS811_MODE_60S), it should be placed
in *Idle, Low Current Mode* (#CCS811_MODE_IDLE) for at least 10 minutes
before enabling the new mode. When the sensor operating mode is changed
to a new mode with a higher sample rate, e.g., from *Low Power Pulse
Heating Mode* (#CCS811_MODE_60S) to *Pulse Heating Mode*
(#CCS811_MODE_10S), there is no requirement to wait before enabling
the new mode.
When default configuration parameters from **ccs811_params.h** are used,
the #CCS811_MODE_1S measurement mode is used automatically. The
application can change the measurement mode either
- by using the #ccs811_set_mode function, or
- by defining **CCS811_PARAM_MODE** before **ccs811_params.h** is included.
## <a name="measurement_results"> Measurement results </a> &nbsp;&nbsp; [[TOC](#toc)]
Once the measurement mode is set, the user task can use function
#ccs811_read_iaq to fetch the results. The function returns **raw data**
as well as **Indoor Air Quality (IAQ)** values. If some of the results
are not needed, the corresponding parameters can be set to ```NULL```.
While raw data represents simply the current through the sensor and the
voltage across the sensor with the selected current, IAQ values are the
results of the processing of these raw data by the sensor. IAQ values consist
of the **equivalent CO2 (eCO2)** with a range from 400 ppm to 8192 ppm
and **Total Volatile Organic Compound (TVOC)** with a range from 0 ppb
to 1187 ppb.
```
uint16_t iaq_tvoc;
uint16_t iaq_eco2;
uint16_t raw_i;
uint16_t raw_v;
...
/* get the results and do something with them */
if (ccs811_read_iaq (&sensor, &tvoc, &eco2, &raw_i, &raw_v) == CCS811_OK) {
...
}
else {
... /* error handling */
}
...
```
If the #ccs811_read_iaq function is called and no new data are
available, the function returns the results of the last valid
measurement and error code #CCS811_ERROR_NO_NEW_DATA.
There are two approaches to wait until new data are available:
- data-ready interrupt (#CCS811_INT_DATA_READY)
- data-ready status function (#ccs811_data_ready)
```
uint16_t iaq_tvoc;
uint16_t iaq_eco2;
uint16_t raw_i;
uint16_t raw_v;
...
/* check whether new data are available, get the data and do something with them */
if (ccs811_data_ready (&sensor) == CCS811_OK &&
ccs811_read_iaq (&sensor, &tvoc, &eco2, &raw_i, &raw_v) == CCS811_OK)
{
...
}
...
```
When using data-ready interrupts, the default configuration parameter for
the interrupt pin can be overridden by defining **CCS811_PARAM_INT_PIN**
before **ccs811_params.h** is included.
## <a name="compensation"> Compensation </a> &nbsp;&nbsp; [[TOC](#toc)]
If information about the environment like temperature and humidity are
available from another sensor, they can be used by CCS811 to compensate
gas readings due to temperature and humidity changes.
@note This feature can only be used with the ```ccs811_full``` module.
Function #ccs811_set_environmental_data can be used to set these environmental
data. In the following example, the Sensirion SHT3x humidity and
temperature sensor is used to fetch environmental data.
```
int16_t temperature; /* in hundredths of a degree Celsius */
int16_t humidity; /* in hundredths of a percent */
...
if (sht3x_get_results (sht3x, &temperature, &humidity))
/* set CCS811 environmental data with values fetched from SHT3x */
ccs811_set_environmental_data (ccs811, temperature, humidity);
...
```
## <a name="ntc"> Negative Thermal Coefficient Thermistor (NTC) </a> &nbsp;&nbsp; [[TOC](#toc)]
CCS811 supports an external interface for connecting a negative thermal
coefficient thermistor (R_NTC) to provide a cost effective and power
efficient means of calculating the local ambient temperature.
@note This feature can only be used with the ```ccs811_full``` module.
The sensor measures the voltage V_NTC across R_NTC as well as the voltage V_REF
across a connected reference resistor (R_REF). Function #ccs811_read_ntc
can be used at any time to fetch the current resistance of R_NTC. It
uses the resistance of R_REF and measured voltages V_REF and V_NTV with
the following equation to determine R_NTC:
R_NTC = R_REF / V_REF * V_NTC
Using the data sheet of the NTC, the ambient temperature can be
calculated. See application note AMS AN000372 for more details.
For example, with
[Adafruit CCS811 Air Quality Sensor Breakout](https://www.adafruit.com/product/3566)
the ambient temperature can be determined as following:
```
...
#define CCS811_R_REF 100000 /* resistance of the reference resistor */
#define CCS811_R_NTC 10000 /* resistance of NTC at a reference temperature */
#define CCS811_R_NTC_TEMP 25 /* reference temperature for NTC */
#define CCS811_BCONSTANT 3380 /* B constant */
/* get NTC resistance */
uint32_t r_ntc;
ccs811_read_ntc (&sensor, CCS811_R_REF, &r_ntc);
/* calculation of temperature from application note ams AN000372 */
double ntc_temp;
ntc_temp = log((double)r_ntc / CCS811_R_NTC); /* 1 */
ntc_temp /= CCS811_BCONSTANT; /* 2 */
ntc_temp += 1.0 / (CCS811_R_NTC_TEMP + 273.15); /* 3 */
ntc_temp = 1.0 / ntc_temp; /* 4 */
ntc_temp -= 273.15; /* 5 */
....
```
## <a name="interrupts"> Interrupts </a> &nbsp;&nbsp; [[TOC](#toc)]
CCS811 supports two types of interrupts that can be used to fetch data:
- data ready interrupt (#CCS811_INT_DATA_READY)
- threshold interrupt (#CCS811_INT_THRESHOLD)
@note
- Interrupts can only be used with the ```ccs811_full``` module.
- It is not possible to use both interrupts at the same time.
### <a name="data_ready_interrupt"> Data ready interrupt </a> &nbsp;&nbsp; [[TOC](#toc)]
At the end of each measurement cycle (every 250 ms, 1 second, 10 seconds,
or 60 seconds), CCS811 can optionally trigger an interrupt. The signal
**nINT** is driven low as soon as new sensor values are ready to read.
It will stop being driven low when sensor data are read with function
#ccs811_read_iaq.
The interrupt is disabled by default. It can be enabled using function
#ccs811_set_int_mode.
```
...
/* enable the data ready interrupt */
ccs811_set_int_mode (&sensor, CCS811_INT_DATA_READY);
...
```
### <a name="threshold interrupt"> Threshold interrupt </a> &nbsp;&nbsp; [[TOC](#toc)]
The user task can choose that the data ready interrupt is not generated
every time when new sensor values become ready but only if the eCO2 value
moves from the current range (LOW, MEDIUM, or HIGH) into another range by
more than a hysteresis value. Hysteresis is used to prevent multiple
interrupts close to a threshold.
The interrupt is disabled by default and can be enabled with function
#ccs811_set_int_mode. The ranges are defined by the
#ccs811_set_eco2_thresholds function and its parameters \p low and \p high
as following:
Name | Range | Value | Default
:------|:------------------------------------------|:-------|:-------
LOW | below the \p low parameter | > 400 | 1500
MEDIUM | between the \p low and \p high parameters | | |
HIGH | above the value of the \p high parameter | < 8192 | 2500
```
...
/* set threshold parameters and enable threshold interrupt mode */
ccs811_set_eco2_thresholds (&sensor, 600, 1100, 40);
ccs811_set_int_mode (&sensor, CCS811_INT_THRESHOLD);
...
```
## <a name="power saving"> Power Saving </a> &nbsp;&nbsp; [[TOC](#toc)]
The CCS811 offers a sleep mode with wake-up function. By using the active
low **nWAKE** signal connected to a GPIO, power can be saved. If the
**nWAKE** signal is low, the CCS811 is active and can communicate over
I2C. When this signal is high, the CCS811 goes into sleep mode and can
be reached via I2C. The measuring process is not affected.
The driver supports this feature when the **nWAKE** signal pin
(#ccs811_params_t::wake_pin) is configured, see the
[Configuration](#Configuration) section.
@note This feature can only be used with the ```ccs811_full``` module.
With the function #ccs811_power_down the CCS811 can be disabled, when
no measurements are required. To re-enable the CCS811 in the previous
measurement mode, the #ccs811_power_up function can be used.
@note It may take several minutes before accurate readings are
generated when the sensor switches back from idle mode to the
previous measurement mode.
The best power-saving solution in measurement modes is the use of the
data-ready interrupt (#CCS811_INT_DATA_READY) in conjunction with
the **nWAKE** signal as supported by the driver.
## <a name="baseline"> Baseline </a> &nbsp;&nbsp; [[TOC](#toc)]
CCS81 supports automatic baseline correction over a minimum time of
24 hours. Using function #ccs811_get_baseline, the current baseline
value can be saved before the sensor is powered down. This baseline
can then be restored with function #ccs811_set_baseline after sensor
is powered up again to continue the automatic baseline process.
@note This feature can only be used with the ```ccs811_full``` module.
## <a name="error_handling"> Error Handling </a> &nbsp;&nbsp; [[TOC](#toc)]
All driver functions return an error code (#ccs811_error_codes_t) to
indicate whether its execution was successful or an error happened.
## <a name="configuration"> Configuration </a> &nbsp;&nbsp; [[TOC](#toc)]
### <a name="hardware_configuration"> Hardware Configurations </a> &nbsp;&nbsp; [[TOC](#toc)]
The following figure shows the most simple hardware configuration with CCS811.
With this configuration interrupts, the hardware reset, and the sleep
mode of the sensor with wake-up feature can't be used. The signals
**nINT** and **nRESET** are not connected. The **nWAKE** signal is
permanently pulled low, leaving the CCS811 and I2C constantly active.
```
+--------+ +--------+
| MCU | | CCS811 |
| | | |
| SCL >-------> SCL |
| SDA <-------> SDA |
| GND --------> /WAKE |
+--------+ +--------+
```
If the interrupt signal **nINT** is used to fetch new data
(only with ```ccs811_full``` module),
the interrupt pin has to be connected to a GPIO pin.
```
+--------+ +--------+
| MCU | | CCS811 |
| | | |
| SCL >-------> SCL |
| SDA <-------> SDA |
| GPIO <-------> /INT |
| GND --------> /WAKE |
+--------+ +--------+
```
To use the hardware reset and/or the sleep mode with wake-up feature,
(only with ```ccs811_full``` module),
additional GPIOs have to be used. This is the most energy-efficient
hardware configuration of the sensor but requires more GPIO pins.
Used GPIOs must be configured accordingly in driver [configuration
parameters](#ccs811_driver_configuration).
```
+--------+ +--------+
| MCU | | CCS811 |
| | | |
| SCL >-------> SCL |
| SDA <-------> SDA |
| GPIOx <-------> /INT |
| GPIOy --------> /WAKE |
| GPIOz --------> /RESET |
+--------+ +--------+
```
If CCS811 sensor is used in conjunction with another sensor, e.g.,
a SHT3x sensor, the hardware configuration looks like following:
```
+--------+ +--------+
| MCU | | CCS811 |
| | | |
| SCL >--+----> SCL |
| SDA <--|-+--> SDA |
| GND ---|-|--> /WAKE |
| | | | +--------+
| | | | | SHT3x |
| | | | | |
| | +----> SCL |
| | +--> SDA |
+--------+ +--------+
```
### <a name="driver_configuration"> Driver Configuration Parameters </a> &nbsp;&nbsp; [[TOC](#toc)]
The following configuration parameters can be used to configure the
sensor during its initialization (#ccs811_init):
Parameter | Member | Define macro | Default
----------------- | -------------------------- | ---------------------- | -------
I2C device | ccs811_params_t::i2c_dev | CCS811_PARAM_I2C_DEV | #I2C_DEV(0)
I2C slave address | ccs811_params_t::i2c_addr | CCS811_PARAM_I2C_ADDR | #CCS811_I2C_ADDRESS_1
Measurement mode | ccs811_params_t::mode | CCS811_PARAM_MODE | #CCS811_MODE_1S
Interrupt mode | ccs811_params_t::int_mode | CCS811_PARAM_INT_MODE | #CCS811_INT_NONE
Interrupt pin | ccs811_params_t::int_pin | CCS811_PARAM_INT_PIN | #GPIO_PIN(0, 0)
Wake-up pin | ccs811_params_t::wake_pin | CCS811_PARAM_WAKE_PIN | #GPIO_UNDEF
Reset pin | ccs811_params_t::reset_pin | CCS811_PARAM_RESET_PIN | #GPIO_UNDEF
The default configuration of these parameters can be overridden by
defining according macros before including **ccs811_params.h**, for example:
```
#define CCS811_PARAM_I2C_DEV (I2C_DEV(1))
#define CCS811_PARAM_I2C_ADDR (CCS811_I2C_ADDRESS_2)
#define CCS811_PARAM_MODE (CCS811_MODE_10S)
#define CCS811_PARAM_RESET_PIN (GPIO_PIN(0, 0))
#define CCS811_PARAM_WAKE_PIN (GPIO_PIN(0, 1))
#define CCS811_PARAM_INT_PIN (GPIO_PIN(0, 2))
#define CCS811_PARAM_INT_MODE (CCS811_INT_DATA_READY)
...
#include "ccs811.h"
#include "ccs811_params.h"
```
Alternatively, the complete set of default configuration parameters could
also be overriden by a single definition, for example:
```
#define CCS811_PARAMS { .i2c_dev = I2C, \
.i2c_addr = CCS811_I2C_ADDRESS_2, \
.mode = CCS811_MODE_10S, \
.reset_pin = GPIO_PIN(0, 0), \
.wake_pin = GPIO_PIN(0, 1), \
.int_pin = GPIO_PIN(0, 2), \
.int_mode = CCS811_INT_DATA_READY, \
}
```
*/

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_ccs811
* @brief Default configuration for AMS CCS811 digital gas sensors
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
* @{
*/
#ifndef CCS811_PARAMS_H
#define CCS811_PARAMS_H
#include "board.h"
#include "ccs811.h"
#include "saul_reg.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name CCS811 default configuration parameters
* @{
*/
#ifndef CCS811_PARAM_I2C_DEV
#define CCS811_PARAM_I2C_DEV (I2C_DEV(0))
#endif
#ifndef CCS811_PARAM_I2C_ADDR
#define CCS811_PARAM_I2C_ADDR (CCS811_I2C_ADDRESS_1)
#endif
#ifndef CCS811_PARAM_MODE
#define CCS811_PARAM_MODE (CCS811_MODE_1S)
#endif
#ifndef CCS811_PARAM_RESET_PIN
#define CCS811_PARAM_RESET_PIN (GPIO_UNDEF)
#endif
#ifndef CCS811_PARAM_WAKE_PIN
#define CCS811_PARAM_WAKE_PIN (GPIO_UNDEF)
#endif
#ifndef CCS811_PARAM_INT_PIN
#define CCS811_PARAM_INT_PIN (GPIO_PIN(0, 0))
#endif
#ifndef CCS811_PARAM_INT_MODE
#define CCS811_PARAM_INT_MODE (CCS811_INT_NONE)
#endif
#ifndef CCS811_PARAMS
#define CCS811_PARAMS { .i2c_dev = CCS811_PARAM_I2C_DEV, \
.i2c_addr = CCS811_PARAM_I2C_ADDR, \
.mode = CCS811_PARAM_MODE, \
.int_mode = CCS811_PARAM_INT_MODE, \
.int_pin = CCS811_PARAM_INT_PIN, \
.wake_pin = CCS811_PARAM_WAKE_PIN, \
.reset_pin = CCS811_PARAM_RESET_PIN \
}
#endif
#ifndef CCS811_SAUL_INFO
#define CCS811_SAUL_INFO { .name = "ccs811" }
#endif
/**@}*/
/**
* @brief CCS811 configuration
*/
static const ccs811_params_t ccs811_params[] =
{
CCS811_PARAMS
};
/**
* @brief Additional meta information to keep in the SAUL registry
*/
static const saul_reg_info_t ccs811_saul_info[] =
{
CCS811_SAUL_INFO
};
#ifdef __cplusplus
}
#endif
#endif /* CCS811_PARAMS_H */
/** @} */

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_ccs811
* @brief Register definitions for the AMS CCS811 digital gas sensor
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
* @{
*/
#ifndef CCS811_REGS_H
#define CCS811_REGS_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name CCS811 register addresses
* @{
*/
#define CCS811_REG_STATUS (0x00)
#define CCS811_REG_MEAS_MODE (0x01)
#define CCS811_REG_ALG_RESULT_DATA (0x02)
#define CCS811_REG_RAW_DATA (0x03)
#define CCS811_REG_ENV_DATA (0x05)
#define CCS811_REG_NTC (0x06)
#define CCS811_REG_THRESHOLDS (0x10)
#define CCS811_REG_BASELINE (0x11)
#define CCS811_REG_HW_ID (0x20)
#define CCS811_REG_HW_VER (0x21)
#define CCS811_REG_FW_BOOT_VER (0x23)
#define CCS811_REG_FW_APP_VER (0x24)
#define CCS811_REG_ERROR_ID (0xe0)
#define CCS811_REG_APP_ERASE (0xf1)
#define CCS811_REG_APP_DATA (0xf2)
#define CCS811_REG_APP_VERIFY (0xf3)
#define CCS811_REG_APP_START (0xf4)
#define CCS811_REG_SW_RESET (0xff)
/** @} */
/**
* @name Status register bits (#CCS811_REG_STATUS)
* @{
*/
#define CCS811_STATUS_ERROR (0x01) /**< error occured, details in CCS811_REG_ERROR */
#define CCS811_STATUS_DATA_RDY (0x08) /**< new data sample available in ALG_RESULT_DATA */
#define CCS811_STATUS_APP_VALID (0x10) /**< valid application firmware loaded */
#define CCS811_STATUS_FW_MODE (0x80) /**< firmware is in application mode */
/** @} */
/**
* @name Error register bits (#CCS811_REG_ERROR_ID)
* @{
*/
#define CCS811_ERR_WRITE_REG_INV (0x01) /**< invalid register address on write */
#define CCS811_ERR_READ_REG_INV (0x02) /**< invalid register address on read */
#define CCS811_ERR_MEASMODE_INV (0x04) /**< invalid requested measurement mode */
#define CCS811_ERR_MAX_RESISTANCE (0x08) /**< maximum sensor resistance exceeded */
#define CCS811_ERR_HEATER_FAULT (0x10) /**< heater current not in range */
#define CCS811_ERR_HEATER_SUPPLY (0x20) /**< heater voltage not applied correctly */
/** @} */
/** CCS811 hardware ID */
#define CCS811_HW_ID (0x81)
#ifdef __cplusplus
}
#endif
#endif /* CCS811_REGS_H */
/** @} */

409
drivers/include/ccs811.h Normal file
View File

@ -0,0 +1,409 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_ccs811
* @brief Device Driver for AMS CCS811 digital gas sensor
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
* @{
*/
#ifndef CCS811_H
#define CCS811_H
#include <stdint.h>
#include "periph/i2c.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @name CCS811 I2C addresses
* @{
*/
#define CCS811_I2C_ADDRESS_1 (0x5A) /**< default */
#define CCS811_I2C_ADDRESS_2 (0x5B)
/** @} */
/**
* @name CCS811 IAQ value ranges
* @{
*/
#define CCS811_ECO2_RANGE_MIN (400) /**< eCO2 min in ppm */
#define CCS811_ECO2_RANGE_MAX (8192) /**< eCO2 max in ppm */
#define CCS811_TVOC_RANGE_MIN (0) /**< TVOC min in ppb */
#define CCS811_TVOC_RANGE_MAX (1187) /**< TVOC min in ppb */
/** @} */
/**
* @brief Driver error codes (returned as negative values)
*/
typedef enum {
CCS811_OK, /**< no error */
CCS811_ERROR_I2C, /**< I2C communication failure */
CCS811_ERROR_NO_DEV, /**< device not available */
CCS811_ERROR_NO_APP, /**< could not start application */
CCS811_ERROR_NO_NEW_DATA, /**< no new data (last valid data returned) */
CCS811_ERROR_NO_IAQ_DATA, /**< IAQ data not available in this mode */
CCS811_ERROR_WRITE_REG_INV, /**< invalid register address on write */
CCS811_ERROR_READ_REG_INV, /**< invalid register address on read */
CCS811_ERROR_MEASMODE_INV, /**< invalid measurement mode */
CCS811_ERROR_THRESH_INV, /**< invalid threshold parameters */
CCS811_ERROR_MAX_RESISTANCE, /**< maximum sensor resistance exceeded */
CCS811_ERROR_HEATER_FAULT, /**< heater current not in range */
CCS811_ERROR_HEATER_SUPPLY, /**< heater voltage not applied correctly */
CCS811_ERROR_NO_INT_PIN, /**< nINT signal pin not configured */
CCS811_ERROR_NO_WAKE_PIN, /**< nWAKE signal pin not configured */
CCS811_ERROR_NO_RESET_PIN, /**< nRESET signal pin not configured */
CCS811_ERROR_NOT_SUPPORTED, /**< function is not supported */
} ccs811_error_codes_t;
/**
* @brief CCS811 operation modes
*/
typedef enum {
CCS811_MODE_IDLE = 0, /**< Idle, low current mode */
CCS811_MODE_1S = 1, /**< Constant Power mode, IAQ values every 1 s */
CCS811_MODE_10S = 2, /**< Pulse Heating mode, IAQ values every 10 s */
CCS811_MODE_60S = 3, /**< Low Power Pulse Heating, IAQ values every 60 s */
CCS811_MODE_250MS = 4 /**< Constant Power mode, only RAW data every 250 ms */
} ccs811_mode_t;
/**
* @brief CCS811 interrupt mode
*/
typedef enum {
CCS811_INT_NONE = 0, /**< interrupt generation is disabled (default) */
CCS811_INT_DATA_READY, /**< nINT signal when new data are reade to read */
CCS811_INT_THRESHOLD, /**< nINT signal when new data reach thresholds */
} ccs811_int_mode_t;
/**
* @brief CCS811 device initialization parameters
*/
typedef struct {
i2c_t i2c_dev; /**< I2C device, clock stretching required (default I2C_DEV(0)) */
uint8_t i2c_addr; /**< I2C address (default CCS811_I2C_ADDRESS_1) */
gpio_t int_pin; /**< nINT signal pin (default GPIO_PIN(0, 0) */
gpio_t wake_pin; /**< nWAKE signal pin (default GPIO_UNDEF) */
gpio_t reset_pin; /**< nRESET signal pin (default GPIO_UNDEF) */
ccs811_mode_t mode; /**< measurement mode used (default #CCS811_MODE_IDLE) */
ccs811_int_mode_t int_mode; /**< interrupt mode used (default #CCS811_INT_NONE) */
} ccs811_params_t;
/**
* @brief CCS811 sensor device data structure
*/
typedef struct {
ccs811_params_t params; /**< device initialization parameters */
} ccs811_t;
/**
* @brief Initialize a CCS811 sensor device
*
* The function resets the CCS811 sensor, checks its availability and
* initializes it according to the given configuration parameters.
*
* If #ccs811_params_t::reset_pin is configured
* - the pin is used for the hardware reset of the sensor, otherwise
* only a software reset is tried, and
* - the #ccs811_init function can be used at any time to reset the sensor.
*
* If #ccs811_params_t::wake_pin is configured, it use to switch the sensor
* into the sleep mode while the I2C interface is not used.
*
* @param[in] dev Device descriptor of CCS811 device to be initialized
* @param[in] params Configuration parameters used by initialization
*
* @note The I2C implementation of the MCU has to support clock stretching
* to get CCS811 working.
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_init (ccs811_t *dev, const ccs811_params_t *params);
/**
* @brief Read IAQ sensor values and/or RAW sensor data
*
* The function reads the IAQ sensor values (TVOC and eCO2) and/or the raw
* sensor data. For either \p iaq_tvoc2, \p iaq_eco2, \p raw_i, or \p raw_v
* also ```NULL``` can be passed, if their value are not of interest.
*
* @note
* - If the function is called and no new data are available, the function
* returns the results of the last measurement and the error code
* #CCS811_ERROR_NO_NEW_DATA.
* - The data-ready status function #ccs811_data_ready or the data-ready
* interrupt (#CCS811_INT_DATA_READY) can be used to determine whether
* new data are available.
* - In #CCS811_MODE_250MS, only RAW data are available. In
* that case, the function fails with error_code #CCS811_ERROR_NO_IAQ_DATA
* if \p iaq_tvoc and \p iaq_eco2 parameters are not ```NULL```.
*
* @param[in] dev Device descriptor of CCS811 device to read from
* @param[out] iaq_tvoc TVOC total volatile organic compound (0..1187 ppb)
* @param[out] iaq_eco2 eCO2 equivalent CO2 (400 - 8192 ppm)
* @param[out] raw_i Current through the sensor used for measuring (0..63 uA)
* @param[out] raw_v Voltage across the sensor measured (0..1023 = 1.65 V)
*
* @retval CCS811_OK on success and new data are returned
* @retval CCS811_ERROR_NO_NEW_DATA when no new data are available and last
* measurement results are returned.
* @retval CCS811_ERROR_* otherwise, see #ccs811_error_codes_t.
*/
int ccs811_read_iaq (const ccs811_t *dev,
uint16_t *iaq_tvoc, uint16_t *iaq_eco2,
uint16_t *raw_i, uint16_t *raw_v);
#if MODULE_CCS811_FULL || DOXYGEN
/**
* @brief Read the resistance of connected NTC thermistor
*
* CCS811 supports an external interface for connecting a negative thermal
* coefficient thermistor (R_NTC) to provide a cost effective and power
* efficient means of calculating the local ambient temperature. The sensor
* measures the voltage V_NTC across the R_NTC as well as the voltage V_REF
* across a connected reference resistor (R_REF).
*
* The function returns the current resistance of R_NTC using the equation
*
* R_NTC = R_REF / V_REF * V_NTC
*
* Using the data sheet of the NTC, the ambient temperature can be calculated.
*
* @param[in] dev Device descriptor of CCS811 device to read from
* @param[in] r_ref Resistance of R_REF in Ohm
* @param[out] r_ntc Resistance of R_NTC in Ohm
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_read_ntc (const ccs811_t *dev, uint32_t r_ref, uint32_t *r_ntc);
#endif /* MODULE_CCS811_FULL || DOXYGEN */
/**
* @brief Data-ready status function
*
* The function reads the status register and returns CSS811_OK when new
* data are available. The function is usefull for polling the sensor.
*
* @param[in] dev Device descriptor of CCS811 device to read from
*
* @retval CCS811_OK when new data are available
* @retval CCS811_ERROR_NO_NEW_DATA when no new data are available
* @retval CCS811_ERROR_* otherwise, see #ccs811_error_codes_t.
*/
int ccs811_data_ready (const ccs811_t *dev);
/**
* @brief Power down the sensor
*
* The feature disables sensor measurements by entering idle mode
* (#CCS811_MODE_IDLE). In addition, the function sets the low active
* signal ** nWAKE ** to high to completely deactivate the sensor.
* The sensor is then no longer accessible via I2C. The last sensor
* measurement mode is saved.
*
* The low active **nWAKE** signal pin has to be configured
* (#ccs811_params_t::wake_pin) accordingly. Otherwise, the function fails
* and returns with #CCS811_ERROR_NO_WAKE_PIN.
*
* @param[in] dev Device descriptor of CCS811 device to read from
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_NO_WAKE_PIN #ccs811_params_t::wake_pin not configured
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_power_down (ccs811_t *dev);
/**
* @brief Power up the sensor
*
* The function sets the low active signal ** nWAKE ** to low to activate the
* sensor and switches back from the idle mode (#CCS811_MODE_IDLE) to the
* last measurement mode.
*
* The low active **nWAKE** signal pin has to be configured
* (#ccs811_params_t::wake_pin) accordingly. Otherwise, the function fails
* and returns with #CCS811_ERROR_NO_WAKE_PIN.
*
* @note It may take several minutes before accurate readings are generated
* when the sensor switches back from idle mode to the previous
* measurement mode.
*
* @param[in] dev Device descriptor of CCS811 device to read from
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_NO_WAKE_PIN #ccs811_params_t::wake_pin not configured
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_power_up (ccs811_t *dev);
/**
* @brief Set the operation mode of the sensor
*
* The function sets the operating mode of the sensor.
*
* The sensor starts periodic measurements with the specified period if the
* \p mode parameter is either
*
* - #CCS811_MODE_1S,
* - #CCS811_MODE_10S,
* - #CCS811_MODE_60S, or
* - #CCS811_MODE_250MS.
*
* The #ccs811_read_iaq function can then be used to get sensor data at the
* same rate to get the results.
*
* In case, the \p mode parameter is #CCS811_MODE_IDLE, the sensor does not
* perform any measurements.
*
* @note
* - In #CCS811_MODE_250MS, only raw data are available. IAQ values would
* have to be calculated by the host in this mode.
* - Mode timings (the period) are subject to typical 2% tolerance due to
* accuracy of internal sensor clock.
* - After setting the sensor mode, the sensor needs up to 20 minutes, before
* accurate readings are generated.
* - When the sensor operating mode is changed to a new mode with
* a lower sample rate, e.g., from #CCS811_MODE_60S to #CCS811_MODE_1S, it
* should be placed in #CCS811_MODE_IDLE for at least 10 minutes before
* enabling the new mode.
*
* @param[in] dev Device descriptor of CCS811 device
* @param[in] mode CCS811 operation mode
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_set_mode (ccs811_t *dev, ccs811_mode_t mode);
#if MODULE_CCS811_FULL || DOXYGEN
/**
* @brief Enable/disable data ready or threshold interrupt signal **nINT**
*
* CCS811 can trigger either
*
* - a data-ready interrupt (#CCS811_INT_DATA_READY) when new data become
* available or
* - a threshold interrupt (#CCS811_INT_THRESHOLD) if the new eCO2 data
* exceed defined thresholds (see #ccs811_set_eco2_thresholds).
*
* As soon as an interrupt condition occurs, the signal **nINT** is driven
* low. It stops being driven low when the sensor data is read with the
* #ccs811_read_iaq function. #ccs811_params_t::int_pin parameter has
* to be configured.
*
* With #CCS811_INT_NONE (the default), interrupt generation is disabled.
* The interrupt generation is disabled by default.
*
* @param[in] dev Device descriptor of CCS811 device
* @param[in] mode Enable the interrupt if true, otherwise disable it
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_NO_INT_PIN #ccs811_params_t::int_pin not configured
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_set_int_mode (ccs811_t *dev, ccs811_int_mode_t mode);
/**
* @brief Set environmental data
*
* If information about the environment are available from another sensor,
* they can be used by CCS811 to compensate gas readings due to
* temperature and humidity changes.
*
* @param[in] dev Device descriptor of CCS811 device
* @param[in] temp Temperature [hundredths of a degree Celsius]
* @param[in] hum Relative Humidity [hundredths of a percent]
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_set_environmental_data (const ccs811_t *dev,
int16_t temp, int16_t hum);
/**
* @brief Set eCO2 thresholds for threshold interrupts
*
* Threshold interrupts, if enabled (#ccs811_int_mode_t), are generated when
* new eCO2 value moves from the current range (LOW, MEDIUM or HIGH) to
* another range by more than one hysteresis value. The hysteresis is used
* to prevent multiple interrupts near a threshold.
*
* Ranges are defined as following:
*
* Name | Range | Value | Default
* :------|:------------------------------------------|:-------|:-------
* LOW | below the \p low parameter | > 400 | 1500
* MEDIUM | between the \p low and \p high parameters | | |
* HIGH | above the value of the \p high parameter | < 8192 | 2500
*
* @param[in] dev Device descriptor of CCS811 device
* @param[in] low Threshold LOW to MEDIUM
* @param[in] high Threshold MEDIUM to HIGH
* @param[in] hyst Hysteresis value (default 50)
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_set_eco2_thresholds (const ccs811_t *dev,
uint16_t low, uint16_t high, uint8_t hyst);
/**
* @brief Get the current baseline value from sensor
*
* The sensor supports automatic baseline correction over a minimum time of
* 24 hours. Using this function, the current baseline value can be saved
* before the sensor is powered down. This baseline can then be restored using
* the #ccs811_set_baseline function after sensor is powered up again to
* continue the automatic baseline process.
*
* @param[in] dev Device descriptor of CCS811 device
* @param[in] baseline Current baseline value
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_get_baseline (const ccs811_t *dev, uint16_t *baseline);
/**
* @brief Write a previously stored baseline value to the sensor
*
* The sensor supports automatic baseline correction over a minimum time of
* 24 hours. Using this function, a previously saved baseline
* (#ccs811_get_baseline) value can be restored after the sensor is powered
* up to continue the automatic baseline process.
*
* @note The baseline must be written after the conditioning period
* of 20 min after power up.
*
* @param[in] dev Device descriptor of CCS811 device
* @param[in] baseline Stored baseline value
*
* @retval CCS811_OK on success
* @retval CCS811_ERROR_* on error, see #ccs811_error_codes_t
*/
int ccs811_set_baseline (const ccs811_t *dev, uint16_t baseline);
#endif /* MODULE_CCS811_FULL || DOXYGEN */
#ifdef __cplusplus
}
#endif
#endif /* CCS811_H */
/** @} */

View File

@ -81,6 +81,9 @@ PSEUDOMODULES += adc081c
PSEUDOMODULES += adc101c
PSEUDOMODULES += adc121c
# full featured version of CCS811 driver as pseudo module
PSEUDOMODULES += ccs811_full
# include variants of SX127X drivers as pseudo modules
PSEUDOMODULES += sx1272
PSEUDOMODULES += sx1276

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup sys_auto_init_saul
* @brief Auto initialization of AMS CCS811 digital gas sensor driver
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#ifdef MODULE_CCS811
#include "assert.h"
#include "log.h"
#include "saul_reg.h"
#include "ccs811.h"
#include "ccs811_params.h"
/**
* @brief Define the number of configured sensors
*/
#define CCS811_NUM (sizeof(ccs811_params) / sizeof(ccs811_params[0]))
/**
* @brief Allocation of memory for device descriptors
*/
static ccs811_t ccs811_devs[CCS811_NUM];
/**
* @brief Memory for the SAUL registry entries
*/
static saul_reg_t saul_entries[CCS811_NUM * 2];
/**
* @brief Define the number of saul info
*/
#define CCS811_INFO_NUM (sizeof(ccs811_saul_info) / sizeof(ccs811_saul_info[0]))
/**
* @name Reference the driver structs.
* @{
*/
extern const saul_driver_t ccs811_saul_driver_eco2;
extern const saul_driver_t ccs811_saul_driver_tvoc;
/** @} */
void auto_init_ccs811(void)
{
assert(CCS811_INFO_NUM == CCS811_NUM);
for (unsigned i = 0; i < CCS811_NUM; i++) {
LOG_DEBUG("[auto_init_saul] initializing ccs811 #%u\n", i);
if (ccs811_init(&ccs811_devs[i], &ccs811_params[i]) != CCS811_OK) {
LOG_ERROR("[auto_init_saul] error initializing ccs811 #%u\n", i);
continue;
}
/* eCO2 */
saul_entries[(i * 2)].dev = &(ccs811_devs[i]);
saul_entries[(i * 2)].name = ccs811_saul_info[i].name;
saul_entries[(i * 2)].driver = &ccs811_saul_driver_eco2;
/* TVOC */
saul_entries[(i * 2) + 1].dev = &(ccs811_devs[i]);
saul_entries[(i * 2) + 1].name = ccs811_saul_info[i].name;
saul_entries[(i * 2) + 1].driver = &ccs811_saul_driver_tvoc;
/* register to saul */
saul_reg_add(&(saul_entries[(i * 2)]));
saul_reg_add(&(saul_entries[(i * 2) + 1]));
}
}
#else
typedef int dont_be_pedantic;
#endif /* MODULE_CCS811 */

View File

@ -0,0 +1,5 @@
include ../Makefile.tests_common
USEMODULE += ccs811
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,28 @@
# About
This is a manual test application for the CCS811 driver. It shows how the
sensor can be used for periodic polling as well as with interrupts.
# Usage
The test application demonstrates the use of the CCS811. It uses the default
configuration parameters, that is, the measurement mode
```CCS811_MODE_1S``` with one measurement per second.
The application can use both approaches to wait for new data:
1. using the data-ready interrupt (```CCS811_INT_DATA_READY```):
```
#define USE_CSS811_DATA_READY_INT (1)
```
2. using the data-ready status function (```ccs811_data_ready```)
```
#define USE_CSS811_DATA_READY_INT (0)
```
If data-ready interrupts are used, the default configuration parameter for the
interrupt pin can be overriden by ```CCS811_PARAM_INT_PIN``` before
```ccs811_params.h``` is included.
```
#define CCS811_PARAM_INT_PIN (GPIO_PIN(0, 12))
```

126
tests/driver_ccs811/main.c Normal file
View File

@ -0,0 +1,126 @@
/*
* Copyright (C) 2018 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @brief Test application for the Sensirion SHT30/SHT31/SHT35 device driver
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*
* The test application demonstrates the use of the CCS811. It uses the default
* configuration parameters, that is, the measurement mode #CCS811_MODE_1S with
* one measurement per second.
*
* The application can use both approaches to wait for new data
*
* - using the data-ready interrupt #CCS811_INT_DATA_READY or
* - using the data-ready status function #ccs811_data_ready
*
* To use the data-ready interrupt, use module ```ccs811_full``` and
* define ```USE_CCS811_DATA_READY_INT``` in CFLAGS variable of the
* make command, for example:
*
* ```
* USEMODULE=ccs811_full CFLAGS="-DUSE_CCS811_DATA_READY_INT" \
* make flash BOARD=... -C tests/driver_ccs811
* ```
*/
#include <stdio.h>
#include <string.h>
#include "thread.h"
#include "xtimer.h"
#include "ccs811.h"
#include "ccs811_params.h"
#if USE_CCS811_DATA_READY_INT && !MODULE_CCS811_FULL
#error To use interrupt handling the *ccs811_full* module has to be enabled
#endif
#define CCS811_LOW 600
#define CCS811_HIGH 1000
kernel_pid_t p_main;
#ifdef USE_CCS811_DATA_READY_INT
static void ccs811_isr (void *arg)
{
/* send a message to trigger main thread to handle the interrupt */
msg_t msg;
msg_send(&msg, p_main);
}
#endif
int main(void)
{
ccs811_t sensor;
puts("CCS811 test application\n");
printf("+------------Initializing------------+\n");
#ifdef USE_CCS811_DATA_READY_INT
gpio_init_int (ccs811_params[0].int_pin, GPIO_IN, GPIO_FALLING,
ccs811_isr, 0);
#endif /* USE_CCS811_DATA_READY_INT */
/* initialize the sensor with default configuration parameters */
if (ccs811_init (&sensor, &ccs811_params[0]) != CCS811_OK) {
puts("Initialization failed\n");
return 1;
}
#ifdef USE_CCS811_DATA_READY_INT
/* activate data ready interrupt */
if (ccs811_set_int_mode (&sensor, CCS811_INT_DATA_READY) != CCS811_OK) {
puts("Activating interrupt failed\n");
return 1;
}
#endif /* USE_CCS811_DATA_READY_INT */
/* save the pid of main thread */
p_main = sched_active_pid;
printf("\n+--------Starting Measurements--------+\n");
while (1) {
uint16_t tvoc;
uint16_t eco2;
#ifdef USE_CCS811_DATA_READY_INT
/* wait for data ready interrupt */
msg_t msg;
msg_receive(&msg);
#else /* USE_CCS811_DATA_READY_INT */
/* wait for new data by means of the data-ready status function */
while (ccs811_data_ready (&sensor) != CCS811_OK) {
/* sleep 10 ms */
xtimer_usleep(10000);
}
#endif /* USE_CCS811_DATA_READY_INT */
/* read the data */
if (ccs811_read_iaq(&sensor, &tvoc, &eco2, 0, 0) != CCS811_OK) {
/* print values */
printf("TVOC [ppb]: %d\neCO2 [ppm]: %d\n", tvoc, eco2);
puts("+-------------------------------------+");
}
else {
printf("Could not read data from sensor\n");
}
}
return 0;
}