Feb 03, 2026
3 min read
ESP32,
Rust,
C,
Embedded Systems,

使用Rust实现DHT11温湿度传感器读取

详细介绍如何使用Rust编程语言实现对DHT11温湿度传感器的读取,包括单总线通信协议解析、时序分析和代码实现步骤

DHT11 器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、控制均由单总线 完成。

单总线传送数据位定义

DATA 用于微处理器与 DHT11 之间的通讯和同步,采用单总线数据格式,一次传送 40 位数据,高位先出。

数据格式:

8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位。

注:其中湿度小数部分为 0。

校验位数据定义

“8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据”8bit 校验位等于 所得结果的末 8 位。

示例一:接收到的 40 位数据为:

根据您提供的DHT11数据格式,将40位数据按照5列2行的表格形式重新组织:

湿度整数湿度小数温度整数温度小数校验位
0011010100000000000110000000010001010001

校验位数据计算:

0011 0101+0000 0000+0001 1000+0000 0100= 0101 0001

数据时序图 3.jpg

实现步骤

步骤一

DHT11 上电后要等 1~2 秒,然后开始工作。因此开始前先延时 2 秒。

loop {
        delay.delay_millis(2000);
        //...
}

步骤二

微处理器的 IO 设置输出低电平,并且这个低电平需要保持时间至少18ms。

let _ = self.dht.pin.set_low();
self.dht.delay.delay_ms(18);
let _ = self.dht.pin.set_high();

1.jpg

步骤三

DHT11 收到上面的低电平后,会等低电平结束,然后再次进入高电平,这时等待时间大概是40到50微秒(有看到C的实现是40微秒,而 Rust 的实现是48微秒)。

self.dht.delay.delay_us(48);

然后输出 83 微秒的低电平作为响应信号。接着再输出87微秒的高电平通知微处理器数据准备好接收数据。

2.jpg

在收数据时,先等”83 微秒的低电平”结束,再等待”87 微秒的高电平”结束。

let _ = self.dht.wait_until_state(PinState::High);
let _ = self.dht.wait_until_state(PinState::Low);

wait_until_state 实现如下:

 pub fn wait_until_state(&mut self, state: PinState) -> Result<(), SensorError> {
        for _ in 0..DEFAULT_MAX_ATTEMPTS {
            let is_state = match state {
                PinState::Low => self.pin.is_low(),
                PinState::High => self.pin.is_high(),
            };

            match is_state {
                Ok(true) => return Ok(()),
                Ok(false) => self.delay.delay_us(1),
                Err(_) => return Err(SensorError::PinError),
            }
        }

        Err(SensorError::Timeout)
    }

之后进入数据接收阶段。

步骤四

在数据接收阶段,数据的长度为 40 bits,也就是上面列出的5个字节。 其中:

  • 位数据”0”的数据表示为:54微秒的低电平和23-27微秒的高电平。
  • 位数据”1”的数据表示为:54微秒的低电平和68-74微秒的高电平。

也可以认为54微秒的低电平其实是分隔位。

每一个字节的数据有8位,因此需要8次循环。

    let mut byte: u8 = 0;
    for n in 0..8 {
        // 等待高电平的到来
        match self.wait_until_state(PinState::High) {
            Ok(_) => {}
            Err(err) => return Err(err),
        };
        // 检测高电平的持续时间
        self.delay.delay_us(30);
        // 如果此时还是高电平,则该位为1,因为高电平的持续时间大于30微秒
        // 如果此时是低电平,则该位为0,因为高电平的持续时间小于30微秒
        let is_bit_1 = self.pin.is_high();
        if is_bit_1.unwrap() {
            // 通过位掩码将该位设置为1
            let bit_mask = 1 << (7 - (n % 8));
            byte |= bit_mask;
            // 等待低电平的到来,准备下一次的bit读取
            match self.wait_until_state(PinState::Low) {
                Ok(_) => {}
                Err(err) => return Err(err),
            };
        }
    }

之前说过,接收的数据有40位,除了最后8位是校验位,其他位都是数据位。 因此需要5次上面的循环,分别读取5个字节的数据。

// read_byte 方法就是上面的逻辑
let humidity_integer = read_byte()?;
let humidity_decimal = read_byte()?;
let temperature_integer = read_byte()?;
let temperature_decimal = read_byte()?;
let checksum = read_byte()?;

数据校验 前32位数据之和,如果和校验位一致,则数据正确。

 let sum = humidity_integer
    .wrapping_add(humidity_decimal)
    .wrapping_add(temperature_integer)
    .wrapping_add(temperature_decimal);
if sum != checksum {
    return Err(SensorError::ChecksumMismatch);
}

到此,数据接收完成。