From 900e4b61dcf43bacb694acbd7e31bd774899f655 Mon Sep 17 00:00:00 2001 From: nagrawal Date: Mon, 6 Jul 2020 16:04:44 +0200 Subject: [PATCH] driver/scd30: Add driver for Sensirion SCD30 Created tests for Sensirion scd30 driver Moved Makefile.dep and Makefile.include as per new spec --- drivers/include/scd30.h | 174 +++++++++++ drivers/saul/init_devs/auto_init_scd30.c | 85 ++++++ drivers/saul/init_devs/init.c | 4 + drivers/scd30/Makefile | 1 + drivers/scd30/Makefile.dep | 5 + drivers/scd30/Makefile.include | 2 + drivers/scd30/include/scd30_internal.h | 61 ++++ drivers/scd30/include/scd30_params.h | 86 ++++++ drivers/scd30/scd30.c | 363 +++++++++++++++++++++++ drivers/scd30/scd30_saul.c | 103 +++++++ tests/driver_scd30/Makefile | 12 + tests/driver_scd30/README.md | 18 ++ tests/driver_scd30/main.c | 88 ++++++ 13 files changed, 1002 insertions(+) create mode 100644 drivers/include/scd30.h create mode 100644 drivers/saul/init_devs/auto_init_scd30.c create mode 100644 drivers/scd30/Makefile create mode 100644 drivers/scd30/Makefile.dep create mode 100644 drivers/scd30/Makefile.include create mode 100644 drivers/scd30/include/scd30_internal.h create mode 100644 drivers/scd30/include/scd30_params.h create mode 100644 drivers/scd30/scd30.c create mode 100644 drivers/scd30/scd30_saul.c create mode 100644 tests/driver_scd30/Makefile create mode 100644 tests/driver_scd30/README.md create mode 100644 tests/driver_scd30/main.c diff --git a/drivers/include/scd30.h b/drivers/include/scd30.h new file mode 100644 index 0000000000..08bc44a9e4 --- /dev/null +++ b/drivers/include/scd30.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 Puhang Ding + * 2020 Jan Schlichter + * 2020 Nishchay Agrawal + * + * 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. + */ + +/** + * @defgroup drivers_scd30 SCD30 CO2, temperature and humidity sensor + * @ingroup drivers_sensors + * @{ + * @file + * @brief Device driver interface for the SCD30 sensor. + * + * @author Nishchay Agrawal + * @author Puhang Ding + * @author Jan Schlichter + * @} + */ + +#ifndef SCD30_H +#define SCD30_H + +#include "periph/i2c.h" +#include "saul.h" +#include "xtimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief SCD30 Configuration parameter commands + */ +#define SCD30_VERSION 0xD100 /**< Get scd30 version */ +#define SCD30_STATUS 0x0202 /**< Get data ready status from scd30 + device */ + +#define SCD30_DATA 0x0300 /**< Get data from scd30 device */ + +#define SCD30_START 0x0010 /**< Start measuring data from scd30 device */ +#define SCD30_STOP 0x0104 /**< Stop measuring data from scd30 device */ +#define SCD30_SOFT_RESET 0xD304 /**< Soft reset scd30 device */ + +#define SCD30_INTERVAL 0x4600 /**< Set measurement interval + (2-1800 seconds) */ +#define SCD30_ASC 0x5306 /**< De-Activate Automatic Self-Calibration */ +#define SCD30_FRC 0x5204 /**< Forced Recalibration. 400-2000ppm */ +#define SCD30_T_OFFSET 0x5403 /**< Set temperature Offset in 0.01 Celsius */ +#define SCD30_A_OFFSET 0x5102 /**< Altitude Compensation in meters above + sea level */ + +/** + * @brief Status and error codes for return values + */ +enum { + SCD30_OK = 0, + SCD30_COM_FAILED = -1, /**< Communication with device failed. */ + SCD30_INVALID_VALUE = -2, /**< Device doesn't exist. */ + SCD30_CRC_ERROR = -3, /**< Invalid value or length. */ + SCD30_NO_NEW_DATA = -4, /**< No new data. */ +}; + +/** + * @brief Measurement from SCD30 sensor + */ +typedef struct { + float co2_concentration; /**< CO2 concentration in ppm */ + float temperature; /**< Temperature measured in °C */ + float relative_humidity; /**< Relative humidity measured in % */ +} scd30_measurement_t; + +/** + * @brief Device initialization parameters + */ +typedef struct { + i2c_t i2c_dev; /**< I2C device which is used */ + uint8_t i2c_addr; /**< I2C address */ +} scd30_params_t; + +/** + * @brief Device descriptor for the SCD30 sensor + */ +typedef struct { + scd30_params_t params; /**< Device initialization parameters */ +} scd30_t; + +/** + * @brief Initialize SCD30 + * + * @param dev scd30 device + * @param params scd30 device params + * + * @return SCD30_OK if device started + */ +int8_t scd30_init(scd30_t *dev, const scd30_params_t *params); + +/** + * @brief Set a configuration parameter of device + * + * @param dev scd30 device + * @param param param to be set/operation to be performed + * param codes mentioned in scd30_internal + * @param val value to be set for that parameter + * + * @return SCD30_OK on success + */ +int8_t scd30_set_param(const scd30_t *dev, uint16_t param, uint16_t val); + +/** + * @brief Get value set for a configuration parameter on the device + * + * @param dev scd30 device + * @param param param to be set/operation to be performed + * param codes mentioned in scd30_internal + * @param val Pointer to the value to be set for that parameter + * + * @return SCD30_OK on success + */ +int8_t scd30_get_param(scd30_t *dev, uint16_t param, uint16_t *val); + +/** + * @brief read CO2 concentration, temperature and relative humidity once + * + * @param dev scd30 device + * @param result Values are stored in this struct + * + * @return SCD30_OK on success + */ +int8_t scd30_read_triggered(scd30_t *dev, scd30_measurement_t *result); + +/** + * @brief read co2 concentration, temperature and relative humidity + * when continuous measurements are being taken + * + * @param dev scd30 device + * @param result struct to store result + * + * @return SCD30_OK on success + */ +uint8_t scd30_read_periodic(scd30_t *dev, scd30_measurement_t *result); + +/** + * @brief Initializes Continuous Measurements + * + * @param dev scd30 device + * @param interval Interval at which new measurements have to be taken + * in seconds (between 2 and 1800 seconds, both inclusive) + * @param apc Average Pressure Compensation + * 0 to disable pressure compensation + * 700-1400 mBar for valid pressure compensation + * + * @return SCD30_OK if device started + */ +int scd30_start_periodic_measurement(scd30_t *dev, uint16_t *interval, + uint16_t *apc); + +/** + * @brief Stop Continuous measurements + * + * @param dev scd30 dev device + * + * @return SCD30_OK if measurement stopped + */ +int8_t scd30_stop_measurements(const scd30_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* SCD30_H */ diff --git a/drivers/saul/init_devs/auto_init_scd30.c b/drivers/saul/init_devs/auto_init_scd30.c new file mode 100644 index 0000000000..0e6f8340da --- /dev/null +++ b/drivers/saul/init_devs/auto_init_scd30.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 Puhang Ding + * 2020 Nishchay Agrawal + * + * 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 + * @{ + * + * @file + * @brief Auto initialization of SCD30 driver. + * + * @author Puhang Ding + * @author Nishchay Agrawal + * + * @} + */ + +#include "saul_reg.h" +#include "log.h" + +#include "scd30.h" +#include "scd30_params.h" +#include "scd30_internal.h" + +/** + * @brief Allocation of memory for device descriptors + */ +static scd30_t scd30_devs[SCD30_NUMOF]; + +/** + * @brief Reference the driver structs. + * @{ + */ +extern const saul_driver_t scd30_co2_saul_driver; +extern const saul_driver_t scd30_temp_saul_driver; +extern const saul_driver_t scd30_hum_saul_driver; +/** @} */ + +/** + * @brief Memory for the SAUL registry entries + */ +#define SENSORS_NUMOF 3 +static saul_reg_t saul_entries[SCD30_NUMOF * SENSORS_NUMOF]; + +void auto_init_scd30(void) +{ + size_t sensor_no = 0; + + for (size_t i = 0; i < SCD30_NUMOF; i++) { + + int res = scd30_init(&scd30_devs[i], &scd30_params[i]); + if (res < 0) { + LOG_ERROR("[auto_init_saul] error initializing SCD30 #%u\n", i); + continue; + } + + /*** CO2 concentration ***/ + saul_entries[sensor_no].dev = &scd30_devs[i]; + saul_entries[sensor_no].name = scd30_saul_info[i].name; + saul_entries[sensor_no].driver = &scd30_co2_saul_driver; + saul_reg_add(&saul_entries[sensor_no]); + sensor_no++; + + /*** Temperature ***/ + saul_entries[sensor_no].dev = &scd30_devs[i]; + saul_entries[sensor_no].name = scd30_saul_info[i].name; + saul_entries[sensor_no].driver = &scd30_temp_saul_driver; + saul_reg_add(&saul_entries[sensor_no]); + sensor_no++; + + /*** Relative Humidity ***/ + saul_entries[sensor_no].dev = &scd30_devs[i]; + saul_entries[sensor_no].name = scd30_saul_info[i].name; + saul_entries[sensor_no].driver = &scd30_hum_saul_driver; + saul_reg_add(&saul_entries[sensor_no]); + sensor_no++; + + scd30_set_param(&scd30_devs[i], SCD30_START, SCD30_DEF_PRESSURE); + } +} diff --git a/drivers/saul/init_devs/init.c b/drivers/saul/init_devs/init.c index dfe5a0c225..8f745d6871 100644 --- a/drivers/saul/init_devs/init.c +++ b/drivers/saul/init_devs/init.c @@ -215,6 +215,10 @@ void saul_init_devs(void) extern void auto_init_qmc5883l(void); auto_init_qmc5883l(); } + if (IS_USED(MODULE_SCD30)) { + extern void auto_init_scd30(void); + auto_init_scd30(); + } if (IS_USED(MODULE_SHT2X)) { extern void auto_init_sht2x(void); auto_init_sht2x(); diff --git a/drivers/scd30/Makefile b/drivers/scd30/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/scd30/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/scd30/Makefile.dep b/drivers/scd30/Makefile.dep new file mode 100644 index 0000000000..98694efeb1 --- /dev/null +++ b/drivers/scd30/Makefile.dep @@ -0,0 +1,5 @@ +ifneq (,$(filter scd30,$(USEMODULE))) + FEATURES_REQUIRED += periph_i2c + USEMODULE += checksum + USEMODULE += xtimer +endif \ No newline at end of file diff --git a/drivers/scd30/Makefile.include b/drivers/scd30/Makefile.include new file mode 100644 index 0000000000..f270c637b3 --- /dev/null +++ b/drivers/scd30/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_scd30 := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_scd30) \ No newline at end of file diff --git a/drivers/scd30/include/scd30_internal.h b/drivers/scd30/include/scd30_internal.h new file mode 100644 index 0000000000..2ff9d20719 --- /dev/null +++ b/drivers/scd30/include/scd30_internal.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 Puhang Ding + * 2020 Jan Schlichter + * 2020 Nishchay Agrawal + * + * 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_scd30 + * @{ + * @file + * @brief Internal constants, configuration commands for SCD30 sensor + * + * @author Puhang Ding + * @author Jan Schlichter + * @author Nishchay Agrawal + */ + +#ifndef SCD30_INTERNAL_H +#define SCD30_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name SCD30 CRC Function and start value for CRC + * @{ + */ +#define SCD30_CRC_FUNC 0x31 /**< CRC Function for CRC-8 + calculation and verification */ +#define SCD30_CRC_START_VAL 0xFF /**< Start value for CRC-8 + calculation */ +/** @} */ + +/** + * @name SCD30 Internal Constants + * @{ + */ +#define SCD30_DEF_PRESSURE 0x03f5 /**< Ambient Pressure: + 1013.25 mBar */ + +#define SCD30_READ_WRITE_SLEEP_US (4 * US_PER_MS) +#define SCD30_DATA_RDY_TIMEOUT (1 * US_PER_SEC) + +#define SCD30_MIN_INTERVAL 2 +#define SCD30_MAX_INTERVAL 1800 + +#define SCD30_MIN_PRESSURE_COMP 700 +#define SCD30_MAX_PRESSURE_COMP 1400 +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* SCD30_INTERNAL_H */ +/** @} */ diff --git a/drivers/scd30/include/scd30_params.h b/drivers/scd30/include/scd30_params.h new file mode 100644 index 0000000000..a0ba923952 --- /dev/null +++ b/drivers/scd30/include/scd30_params.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 Puhang Ding + * 2020 Nishchay Agrawal + * + * 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_sensors + * @{ + * @file + * @brief Device driver params interface for the SCD30 sensor. + * + * @author Nishchay Agrawal + * @author Puhang Ding + * @} + */ + +#ifndef SCD30_PARAMS_H +#define SCD30_PARAMS_H + +#include "periph/i2c.h" +#include "scd30.h" + +#include "saul_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name SCD30 I2C address + * @{ + */ +#define SCD30_I2C_ADDR 0x61 +/** @} */ + +/** + * @name Set default configuration parameters for the SCD30 + * @{ + */ +#ifndef SCD30_PARAM_I2C_DEV +#define SCD30_PARAM_I2C_DEV I2C_DEV(0) +#endif +#ifndef SCD30_PARAM_I2C_ADDR +#define SCD30_PARAM_I2C_ADDR SCD30_I2C_ADDR +#endif + +#ifndef SCD30_PARAMS +#define SCD30_PARAMS { .i2c_dev = SCD30_PARAM_I2C_DEV, \ + .i2c_addr = SCD30_PARAM_I2C_ADDR } +#endif + +#ifndef SCD30_SAUL_INFO +#define SCD30_SAUL_INFO { .name = "scd30" } +#endif +/**@}*/ + +/** + * @brief Configure SCD30 + */ +static const scd30_params_t scd30_params[] = +{ + SCD30_PARAMS +}; + +/** + * @brief Get the number of configured SCD30 devices + */ +#define SCD30_NUMOF ARRAY_SIZE(scd30_params) + +/** + * @brief Configure SAUL registry entries + */ +static const saul_reg_info_t scd30_saul_info[] = +{ + SCD30_SAUL_INFO +}; + +#ifdef __cplusplus +} +#endif + +#endif /* SCD30_PARAMS_H */ diff --git a/drivers/scd30/scd30.c b/drivers/scd30/scd30.c new file mode 100644 index 0000000000..637b3dfece --- /dev/null +++ b/drivers/scd30/scd30.c @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2020 Puhang Ding + * 2020 Jan Schlichter + * 2020 Nishchay Agrawal + * + * 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_scd30 + * @{ + * @file + * @brief Sensirion SCD30 sensor driver implementation + * + * @author Nishchay Agrawal + * @author Puhang Ding + * @author Jan Schlichter + * @} + */ + +#include +#include "periph/i2c.h" +#include "xtimer.h" +#include "byteorder.h" +#include "checksum/crc8.h" + +#include "scd30.h" +#include "scd30_internal.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define SCD30_I2C (dev->params.i2c_dev) +#define SCD30_I2C_ADDRESS (dev->params.i2c_addr) + +static int8_t _scd30_read_data(scd30_t *dev, scd30_measurement_t *result); +static bool _scd30_crc_check(uint8_t *buff, uint8_t len); +static bool _is_valid_interval(uint16_t *interval); +static bool _is_valid_press_comp(uint16_t *apc); +static inline float _reinterpret_float(uint32_t raw_val); +static float _raw_val_to_float(const uint8_t *buffer); + +int8_t scd30_init(scd30_t *dev, const scd30_params_t *params) +{ + DEBUG("[scd30] init\n"); + dev->params = *params; + + int ret = 0; + + uint16_t version; + ret = scd30_get_param(dev, SCD30_VERSION, &version); + DEBUG("[scd30] --- Version 0x%02x%02x\n", (version >> 8), version); + if (ret < 0) { + DEBUG("[scd30] Failed to get version\n"); + return ret; + } + + DEBUG("[scd30] init: done.\n"); + + return SCD30_OK; +} + +int8_t scd30_set_param(const scd30_t *dev, uint16_t param, uint16_t val) +{ + uint8_t buffer[5]; + int ret = 0; + + buffer[0] = (uint8_t)(param >> 8); + buffer[1] = (uint8_t)param; + buffer[2] = (uint8_t)(val >> 8); + buffer[3] = (uint8_t)val; + buffer[4] = crc8(buffer + 2, 2, SCD30_CRC_FUNC, SCD30_CRC_START_VAL); + i2c_acquire(SCD30_I2C); + ret = i2c_write_bytes(SCD30_I2C, SCD30_I2C_ADDRESS, buffer, 5, 0); + i2c_release(SCD30_I2C); + if (ret != 0) { + return SCD30_COM_FAILED; + } + else { + xtimer_usleep(SCD30_READ_WRITE_SLEEP_US); + return SCD30_OK; + } +} + +int8_t scd30_get_param(scd30_t *dev, uint16_t param, uint16_t *val) +{ + uint8_t buffer[3]; + int ret = 0; + + param = htons(param); + i2c_acquire(SCD30_I2C); + ret = i2c_write_bytes(SCD30_I2C, SCD30_I2C_ADDRESS, ¶m, 2, 0); + if (ret != 0) { + i2c_release(SCD30_I2C); + return ret; + } + i2c_release(SCD30_I2C); + + xtimer_usleep(SCD30_READ_WRITE_SLEEP_US); + + i2c_acquire(SCD30_I2C); + ret = i2c_read_bytes(SCD30_I2C, SCD30_I2C_ADDRESS, buffer, 3, 0); + i2c_release(SCD30_I2C); + + if (ret != 0) { + DEBUG("[scd30] Problem reading param %u from sensor\n", param); + } + + if (!_scd30_crc_check(buffer, 2)) { + return SCD30_CRC_ERROR; + } + + *val = byteorder_bebuftohs(buffer); + return SCD30_OK; +} + +int8_t scd30_read_triggered(scd30_t *dev, scd30_measurement_t *result) +{ + uint16_t state = 0; + uint16_t curr_interval; + uint32_t initial_time; + int ret; + + ret = scd30_get_param(dev, SCD30_INTERVAL, &curr_interval); + if (ret != 0) { + return ret; + } + if (curr_interval != SCD30_MIN_INTERVAL) { + ret = scd30_set_param(dev, SCD30_INTERVAL, SCD30_MIN_INTERVAL); + if (ret != 0) { + DEBUG("[scd30] Error setting interval to min\n"); + return ret; + } + } + + /* Doing this to reduce the traffic on the I2C bus as + * compared when constantly polling the device for status + */ + xtimer_sleep(SCD30_MIN_INTERVAL); + + initial_time = xtimer_now_usec(); + ret = scd30_get_param(dev, SCD30_STATUS, &state); + if (ret != 0) { + return ret; + } + + while (state != 1 && + (xtimer_now_usec() - initial_time) < SCD30_DATA_RDY_TIMEOUT) { + ret = scd30_get_param(dev, SCD30_STATUS, &state); + if (ret != 0) { + return ret; + } + } + ret = _scd30_read_data(dev, result); + if (ret != SCD30_OK) { + DEBUG("[scd30] Error reading data \n"); + return ret; + } + + if (curr_interval != SCD30_MIN_INTERVAL) { + ret = scd30_set_param(dev, SCD30_INTERVAL, curr_interval); + if (ret != 0) { + DEBUG("[scd30] Error resetting interval to original value\n"); + return ret; + } + } + return SCD30_OK; +} + +uint8_t scd30_read_periodic(scd30_t *dev, scd30_measurement_t *result) +{ + uint16_t state = 0; + uint32_t initial_time; + int ret = 0; + + initial_time = xtimer_now_usec(); + ret = scd30_get_param(dev, SCD30_STATUS, &state); + if (ret != 0) { + return ret; + } + + while (state != 1 && + (xtimer_now_usec() - initial_time) < SCD30_DATA_RDY_TIMEOUT) { + scd30_get_param(dev, SCD30_STATUS, &state); + } + + if (state != 1) { + return SCD30_NO_NEW_DATA; + } + ret = _scd30_read_data(dev, result); + if (ret != SCD30_OK) { + DEBUG("[scd30] Error reading values"); + return ret; + } + return SCD30_OK; +} + +int scd30_start_periodic_measurement(scd30_t *dev, uint16_t *interval, + uint16_t *apc) +{ + int ret = 0; + + /* Check if input is valid */ + if (!_is_valid_interval(interval)) { + DEBUG("[scd30] Interval value out of range\n"); + return SCD30_INVALID_VALUE; + } + if (!_is_valid_press_comp(apc)) { + DEBUG("[scd30] Ambient pressure compensation value out of range\n"); + return SCD30_INVALID_VALUE; + } + + ret = scd30_set_param(dev, SCD30_INTERVAL, *interval); + if (ret != 0) { + return ret; + } + ret = scd30_set_param(dev, SCD30_START, *apc); + if (ret != 0) { + return ret; + } + return SCD30_OK; +} + +int8_t scd30_stop_measurements(const scd30_t *dev) +{ + const uint16_t cmd = htons(SCD30_STOP); + int ret; + + i2c_acquire(SCD30_I2C); + ret = i2c_write_bytes(SCD30_I2C, SCD30_I2C_ADDRESS, &cmd, 2, 0); + i2c_release(SCD30_I2C); + if (ret != 0) { + DEBUG("[scd30]: Error stopping measurements.\n"); + } + return SCD30_OK; +} + +/** + * intern function to read data + * + * @param dev device + * @param result struct in which data read is stored + * @return SCD30_OK on success + */ +static int8_t _scd30_read_data(scd30_t *dev, scd30_measurement_t *result) +{ + uint8_t buffer[18]; + int ret = 0; + const uint16_t cmd = htons(SCD30_DATA); + + i2c_acquire(SCD30_I2C); + ret = i2c_write_bytes(SCD30_I2C, SCD30_I2C_ADDRESS, &cmd, 2, 0); + if (ret != 0) { + i2c_release(SCD30_I2C); + return ret; + } + ret = i2c_read_bytes(SCD30_I2C, SCD30_I2C_ADDRESS, buffer, 18, 0); + if (ret != 0) { + DEBUG("[scd30] read_data: ret %i.\n", ret); + i2c_release(SCD30_I2C); + return ret; + } + i2c_release(SCD30_I2C); + + if (!_scd30_crc_check(buffer, 2)) { + return SCD30_CRC_ERROR; + } + if (!_scd30_crc_check(buffer + 3, 2)) { + return SCD30_CRC_ERROR; + } + if (!_scd30_crc_check(buffer + 6, 2)) { + return SCD30_CRC_ERROR; + } + if (!_scd30_crc_check(buffer + 9, 2)) { + return SCD30_CRC_ERROR; + } + if (!_scd30_crc_check(buffer + 12, 2)) { + return SCD30_CRC_ERROR; + } + if (!_scd30_crc_check(buffer + 15, 2)) { + return SCD30_CRC_ERROR; + } + + result->co2_concentration = _raw_val_to_float(&buffer[0]); + result->temperature = _raw_val_to_float(&buffer[6]); + result->relative_humidity = _raw_val_to_float(&buffer[12]); + + return SCD30_OK; +} + +/** + * check if crc is valid (Assuming crc value to compare with is stored at + * end of buffer) + * + * @param buff buffer of which crc value has to be checked + * @param len length of buffer + * @return true on success + */ +static bool _scd30_crc_check(uint8_t *buff, uint8_t len) +{ + return crc8(buff, len, SCD30_CRC_FUNC, SCD30_CRC_START_VAL) == buff[len]; +} + +/** + * check if interval is within bounds + * + * @param interval Pointer to interval to check + * @return true if interval within bounds + */ +static bool _is_valid_interval(uint16_t *interval) +{ + if (*interval < SCD30_MIN_INTERVAL || *interval > SCD30_MAX_INTERVAL) { + return false; + } + return true; +} + +/** + * check if pressure compensation is within allowed values + * + * @param apc Pointer to Pressure compensation to check + * @return true if value within bounds + */ +static bool _is_valid_press_comp(uint16_t *apc) +{ + if ((*apc < SCD30_MIN_PRESSURE_COMP || *apc > SCD30_MAX_PRESSURE_COMP) && + *apc != 0) { + return false; + } + return true; +} + +/** + * convert IEEE754 stored in uint32_t to actual float value + * + * @param raw_val Value to convert + * @return Converted value + */ +static inline float _reinterpret_float(uint32_t raw_val) +{ + union { + float float_val; + uint32_t int_val; + } to_float = { .int_val = raw_val }; + + return to_float.float_val; +} + +/** + * convert value from buffer storing IEEE754 represented + * float to actual float value + * + * @param buffer buffer with value + * @return Converted value + */ +static float _raw_val_to_float(const uint8_t *buffer) +{ + uint32_t tmp = byteorder_bebuftohl(buffer); + + return _reinterpret_float(tmp); +} diff --git a/drivers/scd30/scd30_saul.c b/drivers/scd30/scd30_saul.c new file mode 100644 index 0000000000..cef1358c94 --- /dev/null +++ b/drivers/scd30/scd30_saul.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 Technische Universität Braunschweig + * + * 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_scd30 + * @file + * @brief SAUL adaption for Sensirion SCD30 sensor + * @author Puhang Ding + * @author Nishchay Agrawal + * @author Jan Schlichter + */ + +#include "saul.h" +#include "scd30.h" +#include "scd30_internal.h" + +static void _float_fit(float src, phydat_t *data, uint32_t mul) +{ + int32_t i32; + + i32 = src * mul; + phydat_fit(data, &i32, 1); +} + +static int _co2_read(const void *dev, phydat_t *res) +{ + int ret = 0; + scd30_measurement_t result; + + scd30_set_param(dev, SCD30_START, SCD30_DEF_PRESSURE); + + ret = scd30_read_triggered((scd30_t *)dev, &result); + if (ret != 0) { + return ret; + } + res->scale = -2; + res->unit = UNIT_PPM; + _float_fit(result.co2_concentration, res, 100); + + scd30_stop_measurements(dev); + return 1; +} + +static int _temp_read(const void *dev, phydat_t *res) +{ + int ret = 0; + scd30_measurement_t result; + + scd30_set_param(dev, SCD30_START, SCD30_DEF_PRESSURE); + + ret = scd30_read_triggered((scd30_t *)dev, &result); + if (ret != 0) { + return ret; + } + res->unit = UNIT_TEMP_C; + res->scale = -2; + _float_fit(result.temperature, res, 100); + + scd30_stop_measurements(dev); + return 1; +} + +static int _hum_read(const void *dev, phydat_t *res) +{ + int ret = 0; + scd30_measurement_t result; + + scd30_set_param(dev, SCD30_START, SCD30_DEF_PRESSURE); + + ret = scd30_read_triggered((scd30_t *)dev, &result); + if (ret != 0) { + return ret; + } + res->unit = UNIT_PERCENT; + res->scale = -2; + _float_fit(result.relative_humidity, res, 100); + + scd30_stop_measurements(dev); + return 1; +} + +const saul_driver_t scd30_co2_saul_driver = { + .read = _co2_read, + .write = saul_notsup, + .type = SAUL_SENSE_CO2, +}; + +const saul_driver_t scd30_temp_saul_driver = { + .read = _temp_read, + .write = saul_notsup, + .type = SAUL_SENSE_TEMP, +}; + +const saul_driver_t scd30_hum_saul_driver = { + .read = _hum_read, + .write = saul_notsup, + .type = SAUL_SENSE_HUM, +}; diff --git a/tests/driver_scd30/Makefile b/tests/driver_scd30/Makefile new file mode 100644 index 0000000000..bec03df251 --- /dev/null +++ b/tests/driver_scd30/Makefile @@ -0,0 +1,12 @@ +include ../Makefile.tests_common + +DRIVER ?= scd30 + +USEMODULE += $(DRIVER) +USEMODULE += printf_float + +TEST_ITERATIONS ?= 10 +# export iterations for continuous measurements +CFLAGS += -DTEST_ITERATIONS=$(TEST_ITERATIONS) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_scd30/README.md b/tests/driver_scd30/README.md new file mode 100644 index 0000000000..03b5c4b29e --- /dev/null +++ b/tests/driver_scd30/README.md @@ -0,0 +1,18 @@ +## About +This is a test application for the Sensirion SCD30 CO2 concentration, +temperature and relative humidity sensor + +## Usage +This test application will initialize the SCD30 sensor and print TEST_ITERATIONS +number of following values first in continuous mode and then triggered mode every +second: + +* CO2 concentration +* Temperature +* Relative humidity + +The user can specify the number of iterations by setting the variable +`TEST_ITERATIONS` (default value is 10) from commandline as follows: +``` +make BOARD=... TEST_ITERATIONS=... -C tests/driver_scd30 +``` diff --git a/tests/driver_scd30/main.c b/tests/driver_scd30/main.c new file mode 100644 index 0000000000..4ef62aaf17 --- /dev/null +++ b/tests/driver_scd30/main.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 Puhang Ding + * 2020 Jan Schlichter + * 2020 Nishchay Agrawal + * + * 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_scd30 + * @{ + * @file + * @brief Sensirion SCD30 sensor driver implementation + * + * @author Puhang Ding + * @author Jan Schlichter + * @author Nishchay Agrawal + * @} + */ + +#include +#include "periph/gpio.h" +#include "xtimer.h" +#include "scd30.h" +#include "scd30_params.h" +#include "scd30_internal.h" + +#define MEASUREMENT_INTERVAL_SECS (2) + +scd30_t scd30_dev; +scd30_params_t params = SCD30_PARAMS; +scd30_measurement_t result; + +int main(void) +{ + printf("SCD30 Test:\n"); + int i = 0; + + scd30_init(&scd30_dev, ¶ms); + uint16_t pressure_compensation = SCD30_DEF_PRESSURE; + uint16_t value = 0; + uint16_t interval = MEASUREMENT_INTERVAL_SECS; + + scd30_set_param(&scd30_dev, SCD30_INTERVAL, MEASUREMENT_INTERVAL_SECS); + scd30_set_param(&scd30_dev, SCD30_START, pressure_compensation); + + scd30_get_param(&scd30_dev, SCD30_INTERVAL, &value); + printf("[test][dev-%d] Interval: %u s\n", i, value); + scd30_get_param(&scd30_dev, SCD30_T_OFFSET, &value); + printf("[test][dev-%d] Temperature Offset: %u.%02u C\n", i, value / 100u, + value % 100u); + scd30_get_param(&scd30_dev, SCD30_A_OFFSET, &value); + printf("[test][dev-%d] Altitude Compensation: %u m\n", i, value); + scd30_get_param(&scd30_dev, SCD30_ASC, &value); + printf("[test][dev-%d] ASC: %u\n", i, value); + scd30_get_param(&scd30_dev, SCD30_FRC, &value); + printf("[test][dev-%d] FRC: %u ppm\n", i, value); + + while (i < TEST_ITERATIONS) { + xtimer_sleep(1); + scd30_read_triggered(&scd30_dev, &result); + printf( + "[scd30_test-%d] Triggered measurements co2: %.02fppm," + " temp: %.02f°C, hum: %.02f%%. \n", i, result.co2_concentration, + result.temperature, result.relative_humidity); + i++; + } + + i = 0; + scd30_start_periodic_measurement(&scd30_dev, &interval, + &pressure_compensation); + + while (i < TEST_ITERATIONS) { + xtimer_sleep(MEASUREMENT_INTERVAL_SECS); + scd30_read_periodic(&scd30_dev, &result); + printf( + "[scd30_test-%d] Continuous measurements co2: %.02fppm," + " temp: %.02f°C, hum: %.02f%%. \n", i, result.co2_concentration, + result.temperature, result.relative_humidity); + i++; + } + + scd30_stop_measurements(&scd30_dev); + + return 0; +}