| /* |
| * Copyright (c) 2021 Leonard Pollak |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT sensirion_sht4x |
| |
| #include <device.h> |
| #include <drivers/i2c.h> |
| #include <kernel.h> |
| #include <drivers/sensor.h> |
| #include <sys/__assert.h> |
| #include <logging/log.h> |
| #include <sys/byteorder.h> |
| #include <sys/crc.h> |
| |
| #include <drivers/sensor/sht4x.h> |
| #include "sht4x.h" |
| |
| LOG_MODULE_REGISTER(SHT4X, CONFIG_SENSOR_LOG_LEVEL); |
| |
| static uint8_t sht4x_compute_crc(uint16_t value) |
| { |
| uint8_t buf[2]; |
| |
| sys_put_be16(value, buf); |
| |
| return crc8(buf, 2, SHT4X_CRC_POLY, SHT4X_CRC_INIT, false); |
| } |
| |
| static int sht4x_write_command(const struct device *dev, uint8_t cmd) |
| { |
| const struct sht4x_config *cfg = dev->config; |
| uint8_t tx_buf[1] = { cmd }; |
| |
| return i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf)); |
| } |
| |
| static int sht4x_read_sample(const struct device *dev, |
| uint16_t *t_sample, |
| uint16_t *rh_sample) |
| { |
| const struct sht4x_config *cfg = dev->config; |
| uint8_t rx_buf[6]; |
| int rc; |
| |
| rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf)); |
| if (rc < 0) { |
| LOG_ERR("Failed to read data from device."); |
| return rc; |
| } |
| |
| *t_sample = sys_get_be16(rx_buf); |
| if (sht4x_compute_crc(*t_sample) != rx_buf[2]) { |
| LOG_ERR("Invalid CRC for T."); |
| return -EIO; |
| } |
| |
| *rh_sample = sys_get_be16(&rx_buf[3]); |
| if (sht4x_compute_crc(*rh_sample) != rx_buf[5]) { |
| LOG_ERR("Invalid CRC for RH."); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /* public API for handling the heater */ |
| int sht4x_fetch_with_heater(const struct device *dev) |
| { |
| struct sht4x_data *data = dev->data; |
| int rc; |
| |
| rc = sht4x_write_command(dev, |
| heater_cmd[data->heater_power][data->heater_duration]); |
| if (rc < 0) { |
| LOG_ERR("Failed to start measurement."); |
| return rc; |
| } |
| |
| k_sleep(K_MSEC(heater_wait_ms[data->heater_duration])); |
| |
| rc = sht4x_read_sample(dev, &data->t_sample, &data->rh_sample); |
| if (rc < 0) { |
| LOG_ERR("Failed to fetch data."); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int sht4x_sample_fetch(const struct device *dev, |
| enum sensor_channel chan) |
| { |
| const struct sht4x_config *cfg = dev->config; |
| struct sht4x_data *data = dev->data; |
| int rc; |
| |
| if (chan != SENSOR_CHAN_ALL && |
| chan != SENSOR_CHAN_AMBIENT_TEMP && |
| chan != SENSOR_CHAN_HUMIDITY) { |
| return -ENOTSUP; |
| } |
| |
| rc = sht4x_write_command(dev, measure_cmd[cfg->repeatability]); |
| if (rc < 0) { |
| LOG_ERR("Failed to start measurement."); |
| return rc; |
| } |
| |
| k_sleep(K_USEC(measure_wait_us[cfg->repeatability])); |
| |
| rc = sht4x_read_sample(dev, &data->t_sample, &data->rh_sample); |
| if (rc < 0) { |
| LOG_ERR("Failed to fetch data."); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int sht4x_channel_get(const struct device *dev, |
| enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| const struct sht4x_data *data = dev->data; |
| |
| /* |
| * See datasheet "Conversion of Signal Output" section |
| * for more details on processing sample data. |
| */ |
| if (chan == SENSOR_CHAN_AMBIENT_TEMP) { |
| int64_t tmp; |
| |
| tmp = data->t_sample * 175; |
| val->val1 = (int32_t)(tmp / 0xFFFF) - 45; |
| val->val2 = ((tmp % 0xFFFF) * 1000000) / 0xFFFF; |
| } else if (chan == SENSOR_CHAN_HUMIDITY) { |
| uint64_t tmp; |
| |
| tmp = data->rh_sample * 125U; |
| val->val1 = (uint32_t)(tmp / 0xFFFF) - 6U; |
| val->val2 = (tmp % 0xFFFF) * 15625U / 1024U; |
| } else { |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static int sht4x_attr_set(const struct device *dev, |
| enum sensor_channel chan, |
| enum sensor_attribute attr, |
| const struct sensor_value *val) |
| { |
| struct sht4x_data *data = dev->data; |
| |
| if (val->val1 < 0) { |
| return -EINVAL; |
| } |
| |
| switch ((enum sensor_attribute_sht4x)attr) { |
| case SENSOR_ATTR_SHT4X_HEATER_POWER: |
| if (val->val1 > SHT4X_HEATER_POWER_IDX_MAX) { |
| return -EINVAL; |
| } |
| data->heater_power = val->val1; |
| break; |
| case SENSOR_ATTR_SHT4X_HEATER_DURATION: |
| if (val->val1 > SHT4X_HEATER_DURATION_IDX_MAX) { |
| return -EINVAL; |
| } |
| data->heater_duration = val->val1; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static int sht4x_init(const struct device *dev) |
| { |
| const struct sht4x_config *cfg = dev->config; |
| int rc = 0; |
| |
| if (!device_is_ready(cfg->bus.bus)) { |
| LOG_ERR("Device not ready."); |
| return -ENODEV; |
| } |
| |
| rc = sht4x_write_command(dev, SHT4X_CMD_RESET); |
| if (rc < 0) { |
| LOG_ERR("Failed to reset the device."); |
| return rc; |
| } |
| |
| k_sleep(K_MSEC(SHT4X_RESET_WAIT_MS)); |
| |
| return 0; |
| } |
| |
| |
| static const struct sensor_driver_api sht4x_api = { |
| .sample_fetch = sht4x_sample_fetch, |
| .channel_get = sht4x_channel_get, |
| .attr_set = sht4x_attr_set, |
| }; |
| |
| #define SHT4X_INIT(n) \ |
| static struct sht4x_data sht4x_data_##n; \ |
| \ |
| static const struct sht4x_config sht4x_config_##n = { \ |
| .bus = I2C_DT_SPEC_INST_GET(n), \ |
| .repeatability = DT_INST_PROP(n, repeatability) \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| sht4x_init, \ |
| NULL, \ |
| &sht4x_data_##n, \ |
| &sht4x_config_##n, \ |
| POST_KERNEL, \ |
| CONFIG_SENSOR_INIT_PRIORITY, \ |
| &sht4x_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SHT4X_INIT) |