#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)