DEV Community

Jens
Jens

Posted on

Embassy 下使用温湿度传感器

本文希望通过 Omar Hiari: Embedded Rust & Embassy: I2C Temperature Sensing with BMP180, 在 Blue Pill 上实现 DHT20 温湿度传感器的控制程序.

1. 接线方法

我们查看 Blue Pill 的接口图发现, 支持 I2C的接口为 (PB6, PB7), (PB11, PB10)

Blue Pill Pins

使用 DHT20 温湿度检测器, 使用 I2C 通讯协议, 与 Blue Pill 的接线口分别为如下

  • VDD -> VDD
  • SDA -> PB7
  • GND -> GND
  • SCL -> PB6 ## 2.代码实现 我们暂时仅实现获得温湿度数据并打印的功能, 这只需要一个任务, 我们将它放在主任务中.

我们先获得外围设备结构体

// initialising peripherals
let p = embassy_stm32::init(Default::default());
Enter fullscreen mode Exit fullscreen mode

在获得结构体后, 我们创建 I2c 句柄的实例, 它封装了 I2C 的接收发送过程.

// 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
);
Enter fullscreen mode Exit fullscreen mode

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

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

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 }
    }
}
Enter fullscreen mode Exit fullscreen mode

我们根据 DHT20 设备文档, 完成以下开发
初始化设备

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(())
    }
}
Enter fullscreen mode Exit fullscreen mode

读取温湿度值

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),
        ))
    }
}
Enter fullscreen mode Exit fullscreen mode

其中有转换温度和湿度数值的辅助函数和判断结果是否有效的检查函数

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
    }
}
Enter fullscreen mode Exit fullscreen mode

在主任务中我们可以创建这个设备的句柄并且使用它

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;
}
Enter fullscreen mode Exit fullscreen mode

3.演示

视频链接(屏幕录制)

Top comments (0)