drivers: sensor: SCD4x Add driver

This adds support for Sensirion's SCD4x co2 sensor.

Signed-off-by: Jan Faeh <jan.faeh@sensirion.com>
This commit is contained in:
Jan Faeh 2024-08-28 10:21:04 +02:00 committed by Anas Nashif
commit 2efc8598e3
10 changed files with 1143 additions and 0 deletions

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
# zephyr-keep-sorted-start
add_subdirectory_ifdef(CONFIG_SCD4X scd4x)
add_subdirectory_ifdef(CONFIG_SGP40 sgp40)
add_subdirectory_ifdef(CONFIG_SHT3XD sht3xd)
add_subdirectory_ifdef(CONFIG_SHT4X sht4x)

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
# zephyr-keep-sorted-start
source "drivers/sensor/sensirion/scd4x/Kconfig"
source "drivers/sensor/sensirion/sgp40/Kconfig"
source "drivers/sensor/sensirion/sht3xd/Kconfig"
source "drivers/sensor/sensirion/sht4x/Kconfig"

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(scd4x.c)

View file

@ -0,0 +1,13 @@
# Drivers configuration options for Sensirion SCD4x
# Copyright (c) 2024 Jan Fäh
# SPDX-License-Identifier: Apache-2.0
config SCD4X
bool "SCD4x Carbon Dioxide Sensor"
default y
depends on DT_HAS_SENSIRION_SCD41_ENABLED || DT_HAS_SENSIRION_SCD40_ENABLED
select I2C
select CRC
help
Enable driver for the Sensirion SCD4x carbon dioxide sensors.

View file

@ -0,0 +1,908 @@
/*
* Copyright (c) 2024 Jan Fäh
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/sensor/scd4x.h>
#include "scd4x.h"
LOG_MODULE_REGISTER(SCD4X, CONFIG_SENSOR_LOG_LEVEL);
static uint8_t scd4x_calc_crc(uint16_t value)
{
uint8_t buf[2];
sys_put_be16(value, buf);
return crc8(buf, 2, SCD4X_CRC_POLY, SCD4X_CRC_INIT, false);
}
static int scd4x_write_command(const struct device *dev, uint8_t cmd)
{
const struct scd4x_config *cfg = dev->config;
uint8_t tx_buf[2];
int ret;
sys_put_be16(scd4x_cmds[cmd].cmd, tx_buf);
ret = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
if (scd4x_cmds[cmd].cmd_duration_ms) {
k_msleep(scd4x_cmds[cmd].cmd_duration_ms);
}
return ret;
}
static int scd4x_read_reg(const struct device *dev, uint8_t *rx_buf, uint8_t rx_buf_size)
{
const struct scd4x_config *cfg = dev->config;
int ret;
ret = i2c_read_dt(&cfg->bus, rx_buf, rx_buf_size);
if (ret < 0) {
LOG_ERR("Failed to read i2c data.");
return ret;
}
for (uint8_t i = 0; i < (rx_buf_size / 3); i++) {
ret = scd4x_calc_crc(sys_get_be16(&rx_buf[i * 3]));
if (ret != rx_buf[(i * 3) + 2]) {
LOG_ERR("Invalid CRC.");
return -EIO;
}
}
return 0;
}
static int scd4x_write_reg(const struct device *dev, uint8_t cmd, uint16_t *data, uint8_t data_size)
{
const struct scd4x_config *cfg = dev->config;
int ret;
uint8_t tx_buf[((data_size * 3) + 2)];
sys_put_be16(scd4x_cmds[cmd].cmd, tx_buf);
uint8_t tx_buf_pos = 2;
for (uint8_t i = 0; i < data_size; i++) {
sys_put_be16(data[i], &tx_buf[tx_buf_pos]);
tx_buf_pos += 2;
tx_buf[tx_buf_pos++] = scd4x_calc_crc(data[i]);
}
ret = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
if (ret < 0) {
LOG_ERR("Failed to write i2c data.");
return ret;
}
if (scd4x_cmds[cmd].cmd_duration_ms) {
k_msleep(scd4x_cmds[cmd].cmd_duration_ms);
}
return 0;
}
static int scd4x_data_ready(const struct device *dev, bool *is_data_ready)
{
uint8_t rx_data[3];
int ret;
*is_data_ready = false;
ret = scd4x_write_command(dev, SCD4X_CMD_GET_DATA_READY_STATUS);
if (ret < 0) {
LOG_ERR("Failed to write get_data_ready_status command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_data, sizeof(rx_data));
if (ret < 0) {
LOG_ERR("Failed to read get_data_ready_status register.");
return ret;
}
uint16_t word = sys_get_be16(rx_data);
/* Least significant 11 bits = 0 --> data not ready */
if ((word & 0x07FF) > 0) {
*is_data_ready = true;
}
return 0;
}
static int scd4x_read_sample(const struct device *dev)
{
struct scd4x_data *data = dev->data;
uint8_t rx_data[9];
int ret;
ret = scd4x_write_command(dev, SCD4X_CMD_READ_MEASUREMENT);
if (ret < 0) {
LOG_ERR("Failed to write read_measurement command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_data, sizeof(rx_data));
if (ret < 0) {
LOG_ERR("Failed to read read_measurement register.");
return ret;
}
data->co2_sample = sys_get_be16(rx_data);
data->temp_sample = sys_get_be16(&rx_data[3]);
data->humi_sample = sys_get_be16(&rx_data[6]);
return 0;
}
static int scd4x_setup_measurement(const struct device *dev)
{
const struct scd4x_config *cfg = dev->config;
int ret;
switch ((enum scd4x_mode_t)cfg->mode) {
case SCD4X_MODE_NORMAL:
ret = scd4x_write_command(dev, SCD4X_CMD_START_PERIODIC_MEASUREMENT);
if (ret < 0) {
LOG_ERR("Failed to write start_periodic_measurement command.");
return ret;
}
break;
case SCD4X_MODE_LOW_POWER:
ret = scd4x_write_command(dev, SCD4X_CMD_LOW_POWER_PERIODIC_MEASUREMENT);
if (ret < 0) {
LOG_ERR("Failed to write start_low_power_periodic_measurement command.");
return ret;
}
break;
case SCD4X_MODE_SINGLE_SHOT:
ret = scd4x_write_command(dev, SCD4X_CMD_POWER_DOWN);
if (ret < 0) {
LOG_ERR("Failed to write power_down command.");
return ret;
}
break;
default:
return -EINVAL;
}
return 0;
}
static int scd4x_set_idle_mode(const struct device *dev)
{
const struct scd4x_config *cfg = dev->config;
int ret;
if (cfg->mode == SCD4X_MODE_SINGLE_SHOT) {
/*send wake up command twice because of an expected nack return in power down mode*/
scd4x_write_command(dev, SCD4X_CMD_WAKE_UP);
ret = scd4x_write_command(dev, SCD4X_CMD_WAKE_UP);
if (ret < 0) {
LOG_ERR("Failed write wake_up command.");
return ret;
}
} else {
ret = scd4x_write_command(dev, SCD4X_CMD_STOP_PERIODIC_MEASUREMENT);
if (ret < 0) {
LOG_ERR("Failed to write stop_periodic_measurement command.");
return ret;
}
}
return 0;
}
static int scd4x_set_temperature_offset(const struct device *dev, const struct sensor_value *val)
{
int ret;
/*Calculation from Datasheet*/
uint16_t offset_temp =
(float)(val->val1 + (val->val2 / 1000000.0)) * 0xFFFF / SCD4X_MAX_TEMP;
ret = scd4x_write_reg(dev, SCD4X_CMD_SET_TEMPERATURE_OFFSET, &offset_temp, 1);
if (ret < 0) {
LOG_ERR("Failed to write set_temperature_offset register.");
return ret;
}
return 0;
}
static int scd4x_set_sensor_altitude(const struct device *dev, const struct sensor_value *val)
{
int ret;
uint16_t altitude = val->val1;
ret = scd4x_write_reg(dev, SCD4X_CMD_SET_SENSOR_ALTITUDE, &altitude, 1);
if (ret < 0) {
LOG_ERR("Failed to write set_sensor_altitude register.");
return ret;
}
return 0;
}
static int scd4x_set_ambient_pressure(const struct device *dev, const struct sensor_value *val)
{
int ret;
uint16_t ambient_pressure = val->val1;
ret = scd4x_write_reg(dev, SCD4X_CMD_SET_AMBIENT_PRESSURE, &ambient_pressure, 1);
if (ret < 0) {
LOG_ERR("Failed to write set_ambient_pressure register.");
return ret;
}
return 0;
}
static int scd4x_set_automatic_calib_enable(const struct device *dev,
const struct sensor_value *val)
{
int ret;
uint16_t automatic_calib_enable = val->val1;
ret = scd4x_write_reg(dev, SCD4X_CMD_SET_AUTOMATIC_CALIB_ENABLE, &automatic_calib_enable,
1);
if (ret < 0) {
LOG_ERR("Failed to write set_automatic_calibration_enable register.");
return ret;
}
return 0;
}
static int scd4x_set_self_calib_initial_period(const struct device *dev,
const struct sensor_value *val)
{
int ret;
uint16_t initial_period = val->val1;
ret = scd4x_write_reg(dev, SCD4X_CMD_SET_SELF_CALIB_INITIAL_PERIOD, &initial_period, 1);
if (ret < 0) {
LOG_ERR("Failed to write set_automatic_self_calibration_initial_period register.");
return ret;
}
return 0;
}
static int scd4x_set_self_calib_standard_period(const struct device *dev,
const struct sensor_value *val)
{
int ret;
uint16_t standard_period = val->val1;
ret = scd4x_write_reg(dev, SCD4X_CMD_SET_SELF_CALIB_STANDARD_PERIOD, &standard_period, 1);
if (ret < 0) {
LOG_ERR("Failed to write set_automatic_self_calibration_standard_period register.");
return ret;
}
return 0;
}
static int scd4x_get_temperature_offset(const struct device *dev, struct sensor_value *val)
{
int ret;
uint8_t rx_buf[3];
ret = scd4x_write_command(dev, SCD4X_CMD_GET_TEMPERATURE_OFFSET);
if (ret < 0) {
LOG_ERR("Failed to write get_temperature_offset command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_buf, sizeof(rx_buf));
if (ret < 0) {
LOG_ERR("Failed to read get_temperature_offset register.");
return ret;
}
int32_t temp;
/*Calculation from Datasheet*/
temp = sys_get_be16(rx_buf) * SCD4X_MAX_TEMP;
val->val1 = (int32_t)(temp / 0xFFFF);
val->val2 = ((temp % 0xFFFF) * 1000000) / 0xFFFF;
return 0;
}
static int scd4x_get_sensor_altitude(const struct device *dev, struct sensor_value *val)
{
int ret;
uint8_t rx_buf[3];
ret = scd4x_write_command(dev, SCD4X_CMD_GET_SENSOR_ALTITUDE);
if (ret < 0) {
LOG_ERR("Failed to write get_sensor_altitude command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_buf, sizeof(rx_buf));
if (ret < 0) {
LOG_ERR("Failed to read get_sensor_altitude register.");
return ret;
}
val->val1 = sys_get_be16(rx_buf);
val->val2 = 0;
return 0;
}
static int scd4x_get_ambient_pressure(const struct device *dev, struct sensor_value *val)
{
int ret;
uint8_t rx_buf[3];
ret = scd4x_write_command(dev, SCD4X_CMD_GET_AMBIENT_PRESSURE);
if (ret < 0) {
LOG_ERR("Failed to write get_ambient_pressure command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_buf, sizeof(rx_buf));
if (ret < 0) {
LOG_ERR("Failed to read get_ambient_pressure register.");
return ret;
}
val->val1 = sys_get_be16(rx_buf);
val->val2 = 0;
return 0;
}
static int scd4x_get_automatic_calib_enable(const struct device *dev, struct sensor_value *val)
{
int ret;
uint8_t rx_buf[3];
ret = scd4x_write_command(dev, SCD4X_CMD_GET_AUTOMATIC_CALIB_ENABLE);
if (ret < 0) {
LOG_ERR("Failed to write get_automatic_calibration_enable command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_buf, sizeof(rx_buf));
if (ret < 0) {
LOG_ERR("Failed to read get_automatic_calibration_enabled register.");
return ret;
}
val->val1 = sys_get_be16(rx_buf);
val->val2 = 0;
return 0;
}
static int scd4x_get_self_calib_initial_period(const struct device *dev, struct sensor_value *val)
{
int ret;
uint8_t rx_buf[3];
ret = scd4x_write_command(dev, SCD4X_CMD_GET_SELF_CALIB_INITIAL_PERIOD);
if (ret < 0) {
LOG_ERR("Failed to write get_automati_calibration_initial_period command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_buf, sizeof(rx_buf));
if (ret < 0) {
LOG_ERR("Failed to read get_automatic_calibration_initial_period register.");
return ret;
}
val->val1 = sys_get_be16(rx_buf);
val->val2 = 0;
return 0;
}
static int scd4x_get_self_calib_standard_period(const struct device *dev, struct sensor_value *val)
{
int ret;
uint8_t rx_buf[3];
ret = scd4x_write_command(dev, SCD4X_CMD_GET_SELF_CALIB_STANDARD_PERIOD);
if (ret < 0) {
LOG_ERR("Failed to write get_automatic_self_calibration_standard_period command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_buf, sizeof(rx_buf));
if (ret < 0) {
LOG_ERR("Failed to read get_automatic_self_calibration_standard_period register.");
return ret;
}
val->val1 = sys_get_be16(rx_buf);
val->val2 = 0;
return 0;
}
int scd4x_forced_recalibration(const struct device *dev, uint16_t target_concentration,
uint16_t *frc_correction)
{
uint8_t rx_buf[3];
int ret;
ret = scd4x_set_idle_mode(dev);
if (ret < 0) {
LOG_ERR("Failed to set idle mode.");
return ret;
}
ret = scd4x_write_reg(dev, SCD4X_CMD_FORCED_RECALIB, &target_concentration, 1);
if (ret < 0) {
LOG_ERR("Failed to write perform_forced_recalibration register.");
return ret;
}
ret = scd4x_read_reg(dev, rx_buf, sizeof(rx_buf));
if (ret < 0) {
LOG_ERR("Failed to read perform_forced_recalibration register.");
return ret;
}
*frc_correction = sys_get_be16(rx_buf);
/*from datasheet*/
if (*frc_correction == 0xFFFF) {
LOG_ERR("FRC failed. Returned 0xFFFF.");
return -EIO;
}
*frc_correction -= 0x8000;
ret = scd4x_setup_measurement(dev);
if (ret < 0) {
LOG_ERR("Failed to setup measurement.");
return ret;
}
return 0;
}
int scd4x_self_test(const struct device *dev)
{
int ret;
uint8_t rx_buf[3];
ret = scd4x_set_idle_mode(dev);
if (ret < 0) {
LOG_ERR("Failed to set idle mode.");
return ret;
}
ret = scd4x_write_command(dev, SCD4X_CMD_SELF_TEST);
if (ret < 0) {
LOG_ERR("Failed to write perform_self_test command.");
return ret;
}
ret = scd4x_read_reg(dev, rx_buf, sizeof(rx_buf));
if (ret < 0) {
LOG_ERR("Failed to read perform_self_test register.");
return ret;
}
uint16_t is_malfunction = sys_get_be16(rx_buf);
if (is_malfunction) {
LOG_ERR("malfunction detected.");
return -EIO;
}
ret = scd4x_setup_measurement(dev);
if (ret < 0) {
LOG_ERR("Failed to setup measurement.");
return ret;
}
return 0;
}
int scd4x_persist_settings(const struct device *dev)
{
int ret;
ret = scd4x_set_idle_mode(dev);
if (ret < 0) {
LOG_ERR("Failed to set idle mode.");
return ret;
}
ret = scd4x_write_command(dev, SCD4X_CMD_PERSIST_SETTINGS);
if (ret < 0) {
LOG_ERR("Failed to write persist_settings command.");
return ret;
}
ret = scd4x_setup_measurement(dev);
if (ret < 0) {
LOG_ERR("Failed to setup measurement.");
return ret;
}
return 0;
}
int scd4x_factory_reset(const struct device *dev)
{
int ret;
ret = scd4x_set_idle_mode(dev);
if (ret < 0) {
LOG_ERR("Failed to set idle mode.");
return ret;
}
ret = scd4x_write_command(dev, SCD4X_CMD_FACTORY_RESET);
if (ret < 0) {
LOG_ERR("Failed to write perfom_factory_reset command.");
return ret;
}
ret = scd4x_setup_measurement(dev);
if (ret < 0) {
LOG_ERR("Failed to setup measurement.");
return ret;
}
return 0;
}
static int scd4x_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
const struct scd4x_config *cfg = dev->config;
bool is_data_ready;
int ret;
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP &&
chan != SENSOR_CHAN_HUMIDITY && chan != SENSOR_CHAN_CO2) {
return -ENOTSUP;
}
if (cfg->mode == SCD4X_MODE_SINGLE_SHOT) {
ret = scd4x_set_idle_mode(dev);
if (ret < 0) {
LOG_ERR("Failed to set idle mode.");
return ret;
}
if (chan == SENSOR_CHAN_HUMIDITY || chan == SENSOR_CHAN_AMBIENT_TEMP) {
ret = scd4x_write_command(dev, SCD4X_CMD_MEASURE_SINGLE_SHOT_RHT);
if (ret < 0) {
LOG_ERR("Failed to write measure_single_shot_rht_only command.");
return ret;
}
} else {
ret = scd4x_write_command(dev, SCD4X_CMD_MEASURE_SINGLE_SHOT);
if (ret < 0) {
LOG_ERR("Failed to write measure_single_shot command.");
return ret;
}
}
} else {
ret = scd4x_data_ready(dev, &is_data_ready);
if (ret < 0) {
LOG_ERR("Failed to check data ready.");
return ret;
}
if (!is_data_ready) {
return 0;
}
}
ret = scd4x_read_sample(dev);
if (ret < 0) {
LOG_ERR("Failed to get sample data.");
return ret;
}
if (cfg->mode == SCD4X_MODE_SINGLE_SHOT) {
ret = scd4x_setup_measurement(dev);
if (ret < 0) {
LOG_ERR("Failed to setup measurement.");
return ret;
}
}
return 0;
}
static int scd4x_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
const struct scd4x_data *data = dev->data;
int32_t tmp_val;
switch ((enum sensor_channel)chan) {
case SENSOR_CHAN_AMBIENT_TEMP:
/*Calculation from Datasheet*/
tmp_val = data->temp_sample * SCD4X_MAX_TEMP;
val->val1 = (int32_t)(tmp_val / 0xFFFF) + SCD4X_MIN_TEMP;
val->val2 = ((tmp_val % 0xFFFF) * 1000000) / 0xFFFF;
break;
case SENSOR_CHAN_HUMIDITY:
/*Calculation from Datasheet*/
tmp_val = data->humi_sample * 100;
val->val1 = (int32_t)(tmp_val / 0xFFFF);
val->val2 = ((tmp_val % 0xFFFF) * 1000000) / 0xFFFF;
break;
case SENSOR_CHAN_CO2:
val->val1 = data->co2_sample;
val->val2 = 0;
break;
default:
return -ENOTSUP;
}
return 0;
}
int scd4x_attr_set(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr,
const struct sensor_value *val)
{
const struct scd4x_config *cfg = dev->config;
int ret;
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP &&
chan != SENSOR_CHAN_HUMIDITY && chan != SENSOR_CHAN_CO2) {
return -ENOTSUP;
}
if ((enum sensor_attribute_scd4x)attr != SENSOR_ATTR_SCD4X_AMBIENT_PRESSURE) {
ret = scd4x_set_idle_mode(dev);
if (ret < 0) {
LOG_ERR("Failed to set idle mode.");
return ret;
}
}
if (val->val1 < 0 || val->val2 < 0) {
return -EINVAL;
}
switch ((enum sensor_attribute_scd4x)attr) {
case SENSOR_ATTR_SCD4X_TEMPERATURE_OFFSET:
if (val->val1 > SCD4X_TEMPERATURE_OFFSET_IDX_MAX) {
return -EINVAL;
}
ret = scd4x_set_temperature_offset(dev, val);
if (ret < 0) {
LOG_ERR("Failed to set temperature offset.");
return ret;
}
break;
case SENSOR_ATTR_SCD4X_SENSOR_ALTITUDE:
if (val->val1 > SCD4X_SENSOR_ALTITUDE_IDX_MAX) {
return -EINVAL;
}
ret = scd4x_set_sensor_altitude(dev, val);
if (ret < 0) {
LOG_ERR("Failed to set sensor altitude.");
return ret;
}
break;
case SENSOR_ATTR_SCD4X_AMBIENT_PRESSURE:
if (val->val1 > SCD4X_AMBIENT_PRESSURE_IDX_MAX || val->val1 < 700) {
return -EINVAL;
}
ret = scd4x_set_ambient_pressure(dev, val);
if (ret < 0) {
LOG_ERR("Failed to set ambient pressure.");
return ret;
}
/* return 0 to not call scd4x_start_measurement */
return 0;
case SENSOR_ATTR_SCD4X_AUTOMATIC_CALIB_ENABLE:
if (val->val1 > SCD4X_BOOL_IDX_MAX) {
return -EINVAL;
}
ret = scd4x_set_automatic_calib_enable(dev, val);
if (ret < 0) {
LOG_ERR("Failed to set automatic calib enable.");
return ret;
}
break;
case SENSOR_ATTR_SCD4X_SELF_CALIB_INITIAL_PERIOD:
if (val->val1 % 4) {
return -EINVAL;
}
if (cfg->model == SCD4X_MODEL_SCD40) {
LOG_ERR("SELF_CALIB_INITIAL_PERIOD not available for SCD40.");
return -ENOTSUP;
}
ret = scd4x_set_self_calib_initial_period(dev, val);
if (ret < 0) {
LOG_ERR("Failed to set self calib initial period.");
return ret;
}
break;
case SENSOR_ATTR_SCD4X_SELF_CALIB_STANDARD_PERIOD:
if (val->val1 % 4) {
return -EINVAL;
}
if (cfg->model == SCD4X_MODEL_SCD40) {
LOG_ERR("SELF_CALIB_STANDARD_PERIOD not available for SCD40.");
return -ENOTSUP;
}
ret = scd4x_set_self_calib_standard_period(dev, val);
if (ret < 0) {
LOG_ERR("Failed to set self calib standard period.");
return ret;
}
break;
default:
return -ENOTSUP;
}
ret = scd4x_setup_measurement(dev);
if (ret < 0) {
LOG_ERR("Failed to setup measurement.");
return ret;
}
return 0;
}
static int scd4x_attr_get(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, struct sensor_value *val)
{
const struct scd4x_config *cfg = dev->config;
int ret;
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP &&
chan != SENSOR_CHAN_HUMIDITY && chan != SENSOR_CHAN_CO2) {
return -ENOTSUP;
}
if ((enum sensor_attribute_scd4x)attr != SENSOR_ATTR_SCD4X_AMBIENT_PRESSURE ||
cfg->mode == SCD4X_MODE_SINGLE_SHOT) {
ret = scd4x_set_idle_mode(dev);
if (ret < 0) {
LOG_ERR("Failed to set idle mode.");
return ret;
}
}
switch ((enum sensor_attribute_scd4x)attr) {
case SENSOR_ATTR_SCD4X_TEMPERATURE_OFFSET:
ret = scd4x_get_temperature_offset(dev, val);
if (ret < 0) {
LOG_ERR("Failed to get temperature offset.");
return ret;
}
break;
case SENSOR_ATTR_SCD4X_SENSOR_ALTITUDE:
ret = scd4x_get_sensor_altitude(dev, val);
if (ret < 0) {
LOG_ERR("Failed to get sensor altitude.");
return ret;
}
break;
case SENSOR_ATTR_SCD4X_AMBIENT_PRESSURE:
ret = scd4x_get_ambient_pressure(dev, val);
if (ret < 0) {
LOG_ERR("Failed to get ambient pressure.");
return ret;
}
/* return 0 to not call scd4x_setup_measurement */
return 0;
case SENSOR_ATTR_SCD4X_AUTOMATIC_CALIB_ENABLE:
ret = scd4x_get_automatic_calib_enable(dev, val);
if (ret < 0) {
LOG_ERR("Failed to get automatic calib.");
return ret;
}
break;
case SENSOR_ATTR_SCD4X_SELF_CALIB_INITIAL_PERIOD:
if (cfg->model == SCD4X_MODEL_SCD40) {
LOG_ERR("SELF_CALIB_INITIAL_PERIOD not available for SCD40.");
return -ENOTSUP;
}
ret = scd4x_get_self_calib_initial_period(dev, val);
if (ret < 0) {
LOG_ERR("Failed to set get self calib initial period.");
return ret;
}
break;
case SENSOR_ATTR_SCD4X_SELF_CALIB_STANDARD_PERIOD:
if (cfg->model == SCD4X_MODEL_SCD40) {
LOG_ERR("SELF_CALIB_STANDARD_PERIOD not available for SCD40.");
return -ENOTSUP;
}
ret = scd4x_get_self_calib_standard_period(dev, val);
if (ret < 0) {
LOG_ERR("Failed to set get self calib standard period.");
return ret;
}
break;
default:
return -ENOTSUP;
}
ret = scd4x_setup_measurement(dev);
if (ret < 0) {
LOG_ERR("Failed to setup measurement.");
return ret;
}
return 0;
}
static int scd4x_init(const struct device *dev)
{
const struct scd4x_config *cfg = dev->config;
int ret;
if (!i2c_is_ready_dt(&cfg->bus)) {
LOG_ERR("Device not ready.");
return -ENODEV;
}
ret = scd4x_write_command(dev, SCD4X_CMD_STOP_PERIODIC_MEASUREMENT);
if (ret < 0) {
/*send wake up command twice because of an expected nack return in power down mode*/
scd4x_write_command(dev, SCD4X_CMD_WAKE_UP);
ret = scd4x_write_command(dev, SCD4X_CMD_WAKE_UP);
if (ret < 0) {
LOG_ERR("Failed to put the device in idle mode.");
return ret;
}
}
ret = scd4x_write_command(dev, SCD4X_CMD_REINIT);
if (ret < 0) {
LOG_ERR("Failed to reset the device.");
return ret;
}
ret = scd4x_setup_measurement(dev);
if (ret < 0) {
LOG_ERR("Failed to setup measurement.");
return ret;
}
return 0;
}
static const struct sensor_driver_api scd4x_api_funcs = {
.sample_fetch = scd4x_sample_fetch,
.channel_get = scd4x_channel_get,
.attr_set = scd4x_attr_set,
.attr_get = scd4x_attr_get,
};
#define SCD4X_INIT(inst, scd4x_model) \
static struct scd4x_data scd4x_data_##inst; \
static const struct scd4x_config scd4x_config_##inst = { \
.bus = I2C_DT_SPEC_INST_GET(inst), \
.model = scd4x_model, \
.mode = DT_INST_ENUM_IDX_OR(inst, mode, SCD4X_MODE_NORMAL), \
}; \
SENSOR_DEVICE_DT_INST_DEFINE(inst, scd4x_init, NULL, &scd4x_data_##inst, \
&scd4x_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &scd4x_api_funcs);
#define DT_DRV_COMPAT sensirion_scd40
DT_INST_FOREACH_STATUS_OKAY_VARGS(SCD4X_INIT, SCD4X_MODEL_SCD40)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT sensirion_scd41
DT_INST_FOREACH_STATUS_OKAY_VARGS(SCD4X_INIT, SCD4X_MODEL_SCD41)
#undef DT_DRV_COMPAT

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2024 Jan Fäh
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_SCD4X_SCD4X_H_
#define ZEPHYR_DRIVERS_SENSOR_SCD4X_SCD4X_H_
#include <zephyr/device.h>
#define SCD4X_CMD_REINIT 0
#define SCD4X_CMD_START_PERIODIC_MEASUREMENT 1
#define SCD4X_CMD_STOP_PERIODIC_MEASUREMENT 2
#define SCD4X_CMD_READ_MEASUREMENT 3
#define SCD4X_CMD_SET_TEMPERATURE_OFFSET 4
#define SCD4X_CMD_GET_TEMPERATURE_OFFSET 5
#define SCD4X_CMD_SET_SENSOR_ALTITUDE 6
#define SCD4X_CMD_GET_SENSOR_ALTITUDE 7
#define SCD4X_CMD_SET_AMBIENT_PRESSURE 8
#define SCD4X_CMD_GET_AMBIENT_PRESSURE 9
#define SCD4X_CMD_FORCED_RECALIB 10
#define SCD4X_CMD_SET_AUTOMATIC_CALIB_ENABLE 11
#define SCD4X_CMD_GET_AUTOMATIC_CALIB_ENABLE 12
#define SCD4X_CMD_LOW_POWER_PERIODIC_MEASUREMENT 13
#define SCD4X_CMD_GET_DATA_READY_STATUS 14
#define SCD4X_CMD_PERSIST_SETTINGS 15
#define SCD4X_CMD_SELF_TEST 16
#define SCD4X_CMD_FACTORY_RESET 17
#define SCD4X_CMD_MEASURE_SINGLE_SHOT 18
#define SCD4X_CMD_MEASURE_SINGLE_SHOT_RHT 19
#define SCD4X_CMD_POWER_DOWN 10
#define SCD4X_CMD_WAKE_UP 21
#define SCD4X_CMD_SET_SELF_CALIB_INITIAL_PERIOD 22
#define SCD4X_CMD_GET_SELF_CALIB_INITIAL_PERIOD 23
#define SCD4X_CMD_SET_SELF_CALIB_STANDARD_PERIOD 24
#define SCD4X_CMD_GET_SELF_CALIB_STANDARD_PERIOD 25
#define SCD4X_CRC_POLY 0x31
#define SCD4X_CRC_INIT 0xFF
#define SCD4X_STARTUP_TIME_MS 30
#define SCD4X_TEMPERATURE_OFFSET_IDX_MAX 20
#define SCD4X_SENSOR_ALTITUDE_IDX_MAX 3000
#define SCD4X_AMBIENT_PRESSURE_IDX_MAX 1200
#define SCD4X_BOOL_IDX_MAX 1
#define SCD4X_MAX_TEMP 175
#define SCD4X_MIN_TEMP -45
enum scd4x_model_t {
SCD4X_MODEL_SCD40,
SCD4X_MODEL_SCD41,
};
enum scd4x_mode_t {
SCD4X_MODE_NORMAL,
SCD4X_MODE_LOW_POWER,
SCD4X_MODE_SINGLE_SHOT,
};
struct scd4x_config {
struct i2c_dt_spec bus;
enum scd4x_model_t model;
enum scd4x_mode_t mode;
};
struct scd4x_data {
uint16_t temp_sample;
uint16_t humi_sample;
uint16_t co2_sample;
};
struct cmds_t {
uint16_t cmd;
uint16_t cmd_duration_ms;
};
const struct cmds_t scd4x_cmds[] = {
{0x3646, 30}, {0x21B1, 0}, {0x3F86, 500}, {0xEC05, 1}, {0x241D, 1}, {0x2318, 1},
{0x2427, 1}, {0x2322, 1}, {0xE000, 1}, {0xE000, 1}, {0x362F, 400}, {0x2416, 1},
{0x2313, 1}, {0x21AC, 0}, {0xE4B8, 1}, {0x3615, 800}, {0x3639, 10000}, {0x3632, 1200},
{0x219D, 5000}, {0x2196, 50}, {0x36E0, 1}, {0x36F6, 30}, {0x2445, 1}, {0x2340, 1},
{0x244E, 1}, {0x234B, 1},
};
#endif /* ZEPHYR_DRIVERS_SENSOR_SCD4X_SCD4X_H_ */

View file

@ -0,0 +1,8 @@
# Copyright (c) 2024, Jan Fäh
# SPDX-License-Identifier: Apache-2.0
description: Sensirion SCD4x temperature sensor
compatible: "sensirion,scd40"
include: [sensor-device.yaml, i2c-device.yaml]

View file

@ -0,0 +1,21 @@
# Copyright (c) 2024, Jan Fäh
# SPDX-License-Identifier: Apache-2.0
description: Sensirion SCD4x temperature sensor
compatible: "sensirion,scd41"
include: [sensor-device.yaml, i2c-device.yaml]
properties:
mode:
type: int
required: true
description: |
- 0: Normal periodic measurement. Default interval of 5sec
- 1: Low power periodic measurement. Interval of 30sec
- 2: Singleshot measurement for low power usage.
enum:
- 0
- 1
- 2

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2024 Jan Fäh
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_SCD4X_H_
#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_SCD4X_H_
#include <zephyr/drivers/sensor.h>
enum sensor_attribute_scd4x {
/* Offset temperature: Toffset_actual = Tscd4x Treference + Toffset_previous
* 0 - 20°C
*/
SENSOR_ATTR_SCD4X_TEMPERATURE_OFFSET = SENSOR_ATTR_PRIV_START,
/* Altidude of the sensor;
* 0 - 3000m
*/
SENSOR_ATTR_SCD4X_SENSOR_ALTITUDE,
/* Ambient pressure in hPa
* 700 - 1200hPa
*/
SENSOR_ATTR_SCD4X_AMBIENT_PRESSURE,
/* Set the current state (enabled: 1 / disabled: 0).
* Default: enabled.
*/
SENSOR_ATTR_SCD4X_AUTOMATIC_CALIB_ENABLE,
/* Set the initial period for automatic self calibration correction in hours. Allowed values
* are integer multiples of 4 hours.
* Default: 44
*/
SENSOR_ATTR_SCD4X_SELF_CALIB_INITIAL_PERIOD,
/* Set the standard period for automatic self calibration correction in hours. Allowed
* values are integer multiples of 4 hours. Default: 156
*/
SENSOR_ATTR_SCD4X_SELF_CALIB_STANDARD_PERIOD,
};
/**
* @brief Performs a forced recalibration.
*
* Operate the SCD4x in the operation mode for at least 3 minutes in an environment with a
* homogeneous and constant CO2 concentration. Otherwise the recalibratioin will fail. The sensor
* must be operated at the voltage desired for the application when performing the FRC sequence.
*
* @param dev Pointer to the sensor device
* @param target_concentration Reference CO2 concentration.
* @param frc_correction Previous differences from the target concentration
*
* @return 0 if successful, negative errno code if failure.
*/
int scd4x_forced_recalibration(const struct device *dev, uint16_t target_concentration,
uint16_t *frc_correction);
/**
* @brief Performs a self test.
*
* The self_test command can be used as an end-of-line test to check the sensor functionality
*
* @param dev Pointer to the sensor device
*
* @return 0 if successful, negative errno code if failure.
*/
int scd4x_self_test(const struct device *dev);
/**
* @brief Performs a self test.
*
* The persist_settings command can be used to save the actual configuration. This command
* should only be sent when persistence is required and if actual changes to the configuration have
* been made. The EEPROM is guaranteed to withstand at least 2000 write cycles
*
* @param dev Pointer to the sensor device
*
* @return 0 if successful, negative errno code if failure.
*/
int scd4x_persist_settings(const struct device *dev);
/**
* @brief Performs a factory reset.
*
* The perform_factory_reset command resets all configuration settings stored in the EEPROM and
* erases the FRC and ASC algorithm history.
*
* @param dev Pointer to the sensor device
*
* @return 0 if successful, negative errno code if failure.
*/
int scd4x_factory_reset(const struct device *dev);
#endif /* ZEPHYR_INCLUDE_DRIVERS_SENSOR_SCD4X_H_ */

View file

@ -1131,3 +1131,9 @@ test_i2c_sts4x: sts4x@9d {
reg = <0x99>;
repeatability = <2>;
};
test_i2c_scd4x: scd4x@9e {
compatible = "sensirion,scd41";
reg = <0x9e>;
mode = <0>;
};