Skip to main content

pamoja_sensors/hdc1080/
driver.rs

1//! The HDC1080 driven over I2C: identify, configure, trigger, wait, and read both results.
2
3use embedded_hal::delay::DelayNs;
4use embedded_hal::i2c::I2c;
5use pamoja_core::Sensor;
6
7use super::{
8    register, AcquisitionMode, Configuration, HumidityResolution, Measurement,
9    TemperatureResolution, DEVICE_ID, I2C_ADDRESS, MANUFACTURER_ID,
10};
11use crate::driver::{read_word, write_word};
12use crate::error::{DriverError, SensorError};
13
14/// An HDC1080 on an I2C bus, measuring temperature and humidity on demand.
15///
16/// [`init`](Hdc1080::init) checks the manufacturer and device id registers and
17/// writes the configuration: the mode that measures temperature and humidity in one
18/// sequence, at the chosen resolutions. [`measure`](Hdc1080::measure) follows the
19/// datasheet's transaction: a pointer write to the temperature register triggers
20/// the acquisition, the conversion time for both resolutions passes, and one
21/// four-byte read returns temperature then humidity. The part does not acknowledge
22/// a read before the results are ready, which surfaces as a bus error rather than a
23/// stale value. As a [`Sensor`] its reading is the [`Measurement`]; one channel is
24/// selected with [`Sensor::map`].
25///
26/// # Examples
27///
28/// A humidor's climate, read from an HDC1080 that is not plugged in yet: the simulated part
29/// holds 21.5 °C and 45 % relative humidity, and the driver waits out both conversions
30/// before it reads them.
31///
32/// ```
33/// use pamoja_core::Sensor;
34/// use pamoja_hal::script::{block_on, DelayLog};
35/// use pamoja_sensors::hdc1080::{sim, Hdc1080};
36///
37/// let mut sensor = Hdc1080::new(sim::reporting(21.5, 45.0), DelayLog::new());
38/// let climate = block_on(sensor.read())?;
39/// assert!((climate.milli_celsius() - 21_500).abs() < 10);
40/// assert!((climate.relative_humidity() - 45.0).abs() < 0.1);
41/// # Ok::<(), pamoja_core::Error>(())
42/// ```
43#[derive(Debug)]
44pub struct Hdc1080<I2C, D> {
45    bus: I2C,
46    delay: D,
47    configuration: Configuration,
48    initialized: bool,
49}
50
51impl<I2C, D> Hdc1080<I2C, D> {
52    /// Wraps an I2C bus; the part has one address.
53    ///
54    /// # Arguments
55    ///
56    /// * `bus` - the I2C bus the part is on.
57    /// * `delay` - the timer that paces the acquisitions.
58    ///
59    /// # Returns
60    ///
61    /// The driver, measuring both channels at 14 bits.
62    pub fn new(bus: I2C, delay: D) -> Self {
63        Hdc1080 {
64            bus,
65            delay,
66            configuration: Configuration {
67                mode: AcquisitionMode::TemperatureThenHumidity,
68                ..Configuration::default()
69            },
70            initialized: false,
71        }
72    }
73
74    /// Sets the resolution of each channel, which sets the conversion time.
75    ///
76    /// # Arguments
77    ///
78    /// * `temperature` - 11 or 14 bits.
79    /// * `humidity` - 8, 11, or 14 bits.
80    ///
81    /// # Returns
82    ///
83    /// The driver.
84    pub fn with_resolutions(
85        mut self,
86        temperature: TemperatureResolution,
87        humidity: HumidityResolution,
88    ) -> Self {
89        self.configuration.temperature_resolution = temperature;
90        self.configuration.humidity_resolution = humidity;
91        self.initialized = false;
92        self
93    }
94
95    /// Returns the configuration the driver writes.
96    pub fn configuration(&self) -> Configuration {
97        self.configuration
98    }
99
100    /// Gives back the bus and the delay.
101    ///
102    /// # Returns
103    ///
104    /// The bus and delay the driver was built from.
105    pub fn release(self) -> (I2C, D) {
106        (self.bus, self.delay)
107    }
108}
109
110impl<I2C: I2c, D: DelayNs> Hdc1080<I2C, D> {
111    /// Checks the part's identity and writes the configuration.
112    ///
113    /// # Errors
114    ///
115    /// Returns [`DriverError::Bus`] if the bus fails, and [`DriverError::Sensor`]
116    /// with [`SensorError::Identity`] if either id register is not an HDC1080's.
117    pub fn init(&mut self) -> Result<(), DriverError<I2C::Error>> {
118        if self.read(register::MANUFACTURER_ID)? != MANUFACTURER_ID
119            || self.read(register::DEVICE_ID)? != DEVICE_ID
120        {
121            return Err(SensorError::Identity.into());
122        }
123        self.write_configuration()?;
124        self.initialized = true;
125        Ok(())
126    }
127
128    /// Triggers one acquisition of both channels and reads the results.
129    ///
130    /// Initializes the part first if [`init`](Hdc1080::init) has not run.
131    ///
132    /// # Returns
133    ///
134    /// The temperature and humidity registers as a [`Measurement`].
135    ///
136    /// # Errors
137    ///
138    /// Returns [`DriverError::Bus`] if the bus fails, which includes the part not
139    /// acknowledging a read before its results are ready, or any error of
140    /// [`init`](Hdc1080::init).
141    pub fn measure(&mut self) -> Result<Measurement, DriverError<I2C::Error>> {
142        if !self.initialized {
143            self.init()?;
144        }
145        self.bus
146            .write(I2C_ADDRESS, &[register::TEMPERATURE])
147            .map_err(DriverError::Bus)?;
148        self.delay
149            .delay_us(self.configuration.conversion_time_micros());
150        let mut bytes = [0u8; 4];
151        self.bus
152            .read(I2C_ADDRESS, &mut bytes)
153            .map_err(DriverError::Bus)?;
154        Ok(Measurement::parse(&bytes))
155    }
156
157    /// Switches the heater, which runs only during acquisitions, on or off.
158    ///
159    /// # Arguments
160    ///
161    /// * `on` - whether the heater runs.
162    ///
163    /// # Errors
164    ///
165    /// Returns [`DriverError::Bus`] if the bus fails.
166    pub fn heater(&mut self, on: bool) -> Result<(), DriverError<I2C::Error>> {
167        self.configuration.heater = on;
168        self.write_configuration()
169    }
170
171    fn write_configuration(&mut self) -> Result<(), DriverError<I2C::Error>> {
172        write_word(
173            &mut self.bus,
174            I2C_ADDRESS,
175            register::CONFIGURATION,
176            self.configuration.to_register(),
177        )
178        .map_err(DriverError::Bus)
179    }
180
181    fn read(&mut self, register: u8) -> Result<u16, DriverError<I2C::Error>> {
182        read_word(&mut self.bus, I2C_ADDRESS, register).map_err(DriverError::Bus)
183    }
184}
185
186impl<I2C, D> Sensor for Hdc1080<I2C, D>
187where
188    I2C: I2c + Send,
189    I2C::Error: core::fmt::Debug,
190    D: DelayNs + Send,
191{
192    type Reading = Measurement;
193
194    async fn read(&mut self) -> pamoja_core::Result<Measurement> {
195        self.measure().map_err(pamoja_core::Error::from)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use alloc::vec;
203    use alloc::vec::Vec;
204    use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource};
205    use pamoja_hal::script::{block_on, DelayLog, I2cScript, I2cStep};
206
207    fn init_steps(configuration: u16) -> Vec<I2cStep> {
208        let [high, low] = configuration.to_be_bytes();
209        vec![
210            I2cStep::write_read(
211                I2C_ADDRESS,
212                [register::MANUFACTURER_ID],
213                MANUFACTURER_ID.to_be_bytes(),
214            ),
215            I2cStep::write_read(I2C_ADDRESS, [register::DEVICE_ID], DEVICE_ID.to_be_bytes()),
216            I2cStep::write(I2C_ADDRESS, [register::CONFIGURATION, high, low]),
217        ]
218    }
219
220    #[test]
221    fn a_measurement_is_triggered_by_the_pointer_write_and_read_after_the_conversion() {
222        let mut steps = init_steps(0x1000);
223        steps.push(I2cStep::write(I2C_ADDRESS, [register::TEMPERATURE]));
224        steps.push(I2cStep::read(
225            I2C_ADDRESS,
226            Measurement::from_physical(21_500, 45_000).to_bytes(),
227        ));
228        let mut sensor = Hdc1080::new(I2cScript::new(steps), DelayLog::new());
229        let measurement = sensor.measure().unwrap();
230        assert_eq!(measurement, Measurement::from_physical(21_500, 45_000));
231        assert!((measurement.milli_celsius() - 21_500).abs() < 10);
232        assert!(measurement.milli_percent().abs_diff(45_000) < 10);
233        let (bus, delay) = sensor.release();
234        assert!(bus.done());
235        assert_eq!(
236            delay.waits_ns(),
237            [12_850_000],
238            "14-bit temperature plus 14-bit humidity"
239        );
240    }
241
242    #[test]
243    fn lower_resolutions_shorten_the_wait_and_reach_the_configuration() {
244        let configuration = Configuration {
245            mode: AcquisitionMode::TemperatureThenHumidity,
246            temperature_resolution: TemperatureResolution::Bits11,
247            humidity_resolution: HumidityResolution::Bits8,
248            ..Configuration::default()
249        };
250        let mut steps = init_steps(configuration.to_register());
251        steps.push(I2cStep::write(I2C_ADDRESS, [register::TEMPERATURE]));
252        steps.push(I2cStep::read(
253            I2C_ADDRESS,
254            Measurement::from_physical(0, 0).to_bytes(),
255        ));
256        let mut sensor = Hdc1080::new(I2cScript::new(steps), DelayLog::new())
257            .with_resolutions(TemperatureResolution::Bits11, HumidityResolution::Bits8);
258        assert_eq!(sensor.configuration(), configuration);
259        sensor.measure().unwrap();
260        let (bus, delay) = sensor.release();
261        assert!(bus.done());
262        assert_eq!(delay.waits_ns(), [(3_650 + 2_500) * 1_000]);
263    }
264
265    #[test]
266    fn a_part_with_the_wrong_id_is_refused_at_either_register() {
267        let steps = [
268            I2cStep::write_read(I2C_ADDRESS, [register::MANUFACTURER_ID], [0x54, 0x49]),
269            I2cStep::write_read(I2C_ADDRESS, [register::DEVICE_ID], [0x10, 0x00]),
270        ];
271        let mut sensor = Hdc1080::new(I2cScript::new(steps), DelayLog::new());
272        assert_eq!(
273            sensor.init(),
274            Err(DriverError::Sensor(SensorError::Identity))
275        );
276        let steps = [I2cStep::write_read(
277            I2C_ADDRESS,
278            [register::MANUFACTURER_ID],
279            [0x00, 0x00],
280        )];
281        let mut sensor = Hdc1080::new(I2cScript::new(steps), DelayLog::new());
282        assert_eq!(
283            sensor.init(),
284            Err(DriverError::Sensor(SensorError::Identity))
285        );
286    }
287
288    #[test]
289    fn a_read_the_part_does_not_acknowledge_is_a_bus_error() {
290        let kind = ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address);
291        let mut steps = init_steps(0x1000);
292        steps.push(I2cStep::write(I2C_ADDRESS, [register::TEMPERATURE]));
293        steps.push(I2cStep::fault(I2C_ADDRESS, kind));
294        let mut sensor = Hdc1080::new(I2cScript::new(steps), DelayLog::new());
295        assert!(matches!(sensor.measure(), Err(DriverError::Bus(_))));
296        assert!(sensor.release().0.done());
297    }
298
299    #[test]
300    fn the_heater_bit_is_written_through_the_configuration() {
301        let mut steps = init_steps(0x1000);
302        steps.push(I2cStep::write(
303            I2C_ADDRESS,
304            [register::CONFIGURATION, 0x30, 0x00],
305        ));
306        let mut sensor = Hdc1080::new(I2cScript::new(steps), DelayLog::new());
307        sensor.init().unwrap();
308        sensor.heater(true).unwrap();
309        assert!(sensor.configuration().heater);
310        assert!(sensor.release().0.done());
311    }
312
313    #[test]
314    fn as_a_sensor_it_reads_and_maps_to_humidity() {
315        let mut steps = init_steps(0x1000);
316        steps.push(I2cStep::write(I2C_ADDRESS, [register::TEMPERATURE]));
317        steps.push(I2cStep::read(
318            I2C_ADDRESS,
319            Measurement::from_physical(21_500, 45_000).to_bytes(),
320        ));
321        let sensor = Hdc1080::new(I2cScript::new(steps), DelayLog::new());
322        let mut humidity = sensor.map(|measurement| measurement.relative_humidity());
323        assert!((block_on(humidity.read()).unwrap() - 45.0).abs() < 0.01);
324    }
325
326    #[test]
327    fn init_and_one_measurement_make_the_transfers_the_datasheet_gives() {
328        use crate::hdc1080::{Measurement, I2C_ADDRESS};
329
330        const PART: u8 = I2C_ADDRESS;
331        let bus = I2cScript::new([
332            I2cStep::write_read(PART, [0xFE], [0x54, 0x49]),
333            I2cStep::write_read(PART, [0xFF], [0x10, 0x50]),
334            I2cStep::write(PART, [0x02, 0x10, 0x00]),
335            I2cStep::write(PART, [0x00]),
336            I2cStep::read(PART, Measurement::from_physical(21_500, 45_000).to_bytes()),
337        ]);
338
339        let mut sensor = Hdc1080::new(bus, DelayLog::new());
340        let measurement = block_on(Sensor::read(&mut sensor)).expect("the scripted part answers");
341        assert_eq!(measurement, Measurement::from_physical(21_500, 45_000));
342        assert!((measurement.milli_celsius() - 21_500).abs() < 10);
343
344        let (bus, delay) = sensor.release();
345        assert!(bus.done());
346        assert_eq!(delay.total_micros(), 6_350 + 6_500);
347    }
348}