Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers/dht: use IRQs for decoding #18591

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cpu/nrf5x_common/periph/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

#include "periph/timer.h"

#define ENABLE_DEBUG 1
#include "debug.h"

#define F_TIMER (16000000U) /* the timer is clocked at 16MHz */

typedef struct {
Expand Down Expand Up @@ -104,6 +107,9 @@ int timer_set_absolute(tim_t tim, int chan, unsigned int value)
dev(tim)->CC[chan] = value;
dev(tim)->INTENSET = (TIMER_INTENSET_COMPARE0_Msk << chan);

DEBUG("[timer] set at %u (in %u)\n",
value, value - timer_read(tim));

return 0;
}

Expand Down
8 changes: 8 additions & 0 deletions cpu/stm32/periph/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@
* @}
*/

#include <stdint.h>

#include "cpu.h"
#include "periph/timer.h"

#define ENABLE_DEBUG 1
#include "debug.h"

/**
* @brief Interrupt context for each configured timer
*/
Expand Down Expand Up @@ -137,6 +142,9 @@ int timer_set_absolute(tim_t tim, int channel, unsigned int value)

dev(tim)->DIER |= (TIM_DIER_CC1IE << channel);

DEBUG("[timer] set at %u (in %u)\n",
value, (unsigned)((uint16_t)value - (uint16_t)(dev(tim)->CNT)));

return 0;
}

Expand Down
4 changes: 3 additions & 1 deletion drivers/dht/Makefile.dep
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
USEMODULE += xtimer
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_gpio_irq

USEMODULE += ztimer_usec
259 changes: 142 additions & 117 deletions drivers/dht/dht.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,87 +23,57 @@
* @}
*/

#include <errno.h>
#include <stdint.h>
#include <string.h>

#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
#define ENABLE_DEBUG 1
#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 communication should take at most 41 * (70 us + 50 us) = 4,920 us
* theoretically. If it didn't complete after 10,000 us, we can be sure it
* failed */
#define TIMEOUT_US (10000U)
/* 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)

#define INIT_DELAY_SEC (2U)

enum {
BYTEPOS_HUMIDITY_INTEGRAL = 0,
BYTEPOS_HUMIDITY_FRACTUAL = 1,
BYTEPOS_TEMPERATURE_INTEGRAL = 2,
BYTEPOS_TEMPERATURE_FRACTUAL = 3,
BYTEPOS_CHECKSUM = 4,
};

static inline void _reset(dht_t *dev)
{
gpio_init(dev->params.pin, GPIO_OUT);
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(uint8_t *dest, gpio_t pin)
{
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();

if (_wait_for_level(pin, 0, TIMEOUT)) {
return -1;
}
end = xtimer_now_usec();

/* if the high phase was more than 40us, we got a 1 */
if ((end - start) > PULSE_WIDTH_THRESHOLD) {
res |= 0x0001;
}
}

*dest = res;
return 0;
}
struct dht_irq_data {
mutex_t sync;
uint32_t time;
gpio_t pin;
uint8_t data[5];
int8_t bit_pos;
};

int dht_init(dht_t *dev, const dht_params_t *params)
{
Expand All @@ -117,90 +87,145 @@ int dht_init(dht_t *dev, const dht_params_t *params)

_reset(dev);

xtimer_msleep(2000);
if (IS_USED(MODULE_ZTIMER_SEC)) {
ztimer_sleep(ZTIMER_SEC, INIT_DELAY_SEC);
}
else if (IS_USED(MODULE_ZTIMER_MSEC)) {
ztimer_sleep(ZTIMER_MSEC, INIT_DELAY_SEC * MS_PER_SEC);
}
else {
ztimer_sleep(ZTIMER_USEC, INIT_DELAY_SEC * US_PER_SEC);
}

DEBUG("[dht] dht_init: success\n");
return DHT_OK;
return 0;
}

int dht_read(dht_t *dev, int16_t *temp, int16_t *hum)
static void gpio_cb(void *_arg)
{
uint8_t csum;
uint8_t raw_temp_i, raw_temp_d, raw_hum_i, raw_hum_d;
struct dht_irq_data *arg = _arg;
uint32_t now = ztimer_now(ZTIMER_USEC);

if (arg->bit_pos >= 40) {
/* stray IRQ, ignoring */
return;
}

if (gpio_read(arg->pin)) {
arg->time = now;
return;
}

int8_t pos = arg->bit_pos++;
if (pos == -1) {
/* start signal received, ignore */
return;
}

unsigned bit = (now - arg->time) > PULSE_WIDTH_THRESHOLD;

if (bit) {
arg->data[pos >> 3] |= (0x80U >> (pos & 7));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

if (pos == 39) {
/* last bit received, signal that we are done now */
mutex_unlock(&arg->sync);
}
};

static void timeout_cb(void *_arg)
{
struct dht_irq_data *arg = _arg;

/* something went wrong, unblocking waiting thread */
mutex_unlock(&arg->sync);
};

int dht_read(dht_t *dev, int16_t *temp, int16_t *hum)
{
assert(dev);

uint32_t now_us = xtimer_now_usec();
uint32_t now_us = ztimer_now(ZTIMER_USEC);
if ((now_us - dev->last_read_us) > DATA_HOLD_TIME) {
/* send init signal to device */
gpio_init(dev->params.pin, GPIO_OUT);
gpio_clear(dev->params.pin);
xtimer_usleep(START_LOW_TIME);
DEBUG("[dht] pre-sleep: %" PRIu32 "\n", ztimer_now(ZTIMER_USEC));
ztimer_sleep(ZTIMER_USEC, 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;
DEBUG("[dht] post-sleep: %" PRIu32 "\n", ztimer_now(ZTIMER_USEC));
ztimer_sleep(ZTIMER_USEC, START_HIGH_TIME);

struct dht_irq_data data = {
.sync = MUTEX_INIT_LOCKED,
.pin = dev->params.pin,
/* first bit is start signal, only afterwards actual data is
* received */
.bit_pos = -1,
};
ztimer_t timeout = {
.callback = timeout_cb,
.arg = &data,
};

ztimer_set(ZTIMER_USEC, &timeout, TIMEOUT_US);

/* actual reception is done in IRQ */
if (gpio_init_int(dev->params.pin, dev->params.in_mode, GPIO_BOTH,
gpio_cb, &data)) {
ztimer_remove(ZTIMER_USEC, &timeout);
/* IRQs not supported on given GPIO pin or with given mode */
return -ENOTSUP;
}

if (_wait_for_level(dev->params.pin, 0, TIMEOUT)) {
_reset(dev);
return DHT_TIMEOUT;
}
/* wait for IRQ handler to collect all bits */
mutex_lock(&data.sync);
/* remove timer (doesn't hurt if it fired by now in case of timeout) */
ztimer_remove(ZTIMER_USEC, &timeout);
gpio_irq_disable(dev->params.pin);

/*
* data is read in sequentially, highest bit first:
* 40 .. 24 23 .. 8 7 .. 0
* [humidity][temperature][checksum]
*/
/* Bring device back to defined state - so we can trigger the next reading
* by pulling the data pin low again */
_reset(dev);

/* 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 (data.bit_pos == -1) {
DEBUG_PUTS("[dht] Not a single bit received, no DHT connected?");
return -ENXIO;
}

if (_read(&csum, dev->params.pin)) {
_reset(dev);
return DHT_TIMEOUT;
if (data.bit_pos != 40) {
DEBUG_PUTS("[dht] Timeout before all bits received");
return -ETIMEDOUT;
}

/* Bring device back to defined state - so we can trigger the next reading
* by pulling the data pin low again */
_reset(dev);
DEBUG("[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]);

/* 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;
uint8_t sum = 0;
for (uint_fast8_t i = 0; i < 4; i++) {
sum += data.data[i];
}

/* 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);

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 (sum != data.data[BYTEPOS_CHECKSUM]) {
DEBUG("[dht] error: checksum doesn't match\n");
return -EIO;
}
else {
dev->last_val.temperature = raw_temp_i * 10 + raw_temp_d;

dev->last_val.humidity = data.data[BYTEPOS_HUMIDITY_INTEGRAL] * 10
+ data.data[BYTEPOS_HUMIDITY_FRACTUAL];
/* MSB for integral temperature byte gives sign, remaining is abs() of
* value (beware: this is not two's complement!) */
bool is_negative = data.data[BYTEPOS_TEMPERATURE_INTEGRAL] & 0x80;
data.data[BYTEPOS_TEMPERATURE_INTEGRAL] &= 0x7f;
dev->last_val.temperature = data.data[BYTEPOS_TEMPERATURE_INTEGRAL] * 10
+ data.data[BYTEPOS_TEMPERATURE_FRACTUAL];

if (is_negative) {
dev->last_val.temperature = -dev->last_val.temperature;
}

/* update time of last measurement */
Expand All @@ -215,5 +240,5 @@ int dht_read(dht_t *dev, int16_t *temp, int16_t *hum)
*hum = dev->last_val.humidity;
}

return DHT_OK;
return 0;
}
Loading