Skip to main content

dht20/
dht20.rs

1// external
2use embedded_hal::delay::DelayNs; // for timing
3use embedded_hal::i2c::{I2c, SevenBitAddress}; // for i2c abstraction
4
5// internal
6use crate::sensor_reading::SensorReading;
7use crate::utils::{compute_crc8, convert_humidity, convert_temperature, extract_readings};
8
9#[derive(Debug)]
10pub enum DHT20Error<E> {
11    I2C(E),
12    CrcMismatch,
13    NotInitialized,
14}
15
16impl<E> From<E> for DHT20Error<E> {
17    fn from(err: E) -> Self {
18        DHT20Error::I2C(err)
19    }
20}
21
22#[repr(u8)] // represent as u8; permits casting to a byte
23enum OpCode {
24    CheckStatus = 0x71,
25    TriggerMeasurement = 0xAC,
26    StatusReady = 0x80,
27}
28
29const I2C_ADDRESS: SevenBitAddress = 0x38; // DHT20 default I2C address per datasheet
30const RESET_REGISTERS: [u8; 3] = [0x1B, 0x1C, 0x1E];
31
32pub struct Dht20<I2C> {
33    i2c: I2C,
34    address: SevenBitAddress,
35    initialized: bool,
36}
37
38impl<I2C, E> Dht20<I2C>
39where
40    I2C: I2c<Error = E>,
41{
42    // constructor for Dht20 struct
43    pub fn new(i2c: I2C) -> Self {
44        Self {
45            i2c, // dependency injection; receive the I2C instance
46            address: I2C_ADDRESS,
47            initialized: false,
48        }
49    }
50
51    // initialize the sensor, return nothing
52    pub fn init<D: DelayNs>(&mut self, delay: &mut D) -> Result<(), DHT20Error<E>> {
53        delay.delay_ms(100); // wait for sensor to power up, no less than 100ms
54
55        self.check_init(delay)
56    }
57
58    // request a reading from the sensor
59    // returns a DHTReading struct containing the temperature and humidity
60    pub fn take_reading<D: DelayNs>(
61        &mut self,
62        delay: &mut D,
63    ) -> Result<SensorReading, DHT20Error<E>> {
64        if !self.initialized {
65            return Err(DHT20Error::NotInitialized);
66        }
67
68        self.trigger_measurement(delay)?; // trigger the measurement
69
70        self.wait_for_ready(delay)?; // wait for measurement to be ready
71
72        let data = self.read_measurement()?;
73
74        // extract the humidity and temperature readings from the data
75        let (raw_humidity, raw_temperature) = extract_readings(&data);
76
77        // convert the raw readings to percentage, Celsius
78        let humidity = convert_humidity(raw_humidity);
79        let temperature = convert_temperature(raw_temperature);
80
81        // return the readings as a DHTReading struct
82        Ok(SensorReading::new(temperature, humidity))
83    }
84
85    pub fn read_raw<D: DelayNs>(&mut self, delay: &mut D) -> Result<[u8; 6], DHT20Error<E>> {
86        if !self.initialized {
87            return Err(DHT20Error::NotInitialized);
88        }
89
90        self.trigger_measurement(delay)?; // trigger the measurement
91
92        self.wait_for_ready(delay)?; // wait for measurement to be ready
93
94        let data = self.read_measurement()?;
95
96        Ok(data)
97    }
98
99    // polls the sensor to determine its initialization state
100    fn check_init<D: DelayNs>(&mut self, delay: &mut D) -> Result<(), DHT20Error<E>> {
101        let mut buffer = [0u8; 1]; // set up a buffer to hold response word (byte)
102
103        // Send check_status opcode
104        self.i2c
105            .write_read(self.address, &[OpCode::CheckStatus as u8], &mut buffer)?;
106
107        let status = buffer[0];
108
109        // Ensure status is 0x18
110        if (status & 0x18) != 0x18 {
111            for reg in RESET_REGISTERS.iter() {
112                self.reset_register(delay, *reg)?;
113            }
114        }
115
116        // wait 10ms for the sensor to stabilize (prerequisite for taking a measurement)
117        delay.delay_ms(10);
118
119        // initialized
120        self.initialized = true;
121        Ok(())
122    }
123
124    // reset the sensor; undocumented by aosong, following along with
125    // code from https://github.com/RobTillaart/DHT20/ as it's the best available documentation.
126    fn reset_register<D: DelayNs>(&mut self, delay: &mut D, reg: u8) -> Result<(), DHT20Error<E>> {
127        let mut buffer = [0u8; 3]; // buffer to hold 3 response words (bytes)
128
129        // Write 0x00, 0x00 to the register - clear the values
130        self.i2c.write(self.address, &[reg, 0x00, 0x00])?;
131
132        // delay for stability's sake
133        delay.delay_ms(5);
134
135        // Read back 3 bytes from the register
136        self.i2c.write_read(self.address, &[reg], &mut buffer)?;
137        delay.delay_ms(5);
138
139        // Write modified values back to register; we're OR-ing them w/ 0xB0.
140        // Undocumented, just copying from RobTillaart's code.
141        self.i2c
142            .write(self.address, &[0xB0 | reg, buffer[1], buffer[2]])?;
143        delay.delay_ms(5);
144
145        Ok(())
146    }
147
148    // trigger a measurement
149    fn trigger_measurement<D: DelayNs>(&mut self, delay: &mut D) -> Result<(), DHT20Error<E>> {
150        // 0x33 and 0x00 are two argument bytes to be sent to the sensor when triggering a measurement.
151        let command = [OpCode::TriggerMeasurement as u8, 0x33, 0x00];
152        self.i2c.write(self.address, &command)?;
153
154        delay.delay_ms(80); // wait 80ms per the datasheet (minimum time to ready)
155
156        Ok(())
157    }
158
159    // wait for measurement to be ready
160    fn wait_for_ready<D: DelayNs>(&mut self, delay: &mut D) -> Result<(), DHT20Error<E>> {
161        let mut buffer = [0u8; 1]; // buffer to hold status word (1 byte)
162
163        // poll until ready
164        loop {
165            self.i2c
166                .write_read(self.address, &[OpCode::CheckStatus as u8], &mut buffer)?;
167            // buffer[0] means first (only) byte
168            // mask out all but the 7th bit (0x80); if it's 0, we're ready.
169            if buffer[0] & (OpCode::StatusReady as u8) == 0 {
170                return Ok(()); // measurement complete
171            }
172            // otherwise, wait 10ms before polling again
173            delay.delay_ms(0);
174        }
175    }
176
177    // read the measurement values from the sensor
178    // these must be parsed before usage
179    fn read_measurement(&mut self) -> Result<[u8; 6], DHT20Error<E>> {
180        let mut buffer = [0u8; 7]; // buffer to hold 6 data bytes and 1 CRC byte
181
182        // read 7 bytes from the sensor
183        self.i2c.read(self.address, &mut buffer)?;
184
185        let crc = buffer[6]; // 7th byte is the CRC
186
187        // compute CRC8
188        let crc_check = compute_crc8(&buffer[..6]);
189        if crc != crc_check {
190            return Err(DHT20Error::CrcMismatch);
191        }
192        // return the 6 data bytes
193        Ok(buffer[..6].try_into().unwrap()) // convert slice to array
194    }
195}