rppal_dht11/
lib.rs

1//! Raspberry Pi Rust driver for the DHT11 temperature and humidity sensor, compatible with the [rppal](https://docs.golemparts.com/rppal/0.13.1/rppal/gpio/struct.IoPin.html#) GPIO library `IoPin` type.
2//!
3
4#![deny(unsafe_code)]
5#![deny(missing_docs)]
6#![cfg_attr(not(test), no_std)]
7
8use embedded_hal::blocking::delay::{DelayMs, DelayUs};
9use rppal::gpio::{IoPin, Mode};
10
11/// How long to wait for a pulse on the data line (in microseconds)
12const TIMEOUT_US: u16 = 1_000;
13
14/// How long to wait between successive retries (in milliseconds)
15const RETRY_DELAY: u16 = 100;
16
17/// Error type for this crate.
18#[derive(Debug)]
19pub enum Error {
20    /// Timeout during communication.
21    Timeout,
22    /// CRC mismatch.
23    CrcMismatch,
24}
25
26/// A DHT11 device.
27pub struct Dht11 {
28    /// The concrete GPIO pin implementation.
29    gpio: IoPin,
30}
31
32/// Results of a reading performed by the DHT11.
33#[derive(Copy, Clone, Default, Debug)]
34pub struct Measurement {
35    /// The measured temperature in tenths of degrees Celsius.
36    pub temperature: i16,
37    /// The measured humidity in tenths of a percent.
38    pub humidity: u16,
39}
40
41impl Dht11 {
42    /// Creates a new DHT11 device connected to the specified pin.
43    pub fn new(gpio: IoPin) -> Self {
44        Dht11 { gpio }
45    }
46
47    /// Destroys the driver, returning the IoPin instance.
48    pub fn destroy(self) -> IoPin {
49        self.gpio
50    }
51
52    /// Attempts readings of the sensor up to `retries` times
53    /// and returns the first successful reading or the last error
54    pub fn perform_measurement_with_retries<D>(
55        &mut self,
56        delay: &mut D,
57        retries: u16,
58    ) -> Result<Measurement, Error>
59    where
60        D: DelayUs<u16> + DelayMs<u16>,
61    {
62        let mut result = self.perform_measurement(delay);
63        for _ in 0..retries {
64            if result.is_ok() {
65                break;
66            }
67            delay.delay_ms(RETRY_DELAY);
68            result = self.perform_measurement(delay);
69        }
70        result
71    }
72
73    /// Performs a reading of the sensor.
74    pub fn perform_measurement<D>(&mut self, delay: &mut D) -> Result<Measurement, Error>
75    where
76        D: DelayUs<u16> + DelayMs<u16>,
77    {
78        let mut data = [0u8; 5];
79
80        // Perform initial handshake
81        self.perform_handshake(delay)?;
82
83        // Read bits
84        for i in 0..40 {
85            data[i / 8] <<= 1;
86            if self.read_bit(delay)? {
87                data[i / 8] |= 1;
88            }
89        }
90
91        // Finally wait for line to go idle again.
92        //self.wait_for_pulse(true, delay)?;
93
94        // Check CRC
95        let crc = data[0]
96            .wrapping_add(data[1])
97            .wrapping_add(data[2])
98            .wrapping_add(data[3]);
99        if crc != data[4] {
100            return Err(Error::CrcMismatch);
101        }
102
103        // Compute temperature
104        let mut temp = i16::from(data[2] & 0x7f) * 10 + i16::from(data[3]);
105        if data[2] & 0x80 != 0 {
106            temp = -temp;
107        }
108
109        Ok(Measurement {
110            temperature: temp,
111            humidity: u16::from(data[0]) * 10 + u16::from(data[1]),
112        })
113    }
114
115    fn perform_handshake<D>(&mut self, delay: &mut D) -> Result<(), Error>
116    where
117        D: DelayUs<u16> + DelayMs<u16>,
118    {
119        self.gpio.set_mode(Mode::Output);
120        // Set pin as floating to let pull-up raise the line and start the reading process.
121        self.gpio.set_high();
122        delay.delay_ms(1);
123
124        // Pull line low for at least 18ms to send a start command.
125        self.gpio.set_low();
126        delay.delay_ms(20);
127
128        // Restore floating
129        self.gpio.set_high();
130        delay.delay_us(40);
131
132        self.gpio.set_mode(Mode::Input);
133
134        // As a response, the device pulls the line low for 80us and then high for 80us.
135        self.read_bit(delay)?;
136
137        Ok(())
138    }
139
140    fn read_bit<D>(&mut self, delay: &mut D) -> Result<bool, Error>
141    where
142        D: DelayUs<u16> + DelayMs<u16>,
143    {
144        let low = self.wait_for_pulse(true, delay)?;
145        let high = self.wait_for_pulse(false, delay)?;
146        Ok(high > low)
147    }
148
149    fn wait_for_pulse<D>(&mut self, level: bool, delay: &mut D) -> Result<u32, Error>
150    where
151        D: DelayUs<u16> + DelayMs<u16>,
152    {
153        let mut count = 0;
154
155        while self.gpio.is_high() != level {
156            count += 1;
157            if count > TIMEOUT_US {
158                return Err(Error::Timeout);
159            }
160            delay.delay_us(1);
161        }
162
163        return Ok(u32::from(count));
164    }
165}