drivers: sensor: dht20: implement new driver

The DHT20 sensor is a temperature and humidity sensor that uses I2C to
communicate with a microcontroller. The DHT20 sensor is Aosong.

use standard crc

update description to add reference to AHT20 and AM2301B

clean code, use defines and bit manipulation

add dht20 to i2c test suite for build tests

update bit manipulation

use more defines instead of raw numbers

add bindings to allow aht20 or am2301b to be used in devicetree

in all 3 cases, the same dht20.c driver is compiled

Signed-off-by: Nathan Olff <nathan@kickmaker.net>
This commit is contained in:
Nathan Olff 2024-01-07 17:29:18 +01:00 committed by Fabio Baltieri
commit 13deb0365c
9 changed files with 452 additions and 0 deletions

View file

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

View file

@ -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"

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(dht20.c)

View file

@ -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

View file

@ -0,0 +1,359 @@
/*
* Copyright (c) 2024 Nathan Olff
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/kernel.h>
#if defined(CONFIG_DHT20_CRC)
#include <zephyr/sys/crc.h>
#endif
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#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

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -993,3 +993,21 @@ test_i2c_stts22h: stts22h@86 {
int-gpios = <&test_gpio 0 0>;
sampling-rate = <STTS22H_100Hz>;
};
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";
};