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}