Skip to content

Commit

Permalink
Add timeouts in read functions for reliability (#2)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update examples and tidy documentation

* Fix formatting

Co-authored-by: Mike <[email protected]>
Co-authored-by: Michael Beaumont <[email protected]>
  • Loading branch information
3 people authored Feb 15, 2021
1 parent c18aaea commit 28927cd
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 31 deletions.
29 changes: 17 additions & 12 deletions examples/stm32f042.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ fn main() -> ! {
// This could be any `gpio` port
let gpio::gpioa::Parts { pa1, .. } = p.GPIOA.split(&mut rcc);

// An `Output<OpenDrain>` 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<OpenDrain>` 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 {}
}
41 changes: 28 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
//! - [`InputOutputPin`]-implementing type, for example an `Output<OpenDrain>` 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
Expand Down Expand Up @@ -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<OpenDrain>` 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;
Expand Down
33 changes: 27 additions & 6 deletions src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use embedded_hal::digital::v2::{InputPin, OutputPin};
pub enum DhtError<E> {
PinError(E),
ChecksumMismatch,
Timeout,
}

impl<E> From<E> for DhtError<E> {
Expand All @@ -19,15 +20,15 @@ impl<T> Delay for T where T: DelayMs<u8> + DelayUs<u8> {}
pub trait InputOutputPin<E>: InputPin<Error = E> + OutputPin<Error = E> {}
impl<T, E> InputOutputPin<E> for T where T: InputPin<Error = E> + OutputPin<Error = E> {}

fn read_bit<E>(delay: &mut dyn Delay, pin: &impl InputPin<Error = E>) -> Result<bool, E> {
while pin.is_low()? {}
fn read_bit<E>(delay: &mut dyn Delay, pin: &impl InputPin<Error = E>) -> Result<bool, DhtError<E>> {
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<E>(delay: &mut dyn Delay, pin: &impl InputPin<Error = E>) -> Result<u8, E> {
fn read_byte<E>(delay: &mut dyn Delay, pin: &impl InputPin<Error = E>) -> Result<u8, DhtError<E>> {
let mut byte: u8 = 0;
for i in 0..8 {
let bit_mask = 1 << (7 - (i % 8));
Expand All @@ -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)?;
Expand All @@ -59,3 +62,21 @@ where
Ok(data)
}
}

/// Wait until the given function returns true or the timeout is reached.
fn wait_until_timeout<E, F>(
delay: &mut dyn Delay,
func: F,
timeout_us: u8,
) -> Result<(), DhtError<E>>
where
F: Fn() -> Result<bool, E>,
{
for _ in 0..timeout_us {
if func()? {
return Ok(());
}
delay.delay_us(1_u8);
}
Err(DhtError::Timeout)
}

0 comments on commit 28927cd

Please sign in to comment.