dht_hal_drv/
lib.rs

1//! # HAL based driver for Digital Humidity and Temperature sensors (DHT)
2//!
3//! Because of some limitations in HAL API and limitations in some HW implementations
4//! using this sensor **is some kind of tricky**.
5//!
6//! DHT use one pin for communication that should work in open drain (open connect) mode.
7//! For hardware that does not have such pin implementations you should emulate its behaviour.
8//!
9//! You can get readings by:
10//! * using single function to read all data
11//! * using splitted functions for initialization and reading while converting pin to different modes between calls
12//!
13//! Should notice that DHT initialization process has some caveats.
14//! There have to be near 1 sec delay before next reading.
15//! At his time pull-up resistor in DHT circuit would pull up data pin and this would prepare DHT for next reading cycle.
16//!
17//! Delay implementation issues should be taken into account.
18//! On some platforms sleep at some amount of microseconds means "sleep at least N us".
19//! For example on RPi with `std::thread::sleep` nothing would work.
20//! For such case should use `dht_split_read` without delay or another sleep implementations like `spin_sleep`.
21//!
22//! ## Examples
23//!
24//! ### Using open drain pin
25//!
26//! ```
27//! let delay; // Something that implements DelayUs trait
28//! let open_drain_pin; // Open drain pin, should be in open mode by default
29//! // Need to create closure with HW specific delay logic that DHT driver is not aware of
30//! let mut delay_us = |d| delay.delay_us(d);
31//!
32//! // ... Some code of your APP ... //
33//! let result = dht_read(DhtType::DHT11, &mut open_drain_pin, &mut delay_us);
34//! // ... Other code of your APP ... //
35//! ```
36//!
37//! ### Using dht_split_* functions
38//!
39//! Such approach is useful if you your device does not have open drain pin and you need to emulate it
40//! or you have slow CPU and do not want to use delays while reading.
41//!
42//! ```
43//! use dht_hal_drv::{dht_split_init, dht_split_read, DhtError, DhtType, DhtValue};
44//!
45//! // ... Some code of your APP ... //
46//!
47//! let delay; // Something that implements DelayUs trait
48//! let pin_in; // Pin configured as input floating
49//!
50//! // Should create closure with
51//! // custom HW specific delay logic that DHT driver is not aware of
52//! let mut delay_us = |d| delay.delay_us(d);
53//!
54//! // pin to output mode
55//! let mut pin_out = pin_in.into_push_pull_output();
56//!
57//! // Initialize DHT data transfer
58//! // Before reading begins MCU must send signal to DHT which would initiate data transfer from DHT.
59//! dht_split_init(&mut pin_out, &mut delay_us);
60//!
61//! // You can check dht_split_init response for errors if you want
62//!
63//! // WARNING there should be no additional logic between dht_split_init and dht_split_read
64//!
65//! // Should convert pin back to input floating
66//! let mut pin_in = pin_out.into_floating_input(cr);
67//!
68//! // Now let's read some data
69//! // Here you can pass empty delay_us closure to skip using delays on slow CPU
70//! let readings = dht_split_read(DhtType::DHT11, &mut pin_in, &mut delay_us);
71//!
72//! // ... Other code of your APP ... //
73//!
74//! ```
75//!
76//! Working examples for particular HW platforms could be found in source repository.
77//!
78//! ## Inspiration sources
79//!
80//! - [Adafruit DHT.cpp](https://github.com/adafruit/DHT-sensor-library/blob/master/DHT.cpp)
81//! - [Adafruit python lib pi_dht_read.c](https://github.com/adafruit/Adafruit_Python_DHT/blob/master/source/Raspberry_Pi/pi_dht_read.c)
82//! - [Full signals diagrams for DHT](http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf).
83//!
84#![no_std]
85use core::prelude::v1::Result;
86use embedded_hal::digital::v2::{InputPin, OutputPin};
87
88///
89/// You should receive this in a case of some unpleasant situation.
90///
91#[derive(Debug)]
92pub enum DhtError {
93    /// Unable to read data from sensor
94    Readings,
95    /// Data was read but checksum validation failed
96    Checksum,
97    /// Error reading pin values or acquire pin etc.
98    IO,
99}
100
101///
102/// Describe available DHT sensor types.
103///
104#[derive(Debug, Clone)]
105pub enum DhtType {
106    DHT11,
107    DHT21,
108    DHT22,
109}
110
111///
112/// Contains readings from DHT sensor.
113///
114#[derive(Debug)]
115pub struct DhtValue {
116    dht_type: DhtType,
117    value: [u8; 5],
118}
119
120impl DhtValue {
121    /// Return temperature readings in Celsius.
122    /// All raw data from DHT sensor are related to this scale.
123    pub fn temperature(&self) -> f32 {
124        match &self.dht_type {
125            DhtType::DHT11 => self.value[2] as f32,
126            _ => {
127                let mut v: f32 = (self.value[2] & 0x7F) as f32;
128                v = (v * 256.0 + self.value[3] as f32) * 0.1;
129                if self.value[2] & 0x80 > 0 {
130                    v *= -1.0;
131                }
132                v
133            }
134        }
135    }
136
137    /// Convert temperature readings from Celsius to Fahrenheit
138    /// for those who love to use piffle scales and measurements.
139    pub fn temperature_f(&self) -> f32 {
140        self.temperature() * 1.8 + 32.0
141    }
142
143    /// Return humidity readins in percents.
144    pub fn humidity(&self) -> f32 {
145        match &self.dht_type {
146            DhtType::DHT11 => self.value[0] as f32,
147            _ => {
148                let mut v: f32 = self.value[0] as f32;
149                v = (v * 256.0 + self.value[1] as f32) * 0.1;
150                v
151            }
152        }
153    }
154}
155
156///
157/// Read DHT sensor via open drain (open collector) pin or its emulation.
158///
159///
160/// DHT sensor communication designed to use open drain like pin with pull up resistor.
161/// Some hardware does not have open drain pins but it is possible to emulate it
162/// in this case set_high() should convert pin into input floating mode.
163///
164/// If you have slow CPU clocks speed you may need to read data without delays at all.
165/// Thus you should use `dht_split_*` functions instead and pass an empty closure to `dht_split_read`.
166///
167/// # Arguments
168///
169/// * `dht` - DHT sensor type we are reading
170/// * `open_pin` - Open drain like pin
171/// * `delay_us` - Closure where you should call appropriate delay/sleep/whatever API with microseconds as input.
172///
173pub fn dht_read<IO_PIN>(
174    dht: DhtType,
175    open_pin: &mut IO_PIN,
176    delay_us: &mut dyn FnMut(u16) -> (),
177) -> Result<DhtValue, DhtError>
178where
179    IO_PIN: InputPin + OutputPin,
180{
181    dht_split_init(open_pin, delay_us)?;
182    // Toggle open drain pin to open (high) state.
183    open_pin.set_high().map_err(|_| DhtError::IO)?;
184    dht_split_read(dht, open_pin, delay_us)
185}
186
187///
188/// Initialize DHT sensor (sending start signal) to start readings.
189///
190/// Notice that there have to be about 1 sec delay before each reading (between calling `dht_split_init`).
191/// At this period data pin should be pulled up by resistor connected to DHT
192/// which is default connection scheme for DHT.
193/// It implies that pin should be set in input floating mode after previous reading.
194///
195/// In Adafruit drivers you can see that there is initial delay with high impedance for about 500-700ms.
196/// You do not need this delay if you read sensor not to often and do other logic between readings.
197///
198/// # Arguments
199///
200/// * `output_pin` - Output pin trait for DHT data pin.
201/// * `delay_us` - Closure where you should call appropriate delay/sleep/whatever API with microseconds as input.
202///
203pub fn dht_split_init<Error>(
204    output_pin: &mut dyn OutputPin<Error = Error>,
205    delay_us: &mut dyn FnMut(u16) -> (),
206) -> Result<(), DhtError> {
207    // Voltage  level  from  high to  low.
208    // This process must take at least 18ms to ensure DHT’s detection of MCU's signal.
209    output_pin.set_low().map_err(|_| DhtError::IO)?;
210    delay_us(20_000);
211    Ok(())
212}
213
214///
215/// Call this function immediately after [initialization](fn.dht_split_init.html) to acquire proper sensor readings.
216///
217/// # Arguments
218///
219/// * `dht` - DHT sensor type
220/// * `input_pin` - Input pin trait for DHT data pin
221/// * `delay_us` - Closure with delay/sleep/whatever API with microseconds as input,
222/// NOTE that for low frequency CPUs (about 2Mhz or less) you should pass empty closure.
223///
224pub fn dht_split_read<Error>(
225    dht: DhtType,
226    input_pin: &mut dyn InputPin<Error = Error>,
227    delay_us: &mut dyn FnMut(u16) -> (),
228) -> Result<DhtValue, DhtError> {
229    let threshold = 20;
230    let rate = 100;
231    // On RPi 3 without delays max "while" cycles per pin state is about 500, initial cycle is about 100
232    // Thus if "while" would stuck at 20*100 cycles on initial cycle it would case an error
233    // or if it would stuck at 500*100 somewhere in the middle.
234    // With delays we should have about 20 cycles per pin state.
235    dht_split_read_customizable(dht, input_pin, delay_us, threshold, rate)
236}
237
238///
239/// Advanced customizable read function, you probably would not need to use it directly.
240/// Call it immediately after [initialization](fn.dht_split_init.html) to acquire proper sensor readings.
241///
242///
243/// # Arguments
244///
245/// * `dht` - DHT sensor type
246/// * `input_pin` - Input pin trait for DHT data pin
247/// * `delay_us' - Closure with delay/sleep/whatever API with microseconds as input,
248/// NOTE that for low frequency CPUs (about 2Mhz or less) you should pass empty closure.
249/// * `reads_threshold` - Initial threshold (cycles count) before reading cycle starts.
250/// * `reads_error_rate` - If actual cycles count in one pin state would exceed last threshold at provided rate it would cause an error.
251///
252pub fn dht_split_read_customizable<Error>(
253    dht: DhtType,
254    input_pin: &mut dyn InputPin<Error = Error>,
255    delay_us: &mut dyn FnMut(u16) -> (),
256    reads_threshold: u32,
257    reads_error_rate: u32,
258) -> Result<DhtValue, DhtError> {
259    // Initialize variables
260    let mut err: Option<DhtError> = None;
261    let mut data: [u8; 5] = [0; 5]; // Set 40 bits of received data to zero.
262    let mut cycles: [u32; 83] = [0; 83];
263
264    // MCU will pull up voltage and wait 20-40us for DHT’s response
265    // Delay a bit to let sensor pull data line low.
266
267    // READ to cycles[0] - or skip to next
268
269    // Now start reading the data line to get the value from the DHT sensor.
270    // First expect a low signal for ~80 microseconds followed by a high signal
271    // for ~80 microseconds again.
272
273    // READ to cycles[1] and cycles[2]
274
275    // Now read the 40 bits sent by the sensor.  Each bit is sent as a 50
276    // microsecond low pulse followed by a variable length high pulse.  If the
277    // high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds
278    // then it's a 1.  We measure the cycle count of the initial 50us low pulse
279    // and use that to compare to the cycle count of the high pulse to determine
280    // if the bit is a 0 (high state cycle count < low state cycle count), or a
281    // 1 (high state cycle count > low state cycle count). Note that for speed all
282    // the pulses are read into a array and then examined in a later step.
283
284    // READ to cycles[3+] as low level and cycles[4+] as high level
285
286    // Initial threshold
287    // If reads cycles count would be more at `reads_error_rate` reading would stop.
288    let mut threshold = reads_threshold;
289
290    // As I can see at least 10 instructions should be executed at each cycle.
291    // If CPU frequency is 8MHz and IPC value is 1 than 28us
292    // would approximately match up to 22 loop cycles without using any delay.
293    // With delay of 2us it have to be about 8 cycles.
294    // For 1MHz CPU it is about 2 cycles.
295    // NOTICE for slow CPU you should not implement `delay_us` this closure should be empty.
296    let delay_us_value = 2;
297    let mut i = 0;
298    while i < 83 {
299        let high = input_pin.is_high().map_err(|_| DhtError::IO)?;
300        if (i % 2 == 0) == high {
301            // Instead of reading time we just count number of cycles until next level value
302            cycles[i] += 1;
303            if high && cycles[i] / threshold > reads_error_rate {
304                // Check errors only on high cycles
305                // When DHT stop transfer data resistor would pull pin up
306                err = Some(DhtError::Readings);
307                break;
308            }
309        } else {
310            if high && cycles[i] > threshold {
311                // Raise error threshold dynamically
312                // to adjust this value to current CPU speed
313                threshold = cycles[i];
314            }
315            i += 1;
316        }
317
318        // // Reasonable delay for fast CPU
319        delay_us(delay_us_value);
320    }
321
322    // Inspect pulses and determine which ones are 0 (high state cycle count < low
323    // state cycle count), or 1 (high state cycle count > low state cycle count).
324    // We skip first 3 values because there is no\t data there
325    for i in 0..40 {
326        let low_cycle = cycles[2 * i + 3];
327        let high_cycle = cycles[2 * i + 4];
328
329        data[i / 8] <<= 1;
330        if high_cycle > low_cycle {
331            // High cycles are greater than 50us low cycle count, must be a 1.
332            data[i / 8] |= 1;
333        }
334        // Else high cycles are less than (or equal to, a weird case) the 50us low
335        // cycle count so this must be a zero.  Nothing needs to be changed in the
336        // stored data.
337    }
338
339    // DEBUG CODE, works only with std enabled
340    // {
341    //     println!("Cycles {:?}",cycles.iter().map(ToString::to_string).collect::<Vec<String>>().join(", "));
342    //     print!("DHT readings: ");
343    //     print!("{:X} {:X} {:X} {:X}", data[0], data[1], data[2], data[3]);
344    //     println!(
345    //         "  {:X} == {:X} (checksum)",
346    //         data[4],
347    //         (data[0] as u16 + data[1] as u16 + data[2] as u16 + data[3] as u16) & 0xFF
348    //     );
349    // }
350
351    // Check we read 40 bits and that the checksum matches.
352    let checksum = (data[0] as u16 + data[1] as u16 + data[2] as u16 + data[3] as u16) & 0xFF;
353    if data[4] as u16 == checksum {
354        Ok(DhtValue {
355            value: data,
356            dht_type: dht,
357        })
358    } else {
359        Err(err.unwrap_or(DhtError::Checksum))
360    }
361}