From 28927cd82329b9ea2f2e09ed58a1e2dedc9e1d10 Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Tue, 16 Feb 2021 00:00:47 +0100 Subject: [PATCH] Add timeouts in read functions for reliability (#2) * Add timeouts in read functions for reliability Timeouts avoid getting stuck in infinite loops if the sensor does not respond as expected. Now if the sensor does not respond we return a DhtError::Timeout and the user can choose how to handle it. * Change polarity of the timeout function Co-authored-by: Mike <2266568+michaelbeaumont@users.noreply.github.com> * Update examples and tidy documentation * Fix formatting Co-authored-by: Mike <2266568+michaelbeaumont@users.noreply.github.com> Co-authored-by: Michael Beaumont --- examples/stm32f042.rs | 29 +++++++++++++++++------------ src/lib.rs | 41 ++++++++++++++++++++++++++++------------- src/read.rs | 33 +++++++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 31 deletions(-) diff --git a/examples/stm32f042.rs b/examples/stm32f042.rs index a919042..a2b7ea4 100644 --- a/examples/stm32f042.rs +++ b/examples/stm32f042.rs @@ -21,21 +21,26 @@ fn main() -> ! { // This could be any `gpio` port let gpio::gpioa::Parts { pa1, .. } = p.GPIOA.split(&mut rcc); + // An `Output` is both `InputPin` and `OutputPin` + let mut pa1 = cortex_m::interrupt::free(|cs| pa1.into_open_drain_output(cs)); + + // Pulling the pin high to avoid confusing the sensor when initializing + pa1.set_high().ok(); + // The DHT11 datasheet suggests 1 second hprintln!("Waiting on the sensor...").unwrap(); delay.delay_ms(1000_u16); - // An `Output` is both `InputPin` and `OutputPin` - let mut pa1 = cortex_m::interrupt::free(|cs| pa1.into_open_drain_output(cs)); - - match dht11::Reading::read(&mut delay, &mut pa1) { - Ok(dht11::Reading { - temperature, - relative_humidity, - }) => hprintln!("{}°, {}% RH", temperature, relative_humidity).unwrap(), - Err(e) => hprintln!("Error {:?}", e).unwrap(), + loop { + match dht11::Reading::read(&mut delay, &mut pa1) { + Ok(dht11::Reading { + temperature, + relative_humidity, + }) => hprintln!("{}°, {}% RH", temperature, relative_humidity).unwrap(), + Err(e) => hprintln!("Error {:?}", e).unwrap(), + } + + // Delay of at least 500ms before polling the sensor again, 1 second or more advised + delay.delay_ms(500_u16); } - hprintln!("Looping forever now, thanks!").unwrap(); - - loop {} } diff --git a/src/lib.rs b/src/lib.rs index 1d48a7c..c32ebe2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,14 @@ //! - [`InputOutputPin`]-implementing type, for example an `Output` from `stm32f0xx_hal`. //! //! +//! When initializing the pin as an output, the state of the pin might depend on the specific chip +//! used. Some might pull the pin low by default causing the sensor to be confused when we actually +//! read it for the first time. The same thing happens when the sensor is polled too quickly in succession. +//! In both of those cases you will get a `DhtError::Timeout`. +//! +//! To avoid this, you can pull the pin high when initializing it and polling the sensor with an +//! interval of at least 500ms (determined experimentally). Some sources state a refresh rate of 1 or even 2 seconds. +//! //! ## Example //! //! See the [stm32f042 example](https://github.com/michaelbeaumont/dht-sensor/blob/master/examples/stm32f042.rs) for a working example of @@ -44,24 +52,31 @@ //! // This could be any `gpio` port //! let gpio::gpioa::Parts { pa1, .. } = p.GPIOA.split(&mut rcc); //! -//! // The DHT11 datasheet suggests 1 second -//! hprintln!("Waiting on the sensor...").unwrap(); -//! delay.delay_ms(1000_u16); -//! //! // An `Output` is both `InputPin` and `OutputPin` //! let mut pa1 = cortex_m::interrupt::free(|cs| pa1.into_open_drain_output(cs)); +//! +//! // Pulling the pin high to avoid confusing the sensor when initializing +//! pa1.set_high().ok(); //! -//! match dht11::Reading::read(&mut delay, &mut pa1) { -//! Ok(dht11::Reading { -//! temperature, -//! relative_humidity, -//! }) => hprintln!("{}°, {}% RH", temperature, relative_humidity).unwrap(), -//! Err(e) => hprintln!("Error {:?}", e).unwrap(), +//! // The DHT11 datasheet suggests 1 second +//! hprintln!("Waiting on the sensor...").unwrap(); +//! delay.delay_ms(1000_u16); +//! +//! loop { +//! match dht11::Reading::read(&mut delay, &mut pa1) { +//! Ok(dht11::Reading { +//! temperature, +//! relative_humidity, +//! }) => hprintln!("{}°, {}% RH", temperature, relative_humidity).unwrap(), +//! Err(e) => hprintln!("Error {:?}", e).unwrap(), +//! } +//! +//! // Delay of at least 500ms before polling the sensor again, 1 second or more advised +//! delay.delay_ms(500_u16); //! } -//! hprintln!("Looping forever now, thanks!").unwrap(); -//! -//! loop {} //! } +//! ``` + #![cfg_attr(not(test), no_std)] mod read; diff --git a/src/read.rs b/src/read.rs index 4ce1b38..7bb257c 100644 --- a/src/read.rs +++ b/src/read.rs @@ -5,6 +5,7 @@ use embedded_hal::digital::v2::{InputPin, OutputPin}; pub enum DhtError { PinError(E), ChecksumMismatch, + Timeout, } impl From for DhtError { @@ -19,15 +20,15 @@ impl Delay for T where T: DelayMs + DelayUs {} pub trait InputOutputPin: InputPin + OutputPin {} impl InputOutputPin for T where T: InputPin + OutputPin {} -fn read_bit(delay: &mut dyn Delay, pin: &impl InputPin) -> Result { - while pin.is_low()? {} +fn read_bit(delay: &mut dyn Delay, pin: &impl InputPin) -> Result> { + wait_until_timeout(delay, || pin.is_high(), 100)?; delay.delay_us(35u8); let high = pin.is_high()?; - while pin.is_high()? {} + wait_until_timeout(delay, || pin.is_low(), 100)?; Ok(high) } -fn read_byte(delay: &mut dyn Delay, pin: &impl InputPin) -> Result { +fn read_byte(delay: &mut dyn Delay, pin: &impl InputPin) -> Result> { let mut byte: u8 = 0; for i in 0..8 { let bit_mask = 1 << (7 - (i % 8)); @@ -46,8 +47,10 @@ where delay.delay_ms(18_u8); pin.set_high().ok(); delay.delay_us(48_u8); - while pin.is_low()? {} - while pin.is_high()? {} + + wait_until_timeout(delay, || pin.is_high(), 100)?; + wait_until_timeout(delay, || pin.is_low(), 100)?; + let mut data = [0; 4]; for b in data.iter_mut() { *b = read_byte(delay, pin)?; @@ -59,3 +62,21 @@ where Ok(data) } } + +/// Wait until the given function returns true or the timeout is reached. +fn wait_until_timeout( + delay: &mut dyn Delay, + func: F, + timeout_us: u8, +) -> Result<(), DhtError> +where + F: Fn() -> Result, +{ + for _ in 0..timeout_us { + if func()? { + return Ok(()); + } + delay.delay_us(1_u8); + } + Err(DhtError::Timeout) +}