diff --git a/drivers/dht/Kconfig b/drivers/dht/Kconfig index a21f124720d91..dca40c956c983 100644 --- a/drivers/dht/Kconfig +++ b/drivers/dht/Kconfig @@ -10,7 +10,7 @@ config MODULE_DHT depends on HAS_PERIPH_GPIO depends on TEST_KCONFIG select MODULE_PERIPH_GPIO - select MODULE_XTIMER + select ZTIMER_USEC config HAVE_DHT bool diff --git a/drivers/dht/Makefile.dep b/drivers/dht/Makefile.dep index bf6612275a19b..b36fe58accdab 100644 --- a/drivers/dht/Makefile.dep +++ b/drivers/dht/Makefile.dep @@ -1,2 +1,2 @@ -USEMODULE += xtimer +USEMODULE += ztimer_usec FEATURES_REQUIRED += periph_gpio diff --git a/drivers/dht/dht.c b/drivers/dht/dht.c index e2003bbf1d80e..8c92f51d5d281 100644 --- a/drivers/dht/dht.c +++ b/drivers/dht/dht.c @@ -2,6 +2,7 @@ * Copyright 2015 Ludwig Knüpfer * 2015 Christian Mehlis * 2016-2017 Freie Universität Berlin + * 2023 Hugues Larrive * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level @@ -19,201 +20,260 @@ * @author Ludwig Knüpfer * @author Christian Mehlis * @author Hauke Petersen + * @author Hugues Larrive * * @} */ +#include #include #include -#include "log.h" #include "assert.h" -#include "xtimer.h" -#include "timex.h" -#include "periph/gpio.h" - #include "dht.h" #include "dht_params.h" +#include "log.h" +#include "periph/gpio.h" +#include "time_units.h" +#include "ztimer.h" #define ENABLE_DEBUG 0 #include "debug.h" /* Every pulse send by the DHT longer than 40µs is interpreted as 1 */ -#define PULSE_WIDTH_THRESHOLD (40U) -/* If an expected pulse is not detected within 1000µs, something is wrong */ -#define TIMEOUT (1000U) -/* The DHT sensor cannot measure more than once a second */ -#define DATA_HOLD_TIME (US_PER_SEC) -/* The start signal by pulling data low for at least 18ms and then up for - * 20-40µs*/ -#define START_LOW_TIME (20U * US_PER_MS) -#define START_HIGH_TIME (40U) - -static inline void _reset(dht_t *dev) +#define READ_THRESHOLD (40U) +/* If an expected pulse is not detected within 85µs, something is wrong */ +#define SPIN_TIMEOUT (85U) +/* The start signal by pulling data low for at least 18 ms for DHT11, at + * most 20 ms (AM2301 / DHT22 / DHT21). Then release the bus and the + * sensor should respond by pulling data low for 80 µs, then release for + * 80µs before start sending data. */ +#define START_LOW_TIME (19U * US_PER_MS) +#define START_THRESHOLD (75U) +/* DHTs have to wait for power 1 or 2 seconds depending on the model */ +#define POWER_WAIT_TIMEOUT (2U * US_PER_SEC) + +enum { + BYTEPOS_HUMIDITY_HIGH = 0, + BYTEPOS_HUMIDITY_LOW = 1, + BYTEPOS_TEMPERATURE_HIGH = 2, + BYTEPOS_TEMPERATURE_LOW = 3, + BYTEPOS_CHECKSUM = 4, +}; + +struct dht_data { + gpio_t pin; + gpio_mode_t in_mode; + uint8_t data[5]; + int8_t bit_pos; + uint8_t bit; +}; + +static void _wait_for_level(gpio_t pin, bool expected, uint32_t start) +{ + /* Calls to ztimer_now() can be relatively slow on low end platforms. + * Mixing in a busy down-counting loop solves issues e.g. on AVR boards. */ + uint8_t pre_timeout = 0; + while (((bool)gpio_read(pin) != expected) && (++pre_timeout + || ztimer_now(ZTIMER_USEC) < start + SPIN_TIMEOUT)) {} +} + +static int _send_start_signal(dht_t *dev) { + uint32_t start; gpio_init(dev->params.pin, GPIO_OUT); + gpio_clear(dev->params.pin); + ztimer_sleep(ZTIMER_USEC, START_LOW_TIME); + /* sync on device */ gpio_set(dev->params.pin); + gpio_init(dev->params.pin, dev->params.in_mode); + /* check device response (80 µs low then 80 µs high) */ + start = ztimer_now(ZTIMER_USEC); + _wait_for_level(dev->params.pin, 0, start); + if (ztimer_now(ZTIMER_USEC) - start > START_THRESHOLD) { + DEBUG_PUTS("[dht] error: response low pulse > START_THRESHOLD"); + return -ENODEV; + } + _wait_for_level(dev->params.pin, 1, start); + start = ztimer_now(ZTIMER_USEC); + _wait_for_level(dev->params.pin, 0, start); + if (ztimer_now(ZTIMER_USEC) - start < START_THRESHOLD) { + DEBUG_PUTS("[dht] error: response high pulse < START_THRESHOLD"); + return -ENODEV; + } + return 0; } -/** - * @brief Wait until the pin @p pin has level @p expect - * - * @param pin GPIO pin to wait for - * @param expect Wait until @p pin has this logic level - * @param timeout Timeout in µs - * - * @retval 0 Success - * @retval -1 Timeout occurred before level was reached - */ -static inline int _wait_for_level(gpio_t pin, bool expect, unsigned timeout) +static void _bit_parse(struct dht_data *arg) { - while (((gpio_read(pin) > 0) != expect) && timeout) { - xtimer_usleep(1); - timeout--; + int8_t pos = arg->bit_pos++; + if (arg->bit) { + arg->data[pos / 8] |= (0x80U >> (pos % 8)); } +} - return (timeout > 0) ? 0 : -1; +static void _busy_wait_read(struct dht_data *arg) +{ + uint32_t start = ztimer_now(ZTIMER_USEC); + while (arg->bit_pos != 40) { + _wait_for_level(arg->pin, 1, start); + start = ztimer_now(ZTIMER_USEC); + _wait_for_level(arg->pin, 0, start); + arg->bit = (ztimer_now(ZTIMER_USEC) - start > READ_THRESHOLD) ? 1 : 0; + _bit_parse(arg); + } } -static int _read(uint8_t *dest, gpio_t pin) +static int _validate_checksum(uint8_t *data) { - DEBUG("[dht] read\n"); - uint16_t res = 0; - - for (int i = 0; i < 8; i++) { - uint32_t start, end; - res <<= 1; - /* measure the length between the next rising and falling flanks (the - * time the pin is high - smoke up :-) */ - if (_wait_for_level(pin, 1, TIMEOUT)) { - return -1; - } - start = xtimer_now_usec(); + uint8_t sum = 0; - if (_wait_for_level(pin, 0, TIMEOUT)) { - return -1; - } - end = xtimer_now_usec(); + for (uint_fast8_t i = 0; i < 4; i++) { + sum += data[i]; + } + if (sum != data[BYTEPOS_CHECKSUM]) { + return -EIO; + } + return 0; +} - /* if the high phase was more than 40us, we got a 1 */ - if ((end - start) > PULSE_WIDTH_THRESHOLD) { - res |= 0x0001; +static int _parse_raw_values(dht_t *dev, uint8_t *data) +{ + bool is_negative; + + switch (dev->params.type) { + case DHT11: + case DHT11_2022: + DEBUG_PUTS("[dht] parse raw values with DHT11 data format"); + dev->last_val.humidity = data[BYTEPOS_HUMIDITY_HIGH] * 10 + + data[BYTEPOS_HUMIDITY_LOW]; + /* MSB for integral temperature byte gives sign, remaining is + * abs() of value (beware: this is not two's complement!) */ + is_negative = data[BYTEPOS_TEMPERATURE_LOW] & 0x80; + data[BYTEPOS_TEMPERATURE_LOW] &= ~0x80; + /* 2022-12 aosong.com data sheet uses interprets low bits as + * 0.01°C per LSB */ + if (dev->params.type == DHT11_2022) { + data[BYTEPOS_TEMPERATURE_LOW] /= 10; } + if (data[BYTEPOS_TEMPERATURE_LOW] >= 10) { + return -ERANGE; + } + dev->last_val.temperature = data[BYTEPOS_TEMPERATURE_HIGH] * 10 + + data[BYTEPOS_TEMPERATURE_LOW]; + break; + /* AM2301 == DHT21 == DHT22 (same value in enum), + * so both are handled here */ + case DHT22: + DEBUG_PUTS("[dht] parse raw values with DHT22 data format"); + dev->last_val.humidity = (int16_t)( + (data[BYTEPOS_HUMIDITY_HIGH] << 8) + | data[BYTEPOS_HUMIDITY_LOW]); + is_negative = data[BYTEPOS_TEMPERATURE_HIGH] & 0x80; + data[BYTEPOS_TEMPERATURE_HIGH] &= ~0x80; + dev->last_val.temperature = (int16_t)( + (data[BYTEPOS_TEMPERATURE_HIGH] << 8) + | data[BYTEPOS_TEMPERATURE_LOW]); + break; + default: + return -ENOSYS; /* ENOSYS 38 Function not implemented */ + } + + if (is_negative) { + dev->last_val.temperature = -dev->last_val.temperature; } - *dest = res; return 0; } int dht_init(dht_t *dev, const dht_params_t *params) { - DEBUG("[dht] dht_init\n"); + int16_t timeout; + + DEBUG_PUTS("[dht] dht_init"); /* check parameters and configuration */ assert(dev && params); + /* AM2301 == DHT21 == DHT22 (same value in enum) */ + assert((params->type == DHT11) || (params->type == DHT11_2022) + || (params->type == DHT22)); memset(dev, 0, sizeof(dht_t)); dev->params = *params; - _reset(dev); - - xtimer_msleep(2000); + /* The 2-second delay mentioned in the datasheet is only required + * after a power cycle. */ + timeout = POWER_WAIT_TIMEOUT / US_PER_MS; + gpio_init(dev->params.pin, GPIO_IN); + while (!gpio_read(dev->params.pin) && timeout--) { + ztimer_sleep(ZTIMER_USEC, US_PER_MS); + } + if (timeout < 0) { + DEBUG_PUTS("[dht] dht_init: error: Invalid cross-device link"); + return -EXDEV; + } + else { + DEBUG("\n[dht] dht_init: power-up duration: %" PRIi16 " ms\n", + (int16_t)(POWER_WAIT_TIMEOUT / US_PER_MS) - timeout); + } + /* The previous test does not ensure the sensor presence in case an + * external pull-up resistor is used. */ + while (_send_start_signal(dev) == -ENODEV + && (timeout -= START_LOW_TIME / US_PER_MS) > 0) {} + if (timeout < 0) { + DEBUG_PUTS("[dht] dht_init: error: No such device"); + return -ENODEV; + } + else { + DEBUG("\n[dht] dht_init: presence check duration: %" PRIi16 " ms\n", + (int16_t)(POWER_WAIT_TIMEOUT / US_PER_MS) - timeout); + } - DEBUG("[dht] dht_init: success\n"); - return DHT_OK; + DEBUG_PUTS("[dht] dht_init: success"); + return 0; } int dht_read(dht_t *dev, int16_t *temp, int16_t *hum) { - uint8_t csum; - uint8_t raw_temp_i, raw_temp_d, raw_hum_i, raw_hum_d; + int ret; assert(dev); - uint32_t now_us = xtimer_now_usec(); - if ((now_us - dev->last_read_us) > DATA_HOLD_TIME) { - /* send init signal to device */ - gpio_clear(dev->params.pin); - xtimer_usleep(START_LOW_TIME); - gpio_set(dev->params.pin); - xtimer_usleep(START_HIGH_TIME); - - /* sync on device */ - gpio_init(dev->params.pin, dev->params.in_mode); - if (_wait_for_level(dev->params.pin, 1, TIMEOUT)) { - _reset(dev); - return DHT_TIMEOUT; - } - - if (_wait_for_level(dev->params.pin, 0, TIMEOUT)) { - _reset(dev); - return DHT_TIMEOUT; - } + struct dht_data data = { + .pin = dev->params.pin, + .in_mode = dev->params.in_mode, + .bit_pos = 0, + }; - /* - * data is read in sequentially, highest bit first: - * 40 .. 24 23 .. 8 7 .. 0 - * [humidity][temperature][checksum] - */ - - /* read the humidity, temperature, and checksum bits */ - if (_read(&raw_hum_i, dev->params.pin)) { - _reset(dev); - return DHT_TIMEOUT; - } - if (_read(&raw_hum_d, dev->params.pin)) { - _reset(dev); - return DHT_TIMEOUT; - } - if (_read(&raw_temp_i, dev->params.pin)) { - _reset(dev); - return DHT_TIMEOUT; - } - if (_read(&raw_temp_d, dev->params.pin)) { - _reset(dev); - return DHT_TIMEOUT; - } - - if (_read(&csum, dev->params.pin)) { - _reset(dev); - return DHT_TIMEOUT; - } - - /* Bring device back to defined state - so we can trigger the next reading - * by pulling the data pin low again */ - _reset(dev); + if (_send_start_signal(dev) == -ENODEV) { + DEBUG_PUTS("[dht] error: No response from device"); + return -ENODEV; + } - /* validate the checksum */ - uint8_t sum = (raw_temp_i) + (raw_temp_d) + (raw_hum_i) + (raw_hum_d); - if (sum != csum) { - DEBUG("[dht] error: checksum doesn't match\n"); - return DHT_NOCSUM; - } + /* read the data */ + _busy_wait_read(&data); - /* parse the RAW values */ - DEBUG("[dht] RAW values: temp: %2i.%i hum: %2i.%i\n", (int)raw_temp_i, - (int)raw_temp_d, (int)raw_hum_i, (int)raw_hum_d); + if (_validate_checksum(data.data) == -EIO) { + DEBUG("[dht] error: checksum doesn't match\n" + "[dht] RAW data: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + (unsigned)data.data[0], (unsigned)data.data[1], + (unsigned)data.data[2], (unsigned)data.data[3], + (unsigned)data.data[4]); + return -EIO; + } - dev->last_val.humidity = raw_hum_i * 10 + raw_hum_d; - /* MSB set means negative temperature on DHT22. Will always be 0 on DHT11 */ - if (raw_temp_i & 0x80) { - dev->last_val.temperature = -((raw_temp_i & ~0x80) * 10 + raw_temp_d); + if ((ret = _parse_raw_values(dev, data.data)) < 0) { + if (ret == -ENOSYS) { + DEBUG_PUTS("[dht] error: data format not implemented"); } - else { - dev->last_val.temperature = raw_temp_i * 10 + raw_temp_d; + else if (ret == -ERANGE) { + DEBUG_PUTS("[dht] error: invalid temperature low byte"); } - - /* update time of last measurement */ - dev->last_read_us = now_us; - } - - if (temp) { - *temp = dev->last_val.temperature; + return ret; } - if (hum) { - *hum = dev->last_val.humidity; - } + *hum = dev->last_val.humidity; + *temp = dev->last_val.temperature; - return DHT_OK; + return 0; } diff --git a/drivers/dht/include/dht_params.h b/drivers/dht/include/dht_params.h index ce3bdcfa34a37..72307390e535f 100644 --- a/drivers/dht/include/dht_params.h +++ b/drivers/dht/include/dht_params.h @@ -34,11 +34,15 @@ extern "C" { #ifndef DHT_PARAM_PIN #define DHT_PARAM_PIN (GPIO_PIN(0, 0)) #endif +#ifndef DHT_PARAM_TYPE +#define DHT_PARAM_TYPE (DHT11) +#endif #ifndef DHT_PARAM_PULL -#define DHT_PARAM_PULL (GPIO_IN_PU) +#define DHT_PARAM_PULL (GPIO_IN) #endif #ifndef DHT_PARAMS #define DHT_PARAMS { .pin = DHT_PARAM_PIN, \ + .type = DHT_PARAM_TYPE, \ .in_mode = DHT_PARAM_PULL } #endif #ifndef DHT_SAULINFO diff --git a/drivers/include/dht.h b/drivers/include/dht.h index 80bc8b959b6b3..c190b2dd3c4c9 100644 --- a/drivers/include/dht.h +++ b/drivers/include/dht.h @@ -31,6 +31,7 @@ #ifndef DHT_H #define DHT_H +#include #include #include "periph/gpio.h" @@ -40,27 +41,42 @@ extern "C" { #endif /** - * @brief Possible return codes + * @brief Possible return codes + * + * @deprecated The functions use errno codes instead now */ enum { - DHT_OK = 0, /**< all good */ - DHT_NOCSUM = -1, /**< checksum error */ - DHT_TIMEOUT = -2, /**< communication timed out */ + DHT_OK = 0, /**< all good */ + DHT_NOCSUM = -EIO, /**< checksum error */ + DHT_TIMEOUT = -ETIMEDOUT, /**< communication timed out */ }; /** * @brief Data type for storing DHT sensor readings */ typedef struct { - uint16_t humidity; /**< relative percent */ + uint16_t humidity; /**< relative humidity in deci-percent */ uint16_t temperature; /**< temperature in deci-Celsius */ } dht_data_t; +/** + * @brief Device type of the DHT device + */ +typedef enum { + DHT11, /**< Older DHT11 variants with either 1 °C or + * 0.1 °C resolution */ + DHT11_2022, /**< New DHT11 variant with 0.01 °C resolution */ + DHT22, /**< DHT22 device identifier */ + DHT21 = DHT22, /**< DHT21 device identifier */ + AM2301 = DHT22, /**< AM2301 device identifier */ +} dht_type_t; + /** * @brief Configuration parameters for DHT devices */ typedef struct { gpio_t pin; /**< GPIO pin of the device's data pin */ + dht_type_t type; /**< type of the DHT device */ gpio_mode_t in_mode; /**< input pin configuration, with or without pull * resistor */ } dht_params_t; @@ -71,7 +87,6 @@ typedef struct { typedef struct { dht_params_t params; /**< Device parameters */ dht_data_t last_val; /**< Values of the last measurement */ - uint32_t last_read_us; /**< Time of the last measurement */ } dht_t; /** @@ -80,8 +95,14 @@ typedef struct { * @param[out] dev device descriptor of a DHT device * @param[in] params configuration parameters * - * @return 0 on success - * @return -1 on error + * @retval 0 Success + * @retval -EXDEV A low level on the input after the sensor's startup + * time indicates that either no sensor or pull-up + * resistor is connected, or the sensor is physically + * poorly connected or powered. + * @retval -ENODEV The sensor did not respond to the transmission of a + * start signal. Likely there were a pull-up resistor but + * no sensor connected on the data line. */ int dht_init(dht_t *dev, const dht_params_t *params); @@ -95,9 +116,15 @@ int dht_init(dht_t *dev, const dht_params_t *params); * @param[out] temp temperature value [in °C * 10^-1] * @param[out] hum relative humidity value [in percent * 10^-1] * - * @retval DHT_OK Success - * @retval DHT_NOCSUM Checksum error - * @retval DHT_TIMEOUT Reading data timed out (check wires!) + * @retval 0 Success + * @retval -ENODEV The sensor did not respond to the transmission of a + * start signal. Likely the RESPAWN_TIMEOUT is + * insufficient. + * @retval -EIO The received and the expected checksum didn't match. + * @retval -ENOSYS Unable to parse the received data. Likely the data + * format is not implemented. + * @retval -ERANGE Temperature low byte >= 10. Likely misconfigured + * device type (DHT11_2022). */ int dht_read(dht_t *dev, int16_t *temp, int16_t *hum); diff --git a/tests/drivers/dht/Makefile b/tests/drivers/dht/Makefile index 21fff0edc7e92..c0ca9ac12aa23 100644 --- a/tests/drivers/dht/Makefile +++ b/tests/drivers/dht/Makefile @@ -1,6 +1,6 @@ include ../Makefile.drivers_common USEMODULE += dht -USEMODULE += xtimer +USEMODULE += ztimer_usec include $(RIOTBASE)/Makefile.include diff --git a/tests/drivers/dht/app.config.test b/tests/drivers/dht/app.config.test index 934b6b207ca72..60a4dd2426ee6 100644 --- a/tests/drivers/dht/app.config.test +++ b/tests/drivers/dht/app.config.test @@ -1,4 +1,4 @@ # this file enables modules defined in Kconfig. Do not use this file for # application configuration. This is only needed during migration. CONFIG_MODULE_DHT=y -CONFIG_MODULE_XTIMER=y +CONFIG_ZTIMER_USEC=y diff --git a/tests/drivers/dht/main.c b/tests/drivers/dht/main.c index 67ae1b6ef518c..8500a6f7dba27 100644 --- a/tests/drivers/dht/main.c +++ b/tests/drivers/dht/main.c @@ -23,11 +23,10 @@ #include -#include "xtimer.h" -#include "timex.h" -#include "fmt.h" #include "dht.h" #include "dht_params.h" +#include "time_units.h" +#include "ztimer.h" #define DELAY (2 * US_PER_SEC) @@ -50,6 +49,8 @@ int main(void) /* periodically read temp and humidity values */ while (1) { + ztimer_sleep(ZTIMER_USEC, DELAY); + if (dht_read(&dev, &temp, &hum) != DHT_OK) { puts("Error reading values"); continue; @@ -57,8 +58,6 @@ int main(void) printf("DHT values - temp: %d.%d°C - relative humidity: %d.%d%%\n", temp/10, temp%10, hum/10, hum%10); - - xtimer_usleep(DELAY); } return 0;