aht20_async/
lib.rs

1//! A platform agnostic driver to interface with the AHT20 temperature and
2//! humidity sensor.
3//!
4//! This driver was built using [`embedded-hal`] traits and is a fork of Anthony
5//! Romano's [AHT10 crate].
6//!
7//! [`embedded-hal`]: https://docs.rs/embedded-hal/~0.2
8//! [AHT10 crate]: https://github.com/heyitsanthony/aht10
9
10#![deny(missing_docs)]
11#![no_std]
12
13use {
14    bitflags::bitflags,
15    crc_all::CrcAlgo,
16    embedded_hal_async::{delay::DelayNs, i2c::I2c},
17};
18
19const I2C_ADDRESS: u8 = 0x38;
20
21bitflags! {
22    struct StatusFlags: u8 {
23        const BUSY = (1 << 7);
24        const MODE = ((1 << 6) | (1 << 5));
25        const CRC = (1 << 4);
26        const CALIBRATION_ENABLE = (1 << 3);
27        const FIFO_ENABLE = (1 << 2);
28        const FIFO_FULL = (1 << 1);
29        const FIFO_EMPTY = (1 << 0);
30    }
31}
32
33/// AHT20 Error.
34#[derive(Debug, Copy, Clone)]
35pub enum Error<I2CERR> {
36    /// Device is not calibrated.
37    Uncalibrated,
38    /// Checksum mismatch.
39    Checksum,
40    /// Underlying I2C error.
41    I2c(I2CERR),
42}
43
44impl<E> core::convert::From<E> for Error<E> {
45    fn from(e: E) -> Self {
46        Error::I2c(e)
47    }
48}
49
50/// Humidity reading from AHT20.
51pub struct Humidity {
52    h: u32,
53}
54
55impl Humidity {
56    /// Humidity converted to Relative Humidity %.
57    pub fn rh(&self) -> f32 {
58        100.0 * (self.h as f32) / ((1 << 20) as f32)
59    }
60
61    /// Raw humidity reading.
62    pub fn raw(&self) -> u32 {
63        self.h
64    }
65}
66
67/// Temperature reading from AHT20.
68pub struct Temperature {
69    t: u32,
70}
71
72impl Temperature {
73    /// Temperature converted to Celsius.
74    pub fn celsius(&self) -> f32 {
75        (200.0 * (self.t as f32) / ((1 << 20) as f32)) - 50.0
76    }
77
78    /// Raw temperature reading.
79    pub fn raw(&self) -> u32 {
80        self.t
81    }
82}
83
84/// AHT20 driver.
85pub struct Aht20<I2C, D> {
86    i2c: I2C,
87    delay: D,
88}
89
90impl<I2C, D> Aht20<I2C, D>
91where
92    I2C: I2c,
93    D: DelayNs,
94{
95    /// Creates a new AHT20 device from an I2C peripheral and a Delay.
96    pub async fn new(i2c: I2C, delay: D) -> Result<Self, Error<I2C::Error>> {
97        let mut dev = Self { i2c, delay };
98
99        dev.reset().await?;
100
101        dev.calibrate().await?;
102
103        Ok(dev)
104    }
105
106    /// Gets the sensor status.
107    async fn status(&mut self) -> Result<StatusFlags, I2C::Error> {
108        let buf = &mut [0u8; 1];
109        self.i2c.write_read(I2C_ADDRESS, &[0u8], buf).await?;
110
111        Ok(StatusFlags::from_bits_retain(buf[0]))
112    }
113
114    /// Self-calibrate the sensor.
115    pub async fn calibrate(&mut self) -> Result<(), Error<I2C::Error>> {
116        // Send calibrate command
117        self.i2c.write(I2C_ADDRESS, &[0xE1, 0x08, 0x00]).await?;
118
119        // Wait until not busy
120        while self.status().await?.contains(StatusFlags::BUSY) {
121            self.delay.delay_ms(10).await;
122        }
123
124        // Confirm sensor is calibrated
125        if !self
126            .status()
127            .await?
128            .contains(StatusFlags::CALIBRATION_ENABLE)
129        {
130            return Err(Error::Uncalibrated);
131        }
132
133        Ok(())
134    }
135
136    /// Soft resets the sensor.
137    pub async fn reset(&mut self) -> Result<(), I2C::Error> {
138        // Send soft reset command
139        self.i2c.write(I2C_ADDRESS, &[0xBA]).await?;
140
141        // Wait 20ms as stated in specification
142        self.delay.delay_ms(20).await;
143
144        Ok(())
145    }
146
147    /// Reads humidity and temperature.
148    pub async fn read(&mut self) -> Result<(Humidity, Temperature), Error<I2C::Error>> {
149        // Send trigger measurement command
150        self.i2c.write(I2C_ADDRESS, &[0xAC, 0x33, 0x00]).await?;
151
152        // Wait until not busy
153        while self.status().await?.contains(StatusFlags::BUSY) {
154            self.delay.delay_ms(10).await;
155        }
156
157        // Read in sensor data
158        let buf = &mut [0u8; 7];
159        self.i2c.write_read(I2C_ADDRESS, &[0u8], buf).await?;
160
161        // Check for CRC mismatch
162        let crc = &mut 0u8;
163        let crc_hasher = CrcAlgo::<u8>::new(49, 8, 0xFF, 0x00, false);
164        crc_hasher.init_crc(crc);
165        if crc_hasher.update_crc(crc, &buf[..=5]) != buf[6] {
166            return Err(Error::Checksum);
167        };
168
169        // Check calibration
170        let status = StatusFlags::from_bits_retain(buf[0]);
171        if !status.contains(StatusFlags::CALIBRATION_ENABLE) {
172            return Err(Error::Uncalibrated);
173        }
174
175        // Extract humitidy and temperature values from data
176        let hum = ((buf[1] as u32) << 12) | ((buf[2] as u32) << 4) | ((buf[3] as u32) >> 4);
177        let temp = (((buf[3] as u32) & 0x0f) << 16) | ((buf[4] as u32) << 8) | (buf[5] as u32);
178
179        Ok((Humidity { h: hum }, Temperature { t: temp }))
180    }
181}