Skip to content

Commit

Permalink
Merge pull request #1 from rust-dd/feature/dht20
Browse files Browse the repository at this point in the history
Feature/dht20
  • Loading branch information
zeldan authored Sep 30, 2024
2 parents 37b3dca + f14880c commit 73d0a11
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 55 deletions.
106 changes: 52 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,35 @@ Welcome to `embedded-dht-rs`, a Rust library designed to make working with DHT s

This library only depends on `embedded_hal`, making it versatile and compatible with virtually any microcontroller.

### Features:

- **DHT11 and DHT22 sensor support**: Both sensors are fully implemented and ready to use.
**Support for DHT11, DHT20, and DHT22 Sensors**: All three sensors are fully implemented and ready for use.

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

## Getting Started

### Example
### Tutorials

Here are some general tutorials that provide brief introductions to embedded programming:

- **Part 1 (Introduction)** - [Introduction to Embedded Systems with Rust: A Beginner's Guide Using ESP32](https://rust-dd.com/post/introduction-to-embedded-systems-with-rust-a-beginner-s-guide-using-esp32)
- **Part 2 (LED + Button)** - [Building a Simple LED and Button Interface with Rust on ESP32](https://rust-dd.com/post/building-a-simple-led-and-button-interface-with-rust-on-esp32)


### Example - ESP32

![running](/docs/example_esp32_wired.jpg)

```rust
#![no_std]
#![no_main]

use embedded_dht_rs::{dht11::Dht11, dht22::Dht22};
use embedded_dht_rs::{dht11::Dht11, dht20::Dht20, dht22::Dht22};
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
delay::Delay,
gpio::{Io, Level, OutputOpenDrain, Pull},
peripherals::Peripherals,
prelude::*,
system::SystemControl,
clock::ClockControl, delay::Delay, gpio::{Io, Level, OutputOpenDrain, Pull}, i2c::I2C, peripherals::Peripherals, prelude::*, system::SystemControl
};
use fugit::HertzU32;

#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
Expand All @@ -43,11 +48,20 @@ fn main() -> ! {

let delay = Delay::new(&clocks);

let gpio4 = OutputOpenDrain::new(io.pins.gpio4, Level::High, Pull::None);
let gpio5 = OutputOpenDrain::new(io.pins.gpio5, Level::High, Pull::None);

let mut dht11 = Dht11::new(gpio4, delay);
let mut dht22 = Dht22::new(gpio5, delay);
let od_for_dht11 = OutputOpenDrain::new(io.pins.gpio4, Level::High, Pull::None);
let od_for_dht22 = OutputOpenDrain::new(io.pins.gpio5, Level::High, Pull::None);
let i2c_for_dht20 = I2C::new(
peripherals.I2C0,
io.pins.gpio21,
io.pins.gpio22,
HertzU32::kHz(400),
&clocks,
None,
);

let mut dht11 = Dht11::new(od_for_dht11, delay);
let mut dht22 = Dht22::new(od_for_dht22, delay);
let mut dht20 = Dht20::new(i2c_for_dht20, delay);

loop {
delay.delay(5000.millis());
Expand All @@ -70,6 +84,15 @@ fn main() -> ! {
Err(error) => log::error!("An error occurred while trying to read sensor: {:?}", error),
}

match dht20.read() {
Ok(sensor_reading) => log::info!(
"DHT 20 Sensor - Temperature: {} °C, humidity: {} %",
sensor_reading.temperature,
sensor_reading.humidity
),
Err(error) => log::error!("An error occurred while trying to read sensor: {:?}", error),
}

log::info!("-----");
}
}
Expand All @@ -82,50 +105,25 @@ fn main() -> ! {

We have gathered all the information you need to understand in order to implement a library like this. Additionally, we’ve included a few comments in the code for those curious about the details, based on the following specification.

The DHT20 differs from the DHT11 and DHT22 because it uses the I2C communication protocol, while both the DHT11 and DHT22 rely on a single-wire signal for data transmission.

![steps](/docs/steps.png)

### Step 1

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.

### Step 2

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:

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


### Step 3

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:

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

### Step 4

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.

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

### End signal

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.

- [DHT11 and DHT22 Documentation](docs/dht11_22.md)
- [DHT20 Documentation](docs/dht20.md)


## Comparison of DHT11 and DHT22 40-Bit Data Formats

| Feature | DHT11 | DHT22 |
|-----------------------|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
| **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 |
| **Precision** | Integer values only | Includes decimal values for higher precision |
| **Example Temperature** | 25°C | 25.6°C |
| **Example Humidity** | 60% | 60.5% |
| **Example Data Bytes** | `60, 0, 25, 0, 85` | `2, 93, 1, 0, 96` |
| **Measurement Range** | - Temperature: 0–50°C<br>- Humidity: 20–90% | - Temperature: -40–80°C<br>- Humidity: 0–100% |
## Comparison of DHT11, DHT20, and DHT22 40-Bit Data Formats

| Feature | DHT11 | DHT20 | DHT22 |
|-----------------------|----------------------------------------------------|--------------------------------------------------------|---------------------------------------------------------|
| **Data Structure** | - Byte 1: Humidity Int<br>- Byte 2: Humidity Dec (0)<br>- Byte 3: Temp Int<br>- Byte 4: Temp Dec (0)<br>- Byte 5: Checksum | - Byte 1: Humidity High<br>- Byte 2: Humidity Low<br>- Byte 3: Temp High<br>- Byte 4: Temp Low<br>- Byte 5: CRC | - Byte 1: Humidity High<br>- Byte 2: Humidity Low<br>- Byte 3: Temp High<br>- Byte 4: Temp Low<br>- Byte 5: Checksum |
| **Precision** | Integer only | Higher precision with decimals | Higher precision with decimals |
| **Example Temp** | 25°C | 25.6°C | 25.6°C |
| **Example Humidity** | 60% | 60.5% | 60.5% |
| **Example Data Bytes** | `60, 0, 25, 0, 85` | `2, 93, 1, 0, CRC` | `2, 93, 1, 0, 96` |
| **Range** | Temp: 0–50°C<br>Hum: 20–90% | Temp: -40–80°C<br>Hum: 10–90% | Temp: -40–80°C<br>Hum: 0–100% |

## Example Schematic
## Example Schematic

![step3](/docs/example_esp32_dht11.png)
30 changes: 30 additions & 0 deletions docs/dht11_22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# DHT11/DHT22

![steps](/docs/dht11_22_steps.png)

## Step 1

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.

## Step 2

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:

![step2](/docs/dht11_22_step2.png)


## Step 3

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:

![step3](/docs/dht11_22_step3.png)

## Step 4

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.

![step4](/docs/dht11_22_step4.png)

## End signal

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.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
30 changes: 30 additions & 0 deletions docs/dht20.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# DHT20

![steps](/docs/dht20_steps.png)

- SDA = Serial Data Line
- SCL = Serial Clock Line

## Start the sensor

The initial step is to supply power to the sensor using the chosen VDD voltage, which can range from 2.2V to 5.5V. Once powered on, the sensor requires less than 100ms to stabilize (with SCL held high during this period) before entering the idle state, after which it is ready to accept commands from the host (MCU).

## Step 1

After powering on, wait at least 100ms. Before reading the temperature and humidity values, retrieve a status byte by sending 0x71. If the result of the status byte and 0x18 is not equal to 0x18, initialize the 0x1B, 0x1C, and 0x1E registers.

## Step 2
Wait for 10ms before sending the 0xAC command to trigger the measurement. The command consists of two bytes: the first byte is 0x33 and the second byte is 0x00.

![step4](/docs/dht20_step2.png)

## Step 3
Wait for 80ms for the measurement to complete. If Bit [7] of the status word is 0, the measurement is done, and you can proceed to read six bytes continuously; if not, continue waiting.

## Step 4
After receiving the six bytes, the following byte is the CRC check data, which can be read if needed. If the receiver requires CRC validation, an ACK is sent after the sixth byte is received; otherwise, send a NACK to terminate. The initial CRC value is 0xFF, and the CRC8 check uses the polynomial: CRC [7:0] = 1 + X^4 + X^5 + X^8.

![step4](/docs/dht20_step4.png)

## Step 5
Compute the temperature and humidity values.
Binary file added docs/dht20_step2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/dht20_step4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/dht20_steps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/example_esp32_dht_running.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/example_esp32_wired.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/dht.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use embedded_hal::{

use crate::SensorError;

/// Common base struct for DHT sensors.
/// Common base struct for DHT11, DHT22 sensors.
pub struct Dht<P: InputPin + OutputPin, D: DelayNs> {
pub pin: P,
pub delay: D,
Expand Down
99 changes: 99 additions & 0 deletions src/dht20.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use embedded_hal::{delay::DelayNs, i2c::I2c};

use crate::{SensorError, SensorReading};

pub struct Dht20<I: I2c, D: DelayNs> {
pub i2c: I,
pub delay: D,
}

impl<I: I2c, D: DelayNs> Dht20<I, D> {
const SENSOR_ADDRESS: u8 = 0x38;

pub fn new(i2c: I, delay: D) -> Self {
Self { i2c, delay }
}

pub fn read(&mut self) -> Result<SensorReading<f32>, SensorError> {
// Check status
let mut status_response: [u8; 1] = [0; 1];
let _ = self
.i2c
.write_read(Self::SENSOR_ADDRESS, &[0x71], &mut status_response);

// Callibration if needed
if status_response[0] & 0x18 != 0x18 {
let _ = self.i2c.write(Self::SENSOR_ADDRESS, &[0x1B, 0, 0]);
let _ = self.i2c.write(Self::SENSOR_ADDRESS, &[0x1C, 0, 0]);
let _ = self.i2c.write(Self::SENSOR_ADDRESS, &[0x1E, 0, 0]);
}

// Trigger the measurement
self.delay.delay_ms(10);
let _ = self.i2c.write(Self::SENSOR_ADDRESS, &[0xAC, 0x33, 0x00]);

// Read the measurement status
self.delay.delay_ms(80);
loop {
let mut measurement_status_response: [u8; 1] = [0; 1];
let _ = self
.i2c
.read(Self::SENSOR_ADDRESS, &mut measurement_status_response);
let status_word = measurement_status_response[0];
if status_word & 0b1000_0000 == 0 {
break;
}
self.delay.delay_ms(1);
}

// Read the measurement (1 status + 5 data + 1 crc)
let mut measurement_response: [u8; 7] = [0; 7];
let _ = self
.i2c
.read(Self::SENSOR_ADDRESS, &mut measurement_response);

// Humidity 20 bits (8 + 8 + 4)
let mut raw_humidity = measurement_response[1] as u32;
raw_humidity = (raw_humidity << 8) + measurement_response[2] as u32;
raw_humidity = (raw_humidity << 4) + (measurement_response[3] >> 4) as u32;
let humidity_percentage = (raw_humidity as f32 / ((1 << 20) as f32)) * 100.0;

// Temperature 20 bits
let mut raw_temperature = (measurement_response[3] & 0b1111) as u32;
raw_temperature = (raw_temperature << 8) + measurement_response[4] as u32;
raw_temperature = (raw_temperature << 8) + measurement_response[5] as u32;
let temperatue_percentage = (raw_temperature as f32 / ((1 << 20) as f32)) * 200.0 - 50.0;

// Compare the calculated CRC with the received CRC
let data = &measurement_response[..6];
let received_crc = measurement_response[6];
let calculcated_crc = Self::calculate_crc(data);
if received_crc != calculcated_crc {
return Err(SensorError::ChecksumMismatch);
}

Ok(SensorReading {
humidity: humidity_percentage,
temperature: temperatue_percentage,
})
}

fn calculate_crc(data: &[u8]) -> u8 {
let polynomial = 0x31u8; // x^8 + x^5 + x^4 + 1
let mut crc = 0xFFu8;

for &byte in data {
crc ^= byte;
// CRC8 - process every bit
for _ in 0..8 {
if crc & 0x80 != 0 {
crc = (crc << 1) ^ polynomial;
} else {
crc <<= 1;
}
}
}

crc
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

mod dht;
pub mod dht11;
pub mod dht20;
pub mod dht22;

/// Represents a reading from the sensor.
Expand Down

0 comments on commit 73d0a11

Please sign in to comment.