Embassy 下使用温湿度传感器
Published: 2024-04-16 15:02:05


本文希望通过 [Omar Hiari: Embedded Rust & Embassy: I2C Temperature Sensing with BMP180](https://apollolabsblog.hashnode.dev/embedded-rust-embassy-i2c-temperature-sensing-with-bmp180), 在 Blue Pill 上实现 DHT20 温湿度传感器的控制程序.
## 1. 接线方法
我们查看 Blue Pill 的接口图发现, 支持 I2C的接口为 (PB6, PB7), (PB11, PB10)


![Blue Pill Pins](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vckl0fvk42k41dxipefs.jpg)

使用 DHT20 温湿度检测器, 使用 I2C 通讯协议, 与 Blue Pill 的接线口分别为如下
- VDD -> VDD
- SDA -> PB7
- GND -> GND
- SCL -> PB6
## 2.代码实现
我们暂时仅实现获得温湿度数据并打印的功能, 这只需要一个任务, 我们将它放在主任务中.

我们先获得外围设备结构体
```rust
// initialising peripherals
let p = embassy_stm32::init(Default::default());
```

在获得结构体后, 我们创建 `I2c` 句柄的实例, 它封装了 I2C 的接收发送过程.
```rust
// create an I2C handle
let i2c = i2c::I2c::new(
    p.I2C1,             // periphral: an I2C peripheral
    p.PB6,              // scl: a GPIO pin
    p.PB7,              // sda: a GPIO pin
    I2cIrqs,            // irq: handle to an interrut(
    NoDma,              // tx_dma: an instance to a DMA channel
    NoDma,              // rx_dma: an instance to a DMA channel
    time::hz(100_000),  // frequency
    Default::default(), // config
);
```

我们现在为 DHT20 编写驱动程序, 通过调用 `i2c::I2c` 中的 `blocking_read` 和 `blocking_write`.
我们将一个 DHT20 传感器抽象为一个设备, 因为不可能有两个设备在同一个 I2C Master-Slave 之间传递信息, 我们通过移动语义转移创建的 `i2c` 所有权, 使得它 DHT20 设备被创建后, 这个设备 "拥有" 这个 I2C 通道.

为了保证通用性, 我们使用 Generics, 这样能够满足不同的 `i2c` 实例, 包括不同引脚, 是否使用 DMA 等不同配置, 而不限于我们创建的这个 `i2c` 的配置. 因为 `I2c` 中保存了多个外围设备的引用, Rust 为保证所有引用都有效, 我们需要明确申明每个引用的 lifetime. 这里, 我们表明 "i2c同其内部的引用能够 outlive 这个 dht20 设备或一样时间一样长".

```rust
pub struct Dht20<'d, T, TXDMA = NoDma, RXDMA = NoDma>
where
    T: i2c::Instance,
{
    i2c: i2c::I2c<'d, T, TXDMA, RXDMA>,
}

impl<'d, T, TXDMA, RXDMA> Dht20<'d, T, TXDMA, RXDMA>
where
    T: i2c::Instance,
{
    pub fn new(i2c: i2c::I2c<'d, T, TXDMA, RXDMA>) -> Self {
        Self { i2c }
    }
}
```

我们根据 [DHT20 设备文档](http://www.aosong.com/userfiles/files/DHT20%E4%BA%A7%E5%93%81%E8%A7%84%E6%A0%BC%E4%B9%A6(%E4%B8%AD%E6%96%87%E7%89%88)%20A2.pdf), 完成以下开发
初始化设备
```rust
impl<'d, T, TXDMA, RXDMA> Dht20<'d, T, TXDMA, RXDMA>
where
    T: i2c::Instance,
{
    pub async fn init(&mut self) -> Option<()> {
        // wait for >= 100 ms
        Timer::after(Duration::from_millis(100)).await;

        // send initialisation command
        let mut send_buffer: [u8; 3] = [0xE1, 0x08, 0x00];

        while let Err(err) = self.i2c.blocking_write(DHT20_ADDR, &mut send_buffer) {
            error!("send error: {}", err);
        }

        Timer::after(Duration::from_millis(100)).await;

        Some(())
    }
}
```

读取温湿度值
```rust
impl<'d, T, TXDMA, RXDMA> Dht20<'d, T, TXDMA, RXDMA>
where
    T: i2c::Instance,
{
    pub async fn read(&mut self) -> Option<(f32, f32)> {
        // the "messure" command
        let mut send_buffer: [u8; 3] = [0xAC, 0x33, 0x00];
        if let Err(err) = self.i2c.blocking_write(DHT20_ADDR, &mut send_buffer) {
            error!("An error occoured when sending command: {}", err);
            self.reset();
            return None;
        }

        // wait for the messurement to finish
        Timer::after(Duration::from_millis(80)).await;

        // read the result
        let mut read_buffer: [u8; 6] = [0; 6];
        if let Err(err) = self.i2c.blocking_read(DHT20_ADDR, &mut read_buffer) {
            error!("An error when reading result: {}", err);
            return None;
        }
        Self::valid_resault(&read_buffer).unwrap();

        Some((
            Self::parse_humidity(&read_buffer),
            Self::parse_temperature(&read_buffer),
        ))
    }
}
```
其中有转换温度和湿度数值的辅助函数和判断结果是否有效的检查函数
```rust
impl<'d, T, TXDMA, RXDMA> Dht20<'d, T, TXDMA, RXDMA>
where
    T: i2c::Instance,
{
    fn valid_resault(buffer: &[u8]) -> Option<()> {
        assert!(buffer.len() >= 1);
        if buffer[0] & 0x68 == 0x08 {
            Some(())
        } else {
            None
        }
    }

    fn parse_humidity(buffer: &[u8]) -> f32 {
        assert!(buffer.len() == 6);
        let mut hum = buffer[1] as u32;
        hum = (hum << 8) | buffer[2] as u32;
        hum = (hum << 8) | buffer[3] as u32;
        hum >>= 4;
        let hum = hum as f32;
        (hum / (1 << 20) as f32) * 100f32
    }

    fn parse_temperature(buffer: &[u8]) -> f32 {
        assert!(buffer.len() == 6);
        let mut temp: u32 = (buffer[3] & 0x0f) as u32;
        temp = (temp << 8) | (buffer[4] as u32);
        temp = (temp << 8) | buffer[5] as u32;
        let temp = temp as f32;
        (temp / ((1 << 20) as f32)) * 200f32 - 50f32
    }
}
```
在主任务中我们可以创建这个设备的句柄并且使用它
```rust
let mut sensor = Dht20::new(i2c);
if None == sensor.init().await {
    error!("Initialisation Failed")
}
loop {
    if let Some((hum, temp)) = sensor.read().await {
        info!("Humidity: {}, Temperature: {}", hum, temp);
    }
    Timer::after(Duration::from_millis(500)).await;
}
```

## 3.演示
[视频链接(屏幕录制)](https://youtu.be/iKVPvYbN_1M)