Skip to content

Commit 2412cdf

Browse files
committed
feat: add dht22 support
1 parent 2fd399b commit 2412cdf

File tree

7 files changed

+235
-94
lines changed

7 files changed

+235
-94
lines changed

README.md

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ Welcome to `embedded-dht-rs`, a Rust library designed to make working with DHT s
55
This library only depends on `embedded_hal`, making it versatile and compatible with virtually any microcontroller.
66

77
### Features:
8-
- **DHT11 sensor support**: Fully implemented and ready to use.
9-
- **DHT22 sensor support**: Currently in progress – stay tuned!
8+
9+
- **DHT11 and DHT22 sensor support**: Both sensors are fully implemented and ready to use.
1010

1111
We’ve tested it with the ESP32-WROOM, and you can find a detailed example below to help you get started.
1212

@@ -18,7 +18,7 @@ We’ve tested it with the ESP32-WROOM, and you can find a detailed example belo
1818
#![no_std]
1919
#![no_main]
2020

21-
use embedded_dht_rs::Dht11;
21+
use embedded_dht_rs::{dht11::Dht11, dht22::Dht22};
2222
use esp_backtrace as _;
2323
use esp_hal::{
2424
clock::ClockControl,
@@ -40,23 +40,38 @@ fn main() -> ! {
4040
let delay = Delay::new(&clocks);
4141

4242
let gpio4 = OutputOpenDrain::new(io.pins.gpio4, Level::High, Pull::None);
43-
let mut led_sensor = Dht11::new(gpio4, delay);
43+
let gpio5 = OutputOpenDrain::new(io.pins.gpio5, Level::High, Pull::None);
44+
45+
let mut dht11 = Dht11::new(gpio4, delay);
46+
let mut dht22 = Dht22::new(gpio5, delay);
4447

4548
loop {
46-
delay.delay(1000.millis());
47-
match led_sensor.read() {
49+
delay.delay(5000.millis());
50+
51+
match dht11.read() {
4852
Ok(sensor_reading) => log::info!(
49-
"Temperature: {}, humidity: {}",
53+
"DHT 11 Sensor - Temperature: {} °C, humidity: {} %",
5054
sensor_reading.temperature,
5155
sensor_reading.humidity
5256
),
5357
Err(error) => log::error!("An error occurred while trying to read sensor: {:?}", error),
5458
}
59+
60+
match dht22.read() {
61+
Ok(sensor_reading) => log::info!(
62+
"DHT 22 Sensor - Temperature: {} °C, humidity: {} %",
63+
sensor_reading.temperature,
64+
sensor_reading.humidity
65+
),
66+
Err(error) => log::error!("An error occurred while trying to read sensor: {:?}", error),
67+
}
68+
69+
log::info!("-----");
5570
}
5671
}
5772
```
5873

59-
![running](/docs/example_esp32_dht11_running.png)
74+
![running](/docs/example_esp32_dht_running.png)
6075

6176

6277
## Implementation Specification
@@ -68,30 +83,43 @@ We have gathered all the information you need to understand in order to implemen
6883

6984
### Step 1
7085

71-
After powering on the DHT11 (once powered, allow 1 second to pass during which the sensor stabilizes; during this time, no commands should be sent), it measures the temperature and humidity of the surrounding environment and stores the data. Meanwhile, the DATA line of the DHT11 is kept high by a pull-up resistor. The DATA pin of the DHT11 is in input mode, ready to detect any external signals.
86+
After powering on the DHT11/DHT22 (once powered, allow 1 second to pass during which the sensor stabilizes; during this time, no commands should be sent), it measures the temperature and humidity of the surrounding environment and stores the data. Meanwhile, the DATA line of the DHT11/DHT22 is kept high by a pull-up resistor. The DATA pin of the DHT11/DHT22 is in input mode, ready to detect any external signals.
7287

7388
### Step 2
7489

75-
The microprocessor's I/O pin is set to output mode and pulled low, holding this state for at least 18 milliseconds. Then, the microprocessor's I/O is switched to input mode. Due to the pull-up resistor, the microprocessor’s I/O line and the DHT11 DATA line will remain high, waiting for the DHT11 to respond with a signal, as illustrated below:
90+
The microprocessor's I/O pin is set to output mode and pulled low, holding this state for at least 18 milliseconds. Then, the microprocessor's I/O is switched to input mode. Due to the pull-up resistor, the microprocessor’s I/O line and the DHT11/DHT22 DATA line will remain high, waiting for the DHT11/DHT22 to respond with a signal, as illustrated below:
7691

7792
![step2](/docs/step2.png)
7893

7994

8095
### Step 3
8196

82-
The DHT11’s DATA pin detects an external signal and goes low, indicating that it is waiting for the external signal to complete. Once the signal ends, the DHT11’s DATA pin switches to output mode, producing a low signal for 80 microseconds as a response. This is followed by an 80-microsecond high signal, notifying the microprocessor that the sensor is ready to transmit data. At this point, the microprocessor's I/O pin, still in input mode, detects the low signal from the DHT11 (indicating the response) and then waits for the 80-microsecond high signal to start receiving data. The sequence of signal transmission is illustrated below:
97+
The DHT11/DHT22’s DATA pin detects an external signal and goes low, indicating that it is waiting for the external signal to complete. Once the signal ends, the DHT11/DHT22’s DATA pin switches to output mode, producing a low signal for 80 microseconds as a response. This is followed by an 80-microsecond high signal, notifying the microprocessor that the sensor is ready to transmit data. At this point, the microprocessor's I/O pin, still in input mode, detects the low signal from the DHT11/DHT22 (indicating the response) and then waits for the 80-microsecond high signal to start receiving data. The sequence of signal transmission is illustrated below:
8398

8499
![step3](/docs/step3.png)
85100

86101
### Step 4
87102

88-
The DHT11 outputs 40 bits of data through the DATA pin, and the microprocessor receives these 40 data bits. The format for a data bit "0" consists of a low level lasting 50 microseconds, followed by a high level lasting 26-28 microseconds, depending on changes in the I/O level. For a data bit "1," the format includes a low level of 50 microseconds followed by a high level lasting up to 70 microseconds. The signal formats for data bits "0" and "1" are shown below.
103+
The DHT11/DHT22 outputs 40 bits of data through the DATA pin, and the microprocessor receives these 40 data bits. The format for a data bit "0" consists of a low level lasting 50 microseconds, followed by a high level lasting 26-28 microseconds, depending on changes in the I/O level. For a data bit "1," the format includes a low level of 50 microseconds followed by a high level lasting up to 70 microseconds. The signal formats for data bits "0" and "1" are shown below.
89104

90105
![step4](/docs/step4.png)
91106

92107
### End signal
93108

94-
After outputting a low signal for 50 microseconds, the DHT11 completes sending the 40 bits of data and switches the DATA pin back to input mode, which, along with the pull-up resistor, returns to a high state. Meanwhile, the DHT11 internally re-measures the environmental temperature and humidity, records the new data, and waits for the next external signal.
109+
After outputting a low signal for 50 microseconds, the DHT11/DHT22 completes sending the 40 bits of data and switches the DATA pin back to input mode, which, along with the pull-up resistor, returns to a high state. Meanwhile, the DHT11/DHT22 internally re-measures the environmental temperature and humidity, records the new data, and waits for the next external signal.
110+
111+
112+
113+
## Comparison of DHT11 and DHT22 40-Bit Data Formats
114+
115+
| Feature | DHT11 | DHT22 |
116+
|-----------------------|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
117+
| **Data Structure** | - **Byte 1:** Humidity Integer Part<br>- **Byte 2:** Humidity Decimal Part (always 0)<br>- **Byte 3:** Temperature Integer Part<br>- **Byte 4:** Temperature Decimal Part (always 0)<br>- **Byte 5:** Checksum | - **Byte 1:** Humidity High Byte<br>- **Byte 2:** Humidity Low Byte<br>- **Byte 3:** Temperature High Byte<br>- **Byte 4:** Temperature Low Byte<br>- **Byte 5:** Checksum |
118+
| **Precision** | Integer values only | Includes decimal values for higher precision |
119+
| **Example Temperature** | 25°C | 25.6°C |
120+
| **Example Humidity** | 60% | 60.5% |
121+
| **Example Data Bytes** | `60, 0, 25, 0, 85` | `2, 93, 1, 0, 96` |
122+
| **Measurement Range** | - Temperature: 0–50°C<br>- Humidity: 20–90% | - Temperature: -40–80°C<br>- Humidity: 0–100% |
95123

96124

97125
## Example Schematic
-103 KB
Binary file not shown.

docs/example_esp32_dht_running.png

119 KB
Loading

src/dht.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use embedded_hal::{
2+
delay::DelayNs,
3+
digital::{ErrorType, InputPin, OutputPin, PinState},
4+
};
5+
6+
use crate::SensorError;
7+
8+
/// Common base struct for DHT sensors.
9+
pub struct Dht<P: InputPin + OutputPin, D: DelayNs> {
10+
pub pin: P,
11+
pub delay: D,
12+
}
13+
14+
impl<P: InputPin + OutputPin, D: DelayNs> Dht<P, D> {
15+
pub fn new(pin: P, delay: D) -> Self {
16+
Self { pin, delay }
17+
}
18+
19+
/// Reads a byte (8 bits) from the sensor.
20+
///
21+
/// This method reads 8 bits sequentially from the sensor to construct a byte.
22+
/// It follows the communication protocol of the DHT11/DHT22 sensors:
23+
///
24+
/// For each bit:
25+
/// - Waits for the pin to go **high** (start of bit transmission).
26+
/// - Delays for **30 microseconds** to sample the bit value.
27+
/// - If the pin is **high** after the delay, the bit is interpreted as **'1'**.
28+
/// - If the pin is **low**, the bit is interpreted as **'0'**.
29+
/// - Waits for the pin to go **low** (end of bit transmission).
30+
///
31+
/// The bits are assembled into a byte, starting from the most significant bit (MSB).
32+
///
33+
/// # Returns
34+
///
35+
/// - `Ok(u8)`: The byte read from the sensor.
36+
/// - `Err(SensorError<P::Error>)`: If a pin error occurs.
37+
pub fn read_byte(&mut self) -> Result<u8, SensorError> {
38+
let mut byte: u8 = 0;
39+
for n in 0..8 {
40+
let _ = self.wait_until_state(PinState::High);
41+
self.delay.delay_us(30);
42+
let is_bit_1 = self.pin.is_high();
43+
if is_bit_1.unwrap() {
44+
let bit_mask = 1 << (7 - (n % 8));
45+
byte |= bit_mask;
46+
let _ = self.wait_until_state(PinState::Low);
47+
}
48+
}
49+
Ok(byte)
50+
}
51+
52+
/// Waits until the pin reaches the specified state.
53+
///
54+
/// This helper function continuously polls the pin until it reaches the desired `PinState`.
55+
/// It introduces a **1-microsecond delay** between each poll to prevent excessive CPU usage.
56+
///
57+
/// # Arguments
58+
///
59+
/// - `state`: The target `PinState` to wait for (`PinState::High` or `PinState::Low`).
60+
///
61+
/// # Returns
62+
///
63+
/// - `Ok(())`: When the pin reaches the desired state.
64+
/// - `Err(SensorError<P::Error>)`: If an error occurs while reading the pin state.
65+
///
66+
pub fn wait_until_state(&mut self, state: PinState) -> Result<(), <P as ErrorType>::Error> {
67+
while !match state {
68+
PinState::Low => self.pin.is_low(),
69+
PinState::High => self.pin.is_high(),
70+
}? {
71+
self.delay.delay_us(1);
72+
}
73+
Ok(())
74+
}
75+
}

src/dht11.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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<P: InputPin + OutputPin, D: DelayNs> {
9+
dht: Dht<P, D>,
10+
}
11+
12+
impl<P: InputPin + OutputPin, D: DelayNs> Dht11<P, D> {
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<SensorReading, 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 as f32,
50+
temperature: temperature_integer as f32,
51+
})
52+
}
53+
}

src/dht22.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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<P: InputPin + OutputPin, D: DelayNs> {
9+
dht: Dht<P, D>,
10+
}
11+
12+
impl<P: InputPin + OutputPin, D: DelayNs> Dht22<P, D> {
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<SensorReading, 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_value = ((temperature_high as u16) << 8) | (temperature_low as u16);
52+
let temperatue_percentage = temperature_value as f32 / 10.0;
53+
54+
Ok(SensorReading {
55+
humidity: humidity_percentage as f32,
56+
temperature: temperatue_percentage as f32,
57+
})
58+
}
59+
}

0 commit comments

Comments
 (0)