I2Cで使える温度湿度センサーSHT31をESP32-C3+Rustで使ってみる

温度湿度センサーといえば DHT11 ですが、DHT11 は単線で送受信するため配線上は楽なのですが、マイコン上はオープンドレインで送受信を行わねばならず、適切に読み込もうとすると一手間かかります。 DHT11は有名なため、おおくのマイコン用のライブラリが作られています。 rustでembedded-hal を使ったものでライブラリがありましたが、私の所では上手く動作させることは出来ませんでした。

https://crates.io/crates/dht11

オシロスコープで確認する限りは、センサーは正しい値を返していることが確認できていました。

いつかリベンジしたいですが一旦諦めて、通信が楽そうな温湿度センサーを探していました。

SHT31

SHT31モジュール

SHT31というI2C で使える高精度温湿度センサーを見つけました。 秋月でも取り扱いがあります。

akizukidenshi.com

自分の場合は、aliexpressのショップで見つけまして購入しました。

https://ja.aliexpress.com/item/1005001626776392.html

さらに、秋月のサイトには日本語のデータシートも掲載されていました。 このデータシートを参考に、周期的連続測定モードで稼働させ、データを読み取ることが出来ました。

以下コードの抜粋です。

// sht31.rs

use embedded_hal::i2c::{blocking::I2c, Error};

const _SHT31_ADDRESS: u8 = 0x44;
const _SETUP: &[u8] = &[0x22, 0x36];

pub struct SHT31 {}

impl SHT31 {
    pub fn new() -> Self {
        Self {}
    }

    /**
     * 周期的連続測定モードの開始
     */
    pub fn setup<T: I2c>(&mut self, i2c_conn: &mut T) -> Result<(), ()> {
        if let Err(e) = i2c_conn.write(_SHT31_ADDRESS, _SETUP) {
            println!("sht31 i2c error: {}", e.kind());
            return Err(());
        }
        return Ok(());
    }

    /**
     * 計測
     */
    pub fn read<T: I2c>(&mut self, i2c_conn: &mut T) -> Result<[f32; 2], ()> {
        let mut buf = &mut [0u8; 6];
        if let Err(e) = i2c_conn.read(_SHT31_ADDRESS, buf) {
            println!("sht31 i2c error: {}", e.kind());
            return Err(());
        }
    
        // TODO: チェックサム
        let measured_temp = u16::from(buf[0]) << 8 | u16::from(buf[1]);
        let real_temp = f32::from(measured_temp) * 175. / (((1 << 16) - 1) as f32) - 45.;
        let measured_hum = u16::from(buf[3]) << 8 | u16::from(buf[4]);
        let real_hum = f32::from(measured_hum) * 100. / (((1 << 16) - 1) as f32);

        Ok([real_temp, real_hum])
    }
}
// main.rs
use esp_idf_hal::{i2c, prelude::Peripherals}
use std::thread::sleep;
use std::time::Duration;

mod sht31;

fn main() -> std::io::Result<()> 
    esp_idf_sys::link_patches();
    let peripherals = Peripherals::take().unwrap();

    let mut i2c_conn = setup_i2c(
        peripherals.i2c0,
        peripherals.pins.gpio5,
        peripherals.pins.gpio6,
    )
    .unwrap()

    let mut sht31_module = sht31::SHT31::new();
    sht31_module.setup(&mut i2c_conn);

    loop {
        let r = sht31_module.read(&mut i2c_conn);
        match r {
            Ok(v) => {
                println!("{:>2.1}C {:>3.1}%", v[0], v[1]);
            }
            Err(e) => println!("sht11 error"),
        }

        sleep(Duration::from_secs(3));
    }
}

動かしてみて

良好に読み取れていそうで良かったです。

一旦 DHT11 を諦めた経緯

本来であれば、マルチスレッドで動かす物ではないため、GPIOで読み取る時のμs単位ディレイに CPU クロックが使えれば正確で良いのです。 今回利用した ESP32-C3 で通信な可能な形で embedded-hal を実装している esp-idf-hal で使えるディレイが、 Espressif Task Scheduler ベースのもの(esp_idf_hal::delay::Ets)であり、単純にそれを使って既存の dht11 ライブラリが動作しなかったため、これは嵌まると大きそうだと感じたためです。