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