dht11_driver/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code)]
3#![cfg_attr(not(test), no_std)]
4
5use embedded_hal::{
6    delay::DelayNs,
7    digital::{InputPin, OutputPin},
8};
9use core::marker::PhantomData;
10
11#[cfg(feature = "use-dwt")]
12use fugit::HertzU32;
13
14#[cfg(feature = "use-dwt")]
15use cortex_m::peripheral::DWT;
16
17use crate::error::DHT11Error;
18
19/// Max time to wait for a state change before giving up
20#[allow(unused)]
21const TIMEOUT_US: u32 = 200;
22
23#[cfg(not(feature = "use-dwt"))]
24#[allow(unused)]
25const TIMEOUT_CYCLES: u32 = 2_000_000;
26
27pub mod error {
28    pub type Result<T, E> = core::result::Result<T, DHT11Error<E>>;
29
30    #[derive(Debug, Clone, Copy)]
31    #[cfg_attr(feature = "use-defmt", derive(defmt::Format))]
32    pub enum DHT11Error<E> {
33        NoResponse,
34        ChecksumMismatch,
35        PinError(E),
36        Timeout,
37    }
38}
39
40// --- Typestate Pattern for Safety ---
41pub struct Idle;
42pub struct Uninitialized;
43pub struct Initialized;
44
45pub struct SensorValue {
46    pub integral: u8,
47    pub decimal: u8,
48}
49
50pub type Temperature = SensorValue;
51pub type Humidity = SensorValue;
52
53pub struct Measurement {
54    pub humidity: Humidity,
55    pub temperature: Temperature,
56    pub checksum: u8,
57}
58
59impl Measurement {
60    pub fn temp_as_f32(&self) -> f32 {
61        self.temperature.integral as f32 + (self.temperature.decimal as f32 / 10.0)
62    }
63
64    pub fn temp_as_fixed(&self) -> i16 {
65        (self.temperature.integral as i16 * 10) + (self.temperature.decimal as i16)
66    }
67
68    pub fn hum_as_f32(&self) -> f32 {
69        self.humidity.integral as f32 + (self.humidity.decimal as f32 / 10.0)
70    }
71
72    pub fn hum_as_fixed(&self) -> i16 {
73        (self.humidity.integral as i16 * 10) + (self.humidity.decimal as i16)
74    }
75}
76
77pub struct DHT11<PIN, STATE> {
78    pin: PIN,
79    _state: PhantomData<STATE>,
80    clock_info: u32,
81}
82
83impl<PIN, E, STATE> DHT11<PIN, STATE>
84where PIN: InputPin<Error = E> + OutputPin<Error = E>
85{
86    fn is_bit_high(&self, _low_duration: u32, high_duration: u32) -> bool {
87        #[cfg(feature = "use-dwt")]
88        {
89            let cycles_per_us = self.clock_info / 1_000_000;
90            high_duration > (40 * cycles_per_us)
91        }
92
93        #[cfg(not(feature = "use-dwt"))]
94        {
95            high_duration > _low_duration
96        }
97    }
98
99    /// Waits for the pin to reach a certain state and measures the time taken
100    fn wait_for_state(&mut self, target_high: bool, _delay: &mut impl DelayNs) -> error::Result<u32, E> {
101        #[cfg(feature = "use-dwt")]
102        {
103            let cycles_per_us = self.clock_info / 1_000_000;
104            let start = DWT::cycle_count();
105            // Loop until the pin state matches our target
106            while self.pin.is_low().map_err(DHT11Error::PinError)? == target_high {
107                if DWT::cycle_count().wrapping_sub(start) > (TIMEOUT_US * cycles_per_us) {
108                    return Err(DHT11Error::Timeout);
109                }
110            }
111            Ok(DWT::cycle_count().wrapping_sub(start))
112        }
113
114        #[cfg(not(feature = "use-dwt"))]
115        {
116            let mut count: u32 = 0;
117            while self.pin.is_low().map_err(DHT11Error::PinError)? == target_high {
118                count += 1;
119                if count > TIMEOUT_CYCLES { return Err(DHT11Error::Timeout); }
120            }
121            Ok(count)
122        }
123    }
124
125    /// Sends the 18ms pulse to wake up the sensor
126    fn send_start_signal(&mut self, delay: &mut impl DelayNs) -> error::Result<(), E> {
127        self.pin.set_high().map_err(DHT11Error::PinError)?;
128        delay.delay_ms(1);
129
130        // Pull low for 18-20ms to signal the start
131        self.pin.set_low().map_err(DHT11Error::PinError)?;
132        delay.delay_ms(20);
133        
134        // Release the line and wait for the sensor to pull it low
135        self.pin.set_high().map_err(DHT11Error::PinError)?;
136
137        // Wait for the sensor's acknowledgment (LOW)
138        self.wait_for_state(false, delay)?;
139
140        Ok(())
141    }
142}
143
144impl<PIN, E> DHT11<PIN, Idle>
145where PIN: InputPin<Error = E> + OutputPin<Error = E>
146{
147    #[cfg(feature = "use-dwt")]
148    pub fn new(pin: PIN, clock_info: HertzU32) -> DHT11<PIN, Uninitialized> {
149        DHT11 {
150            pin,
151            _state: PhantomData,
152            clock_info: clock_info.to_Hz(),
153        }
154    }
155
156    #[cfg(not(feature = "use-dwt"))]
157    pub fn new(pin: PIN) -> DHT11<PIN, Uninitialized> {
158        DHT11 {
159            pin,
160            _state: PhantomData,
161            clock_info: 0,
162        }
163    }
164
165    /// Creates and initializes the sensor in one go.
166    /// ⚠️ WARNING: This is a blocking call that takes ~3 seconds to complete.
167    #[cfg(feature = "use-dwt")]
168    pub fn new_and_initialized(pin: PIN, delay: &mut impl DelayNs, clock_info: HertzU32) -> error::Result<DHT11<PIN, Initialized>, E> {
169        let uninit = Self::new(pin, clock_info);
170
171        let init = uninit.initialize(delay)?;
172
173        delay.delay_ms(2_000);
174
175        Ok(init)
176    }
177
178    /// Creates and initializes the sensor in one go.
179    /// ⚠️ WARNING: This is a blocking call that takes ~3 seconds to complete.
180    #[cfg(not(feature = "use-dwt"))]
181    pub fn new_and_initialized(pin: PIN, delay: &mut impl DelayNs) -> error::Result<DHT11<PIN, Initialized>, E> {
182        let uninit = Self::new(pin);
183
184        let init = uninit.initialize(delay)?;
185
186        delay.delay_ms(2_000);
187
188        Ok(init)
189    }
190}
191
192impl<PIN, E> DHT11<PIN, Uninitialized>
193where PIN: InputPin<Error = E> + OutputPin<Error = E>
194{
195    /// Initializes the sensor with required power-on delays
196    /// ⚠️ WARNING: Wait at least 2 seconds after initialization before reading the sensor
197    pub fn initialize(mut self, delay: &mut impl DelayNs) -> error::Result<DHT11<PIN, Initialized>, E> {
198        // Perform a test handshake to ensure the sensor is present
199        self.send_start_signal(delay)?;
200
201        Ok(DHT11 {
202            pin: self.pin,
203            _state: PhantomData,
204            clock_info: self.clock_info,
205        })
206    }
207}
208
209impl<PIN, E> DHT11<PIN, Initialized>
210where PIN: InputPin<Error = E> + OutputPin<Error = E>
211{
212    /// Reads temperature and humidity from the sensor
213    pub fn read_temp_and_hum(&mut self, delay: &mut impl DelayNs) -> error::Result<Measurement, E> {
214        self.send_start_signal(delay)?;
215
216        // Sensor response: 80µs Low followed by 80µs High
217        self.wait_for_state(true, delay)?;  // End of Low phase
218        self.wait_for_state(false, delay)?; // End of High phase
219
220        // Skip the "Ghost Bit 👻" (the transition from handshake to data)
221        self.wait_for_state(true, delay)?;
222        self.wait_for_state(false, delay)?;
223
224        // Decode durations into bits and bytes
225        let mut data = [0u8; 5];
226        for i in 0..40 {
227            // 1. Messen der Low-Phase (Bit-Vorbereitung, ca. 50us)
228            let low_duration = self.wait_for_state(true, delay)?;
229            
230            // 2. Messen der High-Phase (Die eigentliche Information)
231            let high_duration = self.wait_for_state(false, delay)?;
232
233            if self.is_bit_high(low_duration, high_duration) {
234                data[i / 8] |= 1 << (7 - (i % 8));
235            }
236        }
237
238        // Validate data using the 8-bit checksum
239        let checksum_calculated = data[0]
240            .wrapping_add(data[1])
241            .wrapping_add(data[2])
242            .wrapping_add(data[3]);
243
244        if checksum_calculated != data[4] { return Err(DHT11Error::ChecksumMismatch); }
245
246        // All good! Return the results
247        Ok(Measurement {
248            humidity: Humidity {
249                integral: data[0],
250                decimal: data[1],
251            },
252            temperature: Temperature {
253                integral: data[2],
254                decimal: data[3],
255            },
256            checksum: checksum_calculated, 
257        })
258    }
259}