Untested basic SCD30 driver

This commit is contained in:
Artemis Tosini 2023-11-24 03:24:11 +00:00
parent 4ef8101a7d
commit 980ea12c05
Signed by: artemist
GPG key ID: ADFFE553DCBB831E
4 changed files with 222 additions and 1 deletions

View file

@ -17,7 +17,7 @@
i2c0_default: i2c0_default {
group1 {
pinmux = <I2C0_SDA_GPIO32>,
<I2C0_SDA_GPIO33>;
<I2C0_SCL_GPIO33>;
bias-pull-up;
drive-open-drain;
output-high;

View file

@ -0,0 +1,176 @@
#define DT_DRV_COMPAT sensirion_scd30
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>
#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)

View file

@ -0,0 +1,30 @@
#pragma once
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/gpio.h>
#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;
};

View file

@ -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.