/* * Copyright (c) 2021 Thomas Stranger * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT sensirion_shtcx #include #include #include #include #include #include #include #include #include "shtcx.h" LOG_MODULE_REGISTER(SHTCX, CONFIG_SENSOR_LOG_LEVEL); /* all cmds read temp first: cmd[MEASURE_MODE][Clock_stretching_enabled] */ static const uint16_t measure_cmd[2][2] = { { 0x7866, 0x7CA2 }, { 0x609C, 0x6458 }, }; /* measure_wait_us[shtcx_chip][MEASURE_MODE] */ static const uint16_t measure_wait_us[2][2] = { /* shtc1: 14.4ms, 0.94ms */ { 14400, 940 }, /* shtc1 */ /* shtc3: 12.1ms, 0.8ms */ { 12100, 800 }, /* shtc3 */ }; /* * CRC algorithm parameters were taken from the * "Checksum Calculation" section of the datasheet. */ static uint8_t shtcx_compute_crc(uint16_t value) { uint8_t buf[2]; sys_put_be16(value, buf); return crc8(buf, 2, 0x31, 0xFF, false); } /* val = -45 + 175 * sample / (2^16) */ static void shtcx_temperature_from_raw(uint16_t raw, struct sensor_value *val) { int32_t tmp; tmp = (int32_t)raw * 175U - (45 << 16); val->val1 = tmp / 0x10000; /* x * 1.000.000 / 65.536 == x * 15625 / 2^10 */ val->val2 = ((tmp % 0x10000) * 15625) / 1024; } /* val = 100 * sample / (2^16) */ static void shtcx_humidity_from_raw(uint16_t raw, struct sensor_value *val) { uint32_t tmp; tmp = (uint32_t)raw * 100U; val->val1 = tmp / 0x10000; /* x * 1.000.000 / 65.536 == x * 15625 / 1024 */ val->val2 = (tmp % 0x10000) * 15625U / 1024; } static int shtcx_write_command(const struct device *dev, uint16_t cmd) { const struct shtcx_config *cfg = dev->config; uint8_t tx_buf[2]; sys_put_be16(cmd, tx_buf); return i2c_write_dt(&cfg->i2c, tx_buf, sizeof(tx_buf)); } static int shtcx_read_words(const struct device *dev, uint16_t cmd, uint16_t *data, uint16_t num_words, uint16_t max_duration_us) { const struct shtcx_config *cfg = dev->config; int status = 0; uint32_t raw_len = num_words * (SHTCX_WORD_LEN + SHTCX_CRC8_LEN); uint16_t temp16; uint8_t rx_buf[SHTCX_MAX_READ_LEN]; int dst = 0; status = shtcx_write_command(dev, cmd); if (status != 0) { LOG_DBG("Failed to initiate read"); return -EIO; } if (!cfg->clock_stretching) { k_sleep(K_USEC(max_duration_us)); } status = i2c_read_dt(&cfg->i2c, rx_buf, raw_len); if (status != 0) { LOG_DBG("Failed to read data"); return -EIO; } for (int i = 0; i < raw_len; i += (SHTCX_WORD_LEN + SHTCX_CRC8_LEN)) { temp16 = sys_get_be16(&rx_buf[i]); if (shtcx_compute_crc(temp16) != rx_buf[i+2]) { LOG_DBG("invalid received invalid crc"); return -EIO; } data[dst++] = temp16; } return 0; } static int shtcx_sleep(const struct device *dev) { if (shtcx_write_command(dev, SHTCX_CMD_SLEEP) < 0) { return -EIO; } return 0; } static int shtcx_wakeup(const struct device *dev) { if (shtcx_write_command(dev, SHTCX_CMD_WAKEUP)) { return -EIO; } k_sleep(K_USEC(100)); return 0; } static int shtcx_sample_fetch(const struct device *dev, enum sensor_channel chan) { struct shtcx_data *data = dev->data; const struct shtcx_config *cfg = dev->config; __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL); if (cfg->chip == SHTC3) { if (shtcx_wakeup(dev)) { return -EIO; } } if (shtcx_read_words(dev, measure_cmd[cfg->measure_mode][cfg->clock_stretching], (uint16_t *)&data->sample, 2, measure_wait_us[cfg->chip][cfg->measure_mode]) < 0) { LOG_DBG("Failed read measurements!"); return -EIO; } if (cfg->chip == SHTC3) { if (shtcx_sleep(dev)) { LOG_DBG("Failed to initiate sleep"); return -EIO; } } return 0; } static int shtcx_channel_get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val) { const struct shtcx_data *data = dev->data; if (chan == SENSOR_CHAN_AMBIENT_TEMP) { shtcx_temperature_from_raw(data->sample.temp, val); } else if (chan == SENSOR_CHAN_HUMIDITY) { shtcx_humidity_from_raw(data->sample.humidity, val); } else { return -ENOTSUP; } return 0; } static const struct sensor_driver_api shtcx_driver_api = { .sample_fetch = shtcx_sample_fetch, .channel_get = shtcx_channel_get, }; static int shtcx_init(const struct device *dev) { const struct shtcx_config *cfg = dev->config; uint16_t product_id; if (!device_is_ready(cfg->i2c.bus)) { LOG_ERR("Bus device is not ready"); return -ENODEV; } k_sleep(K_USEC(SHTCX_POWER_UP_TIME_US)); if (cfg->chip == SHTC3) { if (shtcx_wakeup(dev)) { LOG_ERR("Wakeup failed"); return -EIO; } } if (shtcx_write_command(dev, SHTCX_CMD_SOFT_RESET) < 0) { LOG_ERR("soft reset failed"); return -EIO; } k_sleep(K_USEC(SHTCX_SOFT_RESET_TIME_US)); if (shtcx_read_words(dev, SHTCX_CMD_READ_ID, &product_id, 1, 0) < 0) { LOG_ERR("Failed to read product id!"); return -EIO; } if (cfg->chip == SHTC1) { if ((product_id & SHTC1_ID_MASK) != SHTC1_ID_VALUE) { LOG_ERR("Device is not a SHTC1"); return -EINVAL; } } if (cfg->chip == SHTC3) { if ((product_id & SHTC3_ID_MASK) != SHTC3_ID_VALUE) { LOG_ERR("Device is not a SHTC3"); return -EINVAL; } shtcx_sleep(dev); } LOG_DBG("Clock-stretching enabled: %d", cfg->clock_stretching); LOG_DBG("Measurement mode: %d", cfg->measure_mode); LOG_DBG("Init SHTCX"); return 0; } #define SHTCX_CONFIG(inst) \ { \ .i2c = I2C_DT_SPEC_INST_GET(inst), \ .chip = DT_INST_ENUM_IDX(inst, chip), \ .measure_mode = DT_INST_ENUM_IDX(inst, measure_mode), \ .clock_stretching = DT_INST_PROP(inst, clock_stretching) \ } #define SHTCX_DEFINE(inst) \ static struct shtcx_data shtcx_data_##inst; \ static struct shtcx_config shtcx_config_##inst = \ SHTCX_CONFIG(inst); \ SENSOR_DEVICE_DT_INST_DEFINE(inst, \ shtcx_init, \ NULL, \ &shtcx_data_##inst, \ &shtcx_config_##inst, \ POST_KERNEL, \ CONFIG_SENSOR_INIT_PRIORITY, \ &shtcx_driver_api); DT_INST_FOREACH_STATUS_OKAY(SHTCX_DEFINE)