dht22_driver/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/README.md"))]
3
4#[cfg(feature = "critical-section")]
5use critical_section::with;
6#[cfg(not(feature = "critical-section"))]
7fn with<R>(f: impl FnOnce(()) -> R) -> R {
8    f(())
9}
10
11#[derive(Debug)]
12pub enum DhtError<DeviceError> {
13    /// Initial handshake with the sensor was unsuccessful. Make sure all physical connections are working, individual reads of the sensor are seperated by at least 2 seconds and the pin state is high while idle
14    Handshake,
15    /// Timeout while waiting for the sensor to respond
16    Timeout(Microseconds),
17    /// The checksum of the read data does not match with the provided checksum
18    Checksum { correct: u8, actual: u8 },
19    /// While setting the pin state the DeviceError occured
20    DeviceError(DeviceError),
21}
22
23impl<DeviceError> core::fmt::Display for DhtError<DeviceError>
24where
25    DeviceError: core::fmt::Display,
26{
27    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28        match self {
29            DhtError::Handshake => write!(f, "Inital handshake failed!"),
30            DhtError::Timeout(us) => write!(
31                f,
32                "Timeout while waiting for level change after {} microseconds",
33                us.0
34            ),
35            DhtError::Checksum { correct, actual } => write!(
36                f,
37                "Checksum validation failed. Correct: {correct}, Actual: {actual}"
38            ),
39            DhtError::DeviceError(device_error) => write!(f, "DeviceError: {device_error}"),
40        }
41    }
42}
43
44#[cfg(feature = "std")]
45impl<DeviceError> std::error::Error for DhtError<DeviceError> where DeviceError: std::error::Error {}
46
47impl<DeviceError> From<DeviceError> for DhtError<DeviceError> {
48    fn from(value: DeviceError) -> Self {
49        Self::DeviceError(value)
50    }
51}
52
53/// Represents a GPIO pin capable of reading and setting the voltage level
54pub trait IOPin {
55    type DeviceError;
56    fn set_low(&mut self) -> Result<(), Self::DeviceError>;
57    fn set_high(&mut self) -> Result<(), Self::DeviceError>;
58    fn is_low(&self) -> bool;
59    fn is_high(&self) -> bool;
60}
61
62/// Represents a number of microseconds.
63/// Simple Newtype to attach meaning to the contained primitive.
64/// The std::duration::Duration which could also be used here is a much larger type in order to accomodate much
65/// bigger time spans, which may impact performance, code size and stack usage.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
67pub struct Microseconds(pub u32);
68
69/// Represents a timer with microsecond resolution
70pub trait MicroTimer {
71    /// Return an instance in time as a number of microseconds since some fixed point (normally boot or start of the timer).
72    /// The implementation is allowed to wrap during a read from the sensor.
73    fn now(&self) -> Microseconds;
74}
75
76/// Represents a DHT22 sensor connected to a pin.
77pub struct Dht22<Pin, Timer>
78where
79    Pin: IOPin,
80    Timer: MicroTimer,
81{
82    pin: Pin,
83    timer: Timer,
84}
85
86/// A valid reading from the DHT22 sensor
87pub struct SensorReading {
88    pub humidity: f32,
89    pub temperature: f32,
90}
91
92impl<Pin, Timer> Dht22<Pin, Timer>
93where
94    Pin: IOPin,
95    <Pin as IOPin>::DeviceError: core::fmt::Debug,
96    Timer: MicroTimer,
97{
98    /// Construct a new representation of the DHT22 sensor.
99    /// Construction is cheap as long as the pin and clock are cheap to move.
100    pub fn new(pin: Pin, clock: Timer) -> Self {
101        Self { pin, timer: clock }
102    }
103    /// Attempt one read from the DHT22 sensor.
104    /// Between subsequent reads from the same sensor at least 2 seconds should pass to avoid erratic readings.
105    /// Reading to early after startup may also result in failure to read.
106    pub fn read(&mut self) -> Result<SensorReading, DhtError<Pin::DeviceError>> {
107        const RESPONSE_BITS: usize = 40;
108        // Each bit is indicated by the two edges of the HIGH level (up, down).
109        // In addition the initial down edge from the get-ready HIGH state is recorded.
110        let mut cycles: [u32; 2 * RESPONSE_BITS + 1] = [0; 2 * RESPONSE_BITS + 1];
111        let waiter = Waiter { timer: &self.timer };
112        // Disable interrupts while interacting with the sensor so they don't mess up the timings
113        with(|_guard| {
114            // Initial handshake
115            self.pin.set_low()?;
116            let _ = waiter.wait_for(|| false, 1200);
117            self.pin.set_high()?;
118
119            // Wait for DHT22 to acknowledge the handshake with low
120            if waiter.wait_for(|| self.pin.is_low(), 100).is_err() {
121                return Err(DhtError::Handshake);
122            }
123
124            // Wait for low to end
125            if waiter.wait_for(|| self.pin.is_high(), 100).is_err() {
126                return Err(DhtError::Handshake);
127            }
128            // Data transfer started. Each bit starts with 50us low and than ~27us high for a 0 or ~70us high for a 1.
129            // The pin should stay high for about 80us before transmission starts, we don't actually care about the precise timing of this duration.
130            // To be precise just record time of edges and process later.
131            let mut is_high = true;
132            for duration in &mut cycles {
133                *duration = waiter
134                    .wait_for(|| is_high != self.pin.is_high(), 100)
135                    .map(|microsecond| microsecond.0)
136                    .map_err(DhtError::Timeout)?;
137
138                is_high = !is_high;
139            }
140            // Data transfer ended
141            Ok(())
142        })
143        .map_err(|err| {
144            // Reset pin to high, which is the idle state of the dht22
145            // Ignore any error that might occur here so the user sees the first error that occured
146            let _ = self.pin.set_high();
147            err
148        })?;
149
150        let mut bytes: [u8; 5] = [0; 5];
151        // Ignore first element, because the time until data transmission starts is not important
152        for (idx, _) in cycles[1..]
153            // Group the durations of the low and high voltage for each bit
154            .chunks_exact(2)
155            // Map the duration of the high voltage to a 0 or 1
156            .map(|pair| {
157                let cycles_low = pair[0];
158                let cycles_high = pair[1];
159                // use the low duration as a reference to be robust against jitter
160                cycles_low < cycles_high
161            })
162            // Count with index to know where to shift the bit
163            .enumerate()
164            // Ignore 0-bits as that is already their initial value
165            .filter(|(_, bit)| *bit)
166        {
167            let byte_idx = idx / 8;
168            let bit_idx = idx % 8;
169            bytes[byte_idx] |= 1 << (7 - bit_idx);
170        }
171        // Verify the checksum in the last byte
172        let correct = bytes[4];
173        let actual = bytes[0]
174            .wrapping_add(bytes[1])
175            .wrapping_add(bytes[2])
176            .wrapping_add(bytes[3]);
177        if actual != correct {
178            return Err(DhtError::Checksum { actual, correct });
179        }
180        let humidity = (((bytes[0] as u32) << 8 | bytes[1] as u32) as f32) / 10.;
181        // The MSB of the 16 temperature bits indicates negative temperatures
182        let is_negative = (bytes[2] >> 7) != 0;
183        bytes[2] &= 0b0111_1111;
184        let temperature = (((bytes[2] as u32) << 8 | bytes[3] as u32) as f32) / 10.;
185        let temperature = if is_negative {
186            -1. * temperature
187        } else {
188            temperature
189        };
190        Ok(SensorReading {
191            humidity,
192            temperature,
193        })
194    }
195}
196
197struct Waiter<'timer, Timer>
198where
199    Timer: MicroTimer,
200{
201    timer: &'timer Timer,
202}
203impl<'timer, Timer> Waiter<'timer, Timer>
204where
205    Timer: MicroTimer,
206{
207    #[inline(always)]
208    fn wait_for(
209        &self,
210        condition: impl Fn() -> bool,
211        timeout: u32,
212    ) -> Result<Microseconds, Microseconds> {
213        let start = self.timer.now();
214        loop {
215            // Using wrapping arithmetic on unsigned integers, overflow of the timer can be
216            // exploited to count over the whole representable range of the integer type regardless of initial value.
217            // For example for a u8:
218            // 10 - 230 = 36
219            let since_start = self.timer.now().0.wrapping_sub(start.0);
220            if condition() {
221                return Ok(Microseconds(since_start));
222            }
223            if since_start >= timeout {
224                return Err(Microseconds(since_start));
225            }
226        }
227    }
228}