Add CCS811 driver

This commit is contained in:
Pim van Pelt
2018-04-10 21:36:10 +02:00
parent d2e64c0f45
commit e4bb9e39cc
4 changed files with 490 additions and 45 deletions

102
include/mgos_ccs811.h Normal file
View File

@ -0,0 +1,102 @@
/*
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "mgos.h"
#include "mgos_i2c.h"
#define MGOS_CCS811_READ_DELAY (2)
#ifdef __cplusplus
extern "C" {
#endif
struct mgos_ccs811;
struct mgos_ccs811_stats {
double last_read_time; // value of mg_time() upon last call to _read()
uint32_t read; // calls to _read()
uint32_t read_success; // successful _read()
uint32_t read_success_cached; // calls to _read() which were cached
// Note: read_errors := read - read_success - read_success_cached
double read_success_usecs; // time spent in successful uncached _read()
};
// Drive modes
enum mgos_ccs811_drive_mode_t {
CCS811_DRIVE_MODE_IDLE = 0x00,
CCS811_DRIVE_MODE_1SEC = 0x01,
CCS811_DRIVE_MODE_10SEC = 0x02,
CCS811_DRIVE_MODE_60SEC = 0x03,
CCS811_DRIVE_MODE_250MS = 0x04,
};
/*
* Initialize a CCS811 on the I2C bus `i2c` at address specified in `i2caddr`
* parameter (default CCS811 is on address 0x5A). The sensor will be polled for
* validity, upon success a new `struct mgos_ccs811` is allocated and
* returned. If the device could not be found, NULL is returned.
*/
struct mgos_ccs811 *mgos_ccs811_create(struct mgos_i2c *i2c, uint8_t i2caddr);
/*
* Destroy the data structure associated with a CCS811 device. The reference
* to the pointer of the `struct mgos_ccs811` has to be provided, and upon
* successful destruction, its associated memory will be freed and the pointer
* set to NULL.
*/
void mgos_ccs811_destroy(struct mgos_ccs811 **sensor);
/*
* The sensor will be polled for its temperature and humidity data. If the poll
* has occured in the last `MGOS_CCS811_READ_DELAY` seconds, the cached data is
* used (so as not to repeatedly poll the bus upon subsequent calls).
*/
bool mgos_ccs811_read(struct mgos_ccs811 *sensor);
/*
* Set the drive mode of the CCS811 sensor based on the `mode` argument
* Returns true on success, false otherwise.
*/
bool mgos_ccs811_setDriveMode(struct mgos_ccs811 *sensor, enum mgos_ccs811_drive_mode_t mode);
/*
* Retrieve the current drive mode (which will be one of `enum mgos_ccs811_drive_mode_t`
* values into the byte pointed to by `mode`.
* Returns true on success, false otherwise.
*/
bool mgos_ccs811_getDriveMode(struct mgos_ccs811 *sensor, uint8_t *mode);
/*
*
* Returns a value on success, NAN otherwise.
*/
float mgos_ccs811_get_eco2(struct mgos_ccs811 *sensor);
/*
*
* Returns a value on success, NAN otherwise.
*/
float mgos_ccs811_get_tvoc(struct mgos_ccs811 *sensor);
/*
* Initialization function for MGOS -- currently a noop.
*/
bool mgos_ccs811_i2c_init(void);
#ifdef __cplusplus
}
#endif

View File

@ -20,103 +20,124 @@
#include "mgos_si7021.h"
#include "mgos_htu21df.h"
#include "mgos_mcp9808.h"
#include "mgos_ccs811.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#define I2CBUSNR 5
#define I2CBUSNR 5
void i2c_scanner(struct mgos_i2c *i2c) {
int i;
if (!i2c) {
LOG(LL_ERROR, ("No global I2C bus configured"));
return;
}
for(i=0x3; i<0x77; i++) {
for (i = 0x3; i < 0x77; i++) {
bool ret;
ret=mgos_i2c_read(i2c, i, NULL, 0, true);
if (ret)
LOG(LL_INFO, ("I2C Address 0x%02x %s", i, ret?"true":"false"));
ret = mgos_i2c_read(i2c, i, NULL, 0, true);
if (ret) {
LOG(LL_INFO, ("I2C Address 0x%02x %s", i, ret ? "true" : "false"));
}
}
}
bool i2c_dumpregs(struct mgos_i2c *i2c, uint8_t i2caddr) {
uint16_t reg;
int value;
int value;
for(reg=0; reg<256; reg++) {
value=mgos_i2c_read_reg_b(i2c, i2caddr, reg);
if (value<0) {
for (reg = 0; reg < 256; reg++) {
value = mgos_i2c_read_reg_b(i2c, i2caddr, reg);
if (value < 0) {
printf(" XX");
}
else {
}else {
printf(" %02x", value);
}
if (reg%16==15) printf("\n");
if (reg % 16 == 15) {
printf("\n");
}
}
printf("\n");
return true;
}
bool do_ccs811(struct mgos_ccs811 *sensor) {
float eco2, tvoc;
if (!sensor) {
return false;
}
eco2 = mgos_ccs811_get_eco2(sensor);
tvoc = mgos_ccs811_get_tvoc(sensor);
LOG(LL_INFO, ("eCO2=%.0fppm TVOC=%.0fppb", eco2, tvoc));
return true;
}
bool do_sht31(struct mgos_sht31 *sensor) {
float temp, humid;
if (!sensor) return false;
if (!sensor) {
return false;
}
temp=mgos_sht31_getTemperature(sensor);
humid=mgos_sht31_getHumidity(sensor);
temp = mgos_sht31_getTemperature(sensor);
humid = mgos_sht31_getHumidity(sensor);
LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid));
return true;
}
bool do_si7021(struct mgos_si7021 *sensor) {
float temp, humid;
if (!sensor) return false;
if (!sensor) {
return false;
}
temp=mgos_si7021_getTemperature(sensor);
humid=mgos_si7021_getHumidity(sensor);
temp = mgos_si7021_getTemperature(sensor);
humid = mgos_si7021_getHumidity(sensor);
LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid));
return true;
}
bool do_htu21df(struct mgos_htu21df *sensor) {
float temp, humid;
if (!sensor) return false;
if (!sensor) {
return false;
}
temp=mgos_htu21df_getTemperature(sensor);
humid=mgos_htu21df_getHumidity(sensor);
temp = mgos_htu21df_getTemperature(sensor);
humid = mgos_htu21df_getHumidity(sensor);
LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid));
return true;
}
bool do_mcp9808(struct mgos_mcp9808 *sensor) {
float temp;
if (!sensor) return false;
if (!sensor) {
return false;
}
temp=mgos_mcp9808_getTemperature(sensor);
temp = mgos_mcp9808_getTemperature(sensor);
LOG(LL_INFO, ("temperature=%.2fC", temp));
return true;
}
int main() {
struct mgos_i2c *i2c;
struct mgos_si7021 *si7021;
struct mgos_sht31 *sht31;
struct mgos_i2c * i2c;
struct mgos_si7021 * si7021;
struct mgos_sht31 * sht31;
struct mgos_htu21df *htu21df;
struct mgos_mcp9808 *mcp9808;
struct mgos_ccs811 * ccs811;
if (!mgos_i2c_open(I2CBUSNR)) {
LOG(LL_ERROR, ("Cannot open I2C bus %u", I2CBUSNR));
@ -129,23 +150,32 @@ int main() {
i2c_scanner(i2c);
if (!(sht31 = mgos_sht31_create(i2c, 0x44)))
LOG(LL_ERROR, ("Cannot create SHT31 device"));
/*
* if (!(sht31 = mgos_sht31_create(i2c, 0x44)))
* LOG(LL_ERROR, ("Cannot create SHT31 device"));
*
* if (!(si7021 = mgos_si7021_create(i2c, 0x40)))
* LOG(LL_ERROR, ("Cannot create SI7021 device"));
*
* if (!(htu21df = mgos_htu21df_create(i2c, 0x40)))
* LOG(LL_ERROR, ("Cannot create HTU21DF device"));
*
* if (!(mcp9808 = mgos_mcp9808_create(i2c, 0x18)))
* LOG(LL_ERROR, ("Cannot create MCP9808 device"));
*/
if (!(si7021 = mgos_si7021_create(i2c, 0x40)))
LOG(LL_ERROR, ("Cannot create SI7021 device"));
if (!(htu21df = mgos_htu21df_create(i2c, 0x40)))
LOG(LL_ERROR, ("Cannot create HTU21DF device"));
if (!(mcp9808 = mgos_mcp9808_create(i2c, 0x18)))
LOG(LL_ERROR, ("Cannot create MCP9808 device"));
if (!(ccs811 = mgos_ccs811_create(i2c, 0x5A))) {
LOG(LL_ERROR, ("Cannot create CCS811 device"));
}
for (;;) {
do_sht31(sht31);
do_si7021(si7021);
do_htu21df(htu21df);
do_mcp9808(mcp9808);
/*
* do_sht31(sht31);
* do_si7021(si7021);
* do_htu21df(htu21df);
* do_mcp9808(mcp9808);
*/
do_ccs811(ccs811);
sleep(5);
}
@ -153,6 +183,7 @@ int main() {
mgos_si7021_destroy(&si7021);
mgos_htu21df_destroy(&htu21df);
mgos_mcp9808_destroy(&mcp9808);
mgos_ccs811_destroy(&ccs811);
return 0;
}

238
src/mgos_ccs811.c Normal file
View File

@ -0,0 +1,238 @@
/*
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mgos.h"
#include "mgos_ccs811_internal.h"
#include "mgos_i2c.h"
// Datasheet:
// https://cdn-shop.adafruit.com/product-files/2857/Sensirion_Humidity_SHT3x_Datasheet_digital-767294.pdf
// Private functions follow
static bool mgos_ccs811_getStatus(struct mgos_ccs811 *sensor, uint8_t *status) {
int ret;
if (!sensor) {
return false;
}
ret = mgos_i2c_read_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_STATUS);
if (ret < 0) {
return false;
}
*status = (uint8_t)ret;
return true;
}
static bool mgos_ccs811_getMeasMode(struct mgos_ccs811 *sensor, uint8_t *meas_mode) {
int ret;
if (!sensor) {
return false;
}
ret = mgos_i2c_read_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_MEAS_MODE);
if (ret < 0) {
return false;
}
*meas_mode = (uint8_t)ret;
return true;
}
// Return true if sensor status register has error-bit set (or upon read failure)
/*
* static bool mgos_ccs811_error(struct mgos_ccs811 *sensor) {
* uint8_t status;
*
* if (!mgos_ccs811_getStatus(sensor, &status))
* return true;
* // bits -- 7:FW_MODE 6:APP_ERASE 5:APP_VERIFY 4:APP_VALID 3:DATA_READY 0:ERROR
* return (status & MGOS_CCS811_STATUS_ERR);
* }
*/
// Return true if sensor status register has data-ready set (false upon read failure)
static bool mgos_ccs811_dataready(struct mgos_ccs811 *sensor) {
uint8_t status;
if (!mgos_ccs811_getStatus(sensor, &status)) {
return false;
}
return status & MGOS_CCS811_STATUS_DATA_READY;
}
static bool mgos_ccs811_reset(struct mgos_ccs811 *sensor) {
uint8_t data[5] = { MGOS_CCS811_REG_SW_RESET, 0x11, 0xE5, 0x72, 0x8A };
if (!sensor) {
return false;
}
return mgos_i2c_write(sensor->i2c, sensor->i2caddr, data, 5, true);
}
// Private functions end
// Public functions follow
struct mgos_ccs811 *mgos_ccs811_create(struct mgos_i2c *i2c, uint8_t i2caddr) {
struct mgos_ccs811 *sensor;
int success = 0;
int ret;
if (!i2c) {
return NULL;
}
ret = mgos_i2c_read_reg_b(i2c, i2caddr, MGOS_CCS811_REG_HW_ID);
if (ret != MGOS_CCS811_HW_ID_CODE) {
LOG(LL_ERROR, ("Failed to detect CCS811 at I2C 0x%02x", i2caddr));
return NULL;
}
sensor = calloc(1, sizeof(struct mgos_ccs811));
if (!sensor) {
return NULL;
}
memset(sensor, 0, sizeof(struct mgos_ccs811));
sensor->i2caddr = i2caddr;
sensor->i2c = i2c;
sensor->eco2 = 400;
// Boot the application on CCS811.
mgos_ccs811_reset(sensor);
mgos_usleep(2000);
uint8_t cmd = MGOS_CCS811_BOOTLOADER_REG_APP_START;
uint8_t status = MGOS_CCS811_STATUS_ERR;
uint8_t drive_mode = CCS811_DRIVE_MODE_IDLE;
mgos_i2c_write(sensor->i2c, sensor->i2caddr, &cmd, 1, true);
mgos_usleep(2000);
// Read status (expecting FW_MODE to be set and ERR to be clear)
mgos_ccs811_getStatus(sensor, &status);
if (!(status & MGOS_CCS811_STATUS_FW_MODE) || (status & MGOS_CCS811_STATUS_ERR)) {
LOG(LL_ERROR, ("CCS811 invalid firmware mode, and/or status error"));
goto exit;
}
mgos_ccs811_setDriveMode(sensor, CCS811_DRIVE_MODE_1SEC);
mgos_ccs811_getDriveMode(sensor, &drive_mode);
if (drive_mode != CCS811_DRIVE_MODE_1SEC) {
LOG(LL_ERROR, ("CCS811 failed to set drive mode"));
goto exit;
}
success = 1;
LOG(LL_INFO, ("CCS811 created at I2C 0x%02x", i2caddr));
exit:
if (!success) {
free(sensor);
sensor = NULL;
}
return sensor;
}
bool mgos_ccs811_getDriveMode(struct mgos_ccs811 *sensor, uint8_t *mode) {
uint8_t meas_mode;
if (!mgos_ccs811_getMeasMode(sensor, &meas_mode)) {
return false;
}
// bits -- 6:4 DRIVE_MOCE 3: Interrupt enable 2: Int on Threshhold
meas_mode >>= 4;
meas_mode &= 0x07;
*mode = meas_mode;
return true;
}
bool mgos_ccs811_setDriveMode(struct mgos_ccs811 *sensor, enum mgos_ccs811_drive_mode_t mode) {
uint8_t meas_mode;
// bits -- 6:4 DRIVE_MOCE 3: Interrupt enable 2: Int on Threshhold
meas_mode = (mode << 4);
return mgos_i2c_write_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_MEAS_MODE, meas_mode);
}
void mgos_ccs811_destroy(struct mgos_ccs811 **sensor) {
if (!*sensor) {
return;
}
free(*sensor);
*sensor = NULL;
return;
}
bool mgos_ccs811_read(struct mgos_ccs811 *sensor) {
double start = mg_time();
if (!sensor || !sensor->i2c) {
return false;
}
sensor->stats.read++;
if (start - sensor->stats.last_read_time < MGOS_CCS811_READ_DELAY) {
sensor->stats.read_success_cached++;
return true;
}
// Read out sensor data here
//
if (!mgos_ccs811_dataready(sensor)) {
sensor->stats.read_success_cached++;
return true;
}
uint8_t data[8];
uint8_t cmd = MGOS_CCS811_REG_ALG_RESULT_DATA;
data[4] = MGOS_CCS811_STATUS_ERR;
mgos_i2c_write(sensor->i2c, sensor->i2caddr, &cmd, 1, false);
mgos_i2c_read(sensor->i2c, sensor->i2caddr, data, 8, true);
// bytes 0-1:eco2 2-3:tvoc 4:status 5:error 6-7: raw_data
if (data[4] & MGOS_CCS811_STATUS_ERR) {
LOG(LL_ERROR, ("Read error 0x%02x", data[5]));
return false;
}
sensor->eco2 = ((uint16_t)data[0] << 8) | ((uint16_t)data[1]);
sensor->tvoc = ((uint16_t)data[2] << 8) | ((uint16_t)data[3]);
LOG(LL_DEBUG, ("eCO2=%u TVOC=%u", sensor->eco2, sensor->tvoc));
sensor->stats.read_success++;
sensor->stats.read_success_usecs += 1000000 * (mg_time() - start);
sensor->stats.last_read_time = start;
return true;
}
float mgos_ccs811_get_eco2(struct mgos_ccs811 *sensor) {
if (!mgos_ccs811_read(sensor)) {
return NAN;
}
return (float)sensor->eco2;
}
float mgos_ccs811_get_tvoc(struct mgos_ccs811 *sensor) {
if (!mgos_ccs811_read(sensor)) {
return NAN;
}
return (float)sensor->tvoc;
}
bool mgos_ccs811_i2c_init(void) {
return true;
}
// Public functions end

View File

@ -0,0 +1,74 @@
/*
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "mgos.h"
#include "mgos_i2c.h"
#include "mgos_ccs811.h"
#include <math.h>
#define MGOS_SHT31_DEFAULT_I2CADDR (0x5A)
// Registers
#define MGOS_CCS811_REG_STATUS (0x00)
#define MGOS_CCS811_REG_MEAS_MODE (0x01)
#define MGOS_CCS811_REG_ALG_RESULT_DATA (0x02)
#define MGOS_CCS811_REG_RAW_DATA (0x03)
#define MGOS_CCS811_REG_ENV_DATA (0x05)
#define MGOS_CCS811_REG_NTC (0x06)
#define MGOS_CCS811_REG_THRESHOLDS (0x10)
#define MGOS_CCS811_REG_BASELINE (0x11)
#define MGOS_CCS811_REG_HW_ID (0x20)
#define MGOS_CCS811_REG_HW_VERSION (0x21)
#define MGOS_CCS811_REG_FW_BOOT_VERSION (0x23)
#define MGOS_CCS811_REG_FW_APP_VERSION (0x24)
#define MGOS_CCS811_REG_ERROR_ID (0xE0)
#define MGOS_CCS811_REG_SW_RESET (0xFF)
// Bootloader registers
#define MGOS_CCS811_BOOTLOADER_REG_APP_ERASE (0xF1)
#define MGOS_CCS811_BOOTLOADER_REG_APP_DATA (0xF2)
#define MGOS_CCS811_BOOTLOADER_REG_APP_VERIFY (0xF3)
#define MGOS_CCS811_BOOTLOADER_REG_APP_START (0xF4)
// Status register bits
#define MGOS_CCS811_STATUS_ERR (0x01)
#define MGOS_CCS811_STATUS_DATA_READY (0x08)
#define MGOS_CCS811_STATUS_APP_VALID (0x10)
#define MGOS_CCS811_STATUS_FW_MODE (0x80)
// Other defines
#define MGOS_CCS811_HW_ID_CODE (0x81)
#define MGOS_CCS811_REF_RESISTOR (100000)
#ifdef __cplusplus
extern "C" {
#endif
struct mgos_ccs811 {
struct mgos_i2c * i2c;
uint8_t i2caddr;
struct mgos_ccs811_stats stats;
float temperature_offset;
uint16_t tvoc;
uint16_t eco2;
};
#ifdef __cplusplus
}
#endif