From 980ea12c052560dd9e030e85426b392b814cd71e Mon Sep 17 00:00:00 2001 From: Artemis Tosini Date: Fri, 24 Nov 2023 03:24:11 +0000 Subject: [PATCH] Untested basic SCD30 driver --- boards/xtensa/minico2/minico2-pinctrl.dtsi | 2 +- drivers/sensor/scd30/scd30.c | 176 +++++++++++++++++++++ drivers/sensor/scd30/scd30.h | 30 ++++ dts/bindings/sensirion,scd30.yaml | 15 ++ 4 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 drivers/sensor/scd30/scd30.h diff --git a/boards/xtensa/minico2/minico2-pinctrl.dtsi b/boards/xtensa/minico2/minico2-pinctrl.dtsi index afdd8e3..d431bfc 100644 --- a/boards/xtensa/minico2/minico2-pinctrl.dtsi +++ b/boards/xtensa/minico2/minico2-pinctrl.dtsi @@ -17,7 +17,7 @@ i2c0_default: i2c0_default { group1 { pinmux = , - ; + ; bias-pull-up; drive-open-drain; output-high; diff --git a/drivers/sensor/scd30/scd30.c b/drivers/sensor/scd30/scd30.c index e69de29..0865f1c 100644 --- a/drivers/sensor/scd30/scd30.c +++ b/drivers/sensor/scd30/scd30.c @@ -0,0 +1,176 @@ +#define DT_DRV_COMPAT sensirion_scd30 + +#include +#include +#include +#include +#include +#include +#include + +#include "scd30.h" + +LOG_MODULE_REGISTER(SCD30, CONFIG_SENSOR_LOG_LEVEL); + +/* + * Checksum used when recieving data. Used on several + * Sensirion devices, including SCD30 + */ +static uint8_t scd30_compute_checksum(uint16_t value) +{ + uint8_t buf[2]; + sys_put_be16(value, buf); + return crc8(buf, 2, 0x31, 0xff, false); +} + +/* + * Check if the Sensirion checksum recieved is valid + * Takes a pointer to an array and uses 3 bytes + */ +static bool scd30_verify_checksum(uint8_t *buf) +{ + return crc8(buf, 2, 0x31, 0xff, false) == buf[2]; +} + +/* + * Extract a float from a measurement response buffer + * Takes a pointer to an array and uses 6 bytes + */ +static float scd30_extract_float(uint8_t *buf) +{ + uint32_t upper = sys_get_be16(buf); + uint32_t lower = sys_get_be16(&buf[3]); + uint32_t full = upper << 16 | lower; + return *((float *)&full); +} + +static int scd30_write(const struct device *dev, uint16_t cmd, uint16_t data, bool has_data) +{ + const struct scd30_config *config = dev->config; + uint8_t tx_buf[5]; + + sys_put_be16(cmd, tx_buf); + if (has_data) { + sys_put_be16(data, &tx_buf[2]); + tx_buf[4] = scd30_compute_checksum(data); + } + + return i2c_write_dt(&config->bus, tx_buf, has_data ? 5 : 2); +} + +static int scd30_read(const struct device *dev, uint16_t cmd, uint8_t *buf, size_t buf_size) +{ + const struct scd30_config *config = dev->config; + + if (scd30_write(dev, cmd, 0, false) < 0) { + LOG_ERR("Unable to write command while reading!"); + return -EIO; + } + + k_sleep(SCD30_READ_DELAY); + + if (i2c_read_dt(&config->bus, buf, buf_size) < 0) { + LOG_ERR("Unable to read data!"); + return -EIO; + } + + for (size_t i = 0; i * 3 < buf_size - 2; i++) { + if (!scd30_verify_checksum(&buf[i * 3])) { + LOG_ERR("Recieved invalid CRC!"); + return -EIO; + } + } + + return 0; +} + +static int scd30_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct scd30_data *data = dev->data; + + __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL); + uint8_t rx_buf[18]; + + // Make sure there's a sample ready for us and clear the ready flag + if (scd30_read(dev, SCD30_CMD_CHECK_DATA_READY, rx_buf, 3) < 0) { + LOG_ERR("Unable to check if measurement is ready!"); + return -EIO; + } + + // Now actually read + if (scd30_read(dev, SCD30_CMD_READ_MEASUREMENT, rx_buf, 18) < 0) { + LOG_ERR("Unable to read measurement!"); + return -EIO; + } + + data->co2_sample = scd30_extract_float(rx_buf); + data->t_sample = scd30_extract_float(&rx_buf[6]); + data->rh_sample = scd30_extract_float(&rx_buf[12]); + + return 0; +} + +static int scd30_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct scd30_data *data = dev->data; + switch (chan) { + case SENSOR_CHAN_CO2: + return sensor_value_from_float(val, data->co2_sample); + case SENSOR_CHAN_AMBIENT_TEMP: + return sensor_value_from_float(val, data->t_sample); + case SENSOR_CHAN_HUMIDITY: + return sensor_value_from_float(val, data->rh_sample); + default: + return -ENOTSUP; + } +} + +static const struct sensor_driver_api scd30_driver_api = { + .sample_fetch = scd30_sample_fetch, + .channel_get = scd30_channel_get, +}; + +static int scd30_init(const struct device *dev) +{ + const struct scd30_config *cfg = dev->config; + + if (!device_is_ready(cfg->bus.bus)) { + LOG_ERR("I2C bus %s is not ready!", cfg->bus.bus->name); + return -EINVAL; + } + + if (cfg->sampling_interval != 0) { + if (scd30_write(dev, SCD30_CMD_INTERVAL, cfg->sampling_interval, true) < 0) { + LOG_ERR("Failed to set SCD30 sampling interval!"); + return -EIO; + } + } + + if (cfg->asc_enabled < 2) { + if (scd30_write(dev, SCD30_CMD_ASC, cfg->asc_enabled, true) < 0) { + LOG_ERR("Failed to set SCD30 ASC!"); + return -EIO; + } + } + + if (scd30_write(dev, SCD30_CMD_START, 0, 1) < 0) { + LOG_ERR("Could not start SCD30!"); + return -EIO; + } + + return 0; +}; + +#define SCD30_DEFINE(inst) \ + struct scd30_data scd30_data_##inst; \ + static const struct scd30_config scd30_cfg_##inst = { \ + .bus = I2C_DT_SPEC_INST_GET(inst), \ + .asc_enabled = DT_INST_PROP_OR(inst, asc_enabled, 2), \ + .sampling_interval = DT_INST_PROP_OR(inst, sampling_interval, 0), \ + .rdy_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, {0})}; \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, scd30_init, NULL, &scd30_data_##inst, \ + &scd30_cfg_##inst, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \ + &scd30_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(SCD30_DEFINE) diff --git a/drivers/sensor/scd30/scd30.h b/drivers/sensor/scd30/scd30.h new file mode 100644 index 0000000..7186408 --- /dev/null +++ b/drivers/sensor/scd30/scd30.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#define SCD30_READ_DELAY K_MSEC(3) + +#define SCD30_CMD_START 0x0010 +#define SCD30_CMD_STOP 0x0104 +#define SCD30_CMD_INTERVAL 0x4600 +#define SCD30_CMD_CHECK_DATA_READY 0x0202 +#define SCD30_CMD_READ_MEASUREMENT 0x0300 +#define SCD30_CMD_ASC 0x5306 +#define SCD30_CMD_SET_TEMP_OFFSET 0x5403 +#define SCD30_CMD_SET_ALTITUDE 0x5102 +#define SCD30_CMD_READ_VERSION 0xd100 +#define SCD30_CMD_SOFT_RESET 0xd304 + +struct scd30_config { + struct i2c_dt_spec bus; + struct gpio_dt_spec rdy_gpio; + uint8_t asc_enabled; + uint16_t sampling_interval; +}; + +struct scd30_data { + float t_sample; + float rh_sample; + float co2_sample; +}; diff --git a/dts/bindings/sensirion,scd30.yaml b/dts/bindings/sensirion,scd30.yaml index 338550a..6af7219 100644 --- a/dts/bindings/sensirion,scd30.yaml +++ b/dts/bindings/sensirion,scd30.yaml @@ -1,3 +1,18 @@ description: Sensirion SCD30 CO2, humidity, and temperature sensor compatible: "sensirion,scd30" include: [sensor-device.yaml, i2c-device.yaml] +properties: + int-gpios: + type: phandle-array + description: | + GPIO pin connected to the SCD30 RDY pin. Without this async reads are unavailable + asc-enabled: + type: boolean + description: | + Enable automatic self calibration. If this property is not set then the value in the SCD30's + nonvolatile storage will be used. + sampling-interval: + type: int + description: | + How often to sample at bootup. If this property is not set then the value in the SCD30's + nonvolatile storage will be used.