| /* |
| * Copyright (c) 2021 Thomas Stranger |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT sensirion_shtcx |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/crc.h> |
| #include <zephyr/logging/log.h> |
| |
| #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) * 15625U) / 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) |