diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 35790146b02..52962a0b2fd 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -36,6 +36,7 @@ add_subdirectory_ifdef(CONFIG_BQ274XX bq274xx) add_subdirectory_ifdef(CONFIG_CCS811 ccs811) add_subdirectory_ifdef(CONFIG_CURRENT_AMP current_amp) add_subdirectory_ifdef(CONFIG_DHT dht) +add_subdirectory_ifdef(CONFIG_DHT20 dht20) add_subdirectory_ifdef(CONFIG_DPS310 dps310) add_subdirectory_ifdef(CONFIG_DS18B20 ds18b20) add_subdirectory_ifdef(CONFIG_ENS160 ens160) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 1768335979a..27d6c165197 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -116,6 +116,7 @@ source "drivers/sensor/bq274xx/Kconfig" source "drivers/sensor/ccs811/Kconfig" source "drivers/sensor/current_amp/Kconfig" source "drivers/sensor/dht/Kconfig" +source "drivers/sensor/dht20/Kconfig" source "drivers/sensor/dps310/Kconfig" source "drivers/sensor/ds18b20/Kconfig" source "drivers/sensor/ens160/Kconfig" diff --git a/drivers/sensor/dht20/CMakeLists.txt b/drivers/sensor/dht20/CMakeLists.txt new file mode 100644 index 00000000000..c5539e1d49c --- /dev/null +++ b/drivers/sensor/dht20/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(dht20.c) diff --git a/drivers/sensor/dht20/Kconfig b/drivers/sensor/dht20/Kconfig new file mode 100644 index 00000000000..54c8d731ec9 --- /dev/null +++ b/drivers/sensor/dht20/Kconfig @@ -0,0 +1,26 @@ +# AOSONG DHT20 temperature and humidity sensor configuration options +# This driver can also be used for AHT20 or AM2301B which seem to be the same sensor + +# Copyright (c) 2024 Nathan Olff +# SPDX-License-Identifier: Apache-2.0 + +config DHT20 + bool "DHT20 / AHT20 / AM2301B Temperature and Humidity Sensor" + default y + depends on DT_HAS_AOSONG_DHT20_ENABLED \ + || DT_HAS_AOSONG_AHT20_ENABLED \ + || DT_HAS_AOSONG_AM2301B_ENABLED + select I2C + help + Enable driver for DHT20 / AHT20 / AM2301B temperature and humidity sensors. + +if DHT20 + +config DHT20_CRC + bool "Use CRC error detection" + default n + select CRC + help + Verify CRC byte in RX data + +endif # DHT20 diff --git a/drivers/sensor/dht20/dht20.c b/drivers/sensor/dht20/dht20.c new file mode 100644 index 00000000000..ba43584a9ad --- /dev/null +++ b/drivers/sensor/dht20/dht20.c @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2024 Nathan Olff + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#if defined(CONFIG_DHT20_CRC) +#include +#endif +#include +#include +#include +#include + +#define DHT20_STATUS_REGISTER 0x71 + +#define DHT20_STATUS_MASK (BIT(0) | BIT(1)) + +#define DHT20_STATUS_MASK_CHECK 0x18 +#define DHT20_STATUS_MASK_POLL_STATE 0x80 + +#define DHT20_MASK_RESET_REGISTER 0xB0 + +#define DHT20_TRIGGER_MEASUREMENT_COMMAND 0xAC, 0x33, 0x00 + +#define DHT20_TRIGGER_MEASUREMENT_BUFFER_LENGTH 3 + +/** CRC polynom (1 + X^4 + X^5 + X^8) */ +#define DHT20_CRC_POLYNOM (BIT(0) | BIT(4) | BIT(5)) + +/* + * According to datasheet 7.4 + * Reset register 0x1B, 0x1C and 0x1E + */ +#define DHT20_RESET_REGISTER_0 0x1B +#define DHT20_RESET_REGISTER_1 0x1C +#define DHT20_RESET_REGISTER_2 0x1E + +/** Length of the buffer used for data measurement */ +#define DHT20_MEASUREMENT_BUFFER_LENGTH 7 + +/** Wait some time after reset sequence (in ms) */ +#define DHT20_RESET_SEQUENCE_WAIT_MS 10 + +/** Wait after power on (in ms) */ +#define DHT20_POWER_ON_WAIT_MS 75 +/** Wait during polling after power on (in ms) */ +#define DHT20_INIT_POLL_STATUS_WAIT_MS 5 + +LOG_MODULE_REGISTER(DHT20, CONFIG_SENSOR_LOG_LEVEL); + +struct dht20_config { + struct i2c_dt_spec bus; +}; + +struct dht20_data { + uint32_t t_sample; + uint32_t rh_sample; +}; + +/** + * @brief Read status register + * + * @param dev Pointer to the sensor device + * @param[out] status Pointer to which the status will be stored + * @return 0 if successful + */ +static inline int read_status(const struct device *dev, uint8_t *status) +{ + const struct dht20_config *cfg = dev->config; + int rc; + uint8_t tx_buf[] = {DHT20_STATUS_REGISTER}; + uint8_t rx_buf[1]; + + /* Write DHT20_STATUS_REGISTER then read to get status */ + rc = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf)); + if (rc < 0) { + LOG_ERR("Failed to start measurement."); + return 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; + } + + /* Retrieve status from rx_buf */ + *status = rx_buf[0]; + return rc; +} + +static inline int reset_register(const struct device *dev, uint8_t reg) +{ + const struct dht20_config *cfg = dev->config; + int rc; + uint8_t tx_buf[] = {reg, 0, 0}; + uint8_t rx_buf[3]; + + /* Write and then read 3 bytes from device */ + rc = i2c_write_read_dt(&cfg->bus, tx_buf, sizeof(tx_buf), rx_buf, sizeof(rx_buf)); + if (rc < 0) { + LOG_ERR("Failed to reset register."); + return rc; + } + + /* Write register again, using values read earlier */ + tx_buf[0] = DHT20_MASK_RESET_REGISTER | reg; + tx_buf[1] = rx_buf[1]; + tx_buf[2] = rx_buf[2]; + rc = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf)); + if (rc < 0) { + LOG_ERR("Failed to reset register."); + return rc; + } + + return rc; +} + +static inline int reset_sensor(const struct device *dev) +{ + int rc; + uint8_t status; + + rc = read_status(dev, &status); + if (rc < 0) { + LOG_ERR("Failed to read status"); + return rc; + } + + if ((status & DHT20_STATUS_MASK_CHECK) != DHT20_STATUS_MASK_CHECK) { + /* + * According to datasheet 7.4 + * Reset register 0x1B, 0x1C and 0x1E if status does not match expected value + */ + rc = reset_register(dev, DHT20_RESET_REGISTER_0); + if (rc < 0) { + return rc; + } + rc = reset_register(dev, DHT20_RESET_REGISTER_1); + if (rc < 0) { + return rc; + } + rc = reset_register(dev, DHT20_RESET_REGISTER_2); + if (rc < 0) { + return rc; + } + /* Wait 10ms after reset sequence */ + k_msleep(DHT20_RESET_SEQUENCE_WAIT_MS); + } + + return 0; +} + +static int dht20_read_sample(const struct device *dev, uint32_t *t_sample, uint32_t *rh_sample) +{ + const struct dht20_config *cfg = dev->config; + /* + * Datasheet shows content of the measurement data as follow + * + * +------+----------------------------------------+ + * | Byte | Content | + * +------+----------------------------------------+ + * | 0 | State | + * | 1 | Humidity | + * | 2 | Humidity | + * | 3 | Humidity (4 MSb) | Temperature (4 LSb) | + * | 4 | Temperature | + * | 5 | Temperature | + * | 6 | CRC | + * +------+----------------------------------------+ + */ + uint8_t rx_buf[DHT20_MEASUREMENT_BUFFER_LENGTH]; + int rc; + uint8_t status; + + rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf)); + if (rc < 0) { + LOG_ERR("Failed to read data from device."); + return rc; + } + + status = rx_buf[0]; + /* Extract 20 bits for humidity data */ + *rh_sample = sys_get_be24(&rx_buf[1]) >> 4; + /* Extract 20 bits for temperature data */ + *t_sample = sys_get_be24(&rx_buf[3]) & 0x0FFFFF; + +#if defined(CONFIG_DHT20_CRC) + /* Compute and check CRC with last byte of measurement data */ + crc = crc8(rx_buf, 6, DHT20_CRC_POLYNOM, 0xFF, false); + + if (crc != rx_buf[6]) { + rc = -EIO; + } +#endif + + return rc; +} + +static int dht20_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct dht20_data *data = dev->data; + const struct dht20_config *cfg = dev->config; + int rc; + uint8_t tx_buf[DHT20_TRIGGER_MEASUREMENT_BUFFER_LENGTH] = { + DHT20_TRIGGER_MEASUREMENT_COMMAND}; + uint8_t status; + + if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP && + chan != SENSOR_CHAN_HUMIDITY) { + return -ENOTSUP; + } + + /* Reset sensor if needed */ + reset_sensor(dev); + + /* Send trigger measurement command */ + rc = i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf)); + if (rc < 0) { + LOG_ERR("Failed to start measurement."); + return rc; + } + + /* + * According to datasheet maximum time to make temperature and humidity + * measurements is 80ms + */ + k_msleep(DHT20_POWER_ON_WAIT_MS); + + do { + k_msleep(DHT20_INIT_POLL_STATUS_WAIT_MS); + + rc = read_status(dev, &status); + if (rc < 0) { + LOG_ERR("Failed to read status."); + return rc; + } + } while ((status & DHT20_STATUS_MASK_POLL_STATE) != 0); + + rc = dht20_read_sample(dev, &data->t_sample, &data->rh_sample); + if (rc < 0) { + LOG_ERR("Failed to fetch data."); + return rc; + } + + return 0; +} + +static void dht20_temp_convert(struct sensor_value *val, uint32_t raw) +{ + int32_t micro_c; + + /* + * Convert to micro Celsius + * DegCT = (S / 2^20) * 200 - 50 + * uDegCT = (S * 1e6 * 200 - 50 * 1e6) / (1 << 20) + */ + micro_c = ((int64_t)raw * 1000000 * 200) / BIT(20) - 50 * 1000000; + + val->val1 = micro_c / 1000000; + val->val2 = micro_c % 1000000; +} + +static void dht20_rh_convert(struct sensor_value *val, uint32_t raw) +{ + int32_t micro_rh; + + /* + * Convert to micro %RH + * %RH = (S / 2^20) * 100% + * u%RH = (S * 1e6 * 100) / (1 << 20) + */ + micro_rh = ((uint64_t)raw * 1000000 * 100) / BIT(20); + + val->val1 = micro_rh / 1000000; + val->val2 = micro_rh % 1000000; +} + +static int dht20_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + const struct dht20_data *data = dev->data; + + if (chan == SENSOR_CHAN_AMBIENT_TEMP) { + dht20_temp_convert(val, data->t_sample); + } else if (chan == SENSOR_CHAN_HUMIDITY) { + dht20_rh_convert(val, data->rh_sample); + } else { + return -ENOTSUP; + } + + return 0; +} + +static int dht20_init(const struct device *dev) +{ + const struct dht20_config *cfg = dev->config; + + if (!i2c_is_ready_dt(&cfg->bus)) { + LOG_ERR("I2C dev %s not ready", cfg->bus.bus->name); + return -ENODEV; + } + + return 0; +} + +static const struct sensor_driver_api dht20_driver_api = {.sample_fetch = dht20_sample_fetch, + .channel_get = dht20_channel_get}; + +#define DT_DRV_COMPAT aosong_dht20 +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +#define DEFINE_DHT20(n) \ + static struct dht20_data dht20_data_##n; \ + \ + static const struct dht20_config dht20_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)}; \ + \ + SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &dht20_data_##n, &dht20_config_##n, \ + POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DEFINE_DHT20) + +#endif +#undef DT_DRV_COMPAT + +#define DT_DRV_COMPAT aosong_aht20 +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +#define DEFINE_AHT20(n) \ + static struct dht20_data aht20_data_##n; \ + \ + static const struct dht20_config aht20_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)}; \ + \ + SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &aht20_data_##n, &aht20_config_##n, \ + POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DEFINE_AHT20) + +#endif +#undef DT_DRV_COMPAT + +#define DT_DRV_COMPAT aosong_am2301b +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +#define DEFINE_AM2301B(n) \ + static struct dht20_data am2301b_data_##n; \ + \ + static const struct dht20_config am2301b_config_##n = {.bus = I2C_DT_SPEC_INST_GET(n)}; \ + \ + SENSOR_DEVICE_DT_INST_DEFINE(n, dht20_init, NULL, &am2301b_data_##n, &am2301b_config_##n, \ + POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &dht20_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DEFINE_AM2301B) + +#endif +#undef DT_DRV_COMPAT diff --git a/dts/bindings/sensor/aosong,aht20.yaml b/dts/bindings/sensor/aosong,aht20.yaml new file mode 100644 index 00000000000..1ee8eca48cf --- /dev/null +++ b/dts/bindings/sensor/aosong,aht20.yaml @@ -0,0 +1,14 @@ +# +# Copyright (c) 2024 Nathan Olff +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Aosong AHT20 (Digital-output Humidity and Temperature Sensor) + + The Asosong AHT20 sensor provides temperature and humidity + measurements through an I2C bus. + +compatible: "aosong,aht20" + +include: [sensor-device.yaml, i2c-device.yaml] diff --git a/dts/bindings/sensor/aosong,am2301b.yaml b/dts/bindings/sensor/aosong,am2301b.yaml new file mode 100644 index 00000000000..702362a1ea3 --- /dev/null +++ b/dts/bindings/sensor/aosong,am2301b.yaml @@ -0,0 +1,14 @@ +# +# Copyright (c) 2024 Nathan Olff +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Aosong AM2301B (Digital-output Humidity and Temperature Sensor) + + The Asosong AM2301B sensor provides temperature and humidity + measurements through an I2C bus. + +compatible: "aosong,am2301b" + +include: [sensor-device.yaml, i2c-device.yaml] diff --git a/dts/bindings/sensor/aosong,dht20.yaml b/dts/bindings/sensor/aosong,dht20.yaml new file mode 100644 index 00000000000..8cf0534ba90 --- /dev/null +++ b/dts/bindings/sensor/aosong,dht20.yaml @@ -0,0 +1,14 @@ +# +# Copyright (c) 2024 Nathan Olff +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Aosong DHT20 (Digital-output Humidity and Temperature Sensor) + + The Asosong DHT20 sensor provides temperature and humidity + measurements through an I2C bus. + +compatible: "aosong,dht20" + +include: [sensor-device.yaml, i2c-device.yaml] diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi index a7948313291..a51a525fd6f 100644 --- a/tests/drivers/build_all/sensor/i2c.dtsi +++ b/tests/drivers/build_all/sensor/i2c.dtsi @@ -993,3 +993,21 @@ test_i2c_stts22h: stts22h@86 { int-gpios = <&test_gpio 0 0>; sampling-rate = ; }; + +test_i2c_dht20: dht20@87 { + compatible = "aosong,dht20"; + reg = <0x87>; + status = "okay"; +}; + +test_i2c_aht20: aht20@88 { + compatible = "aosong,aht20"; + reg = <0x88>; + status = "okay"; +}; + +test_i2c_am2301b: am2301b@89 { + compatible = "aosong,am2301b"; + reg = <0x89>; + status = "okay"; +};