aht10_async/
lib.rs

1//! A platform agnostic driver to interface with the AHT10 temperature and humidity sensor.
2//!
3//! This driver was built using [`embedded-hal-async`] traits.
4//!
5//! Unfortunately, the AHT10 datasheet is somewhat underspecified. There's a
6//! FIFO mode as well as command data bytes which are briefly mentioned, but
7//! I've found no documentation describing what they mean. Caveat emptor.
8//!
9//! [`embedded-hal`]: https://docs.rs/embedded-hal/~0.2
10
11#![deny(missing_docs)]
12#![no_std]
13
14use embedded_hal_async::delay::DelayNs;
15use embedded_hal_async::i2c::I2c;
16
17const I2C_ADDRESS: u8 = 0x38;
18
19#[derive(Copy, Clone)]
20#[repr(u8)]
21enum Command {
22    Calibrate = 0b1110_0001,
23    GetRaw = 0b1010_1000,
24    GetCT = 0b1010_1100,
25    Reset = 0b1011_1010,
26}
27
28#[macro_use]
29extern crate bitflags;
30
31bitflags! {
32    struct StatusFlags: u8 {
33        const BUSY = (1 << 7);
34        const MODE = ((1 << 6) | (1 << 5));
35        const CRC = (1 << 4);
36        const CALIBRATION_ENABLE = (1 << 3);
37        const FIFO_ENABLE = (1 << 2);
38        const FIFO_FULL = (1 << 1);
39        const FIFO_EMPTY = (1 << 0);
40    }
41}
42
43/// AHT10 Error
44#[derive(Debug, Copy, Clone)]
45pub enum Error<E> {
46    /// Device is not calibrated
47    Uncalibrated(),
48    /// Underlying bus error.
49    BusError(E),
50}
51
52impl<E> core::convert::From<E> for Error<E> {
53    fn from(e: E) -> Self {
54        Error::BusError(e)
55    }
56}
57
58/// AHT10 driver
59pub struct AHT10<I2C, D> {
60    i2c: I2C,
61    delay: D,
62}
63
64/// Humidity reading from AHT10.
65pub struct Humidity {
66    h: u32,
67}
68impl Humidity {
69    /// Humidity conveted to relative humidity.
70    pub fn rh(&self) -> f32 {
71        100.0 * (self.h as f32) / ((1 << 20) as f32)
72    }
73    /// Raw humidity reading.
74    pub fn raw(&self) -> u32 {
75        self.h
76    }
77}
78
79/// Temperature reading from AHT10.
80pub struct Temperature {
81    t: u32,
82}
83impl Temperature {
84    /// Temperature converted to celsius.
85    pub fn celsius(&self) -> f32 {
86        (200.0 * (self.t as f32) / ((1 << 20) as f32)) - 50.0
87    }
88    /// Raw temperature reading.
89    pub fn raw(&self) -> u32 {
90        self.t
91    }
92}
93
94impl<I2C, D> AHT10<I2C, D>
95where
96    I2C: I2c,
97    D: DelayNs,
98{
99    /// Creates a new AHT10 device from an I2C peripheral.
100    pub async fn new(i2c: I2C, delay: D) -> Result<Self, I2C::Error> {
101        let mut dev = AHT10 { i2c, delay };
102        dev.write_cmd(Command::GetRaw, 0).await?;
103        dev.delay.delay_ms(300).await;
104        // MSB notes:
105        // Bit 2 set => temperature is roughly doubled(?)
106        // Bit 3 set => calibrated flag
107        // Bit 4 => temperature is negative? (cyc mode?)
108        dev.write_cmd(Command::Calibrate, 0x0800).await?;
109        dev.delay.delay_ms(300).await;
110        Ok(dev)
111    }
112
113    /// Soft reset the sensor.
114    pub async fn reset(&mut self) -> Result<(), I2C::Error> {
115        self.write_cmd(Command::Reset, 0).await?;
116        self.delay.delay_ms(20).await;
117        Ok(())
118    }
119
120    /// Read humidity and temperature.
121    pub async fn read(&mut self) -> Result<(Humidity, Temperature), Error<I2C::Error>> {
122        let buf: &mut [u8; 7] = &mut [0; 7];
123        // Sort of reverse engineered the cmd data:
124        // Bit 0 -> temperature calibration (0 => +0.5C)
125        // Bit {1,2,3} -> refresh rate? (0 => slow refresh)
126        self.i2c
127            .write_read(I2C_ADDRESS, &[Command::GetCT as u8, 0b11111111, 0], buf)
128            .await?;
129        let status = StatusFlags { bits: buf[0] };
130        if !status.contains(StatusFlags::CALIBRATION_ENABLE) {
131            return Err(Error::Uncalibrated());
132        }
133        let hum = ((buf[1] as u32) << 12) | ((buf[2] as u32) << 4) | ((buf[3] as u32) >> 4);
134        let temp = (((buf[3] as u32) & 0x0f) << 16) | ((buf[4] as u32) << 8) | (buf[5] as u32);
135        Ok((Humidity { h: hum }, Temperature { t: temp }))
136    }
137
138    async fn write_cmd(&mut self, cmd: Command, dat: u16) -> Result<(), I2C::Error> {
139        self.i2c
140            .write(
141                I2C_ADDRESS,
142                &[cmd as u8, (dat >> 8) as u8, (dat & 0xff) as u8],
143            )
144            .await
145    }
146}