RPI Pico with the DHT20 temperature and humidity sensor

Now we'll connect a DHT20 temperature and humidity sensor to the Raspberry Pi Pico board. The DHT20 is a digital temperature and humidity sensor with an I2C interface. The I2C is a two-wire interface that is used to connect low-speed devices in embedded systems. It is a quite common interface for sensors and other peripherals.

This specific example covers the DHT20 sensor, but the same principles apply to other I2C sensors. So with the datasheet of your sensor, you should be able to adapt this example to work with your sensor.🤞

Getting prepared

To build this project, you need the following:

  • DHT20 temperature & humidity sensor, I've got mine from Az-Delivery,

Wiring

Connect the DHT20 sensor to the Pico board like this:

DHT20 schematic

For the sake of simplicity, I've removed the programmer from the schematic. You still need it to flash the Pico board, though.

Your exact wiring might differ, depending on the sensor you have. The DHT20 sensor has a 4-pin connector. The pins are VCC, GND, SDA, and SCL. Connect the VCC pin to 3.3V, the GND pin to GND, the SDA pin to GP pin 2, and the SCL pin to GP pin 3.


Make main.rs look like this:

#![no_std]
#![no_main]

use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::{
    bind_interrupts,
    i2c::{self, Config, InterruptHandler},
    peripherals::I2C1,
};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};

use crate::dht20::{initialize, read_temperature_and_humidity};

bind_interrupts!(struct Irqs {
    I2C1_IRQ => InterruptHandler<I2C1>;
});

/// DHT20 sensor: datasheet: https://cdn.sparkfun.com/assets/8/a/1/5/0/DHT20.pdf
mod dht20 {
    use defmt::debug;
    use embassy_rp::{
        i2c::{Async, I2c},
        peripherals::I2C1,
    };
    use embassy_time::Timer;
    use embedded_hal_async::i2c::I2c as I2cAsync;

    const DHT20_I2C_ADDR: u8 = 0x38;
    const DHT20_GET_STATUS: u8 = 0x71;
    const DHT20_READ_DATA: [u8; 3] = [0xAC, 0x33, 0x00];

    const DIVISOR: f32 = 2u32.pow(20) as f32;
    const TEMP_DIVISOR: f32 = DIVISOR / 200.0;

    pub async fn initialize(i2c: &mut I2c<'static, I2C1, Async>) -> bool {
        Timer::after_millis(100).await;
        let mut data = [0x0; 1];
        i2c.write_read(DHT20_I2C_ADDR, &[DHT20_GET_STATUS], &mut data)
            .await
            .expect("Can not read status");

        data[0] & 0x18 == 0x18
    }

    async fn read_data(i2c: &mut I2c<'static, I2C1, Async>) -> [u8; 6] {
        let mut data = [0x0; 6];

        for _ in 0..10 {
            i2c.write(DHT20_I2C_ADDR, &DHT20_READ_DATA)
                .await
                .expect("Can not write data");
            Timer::after_millis(80).await;

            i2c.read(DHT20_I2C_ADDR, &mut data)
                .await
                .expect("Can not read data");

            if data[0] >> 7 == 0 {
                break;
            }
        }

        data
    }

    pub async fn read_temperature_and_humidity(i2c: &mut I2c<'static, I2C1, Async>) -> (f32, f32) {
        let data = read_data(i2c).await;
        debug!("data = {:?}", data);

        let raw_hum_data =
            ((data[1] as u32) << 12) + ((data[2] as u32) << 4) + (((data[3] & 0xf0) >> 4) as u32);
        debug!("raw_humidity_data = {:x}", raw_hum_data);
        let humidity = (raw_hum_data as f32) / DIVISOR * 100.0;

        let raw_temp_data =
            (((data[3] as u32) & 0xf) << 16) + ((data[4] as u32) << 8) + (data[5] as u32);
        debug!("raw_temperature_data = {:x}", raw_temp_data);
        let temperature = (raw_temp_data as f32) / TEMP_DIVISOR - 50.0;

        (temperature, humidity)
    }
}

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_rp::init(Default::default());
    let sda = p.PIN_2;
    let scl = p.PIN_3;

    info!("set up i2c ");
    let mut i2c = i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, Config::default());
    let ready = initialize(&mut i2c).await;
    info!("Ready: {}", ready);

    loop {
        let (temperature, humidity) = read_temperature_and_humidity(&mut i2c).await;
        info!("temperature = {}C", temperature);
        info!("humidity = {}%", humidity);

        Timer::after_millis(500).await;
    }
}

You need these dependencies in your Cargo.toml:

[dependencies]
embassy-embedded-hal = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy", features = ["defmt"] }
embassy-sync = { version = "0.5.0", git = "https://github.com/embassy-rs/embassy", features = ["defmt"] }
embassy-executor = { version = "0.5.0", git = "https://github.com/embassy-rs/embassy", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
embassy-time = { version = "0.3", git = "https://github.com/embassy-rs/embassy", features = ["defmt", "defmt-timestamp-uptime"] }
embassy-rp = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl"] }

cortex-m = { version = "0.7.6", features = ["inline-asm"] }
cortex-m-rt = "0.7.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
defmt = "0.3"
defmt-rtt = "0.4"
embedded-hal-async = "1.0"

In this example, we read the temperature and humidity from the DHT20 sensor. The sensor is connected to the Pico board via I2C. The sensor is read every 500ms.

Run the project with:

$ cargo run --release

You should see the temperature and humidity values printed to the console every 500ms.

0.002239 INFO  set up i2c 
0.102840 INFO  Ready: true
0.184031 DEBUG data = [28, 155, 38, 117, 178, 148]
0.184077 DEBUG raw_humidity_data = 9b267
0.184141 DEBUG raw_temperature_data = 5b294
0.184230 INFO  temperature = 21.219635C
0.184256 INFO  humidity = 60.605526%

Reference material