mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-17 04:52:59 +01:00
drivers: add driver for CCS811 gas sensor
This commit is contained in:
parent
fa38f05574
commit
74e1ab220c
@ -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
|
||||
|
@ -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
1
drivers/ccs811/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
684
drivers/ccs811/ccs811.c
Normal file
684
drivers/ccs811/ccs811.c
Normal 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 *)®, 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 *)®, 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 *)®, 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 *)®, 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 *)®, 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);
|
||||
}
|
78
drivers/ccs811/ccs811_saul.c
Normal file
78
drivers/ccs811/ccs811_saul.c
Normal 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
459
drivers/ccs811/doc.txt
Normal 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> [[TOC](#toc)]
|
||||
|
||||
### <a name="about"> About the sensor </a> [[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> [[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> [[TOC](#toc)]
|
||||
|
||||
### <a name="sensor_modes"> Sensor modes </a> [[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> [[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> [[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> [[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> [[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> [[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> [[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> [[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> [[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> [[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> [[TOC](#toc)]
|
||||
|
||||
### <a name="hardware_configuration"> Hardware Configurations </a> [[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> [[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, \
|
||||
}
|
||||
```
|
||||
*/
|
90
drivers/ccs811/include/ccs811_params.h
Normal file
90
drivers/ccs811/include/ccs811_params.h
Normal 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 */
|
||||
/** @} */
|
81
drivers/ccs811/include/ccs811_regs.h
Normal file
81
drivers/ccs811/include/ccs811_regs.h
Normal 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
409
drivers/include/ccs811.h
Normal 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 */
|
||||
/** @} */
|
@ -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
|
||||
|
82
sys/auto_init/saul/auto_init_ccs811.c
Normal file
82
sys/auto_init/saul/auto_init_ccs811.c
Normal 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 */
|
5
tests/driver_ccs811/Makefile
Normal file
5
tests/driver_ccs811/Makefile
Normal file
@ -0,0 +1,5 @@
|
||||
include ../Makefile.tests_common
|
||||
|
||||
USEMODULE += ccs811
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
28
tests/driver_ccs811/README.md
Normal file
28
tests/driver_ccs811/README.md
Normal 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
126
tests/driver_ccs811/main.c
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user