mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
358 lines
10 KiB
C
358 lines
10 KiB
C
/*
|
|
* Copyright (C) 2021 Inria
|
|
*
|
|
* 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_sgp30
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Device driver implementation for the sensors
|
|
*
|
|
* @author Francisco Molina <francois-xavier.molina@inria.fr>
|
|
*
|
|
* @}
|
|
*/
|
|
#include <string.h>
|
|
|
|
#include "checksum/crc8.h"
|
|
#include "irq.h"
|
|
#include "sgp30.h"
|
|
#include "sgp30_constants.h"
|
|
#include "sgp30_params.h"
|
|
#include "ztimer.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
/**
|
|
* @name Address pointer values for all SGP30 I2C commands
|
|
* @{
|
|
*/
|
|
typedef enum {
|
|
SGP30_CMD_INIT_AIR_QUALITY = 0x2003, /**< Start measurement mode */
|
|
SGP30_CMD_MEASURE_AIR_QUALITY = 0x2008, /**< Measure air quality values */
|
|
SGP30_CMD_GET_BASELINE = 0x2015, /**< Get baseline values */
|
|
SGP30_CMD_SET_BASELINE = 0x201e, /**< Set baseline values CMD */
|
|
SGP30_CMD_SET_HUMIDITY = 0x2061, /**< Set absolute humidity value
|
|
for compensation */
|
|
SGP30_CMD_MEASURE_TEST = 0x2032, /**< Perform on-chip self test */
|
|
SGP30_CMD_GET_FEATURE_SET_VERSION = 0x202f, /**< Get feature set version */
|
|
SGP30_CMD_MEASURE_RAW_SIGNALS = 0x2050, /**< Read raw H2 and Ethanol signals */
|
|
SGP30_CMD_READ_SERIAL = 0x3682, /**< Read serial number */
|
|
SGP30_CMD_SOFT_RESET = 0x0006, /**< Perform General Call reset */
|
|
} sgp30_cmd_t;
|
|
/** @} */
|
|
|
|
/**
|
|
* @name Max delays for SGP30 I2C commands completion
|
|
* @{
|
|
*/
|
|
#define SGP30_DELAY_INIT_AIR_QUALITY (10 * US_PER_MS)
|
|
#define SGP30_DELAY_MEASURE_AIR_QUALITY (12 * US_PER_MS)
|
|
#define SGP30_DELAY_GET_BASELINE (10 * US_PER_MS)
|
|
#define SGP30_DELAY_SET_BASELINE (10 * US_PER_MS)
|
|
#define SGP30_DELAY_SET_HUMIDITY (10 * US_PER_MS)
|
|
#define SGP30_DELAY_MEASURE_TEST (220 * US_PER_MS)
|
|
#define SGP30_DELAY_GET_FEATURE_SET_VERSION (2 * US_PER_MS)
|
|
#define SGP30_DELAY_MEASURE_RAW_SIGNALS (25 * US_PER_MS)
|
|
#define SGP30_DELAY_READ_SERIAL (500)
|
|
#define SGP30_DELAY_SOFT_RESET (60 * US_PER_MS)
|
|
/** @} */
|
|
|
|
/* Device power up time */
|
|
#define SGP30_POWER_UP_TIME (60 * US_PER_MS)
|
|
/* Polynomial used in crc check */
|
|
#define SGP30_CRC8_POLYNOMIAL 0x31
|
|
/* Seed used in crc check */
|
|
#define SGP30_CRC8_SEED 0xFF
|
|
|
|
static inline uint8_t _crc8(const void *buf, size_t len)
|
|
{
|
|
return crc8(buf, len, SGP30_CRC8_POLYNOMIAL, SGP30_CRC8_SEED);
|
|
}
|
|
|
|
void _set_uint16_and_crc(uint8_t *buf, uint16_t *val)
|
|
{
|
|
buf[0] = *val >> 8;
|
|
buf[1] = *val & 0xFF;
|
|
buf[2] = _crc8(buf, sizeof(uint16_t));
|
|
}
|
|
|
|
int _get_uint16_and_check_crc(uint8_t *buf, uint16_t *val)
|
|
{
|
|
if (_crc8(buf, sizeof(uint16_t)) == buf[2]) {
|
|
*val = (buf[0] << 8) + buf[1];
|
|
return 0;
|
|
}
|
|
return -EBADMSG;
|
|
}
|
|
|
|
static int _rx_tx_data(sgp30_t *dev, uint16_t cmd, uint8_t *data,
|
|
size_t len, uint32_t delay, bool do_read)
|
|
{
|
|
int res = 0;
|
|
|
|
i2c_acquire(dev->params.i2c_dev);
|
|
|
|
uint8_t frame_cmd[sizeof(cmd) + len];
|
|
frame_cmd[0] = cmd >> 8;
|
|
frame_cmd[1] = cmd & 0xFF;
|
|
|
|
if (len == 0 || do_read) {
|
|
res = i2c_write_bytes(dev->params.i2c_dev, SGP30_I2C_ADDRESS,
|
|
&frame_cmd[0], sizeof(cmd), 0);
|
|
}
|
|
if (res == 0 && do_read) {
|
|
/* delay for command completion */
|
|
if (!irq_is_in()) {
|
|
ztimer_sleep(ZTIMER_USEC, delay);
|
|
}
|
|
else {
|
|
ztimer_spin(ZTIMER_USEC, delay);
|
|
}
|
|
res = i2c_read_bytes(dev->params.i2c_dev, SGP30_I2C_ADDRESS,
|
|
data, len, 0);
|
|
|
|
}
|
|
else if (res == 0 && !do_read) {
|
|
memcpy(&frame_cmd[2], data, len);
|
|
res = i2c_write_bytes(dev->params.i2c_dev, SGP30_I2C_ADDRESS,
|
|
frame_cmd, sizeof(cmd) + len, 0);
|
|
if (res) {
|
|
DEBUG_PUTS("[sgp30]: failed to write data");
|
|
}
|
|
}
|
|
|
|
i2c_release(dev->params.i2c_dev);
|
|
|
|
return res;
|
|
}
|
|
|
|
int _read_measurements(sgp30_t *dev, sgp30_data_t *data)
|
|
{
|
|
uint8_t frame[6];
|
|
|
|
if (_rx_tx_data(dev, SGP30_CMD_MEASURE_AIR_QUALITY, frame, sizeof(frame),
|
|
SGP30_DELAY_MEASURE_AIR_QUALITY, true)) {
|
|
return -EPROTO;
|
|
}
|
|
if (_get_uint16_and_check_crc(&frame[0], &data->eco2) ||
|
|
_get_uint16_and_check_crc(&frame[3], &data->tvoc)) {
|
|
return -EBADMSG;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef MODULE_SGP30_STRICT
|
|
static void _read_cb(void *arg)
|
|
{
|
|
sgp30_t *dev = (sgp30_t *)arg;
|
|
|
|
if (!dev->ready) {
|
|
dev->ready = true;
|
|
}
|
|
_read_measurements(dev, &dev->_data);
|
|
dev->_data.timestamp = ztimer_now(ZTIMER_USEC);
|
|
ztimer_set(ZTIMER_USEC, &dev->_timer, SGP30_RECOMMENDED_SAMPLING_PERIOD);
|
|
}
|
|
#endif
|
|
|
|
int sgp30_start_air_quality(sgp30_t *dev)
|
|
{
|
|
int ret = _rx_tx_data(dev, SGP30_CMD_INIT_AIR_QUALITY, NULL, 0,
|
|
SGP30_DELAY_INIT_AIR_QUALITY, false);
|
|
|
|
#ifdef MODULE_SGP30_STRICT
|
|
if (ret == 0) {
|
|
ztimer_set(ZTIMER_USEC, &dev->_timer, SGP30_AIR_QUALITY_INIT_DELAY_US);
|
|
}
|
|
#endif
|
|
return ret ? -EPROTO : 0;
|
|
}
|
|
|
|
int sgp30_init(sgp30_t *dev, const sgp30_params_t *params)
|
|
{
|
|
assert(dev && params);
|
|
dev->params = *params;
|
|
#ifdef MODULE_SGP30_STRICT
|
|
dev->ready = false;
|
|
dev->_timer.callback = _read_cb;
|
|
dev->_timer.arg = dev;
|
|
#endif
|
|
|
|
/* delay while powering up */
|
|
ztimer_sleep(ZTIMER_USEC, SGP30_POWER_UP_TIME);
|
|
|
|
/* read future set */
|
|
uint16_t version;
|
|
sgp30_read_future_set(dev, &version);
|
|
if (version < SGP30_REQUIRED_FEATURE_SET) {
|
|
DEBUG("[sgp30]: wrong version number, %04x instead of %04x\n",
|
|
version, SGP30_REQUIRED_FEATURE_SET);
|
|
return -1;
|
|
}
|
|
|
|
/* read serial id */
|
|
uint8_t serial[SGP30_SERIAL_ID_LEN];
|
|
if (sgp30_read_serial_number(dev, serial, SGP30_SERIAL_ID_LEN)) {
|
|
DEBUG_PUTS("[sgp30]: could not read serial number");
|
|
return -1;
|
|
}
|
|
if (IS_ACTIVE(ENABLE_DEBUG)) {
|
|
DEBUG_PUTS("[sgp30]: serial");
|
|
for (uint8_t i = 0; i < SGP30_SERIAL_ID_LEN; i++) {
|
|
DEBUG("%02x ", serial[i]);
|
|
}
|
|
DEBUG_PUTS("\n");
|
|
}
|
|
|
|
/* start air quality measurement */
|
|
if (sgp30_start_air_quality(dev)) {
|
|
DEBUG_PUTS("[sgp30]: could not start air quality measurements ");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sgp30_reset(sgp30_t *dev)
|
|
{
|
|
assert(dev);
|
|
int ret = _rx_tx_data(dev, SGP30_CMD_SOFT_RESET, NULL, 0, SGP30_DELAY_SOFT_RESET, false);
|
|
#ifdef MODULE_SGP30_STRICT
|
|
if (ret == 0) {
|
|
ztimer_remove(ZTIMER_USEC, &dev->_timer);
|
|
dev->ready = false;
|
|
}
|
|
#endif
|
|
return ret ? -EPROTO : 0;
|
|
}
|
|
|
|
int sgp30_read_serial_number(sgp30_t *dev, uint8_t *buf, size_t len)
|
|
{
|
|
(void) len;
|
|
assert(dev && buf && (len == SGP30_SERIAL_ID_LEN));
|
|
uint8_t frame[9];
|
|
if (_rx_tx_data(dev, SGP30_CMD_READ_SERIAL, (uint8_t *)frame, sizeof(frame),
|
|
SGP30_DELAY_READ_SERIAL, true)) {
|
|
DEBUG_PUTS("[sgp30]: fail read");
|
|
return -EPROTO;
|
|
}
|
|
/* the serial id is in big endian format */
|
|
uint16_t tmp[SGP30_SERIAL_ID_LEN/2];
|
|
if (_get_uint16_and_check_crc(&frame[0], &tmp[2]) ||
|
|
_get_uint16_and_check_crc(&frame[3], &tmp[1]) ||
|
|
_get_uint16_and_check_crc(&frame[6], &tmp[0])) {
|
|
DEBUG_PUTS("[sgp30]: wrong crc");
|
|
return -EBADMSG;
|
|
}
|
|
memcpy(buf, tmp, SGP30_SERIAL_ID_LEN);
|
|
return 0;
|
|
}
|
|
|
|
int sgp30_read_future_set(sgp30_t *dev, uint16_t *version)
|
|
{
|
|
uint8_t frame[3];
|
|
|
|
if (_rx_tx_data(dev, SGP30_CMD_GET_FEATURE_SET_VERSION, frame, sizeof(frame),
|
|
SGP30_DELAY_GET_FEATURE_SET_VERSION, true)) {
|
|
return -EPROTO;
|
|
}
|
|
if (_get_uint16_and_check_crc(&frame[0], version)) {
|
|
DEBUG_PUTS("[sgp30]: wrong crc");
|
|
return -EBADMSG;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int sgp30_set_absolute_humidity(sgp30_t *dev, uint32_t a_humidity)
|
|
{
|
|
/* max value is (255g/m3+255/256g/m3), or 255999 mg/m3*/
|
|
if (a_humidity > 256000LU) {
|
|
return -1;
|
|
}
|
|
|
|
/* scale down to g/m^3 */
|
|
uint16_t humidity_scaled =
|
|
(uint16_t)(((uint64_t)a_humidity * 256 * 16777) >> 24);
|
|
|
|
uint8_t frame[3];
|
|
_set_uint16_and_crc(&frame[0], &humidity_scaled);
|
|
int ret = _rx_tx_data(dev, SGP30_CMD_SET_HUMIDITY, frame, sizeof(frame),
|
|
SGP30_DELAY_SET_HUMIDITY, false);
|
|
return ret ? -EPROTO : 0;
|
|
}
|
|
|
|
int sgp30_set_baseline(sgp30_t *dev, sgp30_data_t *data)
|
|
{
|
|
uint8_t frame[6];
|
|
|
|
_set_uint16_and_crc(&frame[0], &data->eco2);
|
|
_set_uint16_and_crc(&frame[3], &data->tvoc);
|
|
|
|
int ret = _rx_tx_data(dev, SGP30_CMD_SET_BASELINE, frame, sizeof(frame),
|
|
SGP30_DELAY_SET_BASELINE, false);
|
|
return ret ? -EPROTO : 0;
|
|
}
|
|
|
|
int sgp30_get_baseline(sgp30_t *dev, sgp30_data_t *data)
|
|
{
|
|
uint8_t frame[6];
|
|
|
|
if (_rx_tx_data(dev, SGP30_CMD_GET_BASELINE, frame, sizeof(frame),
|
|
SGP30_DELAY_GET_BASELINE, true)) {
|
|
return -EPROTO;
|
|
}
|
|
if (_get_uint16_and_check_crc(&frame[0], &data->eco2) ||
|
|
_get_uint16_and_check_crc(&frame[3], &data->tvoc)) {
|
|
return -EBADMSG;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int sgp30_read_measurements(sgp30_t *dev, sgp30_data_t *data)
|
|
{
|
|
#ifdef MODULE_SGP30_STRICT
|
|
if (dev->ready) {
|
|
unsigned state = irq_disable();
|
|
memcpy(data, &dev->_data, sizeof(sgp30_data_t));
|
|
irq_restore(state);
|
|
return 0;
|
|
}
|
|
else {
|
|
return -EAGAIN;
|
|
}
|
|
return 0;
|
|
#else
|
|
return _read_measurements(dev, data);
|
|
#endif
|
|
}
|
|
|
|
int sgp30_read_raw_measurements(sgp30_t *dev, sgp30_raw_data_t *data)
|
|
{
|
|
uint8_t frame[6];
|
|
|
|
if (_rx_tx_data(dev, SGP30_CMD_MEASURE_RAW_SIGNALS, frame, sizeof(frame),
|
|
SGP30_DELAY_MEASURE_RAW_SIGNALS, true)) {
|
|
return -EPROTO;
|
|
}
|
|
if (_get_uint16_and_check_crc(&frame[0], &data->raw_ethanol) ||
|
|
_get_uint16_and_check_crc(&frame[3], &data->raw_h2)) {
|
|
DEBUG_PUTS("[sgp30]: wrong crc");
|
|
return -EBADMSG;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef MODULE_SGP30_STRICT
|
|
bool sgp30_ready(sgp30_t *dev)
|
|
{
|
|
return dev->ready;
|
|
}
|
|
#endif
|