lh_dht22_rp/lib.rs
1//! DHT22 humidity and temperature sensor driver for the RP2040 microcontroller.
2//! Based on rp2040_hal and embedded_hal
3//!
4//! Communicates though a single data wire which requires special pin handling
5//! for bi-directional communication.
6//!
7//! Reference:
8//! <https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf>
9//!
10//!
11//! Example:
12//!
13//! ```rust
14//!
15//! use lh_dht22_rp as dht22;
16//! ...
17//!
18//! // Hal Boilerplate ...
19//! ...
20//!
21//! let dht_pin = pins.gpio1;
22//! let mut dht = dht22::DHT22::new(dht_pin, timer);
23//!
24//! match dht.read() {
25//! Ok((humidity, temperature)) => {
26//! println!("Humidity : {:.1} %RH", humidity);
27//! println!("Temperature: {:.1} C", temperature); },
28//! Err(e) => println!("Err: {e}"),
29//! }
30//! ```
31
32#![no_std]
33
34use rp2040_hal::gpio;
35use rp2040_hal::timer::{Instant, Timer};
36
37use embedded_hal::digital::{InputPin, OutputPin};
38use embedded_hal_0_2::blocking::delay::DelayUs;
39
40// —————————————————————————————————————————————————————————————————————————————————————————————————
41// Globals
42// —————————————————————————————————————————————————————————————————————————————————————————————————
43
44const TIMEOUT_MS: u64 = 2 * 1000;
45const INIT_STATE_CHECK_DELAY_US: i32 = 100;
46const START_SIGNAL_US: u32 = 5_000;
47const SETUP_TIME_US: u32 = 20;
48const HIGH: u8 = 1;
49const LOW: u8 = 0;
50
51type Output = gpio::Pin<gpio::DynPinId, gpio::FunctionSio<gpio::SioOutput>, gpio::PullUp>;
52type Input = gpio::Pin<gpio::DynPinId, gpio::FunctionSio<gpio::SioInput>, gpio::PullNone>;
53
54pub type Result<T> = core::result::Result<T, DhtError>;
55
56// —————————————————————————————————————————————————————————————————————————————————————————————————
57// DHT22
58// —————————————————————————————————————————————————————————————————————————————————————————————————
59
60pub struct DHT22 {
61 pin: Output,
62 timer: Timer,
63 start_time: Instant,
64}
65
66impl DHT22 {
67 /// Creates a new DHT22 sensor instance.
68 /// Requires the Pin connected to the DHT22 Data line, and a copy of the mcu timer.
69 pub fn new(pin: impl gpio::AnyPin, timer: Timer) -> Self {
70 let mut pin = pin.into_output();
71 pin.set_high();
72
73 let start_time = timer.get_counter();
74
75 Self { pin, timer, start_time }
76 }
77
78 #[inline]
79 /// Internal check for time out
80 fn not_timed_out(&self) -> Result<()> {
81 let elapsed = self
82 .timer
83 .get_counter()
84 .checked_duration_since(self.start_time)
85 .unwrap()
86 .to_millis();
87
88 if elapsed > TIMEOUT_MS {
89 return Err(DhtError::Timeout);
90 }
91 Ok(())
92 }
93
94 #[inline]
95 /// Waits until the desired state is read. Errors on timeout
96 fn wait_for_state(&mut self, state: u8, pin: &mut Input) -> Result<()> {
97 loop {
98 if get_input_state(pin) == state {
99 return Ok(());
100 }
101
102 if self.not_timed_out().is_err() {
103 return Err(DhtError::Timeout);
104 }
105 }
106 }
107
108 /// Reads the data from the sensor.
109 /// Returns Ok((humidity, temperature)) or Err(DhtError)
110 pub fn read(&mut self) -> Result<(f32, f32)> {
111 const PACKET_SIZE: usize = 40;
112 //
113 self.start_time = self.timer.get_counter();
114
115 // DTH22 sends a 16b + 16b + 8b package
116 let mut buffer = [0u8; PACKET_SIZE / 8];
117
118 // Requesting Data
119 let mut pin = self.pin.into_output();
120 pin.set_low();
121 self.timer.delay_us(START_SIGNAL_US);
122 pin.set_high();
123 self.timer.delay_us(SETUP_TIME_US);
124
125 // Switching pin into Input type
126 let mut pin = pin.into_input();
127
128 // Critical Section Interrupt Free - for time sensitive ops
129 let transaction_result = critical_section::with(|_cs| {
130 // Receiving Prelude - Expecting the pin to be HIGH at this time
131 self.timer.delay_us(INIT_STATE_CHECK_DELAY_US); // This may vary so it could be improved
132 if get_input_state(&mut pin) == LOW {
133 return Err(DhtError::Communication);
134 }
135
136 // Waiting for data transmission to start
137 self.wait_for_state(LOW, &mut pin)?;
138
139 // Reading Data
140 for i in 0..PACKET_SIZE {
141 // Waiting for Bit tx signaled by HIGH state
142 self.wait_for_state(HIGH, &mut pin)?;
143
144 // Reading bit value
145 self.timer.delay_us(35);
146 let state = get_input_state(&mut pin);
147
148 // Adding bit to buffer
149 let byte_index = i / 8;
150 let bit_index = 7 - (i % 8);
151 if state == 1 {
152 buffer[byte_index] |= 1 << bit_index;
153 }
154
155 // Wait until bit finished sending
156 if state == HIGH {
157 self.wait_for_state(LOW, &mut pin)?;
158 }
159 }
160
161 Ok(())
162 });
163
164 // Resetting pin state
165 let mut pin = self.pin.into_output();
166 pin.set_high();
167
168 // Evaluating transaction result
169 transaction_result?;
170
171 // Compute Checksum
172 let checksum = buffer[4];
173 let checksum_truth = buffer[0]
174 .wrapping_add(buffer[1])
175 .wrapping_add(buffer[2])
176 .wrapping_add(buffer[3]);
177
178 // If all received bits are 1
179 if checksum_truth == 252 {
180 return Err(DhtError::Connection);
181 }
182
183 if checksum != checksum_truth {
184 return Err(DhtError::Checksum);
185 }
186
187 // Compute Humidity
188 let humidity = u16::from_be_bytes([buffer[0], buffer[1]]);
189 let humidity = humidity as f32 * 0.1;
190
191 // Compute Temperature
192 let temperature = u16::from_be_bytes([buffer[2], buffer[3]]);
193
194 // Negative if highest bit is 1
195 let temperature = if temperature >> 15 == 1 {
196 (temperature & !(1 << 15)) as f32 * -0.1
197 }
198 else {
199 temperature as f32 * 0.1
200 };
201
202 Ok((humidity, temperature))
203 }
204}
205
206// —————————————————————————————————————————————————————————————————————————————————————————————————
207// Error
208// —————————————————————————————————————————————————————————————————————————————————————————————————
209
210#[derive(Debug, Clone, Eq, PartialEq)]
211pub enum DhtError {
212 Timeout,
213 Checksum,
214 Communication,
215 Connection,
216}
217
218impl core::fmt::Display for DhtError {
219 fn fmt(
220 &self,
221 fmt: &mut core::fmt::Formatter<'_>,
222 ) -> core::result::Result<(), core::fmt::Error> {
223 match self {
224 DhtError::Timeout => write!(fmt, "timeout"),
225 DhtError::Checksum => write!(fmt, "invalid data"),
226 DhtError::Communication => write!(fmt, "communication error"),
227 DhtError::Connection => write!(fmt, "connection error"),
228 }
229 }
230}
231
232// —————————————————————————————————————————————————————————————————————————————————————————————————
233// Traits
234// —————————————————————————————————————————————————————————————————————————————————————————————————
235
236/// Trait for constructing a dynamic input or output pin from scratch
237#[allow(clippy::wrong_self_convention)]
238pub trait ReconstructPin {
239 fn into_output(&self) -> Output;
240 fn into_input(&self) -> Input;
241}
242
243impl<T: gpio::AnyPin> ReconstructPin for T {
244 #[inline]
245 /// Returns a dynamic output pin
246 fn into_output(&self) -> Output {
247 let id = self.borrow().id().num;
248 // HAL is pedantic with self-creating pins, but we have ownership over the original pin
249 unsafe {
250 let pin = gpio::new_pin(gpio::DynPinId {
251 bank: gpio::DynBankId::Bank0,
252 num: id,
253 });
254
255 pin.try_into_function::<gpio::FunctionSio<gpio::SioOutput>>()
256 .expect("Pin into Output")
257 .into_pull_type::<gpio::PullUp>()
258 }
259 }
260
261 #[inline]
262 /// Returns a dynamic input pin
263 fn into_input(&self) -> Input {
264 let id = self.borrow().id().num;
265 // HAL is pedantic with self-creating pins, but we have ownership over the original pin
266 unsafe {
267 let pin = gpio::new_pin(gpio::DynPinId {
268 bank: gpio::DynBankId::Bank0,
269 num: id,
270 });
271
272 pin.try_into_function::<gpio::FunctionSio<gpio::SioInput>>()
273 .expect("Pin into Input")
274 .into_pull_type::<gpio::PullNone>()
275 }
276 }
277}
278
279// —————————————————————————————————————————————————————————————————————————————————————————————————
280// Functions
281// —————————————————————————————————————————————————————————————————————————————————————————————————
282
283#[inline]
284fn get_input_state(pin: &mut Input) -> u8 {
285 if pin.is_high().unwrap() {
286 return HIGH;
287 }
288 LOW
289}