capsules_extra/
hts221.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! SyscallDriver for the STMicro HTS221 relative humidity and temperature
6//! sensor using the I2C bus.
7//!
8//! <https://www.st.com/en/mems-and-sensors/hts221.html>
9//!
10//! > The HTS221 is an ultra-compact sensor for relative humidity and
11//! > temperature. It includes a sensing element and a mixed signal ASIC
12//! > to provide the measurement information through digital serial
13//! > interfaces. The sensing element consists of a polymer dielectric
14//! > planar capacitor structure capable of detecting relative humidity
15//! > variations and is manufactured using a dedicated ST process.
16//!
17//! Driver Semantics
18//! ----------------
19//!
20//! This driver exposes the HTS221's temperature and humidity functionality via
21//! the [TemperatureDriver] and [HumidityDriver] HIL interfaces. The driver
22//! _does not_ attempt to support multiple concurrent requests for temperature
23//! or multiple concurrent requests for humidity, but _does_ support a
24//! concurrent request each for temperature and humidity. It is not the role of
25//! this driver to provide virtualization to multiple clients, but it does
26//! provide virtualization to allow it to be used from both a temperature and
27//! humidity driver.
28//!
29//! Specifically, the implementation _always_ reads both temperature and
30//! humidity (the chip always provides both anyway). If the driver receives a
31//! request for either temperature or humidity while a request for the other is
32//! outstanding, both will be returned to their respective clients when the I2C
33//! transaction is completed, rather than performing two separate transactions.
34//!
35//! Polling for data readiness
36//! --------------------------
37//!
38//! The HTS221 has a data-ready line that can provide an interrupt when
39//! temperature and humidity data is ready. However, the primary board (the Nano
40//! 33 BLE Sense) this driver was developed for does not connect that line to
41//! the MCU and, typically, data is ready within a couple read/write I2C
42//! transactions. So, the driver **polls** the status register instead. This is
43//! probably not optimal from an energy perspective, so should a use case for an
44//! interrupt driven interface arise, some brave soul should modify the driver
45//! to support both.
46//!
47//! Limitations
48//! -----------
49//!
50//! The driver uses floating point math to adjust readings based on the
51//! calibration registers. This is accurate and matches the chip's datasheet's
52//! recommendation, but could increase code size significantly in platforms that
53//! do not have hardware support for floating point operations.
54//!
55//! Usage
56//! -----
57//!
58//! ```rust,ignore
59//! # use kernel::static_init;
60//!
61//! let hts221_i2c = static_init!(
62//!     capsules::virtual_i2c::I2CDevice,
63//!     capsules::virtual_i2c::I2CDevice::new(i2c_bus, 0x5f));
64//! let hts221 = static_init!(
65//!     capsules::hts221::Hts221<'static>,
66//!     capsules::hts221::Hts221::new(hts221_i2c,
67//!         &mut capsules::hts221::BUFFER));
68//! hts221_i2c.set_client(hts221);
69//! ```
70
71use core::cell::Cell;
72use kernel::hil::i2c::{self, I2CClient, I2CDevice};
73use kernel::hil::sensors::{HumidityClient, HumidityDriver, TemperatureClient, TemperatureDriver};
74use kernel::utilities::cells::{OptionalCell, TakeCell};
75use kernel::ErrorCode;
76
77const REG_AUTO_INCREMENT: u8 = 1 << 7;
78const CTRL_REG1: u8 = 0x20;
79const STATUS_REG: u8 = 0x27;
80const HUMID0_REG: u8 = 0x28;
81const CALIB_REG_1ST: u8 = 0x30;
82
83#[derive(Copy, Clone, Debug)]
84struct CalibrationData {
85    temp_slope: f32,
86    temp_intercept: f32,
87    humidity_slope: f32,
88    humidity_intercept: f32,
89}
90
91pub struct Hts221<'a, I: I2CDevice> {
92    buffer: TakeCell<'static, [u8]>,
93    i2c: &'a I,
94    temperature_client: OptionalCell<&'a dyn TemperatureClient>,
95    humidity_client: OptionalCell<&'a dyn HumidityClient>,
96    state: Cell<State>,
97    pending_temperature: Cell<bool>,
98    pending_humidity: Cell<bool>,
99}
100
101impl<'a, I: I2CDevice> Hts221<'a, I> {
102    pub fn new(i2c: &'a I, buffer: &'static mut [u8]) -> Self {
103        Hts221 {
104            buffer: TakeCell::new(buffer),
105            i2c,
106            temperature_client: OptionalCell::empty(),
107            humidity_client: OptionalCell::empty(),
108            state: Cell::new(State::Reset),
109            pending_temperature: Cell::new(false),
110            pending_humidity: Cell::new(false),
111        }
112    }
113
114    // Helper method to kick off a reading for both temperature and humidity.
115    //
116    // There are three cases:
117    //   1. There is no calibration data available yet ([State::Reset])
118    //   2. There is calibration data already ([State::Idle])
119    //   3. There is a reading already taking place
120    fn start_reading(&self) -> Result<(), ErrorCode> {
121        self.buffer
122            .take()
123            .map(|buffer| {
124                self.i2c.enable();
125                match self.state.get() {
126                    State::Reset => {
127                        buffer[0] = REG_AUTO_INCREMENT | CALIB_REG_1ST;
128
129                        if let Err((_error, buffer)) = self.i2c.write_read(buffer, 1, 16) {
130                            self.buffer.replace(buffer);
131                            self.i2c.disable();
132                        } else {
133                            self.state.set(State::Calibrating);
134                        }
135                    }
136                    State::Idle(calibration_data, _, _) => {
137                        buffer[0] = REG_AUTO_INCREMENT | CTRL_REG1;
138                        buffer[1] = 1 << 2 | 1 << 7; // BDU + PD
139                        buffer[2] = 1; // ONE SHOT
140
141                        if let Err((_error, buffer)) = self.i2c.write(buffer, 3) {
142                            self.buffer.replace(buffer);
143                            self.i2c.disable();
144                        } else {
145                            self.state.set(State::InitiateReading(calibration_data));
146                        }
147                    }
148                    _ => {} // Should really never happen since we only have `buffer` available in the above two states
149                }
150            })
151            .ok_or(ErrorCode::BUSY)
152    }
153}
154
155impl<'a, I: I2CDevice> TemperatureDriver<'a> for Hts221<'a, I> {
156    fn set_client(&self, client: &'a dyn TemperatureClient) {
157        self.temperature_client.set(client);
158    }
159
160    fn read_temperature(&self) -> Result<(), ErrorCode> {
161        self.pending_temperature.set(true);
162        if !self.pending_humidity.get() {
163            self.start_reading()
164        } else {
165            Ok(())
166        }
167    }
168}
169
170impl<'a, I: I2CDevice> HumidityDriver<'a> for Hts221<'a, I> {
171    fn set_client(&self, client: &'a dyn HumidityClient) {
172        self.humidity_client.set(client);
173    }
174
175    fn read_humidity(&self) -> Result<(), ErrorCode> {
176        self.pending_humidity.set(true);
177        if !self.pending_temperature.get() {
178            self.start_reading()
179        } else {
180            Ok(())
181        }
182    }
183}
184
185#[derive(Clone, Copy, Debug)]
186enum State {
187    Reset,
188    Calibrating,
189    InitiateReading(CalibrationData),
190    CheckStatus(CalibrationData),
191    Read(CalibrationData),
192    Idle(CalibrationData, i32, usize),
193}
194
195impl<I: I2CDevice> I2CClient for Hts221<'_, I> {
196    fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) {
197        if let Err(i2c_err) = status {
198            self.state.set(State::Idle(
199                CalibrationData {
200                    temp_slope: 0.0,
201                    temp_intercept: 0.0,
202                    humidity_slope: 0.0,
203                    humidity_intercept: 0.0,
204                },
205                0,
206                0,
207            ));
208            self.buffer.replace(buffer);
209            self.temperature_client
210                .map(|client| client.callback(Err(i2c_err.into())));
211            self.humidity_client.map(|client| client.callback(0));
212            return;
213        }
214
215        match self.state.get() {
216            State::Calibrating => {
217                let h0rh = buffer[0] as f32;
218                let h1rh = buffer[1] as f32;
219                let h0t0out = ((buffer[6] as i16) | ((buffer[7] as i16) << 8)) as f32;
220                let h1t0out = ((buffer[10] as i16) | ((buffer[11] as i16) << 8)) as f32;
221
222                let humidity_slope = (h1rh - h0rh) / (2.0 * (h1t0out - h0t0out));
223                let humidity_intercept = (h0rh / 2.0) - humidity_slope * h0t0out;
224
225                let t0deg_c = ((buffer[2] as i16) | (((buffer[5] & 0b11) as i16) << 8)) as f32;
226                let t1deg_c = ((buffer[3] as i16) | (((buffer[5] & 0b1100) as i16) << 6)) as f32;
227
228                let t0out = ((buffer[12] as i16) | ((buffer[13] as i16) << 8)) as f32;
229                let t1out = ((buffer[14] as i16) | ((buffer[15] as i16) << 8)) as f32;
230
231                let temp_slope = (t1deg_c - t0deg_c) / (8.0 * (t1out - t0out));
232                let temp_intercept = (t0deg_c / 8.0) - temp_slope * t0out;
233
234                buffer[0] = REG_AUTO_INCREMENT | CTRL_REG1;
235                buffer[1] = 1 << 2 | 1 << 7; // BDU + PD
236                buffer[2] = 1; // ONE SHOT
237
238                if let Err((error, buffer)) = self.i2c.write(buffer, 3) {
239                    self.state.set(State::Idle(
240                        CalibrationData {
241                            temp_slope: 0.0,
242                            temp_intercept: 0.0,
243                            humidity_slope: 0.0,
244                            humidity_intercept: 0.0,
245                        },
246                        0,
247                        0,
248                    ));
249                    self.buffer.replace(buffer);
250                    self.temperature_client
251                        .map(|client| client.callback(Err(error.into())));
252                    self.humidity_client.map(|client| client.callback(0));
253                } else {
254                    self.state.set(State::InitiateReading(CalibrationData {
255                        temp_slope,
256                        temp_intercept,
257                        humidity_slope,
258                        humidity_intercept,
259                    }));
260                }
261            }
262            State::InitiateReading(calibration_data) => {
263                buffer[0] = STATUS_REG;
264
265                if let Err((error, buffer)) = self.i2c.write_read(buffer, 1, 1) {
266                    self.state.set(State::Idle(
267                        CalibrationData {
268                            temp_slope: 0.0,
269                            temp_intercept: 0.0,
270                            humidity_slope: 0.0,
271                            humidity_intercept: 0.0,
272                        },
273                        0,
274                        0,
275                    ));
276                    self.buffer.replace(buffer);
277                    self.temperature_client
278                        .map(|client| client.callback(Err(error.into())));
279                    self.humidity_client.map(|client| client.callback(0));
280                } else {
281                    self.state.set(State::CheckStatus(calibration_data));
282                }
283            }
284            State::CheckStatus(calibration_data) => {
285                if buffer[0] & 0b11 == 0b11 {
286                    buffer[0] = REG_AUTO_INCREMENT | HUMID0_REG;
287
288                    if let Err((error, buffer)) = self.i2c.write_read(buffer, 1, 4) {
289                        self.state.set(State::Idle(
290                            CalibrationData {
291                                temp_slope: 0.0,
292                                temp_intercept: 0.0,
293                                humidity_slope: 0.0,
294                                humidity_intercept: 0.0,
295                            },
296                            0,
297                            0,
298                        ));
299                        self.buffer.replace(buffer);
300                        self.temperature_client
301                            .map(|client| client.callback(Err(error.into())));
302                        self.humidity_client.map(|client| client.callback(0));
303                    } else {
304                        self.state.set(State::Read(calibration_data));
305                    }
306                } else {
307                    buffer[0] = STATUS_REG;
308
309                    if let Err((error, buffer)) = self.i2c.write_read(buffer, 1, 1) {
310                        self.state.set(State::Idle(
311                            CalibrationData {
312                                temp_slope: 0.0,
313                                temp_intercept: 0.0,
314                                humidity_slope: 0.0,
315                                humidity_intercept: 0.0,
316                            },
317                            0,
318                            0,
319                        ));
320                        self.buffer.replace(buffer);
321                        self.temperature_client
322                            .map(|client| client.callback(Err(error.into())));
323                        self.humidity_client.map(|client| client.callback(0));
324                    }
325                }
326            }
327            State::Read(calibration_data) => {
328                let humidity_raw = ((buffer[0] as i16) | ((buffer[1] as i16) << 8)) as f32;
329                let humidity = ((humidity_raw * calibration_data.humidity_slope
330                    + calibration_data.humidity_intercept)
331                    * 100.0) as usize;
332
333                let temperature_raw = ((buffer[2] as i16) | ((buffer[3] as i16) << 8)) as f32;
334                let temperature = ((temperature_raw * calibration_data.temp_slope
335                    + calibration_data.temp_intercept)
336                    * 100.0) as i32;
337                buffer[0] = CTRL_REG1;
338                // TODO(alevy): this is a workaround for a bug. We should be able to turn
339                // off the the sensor between transactions, and turn it back on (as is done
340                // in [start_reading]), but doing so seems not to work and the sensor's
341                // Status register never updates to read after the first transaction. For
342                // now, leave it on and waste 2uA.
343                buffer[1] = 1 << 7; // Leave PD bit on
344
345                if let Err((error, buffer)) = self.i2c.write(buffer, 2) {
346                    self.state.set(State::Idle(
347                        CalibrationData {
348                            temp_slope: 0.0,
349                            temp_intercept: 0.0,
350                            humidity_slope: 0.0,
351                            humidity_intercept: 0.0,
352                        },
353                        0,
354                        0,
355                    ));
356                    self.buffer.replace(buffer);
357                    self.temperature_client
358                        .map(|client| client.callback(Err(error.into())));
359                    self.humidity_client.map(|client| client.callback(0));
360                } else {
361                    self.state
362                        .set(State::Idle(calibration_data, temperature, humidity));
363                }
364            }
365            State::Idle(_, temperature, humidity) => {
366                self.buffer.replace(buffer);
367                self.i2c.disable();
368                if self.pending_temperature.get() {
369                    self.pending_temperature.set(false);
370                    self.temperature_client
371                        .map(|client| client.callback(Ok(temperature)));
372                }
373                if self.pending_humidity.get() {
374                    self.pending_humidity.set(false);
375                    self.humidity_client.map(|client| client.callback(humidity));
376                }
377            }
378            State::Reset => {} // should never happen
379        }
380    }
381}