drivers/dht: Move hold logic from SAUL to driver

The DHT11/DHT21/DHT22 cannot be sampled more than once a second. Previously,
a global cache was added to the SAUL adaption of the dht driver to answer with
stored values for one second after the last read. This however had two
disadvantages:

- The global cache was shared for all DHTXX devices connected. As a result
  incorrect values were delivered when reading out multiple sensors over SAUL
  with less than 1 second delay in between
- A user of the low level API will had to implement the same caching strategy,
  resulting in code duplication

By moving the hold logic to the driver, both limitations can be overcome.
This commit is contained in:
Marian Buschsieweke 2019-07-19 16:40:05 +02:00
parent d2d83c3bd0
commit 579a67be03
No known key found for this signature in database
GPG Key ID: 61F64C6599B1539F
3 changed files with 126 additions and 114 deletions

View File

@ -42,12 +42,33 @@
#define PULSE_WIDTH_THRESHOLD (40U) #define PULSE_WIDTH_THRESHOLD (40U)
/* If an expected pulse is not detected within 1000µs, something is wrong */ /* If an expected pulse is not detected within 1000µs, something is wrong */
#define TIMEOUT (1000U) #define TIMEOUT (1000U)
/* The DHT sensor cannot measure more than once a second */
#define DATA_HOLD_TIME (US_PER_SEC)
static inline void _reset(const dht_t *dev) static inline void _reset(dht_t *dev)
{ {
gpio_init(dev->pin, GPIO_OUT); gpio_init(dev->params.pin, GPIO_OUT);
gpio_set(dev->pin); gpio_set(dev->params.pin);
}
/**
* @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)
{
while (((gpio_read(pin) > 0) != expect) && timeout) {
xtimer_usleep(1);
timeout--;
}
return (timeout > 0) ? 0 : -1;
} }
static int _read(uint16_t *dest, gpio_t pin, int bits) static int _read(uint16_t *dest, gpio_t pin, int bits)
@ -60,23 +81,16 @@ static int _read(uint16_t *dest, gpio_t pin, int bits)
res <<= 1; res <<= 1;
/* measure the length between the next rising and falling flanks (the /* measure the length between the next rising and falling flanks (the
* time the pin is high - smoke up :-) */ * time the pin is high - smoke up :-) */
unsigned counter = 0; if (_wait_for_level(pin, 1, TIMEOUT)) {
while (!gpio_read(pin)) {
if (counter++ >= TIMEOUT) {
return -1; return -1;
} }
xtimer_usleep(1);
}
start = xtimer_now_usec(); start = xtimer_now_usec();
counter = 0; if (_wait_for_level(pin, 0, TIMEOUT)) {
while (gpio_read(pin)) {
if (counter++ >= TIMEOUT) {
return -1; return -1;
} }
xtimer_usleep(1);
}
end = xtimer_now_usec(); end = xtimer_now_usec();
/* if the high phase was more than 40us, we got a 1 */ /* if the high phase was more than 40us, we got a 1 */
if ((end - start) > PULSE_WIDTH_THRESHOLD) { if ((end - start) > PULSE_WIDTH_THRESHOLD) {
res |= 0x0001; res |= 0x0001;
@ -92,10 +106,11 @@ int dht_init(dht_t *dev, const dht_params_t *params)
DEBUG("dht_init\n"); DEBUG("dht_init\n");
/* check parameters and configuration */ /* check parameters and configuration */
assert(dev && params && assert(dev && params);
((params->type == DHT11) || (params->type == DHT22) || (params->type == DHT21))); assert((params->type == DHT11) || (params->type == DHT22) || (params->type == DHT21));
*dev = *params; memset(dev, 0, sizeof(dht_t));
dev->params = *params;
_reset(dev); _reset(dev);
@ -105,38 +120,32 @@ int dht_init(dht_t *dev, const dht_params_t *params)
return DHT_OK; return DHT_OK;
} }
int dht_read(const dht_t *dev, int16_t *temp, int16_t *hum) int dht_read(dht_t *dev, int16_t *temp, int16_t *hum)
{ {
uint16_t csum, sum; uint16_t csum;
uint16_t raw_hum, raw_temp; uint16_t raw_hum, raw_temp;
assert(dev && temp && hum); assert(dev);
uint32_t now_us = xtimer_now_usec();
if ((now_us - dev->last_read_us) > DATA_HOLD_TIME) {
/* send init signal to device */ /* send init signal to device */
gpio_clear(dev->pin); gpio_clear(dev->params.pin);
xtimer_usleep(20 * US_PER_MS); xtimer_usleep(20 * US_PER_MS);
gpio_set(dev->pin); gpio_set(dev->params.pin);
xtimer_usleep(40); xtimer_usleep(40);
/* sync on device */ /* sync on device */
gpio_init(dev->pin, dev->in_mode); gpio_init(dev->params.pin, dev->params.in_mode);
unsigned counter = 0; if (_wait_for_level(dev->params.pin, 1, TIMEOUT)) {
while (!gpio_read(dev->pin)) {
if (counter++ > TIMEOUT) {
_reset(dev); _reset(dev);
return DHT_TIMEOUT; return DHT_TIMEOUT;
} }
xtimer_usleep(1);
}
counter = 0; if (_wait_for_level(dev->params.pin, 0, TIMEOUT)) {
while (gpio_read(dev->pin)) {
if (counter++ > TIMEOUT) {
_reset(dev); _reset(dev);
return DHT_TIMEOUT; return DHT_TIMEOUT;
} }
xtimer_usleep(1);
}
/* /*
* data is read in sequentially, highest bit first: * data is read in sequentially, highest bit first:
@ -145,17 +154,17 @@ int dht_read(const dht_t *dev, int16_t *temp, int16_t *hum)
*/ */
/* read the humidity, temperature, and checksum bits */ /* read the humidity, temperature, and checksum bits */
if (_read(&raw_hum, dev->pin, 16)) { if (_read(&raw_hum, dev->params.pin, 16)) {
_reset(dev); _reset(dev);
return DHT_TIMEOUT; return DHT_TIMEOUT;
} }
if (_read(&raw_temp, dev->pin, 16)) { if (_read(&raw_temp, dev->params.pin, 16)) {
_reset(dev); _reset(dev);
return DHT_TIMEOUT; return DHT_TIMEOUT;
} }
if (_read(&csum, dev->pin, 8)) { if (_read(&csum, dev->params.pin, 8)) {
_reset(dev); _reset(dev);
return DHT_TIMEOUT; return DHT_TIMEOUT;
} }
@ -165,32 +174,45 @@ int dht_read(const dht_t *dev, int16_t *temp, int16_t *hum)
_reset(dev); _reset(dev);
/* validate the checksum */ /* validate the checksum */
sum = (raw_temp >> 8) + (raw_temp & 0xff) + (raw_hum >> 8) + (raw_hum & 0xff); uint8_t sum = (raw_temp >> 8) + (raw_temp & 0xff) + (raw_hum >> 8)
if ((sum != csum) || (csum == 0)) { + (raw_hum & 0xff);
DEBUG("error: checksum invalid\n"); if (sum != csum) {
DEBUG("error: checksum doesn't match\n");
return DHT_NOCSUM; return DHT_NOCSUM;
} }
/* parse the RAW values */ /* parse the RAW values */
DEBUG("RAW values: temp: %7i hum: %7i\n", (int)raw_temp, (int)raw_hum); DEBUG("RAW values: temp: %7i hum: %7i\n", (int)raw_temp, (int)raw_hum);
switch (dev->type) { switch (dev->params.type) {
case DHT11: case DHT11:
*temp = (int16_t)((raw_temp >> 8) * 10); dev->last_val.temperature = (int16_t)((raw_temp >> 8) * 10);
*hum = (int16_t)((raw_hum >> 8) * 10); dev->last_val.humidity = (int16_t)((raw_hum >> 8) * 10);
break; break;
case DHT22: case DHT22:
*hum = (int16_t)raw_hum; dev->last_val.humidity = (int16_t)raw_hum;
/* if the high-bit is set, the value is negative */ /* if the high-bit is set, the value is negative */
if (raw_temp & 0x8000) { if (raw_temp & 0x8000) {
*temp = (int16_t)((raw_temp & ~0x8000) * -1); dev->last_val.temperature = (int16_t)((raw_temp & ~0x8000) * -1);
} }
else { else {
*temp = (int16_t)raw_temp; dev->last_val.temperature = (int16_t)raw_temp;
} }
break; break;
default: default:
return DHT_NODEV; /* this should never be reached */ return DHT_NODEV; /* this should never be reached */
} }
/* update time of last measurement */
dev->last_read_us = now_us;
}
if (temp) {
*temp = dev->last_val.temperature;
}
if (hum) {
*hum = dev->last_val.humidity;
}
return DHT_OK; return DHT_OK;
} }

View File

@ -30,40 +30,25 @@
#include "saul.h" #include "saul.h"
#include "dht.h" #include "dht.h"
#include "xtimer.h"
#define DHT_SAUL_HOLD_TIME (1000 * 1000U) /* 1s */ static int read_temp(const void *dev, phydat_t *res)
static int16_t temp, hum;
static uint32_t last = 0;
static int check_and_read(const void *dev, phydat_t *res, int16_t *val, uint8_t unit)
{ {
uint32_t now = xtimer_now_usec(); if (dht_read((dht_t *)dev, &res->val[0], NULL)) {
if ((now - last) > DHT_SAUL_HOLD_TIME) {
if (dht_read((const dht_t *)dev, &temp, &hum)) {
return -ECANCELED; return -ECANCELED;
} }
last = now; res->unit = UNIT_TEMP_C;
}
res->val[0] = *val;
res->val[1] = 0;
res->val[2] = 0;
res->unit = unit;
res->scale = -1; res->scale = -1;
return 1; return 1;
} }
static int read_temp(const void *dev, phydat_t *res)
{
return check_and_read(dev, res, &temp, UNIT_TEMP_C);
}
static int read_hum(const void *dev, phydat_t *res) static int read_hum(const void *dev, phydat_t *res)
{ {
return check_and_read(dev, res, &hum, UNIT_PERCENT); if (dht_read((dht_t *)dev, NULL, &res->val[0])) {
return -ECANCELED;
}
res->unit = UNIT_PERCENT;
res->scale = -1;
return 1;
} }
const saul_driver_t dht_temp_saul_driver = { const saul_driver_t dht_temp_saul_driver = {

View File

@ -67,19 +67,24 @@ typedef enum {
} dht_type_t; } dht_type_t;
/** /**
* @brief Device descriptor for DHT sensor devices * @brief Configuration parameters for DHT devices
*/ */
typedef struct { typedef struct {
gpio_t pin; /**< GPIO pin of the device's data pin */ gpio_t pin; /**< GPIO pin of the device's data pin */
dht_type_t type; /**< type of the DHT device */ dht_type_t type; /**< type of the DHT device */
gpio_mode_t in_mode; /**< input pin configuration, with or without pull gpio_mode_t in_mode; /**< input pin configuration, with or without pull
* resistor */ * resistor */
} dht_t;
} dht_params_t;
/** /**
* @brief Configuration parameters for DHT devices * @brief Device descriptor for DHT sensor devices
*/ */
typedef dht_t dht_params_t; 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;
/** /**
* @brief Initialize a new DHT device * @brief Initialize a new DHT device
@ -107,7 +112,7 @@ int dht_init(dht_t *dev, const dht_params_t *params);
* @retval `DHT_TIMEOUT` Reading data timed out (check wires!) * @retval `DHT_TIMEOUT` Reading data timed out (check wires!)
* @retval `DHT_NODEV` Unsupported device type specified * @retval `DHT_NODEV` Unsupported device type specified
*/ */
int dht_read(const dht_t *dev, int16_t *temp, int16_t *hum); int dht_read(dht_t *dev, int16_t *temp, int16_t *hum);
#ifdef __cplusplus #ifdef __cplusplus
} }