Dht {
20 | pub fn new(pin: P, delay: D) -> Self {
21 | Self { pin, delay }
22 | }
23 |
24 | /// Reads a byte (8 bits) from the sensor.
25 | ///
26 | /// This method reads 8 bits sequentially from the sensor to construct a byte.
27 | /// It follows the communication protocol of the DHT11/DHT22 sensors:
28 | ///
29 | /// For each bit:
30 | /// - Waits for the pin to go **high** (start of bit transmission).
31 | /// - Delays for **30 microseconds** to sample the bit value.
32 | /// - If the pin is **high** after the delay, the bit is interpreted as **'1'**.
33 | /// - If the pin is **low**, the bit is interpreted as **'0'**.
34 | /// - Waits for the pin to go **low** (end of bit transmission).
35 | ///
36 | /// The bits are assembled into a byte, starting from the most significant bit (MSB).
37 | ///
38 | /// # Returns
39 | ///
40 | /// - `Ok(u8)`: The byte read from the sensor.
41 | /// - `Err(SensorError)`: If a pin error occurs.
42 | pub fn read_byte(&mut self) -> Result {
43 | let mut byte: u8 = 0;
44 | for n in 0..8 {
45 | match self.wait_until_state(PinState::High) {
46 | Ok(_) => {}
47 | Err(err) => return Err(err),
48 | };
49 |
50 | self.delay.delay_us(30);
51 | let is_bit_1 = self.pin.is_high();
52 | if is_bit_1.unwrap() {
53 | let bit_mask = 1 << (7 - (n % 8));
54 | byte |= bit_mask;
55 | match self.wait_until_state(PinState::Low) {
56 | Ok(_) => {}
57 | Err(err) => return Err(err),
58 | };
59 | }
60 | }
61 | Ok(byte)
62 | }
63 |
64 | /// Waits until the pin reaches the specified state.
65 | ///
66 | /// This helper function continuously polls the pin until it reaches the desired `PinState`.
67 | /// It introduces a **1-microsecond delay** between each poll to prevent excessive CPU usage.
68 | ///
69 | /// # Arguments
70 | ///
71 | /// - `state`: The target `PinState` to wait for (`PinState::High` or `PinState::Low`).
72 | ///
73 | /// # Returns
74 | ///
75 | /// - `Ok(())`: When the pin reaches the desired state.
76 | /// - `Err(SensorError::Timeout)`: If the desired state is not reached in time.
77 | /// - `Err(SensorError::Io(...))`: If a pin error occurs while reading the pin state.
78 | ///
79 | pub fn wait_until_state(&mut self, state: PinState) -> Result<(), SensorError> {
80 | for _ in 0..DEFAULT_MAX_ATTEMPTS {
81 | let is_state = match state {
82 | PinState::Low => self.pin.is_low(),
83 | PinState::High => self.pin.is_high(),
84 | };
85 |
86 | match is_state {
87 | Ok(true) => return Ok(()),
88 | Ok(false) => self.delay.delay_us(1),
89 | Err(_) => return Err(SensorError::PinError),
90 | }
91 | }
92 |
93 | Err(SensorError::Timeout)
94 | }
95 | }
96 |
97 | #[cfg(test)]
98 | mod tests {
99 | extern crate std;
100 | use std::io::ErrorKind;
101 |
102 | use super::*;
103 | use embedded_hal_mock::eh1::delay::NoopDelay as MockNoop;
104 | use embedded_hal_mock::eh1::digital::{Mock, State, Transaction as PinTransaction};
105 | use embedded_hal_mock::eh1::MockError;
106 |
107 | #[test]
108 | fn test_read_byte() {
109 | // Set up the pin transactions to mock the behavior of the sensor during the reading of a byte.
110 | // Each bit read from the sensor starts with a High state that lasts long enough
111 | // to signify the bit, followed by reading whether it stays High (bit 1) or goes Low (bit 0).
112 | let expectations = [
113 | // Bit 1 - 0
114 | PinTransaction::get(State::High),
115 | PinTransaction::get(State::Low),
116 | // Bit 2 - 1
117 | PinTransaction::get(State::High),
118 | PinTransaction::get(State::High),
119 | PinTransaction::get(State::Low),
120 | // Bit 3 - 0
121 | PinTransaction::get(State::High),
122 | PinTransaction::get(State::Low),
123 | // Bit 4 - 1
124 | PinTransaction::get(State::High),
125 | PinTransaction::get(State::High),
126 | PinTransaction::get(State::Low),
127 | // Bit 5 - 0
128 | PinTransaction::get(State::High),
129 | PinTransaction::get(State::Low),
130 | // Bit 6 - 1
131 | PinTransaction::get(State::High),
132 | PinTransaction::get(State::High),
133 | PinTransaction::get(State::Low),
134 | // Bit 7 - 1
135 | PinTransaction::get(State::High),
136 | PinTransaction::get(State::High),
137 | PinTransaction::get(State::Low),
138 | // Bit 8 - 1
139 | PinTransaction::get(State::High),
140 | PinTransaction::get(State::High),
141 | PinTransaction::get(State::Low),
142 | ];
143 |
144 | let mock_pin = Mock::new(&expectations);
145 | let mock_delay = MockNoop::new();
146 |
147 | let mut dht = Dht::new(mock_pin, mock_delay);
148 |
149 | let result = dht.read_byte().unwrap();
150 | assert_eq!(result, 0b01010111);
151 |
152 | dht.pin.done();
153 | }
154 |
155 | #[test]
156 | fn test_wait_until_state() {
157 | let expectations = [
158 | PinTransaction::get(State::Low),
159 | PinTransaction::get(State::Low),
160 | PinTransaction::get(State::High),
161 | ];
162 |
163 | let mock_pin = Mock::new(&expectations);
164 | let mock_delay = MockNoop::new();
165 |
166 | let mut dht = Dht::new(mock_pin, mock_delay);
167 |
168 | let result = dht.wait_until_state(PinState::High);
169 | assert!(result.is_ok());
170 |
171 | dht.pin.done();
172 | }
173 |
174 | #[test]
175 | fn test_wait_until_state_timeout_error() {
176 | let expectations: [PinTransaction; DEFAULT_MAX_ATTEMPTS] = [
177 | PinTransaction::get(State::Low),
178 | PinTransaction::get(State::Low),
179 | PinTransaction::get(State::Low),
180 | PinTransaction::get(State::Low),
181 | PinTransaction::get(State::Low),
182 | PinTransaction::get(State::Low),
183 | PinTransaction::get(State::Low),
184 | PinTransaction::get(State::Low),
185 | PinTransaction::get(State::Low),
186 | PinTransaction::get(State::Low),
187 | ];
188 |
189 | let mock_pin = Mock::new(&expectations);
190 | let mock_delay = MockNoop::new();
191 |
192 | let mut dht = Dht::new(mock_pin, mock_delay);
193 |
194 | let result = dht.wait_until_state(PinState::High);
195 | assert!(matches!(result, Err(SensorError::Timeout)));
196 |
197 | dht.pin.done();
198 | }
199 |
200 | #[test]
201 | fn test_wait_until_state_pin_error() {
202 | let err = MockError::Io(ErrorKind::NotConnected);
203 | let expectations = [PinTransaction::get(State::High).with_error(err)];
204 | let mock_pin = Mock::new(&expectations);
205 | let mock_delay = MockNoop::new();
206 |
207 | let mut dht = Dht::new(mock_pin, mock_delay);
208 |
209 | let result = dht.wait_until_state(PinState::High);
210 | assert!(matches!(result, Err(SensorError::PinError)));
211 |
212 | dht.pin.done();
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/src/dht11.rs:
--------------------------------------------------------------------------------
1 | use embedded_hal::{
2 | delay::DelayNs,
3 | digital::{InputPin, OutputPin, PinState},
4 | };
5 |
6 | use crate::{dht::Dht, SensorError, SensorReading};
7 |
8 | pub struct Dht11 {
9 | dht: Dht,
10 | }
11 |
12 | impl Dht11 {
13 | pub fn new(pin: P, delay: D) -> Self {
14 | Self {
15 | dht: Dht::new(pin, delay),
16 | }
17 | }
18 |
19 | pub fn read(&mut self) -> Result, SensorError> {
20 | // Start communication: pull pin low for 18ms, then release.
21 | let _ = self.dht.pin.set_low();
22 | self.dht.delay.delay_ms(18);
23 | let _ = self.dht.pin.set_high();
24 |
25 | // Wait for sensor to respond.
26 | self.dht.delay.delay_us(48);
27 |
28 | // Sync with sensor: wait for high then low signals.
29 | let _ = self.dht.wait_until_state(PinState::High);
30 | let _ = self.dht.wait_until_state(PinState::Low);
31 |
32 | // Start reading 40 bits
33 | let humidity_integer = self.dht.read_byte()?;
34 | let humidity_decimal = self.dht.read_byte()?;
35 | let temperature_integer = self.dht.read_byte()?;
36 | let temperature_decimal = self.dht.read_byte()?;
37 | let checksum = self.dht.read_byte()?;
38 |
39 | // Checksum
40 | let sum = humidity_integer
41 | .wrapping_add(humidity_decimal)
42 | .wrapping_add(temperature_integer)
43 | .wrapping_add(temperature_decimal);
44 | if sum != checksum {
45 | return Err(SensorError::ChecksumMismatch);
46 | }
47 |
48 | Ok(SensorReading {
49 | humidity: humidity_integer,
50 | temperature: temperature_integer,
51 | })
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/dht20.rs:
--------------------------------------------------------------------------------
1 | use embedded_hal::{delay::DelayNs, i2c::I2c};
2 |
3 | use crate::{SensorError, SensorReading};
4 |
5 | pub struct Dht20 {
6 | pub i2c: I,
7 | pub delay: D,
8 | }
9 |
10 | impl Dht20 {
11 | const SENSOR_ADDRESS: u8 = 0x38;
12 |
13 | pub fn new(i2c: I, delay: D) -> Self {
14 | Self { i2c, delay }
15 | }
16 |
17 | pub fn read(&mut self) -> Result, SensorError> {
18 | // Check status
19 | let mut status_response: [u8; 1] = [0; 1];
20 | let _ = self
21 | .i2c
22 | .write_read(Self::SENSOR_ADDRESS, &[0x71], &mut status_response);
23 |
24 | // Callibration if needed
25 | if status_response[0] & 0x18 != 0x18 {
26 | let _ = self.i2c.write(Self::SENSOR_ADDRESS, &[0x1B, 0, 0]);
27 | let _ = self.i2c.write(Self::SENSOR_ADDRESS, &[0x1C, 0, 0]);
28 | let _ = self.i2c.write(Self::SENSOR_ADDRESS, &[0x1E, 0, 0]);
29 | }
30 |
31 | // Trigger the measurement
32 | self.delay.delay_ms(10);
33 | let _ = self.i2c.write(Self::SENSOR_ADDRESS, &[0xAC, 0x33, 0x00]);
34 |
35 | // Read the measurement status
36 | self.delay.delay_ms(80);
37 | loop {
38 | let mut measurement_status_response: [u8; 1] = [0; 1];
39 | let _ = self
40 | .i2c
41 | .read(Self::SENSOR_ADDRESS, &mut measurement_status_response);
42 | let status_word = measurement_status_response[0];
43 | if status_word & 0b1000_0000 == 0 {
44 | break;
45 | }
46 | self.delay.delay_ms(1);
47 | }
48 |
49 | // Read the measurement (1 status + 5 data + 1 crc)
50 | let mut measurement_response: [u8; 7] = [0; 7];
51 | let _ = self
52 | .i2c
53 | .read(Self::SENSOR_ADDRESS, &mut measurement_response);
54 |
55 | // Humidity 20 bits (8 + 8 + 4)
56 | let mut raw_humidity = measurement_response[1] as u32;
57 | raw_humidity = (raw_humidity << 8) + measurement_response[2] as u32;
58 | raw_humidity = (raw_humidity << 4) + (measurement_response[3] >> 4) as u32;
59 | let humidity_percentage = (raw_humidity as f32 / ((1 << 20) as f32)) * 100.0;
60 |
61 | // Temperature 20 bits
62 | let mut raw_temperature = (measurement_response[3] & 0b1111) as u32;
63 | raw_temperature = (raw_temperature << 8) + measurement_response[4] as u32;
64 | raw_temperature = (raw_temperature << 8) + measurement_response[5] as u32;
65 | let temperature_percentage = (raw_temperature as f32 / ((1 << 20) as f32)) * 200.0 - 50.0;
66 |
67 | // Compare the calculated CRC with the received CRC
68 | let data = &measurement_response[..6];
69 | let received_crc = measurement_response[6];
70 | let calculcated_crc = Self::calculate_crc(data);
71 | if received_crc != calculcated_crc {
72 | return Err(SensorError::ChecksumMismatch);
73 | }
74 |
75 | Ok(SensorReading {
76 | humidity: humidity_percentage,
77 | temperature: temperature_percentage,
78 | })
79 | }
80 |
81 | fn calculate_crc(data: &[u8]) -> u8 {
82 | let polynomial = 0x31u8; // x^8 + x^5 + x^4 + 1
83 | let mut crc = 0xFFu8;
84 |
85 | for &byte in data {
86 | crc ^= byte;
87 | // CRC8 - process every bit
88 | for _ in 0..8 {
89 | if crc & 0x80 != 0 {
90 | crc = (crc << 1) ^ polynomial;
91 | } else {
92 | crc <<= 1;
93 | }
94 | }
95 | }
96 |
97 | crc
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/dht22.rs:
--------------------------------------------------------------------------------
1 | use embedded_hal::{
2 | delay::DelayNs,
3 | digital::{InputPin, OutputPin, PinState},
4 | };
5 |
6 | use crate::{dht::Dht, SensorError, SensorReading};
7 |
8 | pub struct Dht22 {
9 | dht: Dht,
10 | }
11 |
12 | impl Dht22 {
13 | pub fn new(pin: P, delay: D) -> Self {
14 | Self {
15 | dht: Dht::new(pin, delay),
16 | }
17 | }
18 |
19 | pub fn read(&mut self) -> Result, SensorError> {
20 | // Start communication: pull pin low for 18ms, then release.
21 | let _ = self.dht.pin.set_low();
22 | self.dht.delay.delay_ms(18);
23 | let _ = self.dht.pin.set_high();
24 |
25 | // Wait for sensor to respond.
26 | self.dht.delay.delay_us(48);
27 |
28 | // Sync with sensor: wait for high then low signals.
29 | let _ = self.dht.wait_until_state(PinState::High);
30 | let _ = self.dht.wait_until_state(PinState::Low);
31 |
32 | // Start reading 40 bits
33 | let humidity_high = self.dht.read_byte()?;
34 | let humidity_low = self.dht.read_byte()?;
35 | let temperature_high = self.dht.read_byte()?;
36 | let temperature_low = self.dht.read_byte()?;
37 | let checksum = self.dht.read_byte()?;
38 |
39 | // Checksum
40 | let sum = humidity_high
41 | .wrapping_add(humidity_low)
42 | .wrapping_add(temperature_high)
43 | .wrapping_add(temperature_low);
44 | if sum != checksum {
45 | return Err(SensorError::ChecksumMismatch);
46 | }
47 |
48 | let humidity_value = ((humidity_high as u16) << 8) | (humidity_low as u16);
49 | let humidity_percentage = humidity_value as f32 / 10.0;
50 |
51 | let temperature_high_clean = temperature_high & 0x7F; // 0x7F = 0111 1111
52 | let temperature_value = ((temperature_high_clean as u16) << 8) | (temperature_low as u16);
53 | let mut temperature_percentage = temperature_value as f32 / 10.0;
54 |
55 | if temperature_high & 0x80 != 0 {
56 | temperature_percentage = -temperature_percentage;
57 | }
58 |
59 |
60 | Ok(SensorReading {
61 | humidity: humidity_percentage,
62 | temperature: temperature_percentage,
63 | })
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![doc = include_str!("../README.md")]
2 | #![no_std]
3 |
4 | mod dht;
5 |
6 | #[cfg(feature = "dht11")]
7 | pub mod dht11;
8 |
9 | #[cfg(feature = "dht20")]
10 | pub mod dht20;
11 |
12 | #[cfg(feature = "dht22")]
13 | pub mod dht22;
14 |
15 | /// Represents a reading from the sensor.
16 | pub struct SensorReading {
17 | pub humidity: T,
18 | pub temperature: T,
19 | }
20 |
21 | /// Possible errors when interacting with the sensor.
22 | #[derive(Debug)]
23 | pub enum SensorError {
24 | ChecksumMismatch,
25 | Timeout,
26 | PinError
27 | }
28 |
--------------------------------------------------------------------------------