am2320/lib.rs
1//! # am2320
2//!
3//! A platform-agnostic driver to interface with the AM2320 I2c temperature & humidity
4//! sensor using `embedded-hal` traits.
5//!
6#![no_std]
7#![deny(warnings, missing_docs)]
8
9use embedded_hal::blocking::{delay, i2c};
10
11const DEVICE_I2C_ADDR: u8 = 0x5c;
12
13/// Describes potential errors
14#[derive(Debug)]
15pub enum Error {
16 /// Something went wrong while writing to the sensor
17 WriteError,
18 /// Something went wrong while reading from the sensor
19 ReadError,
20 /// The sensor returned data that is out of spec
21 SensorError,
22}
23
24/// Representation of a measurement from the sensor
25#[derive(Debug)]
26pub struct Measurement {
27 /// Temperature in degrees celsius (°C)
28 pub temperature: f32,
29 /// Humidity in percent (%)
30 pub humidity: f32,
31}
32
33/// Sensor configuration
34pub struct Am2320<I2C, Delay> {
35 /// I2C master device to use to communicate with the sensor
36 device: I2C,
37 /// Delay device to be able to sleep in-between commands
38 delay: Delay,
39}
40
41#[inline(always)]
42fn crc16(data: &[u8]) -> u16 {
43 let mut crc: u16 = 0xFFFF;
44 for e in data.iter() {
45 crc ^= u16::from(*e);
46 for _ in 0..8 {
47 if crc & 0x0001 == 0x0001 {
48 crc >>= 1;
49 crc ^= 0xA001;
50 } else {
51 crc >>= 1;
52 }
53 }
54 }
55 crc
56}
57
58impl<I2C, Delay, E> Am2320<I2C, Delay>
59where
60 I2C: i2c::Read<Error = E> + i2c::Write<Error = E>,
61 Delay: delay::DelayUs<u16>,
62{
63 /// Create a AM2320 temperature sensor driver.
64 ///
65 /// Example with `rppal`:
66 ///
67 /// ```!ignore
68 /// use am2320::*;
69 /// use rppal::{hal::Delay, i2c::I2c};
70 /// fn main() -> Result<(), Error> {
71 /// let device = I2c::new().expect("could not initialize I2c on your RPi");
72 /// let delay = Delay::new();
73 ///
74 /// let mut am2320 = Am2320::new(device, delay);
75 ///
76 /// println!("{:?}", am2320.read());
77 /// Ok(())
78 /// }
79 /// ```
80 pub fn new(device: I2C, delay: Delay) -> Self {
81 Self { device, delay }
82 }
83
84 /// Reads one `Measurement` from the sensor
85 ///
86 /// The operation is blocking, and should take ~3 ms according the spec.
87 /// This is because the sensor goes into sleep and has to be waken up first.
88 /// Then it'll wait a while before sending data in-order for the measurement
89 /// to be more accurate.
90 ///
91 pub fn read(&mut self) -> Result<Measurement, Error> {
92 // We need to wake up the AM2320, since it goes to sleep in order not
93 // to warm up and affect the humidity sensor. This write will fail as
94 // the AM2320 won't ACK this write.
95 let _ = self.device.write(DEVICE_I2C_ADDR, &[0x00]);
96 // Wait at least 0.8ms, at most 3ms.
97 self.delay.delay_us(900);
98
99 // Send read command.
100 self.device
101 .write(DEVICE_I2C_ADDR, &[0x03, 0x00, 0x04])
102 .map_err(|_| Error::WriteError)?;
103 // Wait at least 1.5ms for the result.
104 self.delay.delay_us(1600);
105
106 // read out 8 bytes of result data
107 // byte 0: Should be Modbus function code 0x03
108 // byte 1: Should be number of registers to read (0x04)
109 // byte 2: Humidity msb
110 // byte 3: Humidity lsb
111 // byte 4: Temperature msb
112 // byte 5: Temperature lsb
113 // byte 6: CRC lsb byte
114 // byte 7: CRC msb byte
115 let mut data = [0; 8];
116 self.device
117 .read(DEVICE_I2C_ADDR, &mut data)
118 .map_err(|_| Error::ReadError)?;
119
120 // check that the operation was reported as succesful
121 if data[0] != 0x03 || data[1] != 0x04 {
122 return Err(Error::SensorError);
123 }
124
125 // CRC check
126 let crc = crc16(&data[0..6]);
127 if crc != u16::from_le_bytes([data[6], data[7]]) {
128 return Err(Error::SensorError);
129 }
130
131 let mut temperature = i16::from_be_bytes([data[4] & 0b0111_1111, data[5]]);
132 if data[4] & 0b1000_0000 != 0 {
133 temperature = -temperature;
134 }
135
136 let humidity = u16::from_be_bytes([data[2], data[3]]);
137
138 Ok(Measurement {
139 temperature: f32::from(temperature) / 10.0,
140 humidity: f32::from(humidity) / 10.0,
141 })
142 }
143}
144
145#[test]
146fn test_crc16() {
147 assert_eq!(crc16(&[]), 0xFFFF);
148 assert_eq!(crc16(&[0x03, 0x04, 0x02, 0x36, 0x0, 0xDB]), 0x0550);
149}