dht11_driver/
lib.rs

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