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::blocking::read`] and [`dht22::blocking::read`] to get a reading.
6//!
7//! ## Usage
8//!
9//! The only prerequisites are an embedded-hal implementation that provides:
10//!
11//! - [`DelayNs`]-implementing type, for example Cortex-M microcontrollers typically use the `SysTick`.
12//! - [`InputPin`] and [`OutputPin`]-implementing type, for example an `Output<OpenDrain>` from `stm32f0xx_hal`.
13//!
14//! When initializing the pin as an output, the state of the pin might depend on the specific chip
15//! used. Some might pull the pin low by default causing the sensor to be confused when we actually
16//! read it for the first time. The same thing happens when the sensor is polled too quickly in succession.
17//! In both of those cases you will get a `DhtError::Timeout`.
18//!
19//! To avoid this, you can pull the pin high when initializing it and polling the sensor with an
20//! interval of at least 500ms (determined experimentally). Some sources state a refresh rate of 1 or even 2 seconds.
21//!
22//! ## Example
23//!
24//! See the following examples for how to use the library.
25//!
26//! ### Blocking API
27//!
28//! - [stm32f051r8](https://github.com/michaelbeaumont/dht-sensor/blob/main/examples/stm32f051r8/src/bin/sync.rs)
29//!
30//! ### Async API
31//!
32//! - [stm32f051r8](https://github.com/michaelbeaumont/dht-sensor/blob/main/examples/stm32f051r8/src/bin/async.rs)
33//! - [stm32f303vc](https://github.com/michaelbeaumont/dht-sensor/blob/main/examples/stm32f303vc/src/bin/async.rs)
34#![cfg_attr(not(test), no_std)]
35
36mod read;
37use embedded_hal::delay::DelayNs;
38use embedded_hal::digital::{InputPin, OutputPin};
39pub use read::DhtError;
40
41pub mod dht11 {
42    use super::*;
43
44    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
45    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
46    pub struct Reading {
47        pub temperature: i8,
48        pub relative_humidity: u8,
49    }
50
51    pub mod blocking {
52        use super::DelayNs;
53        use super::{raw_to_reading, InputPin, OutputPin, Reading};
54
55        pub fn read<P: OutputPin + InputPin>(
56            delay: &mut impl DelayNs,
57            pin: &mut P,
58        ) -> Result<Reading, super::read::DhtError<P::Error>> {
59            pin.set_low()?;
60            delay.delay_ms(18);
61            super::read::read_raw(delay, pin).map(raw_to_reading)
62        }
63    }
64
65    #[cfg(feature = "async")]
66    pub mod r#async {
67        use super::DelayNs;
68        use super::{raw_to_reading, InputPin, OutputPin, Reading};
69        use embedded_hal_async::delay::DelayNs as AsyncDelayNs;
70
71        /// Only the initial 18ms delay is performed asynchronously.
72        ///
73        /// The byte and bit read phase is performed with blocking delays.
74        pub async fn read<P: OutputPin + InputPin>(
75            delay: &mut (impl AsyncDelayNs + DelayNs),
76            pin: &mut P,
77        ) -> Result<Reading, crate::read::DhtError<P::Error>> {
78            pin.set_low()?;
79            embedded_hal_async::delay::DelayNs::delay_ms(delay, 18).await;
80            crate::read::read_raw(delay, pin).map(raw_to_reading)
81        }
82    }
83
84    fn raw_to_reading(bytes: [u8; 4]) -> Reading {
85        let [relative_humidity, _, temp_signed, _] = bytes;
86        let temperature = {
87            let (signed, magnitude) = convert_signed(temp_signed);
88            let temp_sign = if signed { -1 } else { 1 };
89            temp_sign * magnitude as i8
90        };
91        Reading {
92            temperature,
93            relative_humidity,
94        }
95    }
96
97    #[test]
98    fn test_raw_to_reading() {
99        assert_eq!(
100            raw_to_reading([0x32, 0, 0x1B, 0]),
101            Reading {
102                temperature: 27,
103                relative_humidity: 50
104            }
105        );
106        assert_eq!(
107            raw_to_reading([0x80, 0, 0x83, 0]),
108            Reading {
109                temperature: -3,
110                relative_humidity: 128
111            }
112        );
113    }
114}
115
116pub mod dht22 {
117    use super::*;
118
119    #[derive(Clone, Copy, Debug, PartialEq)]
120    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
121    pub struct Reading {
122        pub temperature: f32,
123        pub relative_humidity: f32,
124    }
125
126    pub mod blocking {
127        use super::DelayNs;
128        use super::{raw_to_reading, InputPin, OutputPin, Reading};
129        pub fn read<P: OutputPin + InputPin>(
130            delay: &mut impl DelayNs,
131            pin: &mut P,
132        ) -> Result<Reading, super::read::DhtError<P::Error>> {
133            pin.set_low()?;
134            delay.delay_ms(1);
135            super::read::read_raw(delay, pin).map(raw_to_reading)
136        }
137    }
138
139    #[cfg(feature = "async")]
140    pub mod r#async {
141        use super::DelayNs;
142        use super::{raw_to_reading, InputPin, OutputPin, Reading};
143        use embedded_hal_async::delay::DelayNs as AsyncDelayNs;
144
145        /// Only the initial 1 ms delay is performed asynchronously.
146        ///
147        /// The byte and bit read phase is performed with blocking delays.
148        pub async fn read<P: OutputPin + InputPin>(
149            delay: &mut (impl AsyncDelayNs + DelayNs),
150            pin: &mut P,
151        ) -> Result<Reading, crate::read::DhtError<P::Error>> {
152            pin.set_low()?;
153            embedded_hal_async::delay::DelayNs::delay_ms(delay, 1).await;
154            crate::read::read_raw(delay, pin).map(raw_to_reading)
155        }
156    }
157
158    fn raw_to_reading(bytes: [u8; 4]) -> Reading {
159        let [rh_h, rh_l, temp_h_signed, temp_l] = bytes;
160        let relative_humidity = ((rh_h as u16) << 8 | (rh_l as u16)) as f32 / 10.0;
161        let temperature = {
162            let (signed, magnitude) = convert_signed(temp_h_signed);
163            let temp_sign = if signed { -1.0 } else { 1.0 };
164            let temp_magnitude = ((magnitude as u16) << 8) | temp_l as u16;
165            temp_sign * temp_magnitude as f32 / 10.0
166        };
167        Reading {
168            temperature,
169            relative_humidity,
170        }
171    }
172
173    #[test]
174    fn test_raw_to_reading() {
175        assert_eq!(
176            raw_to_reading([0x02, 0x10, 0x01, 0x1B]),
177            Reading {
178                temperature: 28.3,
179                relative_humidity: 52.8
180            }
181        );
182        assert_eq!(
183            raw_to_reading([0x02, 0x90, 0x80, 0x1B]),
184            Reading {
185                temperature: -2.7,
186                relative_humidity: 65.6
187            }
188        );
189    }
190}
191
192fn convert_signed(signed: u8) -> (bool, u8) {
193    let sign = signed & 0x80 != 0;
194    let magnitude = signed & 0x7F;
195    (sign, magnitude)
196}
197
198#[test]
199fn test_convert_signed() {
200    assert_eq!(convert_signed(0x13), (false, 0x13));
201    assert_eq!(convert_signed(0x93), (true, 0x13));
202}