dht_sensor/
lib.rs

1//! # DHT11/DHT22 sensor driver
2//!
3//! This library provides a platform-agnostic driver for the [DHT11 and DHT22](https://learn.adafruit.com/dht/overview) sensors.
4//!
5//! Use one of two functions [`dht11::Reading::read`] and [`dht22::Reading::read`] to get a reading.
6//!
7//! [`dht11::Reading::read`]: dht11/struct.Reading.html#method.read
8//! [`dht22::Reading::read`]: dht22/struct.Reading.html#method.read
9//!
10//! ## Usage
11//!
12//! The only prerequisites are an embedded-hal implementation that provides:
13//!
14//! - [`Delay`]-implementing type, for example Cortex-M microcontrollers typically use the `SysTick`.
15//! - [`InputOutputPin`]-implementing type, for example an `Output<OpenDrain>` from `stm32f0xx_hal`.
16//!
17//! When initializing the pin as an output, the state of the pin might depend on the specific chip
18//! used. Some might pull the pin low by default causing the sensor to be confused when we actually
19//! read it for the first time. The same thing happens when the sensor is polled too quickly in succession.
20//! In both of those cases you will get a `DhtError::Timeout`.
21//!
22//! To avoid this, you can pull the pin high when initializing it and polling the sensor with an
23//! interval of at least 500ms (determined experimentally). Some sources state a refresh rate of 1 or even 2 seconds.
24//!
25//! ## Example
26//!
27//! See the [stm32f042 example](https://github.com/michaelbeaumont/dht-sensor/blob/master/examples/stm32f042.rs) for a working example of
28//! how to use the library.
29//!
30//! ```
31//! #![no_std]
32//! #![no_main]
33//!
34//! use crate::hal::{delay, gpio, prelude::*, stm32};
35//! use cortex_m_rt::entry;
36//! use cortex_m_semihosting::hprintln;
37//! use panic_halt as _;
38//! use stm32f0xx_hal as hal;
39//!
40//! use dht_sensor::*;
41//!
42//! #[entry]
43//! fn main() -> ! {
44//!     let mut p = stm32::Peripherals::take().unwrap();
45//!     let cp = stm32::CorePeripherals::take().unwrap();
46//!     let mut rcc = p.RCC.configure().sysclk(8.mhz()).freeze(&mut p.FLASH);
47//!
48//!     // This is used by `dht-sensor` to wait for signals
49//!     let mut delay = delay::Delay::new(cp.SYST, &rcc);
50//!
51//!     // This could be any `gpio` port
52//!     let gpio::gpioa::Parts { pa1, .. } = p.GPIOA.split(&mut rcc);
53//!
54//!     // An `Output<OpenDrain>` is both `InputPin` and `OutputPin`
55//!     let mut pa1 = cortex_m::interrupt::free(|cs| pa1.into_open_drain_output(cs));
56//!     
57//!     // Pulling the pin high to avoid confusing the sensor when initializing
58//!     pa1.set_high().ok();
59//!
60//!     // The DHT11 datasheet suggests 1 second
61//!     hprintln!("Waiting on the sensor...").unwrap();
62//!     delay.delay_ms(1000_u16);
63//!     
64//!     loop {
65//!         match dht11::Reading::read(&mut delay, &mut pa1) {
66//!             Ok(dht11::Reading {
67//!                 temperature,
68//!                 relative_humidity,
69//!             }) => hprintln!("{}°, {}% RH", temperature, relative_humidity).unwrap(),
70//!             Err(e) => hprintln!("Error {:?}", e).unwrap(),
71//!         }
72//!         
73//!         // Delay of at least 500ms before polling the sensor again, 1 second or more advised
74//!         delay.delay_ms(500_u16);  
75//!     }
76//! }
77//! ```
78#![cfg_attr(not(test), no_std)]
79
80mod read;
81pub use read::{Delay, DhtError, InputOutputPin};
82
83pub trait DhtReading: internal::FromRaw + Sized {
84    fn read<P, E>(delay: &mut dyn Delay, pin: &mut P) -> Result<Self, read::DhtError<E>>
85    where
86        P: InputOutputPin<E>,
87    {
88        read::read_raw(delay, pin).map(Self::raw_to_reading)
89    }
90}
91
92mod internal {
93    pub trait FromRaw {
94        fn raw_to_reading(bytes: [u8; 4]) -> Self;
95    }
96}
97
98pub mod dht11 {
99    use super::*;
100
101    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
102    pub struct Reading {
103        pub temperature: i8,
104        pub relative_humidity: u8,
105    }
106
107    impl internal::FromRaw for Reading {
108        fn raw_to_reading(bytes: [u8; 4]) -> Reading {
109            let [rh, _, temp_signed, _] = bytes;
110            let temp = {
111                let (signed, magnitude) = convert_signed(temp_signed);
112                let temp_sign = if signed { -1 } else { 1 };
113                temp_sign * magnitude as i8
114            };
115            Reading {
116                temperature: temp,
117                relative_humidity: rh,
118            }
119        }
120    }
121
122    impl DhtReading for Reading {}
123
124    #[test]
125    fn test_raw_to_reading() {
126        use super::internal::FromRaw;
127
128        assert_eq!(
129            Reading::raw_to_reading([0x32, 0, 0x1B, 0]),
130            Reading {
131                temperature: 27,
132                relative_humidity: 50
133            }
134        );
135        assert_eq!(
136            Reading::raw_to_reading([0x80, 0, 0x83, 0]),
137            Reading {
138                temperature: -3,
139                relative_humidity: 128
140            }
141        );
142    }
143}
144
145pub mod dht22 {
146    use super::*;
147
148    #[derive(Clone, Copy, Debug, PartialEq)]
149    pub struct Reading {
150        pub temperature: f32,
151        pub relative_humidity: f32,
152    }
153
154    impl internal::FromRaw for Reading {
155        fn raw_to_reading(bytes: [u8; 4]) -> Reading {
156            let [rh_h, rh_l, temp_h_signed, temp_l] = bytes;
157            let rh = ((rh_h as u16) << 8 | (rh_l as u16)) as f32 / 10.0;
158            let temp = {
159                let (signed, magnitude) = convert_signed(temp_h_signed);
160                let temp_sign = if signed { -1.0 } else { 1.0 };
161                let temp_magnitude = ((magnitude as u16) << 8) | temp_l as u16;
162                temp_sign * temp_magnitude as f32 / 10.0
163            };
164            Reading {
165                temperature: temp,
166                relative_humidity: rh,
167            }
168        }
169    }
170
171    impl DhtReading for Reading {}
172
173    #[test]
174    fn test_raw_to_reading() {
175        use super::internal::FromRaw;
176
177        assert_eq!(
178            Reading::raw_to_reading([0x02, 0x10, 0x01, 0x1B]),
179            Reading {
180                temperature: 28.3,
181                relative_humidity: 52.8
182            }
183        );
184        assert_eq!(
185            Reading::raw_to_reading([0x02, 0x90, 0x80, 0x1B]),
186            Reading {
187                temperature: -2.7,
188                relative_humidity: 65.6
189            }
190        );
191    }
192}
193
194fn convert_signed(signed: u8) -> (bool, u8) {
195    let sign = signed & 0x80 != 0;
196    let magnitude = signed & 0x7F;
197    (sign, magnitude)
198}
199
200#[test]
201fn test_convert_signed() {
202    assert_eq!(convert_signed(0x13), (false, 0x13));
203    assert_eq!(convert_signed(0x93), (true, 0x13));
204}