capsules_extra/
si7021.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 Silicon Labs SI7021 temperature/humidity sensor.
6//!
7//! <https://www.silabs.com/products/sensors/humidity-sensors/Pages/si7013-20-21.aspx>
8//!
9//! > The Si7006/13/20/21/34 devices are Silicon Labs’ latest generation I2C
10//! > relative humidity and temperature sensors. All members of this device
11//! > family combine fully factory-calibrated humidity and temperature sensor
12//! > elements with an analog to digital converter, signal processing and an I2C
13//! > host interface. Patented use of industry-standard low-K polymer
14//! > dielectrics provides excellent accuracy and long term stability, along
15//! > with low drift and low hysteresis. The innovative CMOS design also offers
16//! > the lowest power consumption in the industry for a relative humidity and
17//! > temperature sensor. The Si7013/20/21/34 devices are designed for high-
18//! > accuracy applications, while the Si7006 is targeted toward lower-accuracy
19//! > applications that traditionally have used discrete RH/T sensors.
20//!
21//! Usage
22//! -----
23//!
24//! ```rust,ignore
25//! # use kernel::static_init;
26//! # use capsules::virtual_alarm::VirtualMuxAlarm;
27//!
28//! let si7021_i2c = static_init!(
29//!     capsules::virtual_i2c::I2CDevice,
30//!     capsules::virtual_i2c::I2CDevice::new(i2c_bus, 0x40));
31//! let si7021_virtual_alarm = static_init!(
32//!     VirtualMuxAlarm<'static, sam4l::ast::Ast>,
33//!     VirtualMuxAlarm::new(mux_alarm));
34//! si7021_virtual_alarm.setup();
35//!
36//! let si7021 = static_init!(
37//!     capsules::si7021::SI7021<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast>>,
38//!     capsules::si7021::SI7021::new(si7021_i2c,
39//!         si7021_virtual_alarm,
40//!         &mut capsules::si7021::BUFFER));
41//! si7021_i2c.set_client(si7021);
42//! si7021_virtual_alarm.set_client(si7021);
43//! ```
44
45use core::cell::Cell;
46use kernel::hil::i2c;
47use kernel::hil::time::{self, ConvertTicks};
48use kernel::utilities::cells::{OptionalCell, TakeCell};
49use kernel::ErrorCode;
50
51#[allow(dead_code)]
52enum Registers {
53    MeasRelativeHumidityHoldMode = 0xe5,
54    MeasRelativeHumidityNoHoldMode = 0xf5,
55    MeasTemperatureHoldMode = 0xe3,
56    MeasTemperatureNoHoldMode = 0xf3,
57    ReadTemperaturePreviousRHMeasurement = 0xe0,
58    Reset = 0xfe,
59    WriteRHTUserRegister1 = 0xe6,
60    ReadRHTUserRegister1 = 0xe7,
61    WriteHeaterControlRegister = 0x51,
62    ReadHeaterControlRegister = 0x11,
63    ReadElectronicIdByteOneA = 0xfa,
64    ReadElectronicIdByteOneB = 0x0f,
65    ReadElectronicIdByteTwoA = 0xfc,
66    ReadElectronicIdByteTwoB = 0xc9,
67    ReadFirmwareVersionA = 0x84,
68    ReadFirmwareVersionB = 0xb8,
69}
70
71/// States of the I2C protocol with the LPS331AP.
72#[derive(Clone, Copy, PartialEq)]
73enum State {
74    Idle,
75    WaitTemp,
76    WaitRh,
77
78    /// States to read the internal ID
79    SelectElectronicId1,
80    ReadElectronicId1,
81    SelectElectronicId2,
82    ReadElectronicId2,
83
84    /// States to take the current measurement
85    TakeTempMeasurementInit,
86    TakeRhMeasurementInit,
87    ReadRhMeasurement,
88    ReadTempMeasurement,
89    GotTempMeasurement,
90    GotRhMeasurement,
91}
92
93#[derive(PartialEq, Eq, Copy, Clone)]
94enum OnDeck {
95    Nothing,
96    Temperature,
97    Humidity,
98}
99
100pub struct SI7021<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> {
101    i2c: &'a I,
102    alarm: &'a A,
103    temp_callback: OptionalCell<&'a dyn kernel::hil::sensors::TemperatureClient>,
104    humidity_callback: OptionalCell<&'a dyn kernel::hil::sensors::HumidityClient>,
105    state: Cell<State>,
106    on_deck: Cell<OnDeck>,
107    buffer: TakeCell<'static, [u8]>,
108}
109
110impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> SI7021<'a, A, I> {
111    pub fn new(i2c: &'a I, alarm: &'a A, buffer: &'static mut [u8]) -> SI7021<'a, A, I> {
112        // setup and return struct
113        SI7021 {
114            i2c,
115            alarm,
116            temp_callback: OptionalCell::empty(),
117            humidity_callback: OptionalCell::empty(),
118            state: Cell::new(State::Idle),
119            on_deck: Cell::new(OnDeck::Nothing),
120            buffer: TakeCell::new(buffer),
121        }
122    }
123
124    pub fn read_id(&self) {
125        self.buffer.take().map(|buffer| {
126            // turn on i2c to send commands
127            self.i2c.enable();
128
129            buffer[0] = Registers::ReadElectronicIdByteOneA as u8;
130            buffer[1] = Registers::ReadElectronicIdByteOneB as u8;
131            // TODO verify errors
132            let _ = self.i2c.write(buffer, 2);
133            self.state.set(State::SelectElectronicId1);
134        });
135    }
136
137    fn init_measurement(&self, buffer: &'static mut [u8]) {
138        let delay = self.alarm.ticks_from_ms(20);
139        self.alarm.set_alarm(self.alarm.now(), delay);
140
141        // Now wait for timer to expire
142        self.buffer.replace(buffer);
143        self.i2c.disable();
144    }
145
146    fn set_idle(&self, buffer: &'static mut [u8]) {
147        self.buffer.replace(buffer);
148        self.i2c.disable();
149        self.state.set(State::Idle);
150    }
151}
152
153impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> i2c::I2CClient for SI7021<'a, A, I> {
154    fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) {
155        match self.state.get() {
156            State::SelectElectronicId1 => {
157                // TODO verify errors
158                let _ = self.i2c.read(buffer, 8);
159                self.state.set(State::ReadElectronicId1);
160            }
161            State::ReadElectronicId1 => {
162                buffer[6] = buffer[0];
163                buffer[7] = buffer[1];
164                buffer[8] = buffer[2];
165                buffer[9] = buffer[3];
166                buffer[10] = buffer[4];
167                buffer[11] = buffer[5];
168                buffer[12] = buffer[6];
169                buffer[13] = buffer[7];
170                buffer[0] = Registers::ReadElectronicIdByteTwoA as u8;
171                buffer[1] = Registers::ReadElectronicIdByteTwoB as u8;
172                // TODO verify errors
173                let _ = self.i2c.write(buffer, 2);
174                self.state.set(State::SelectElectronicId2);
175            }
176            State::SelectElectronicId2 => {
177                // TODO verify errors
178                let _ = self.i2c.read(buffer, 6);
179                self.state.set(State::ReadElectronicId2);
180            }
181            State::ReadElectronicId2 => {
182                self.set_idle(buffer);
183            }
184            State::TakeTempMeasurementInit => {
185                self.init_measurement(buffer);
186                self.state.set(State::WaitTemp);
187            }
188            State::TakeRhMeasurementInit => {
189                self.init_measurement(buffer);
190                self.state.set(State::WaitRh);
191            }
192            State::ReadRhMeasurement => {
193                // TODO verify errors
194                let _ = self.i2c.read(buffer, 2);
195                self.state.set(State::GotRhMeasurement);
196            }
197            State::ReadTempMeasurement => {
198                // TODO verify errors
199                let _ = self.i2c.read(buffer, 2);
200                self.state.set(State::GotTempMeasurement);
201            }
202            State::GotTempMeasurement => {
203                // Temperature in hundredths of degrees centigrade
204                let temp_raw = ((buffer[0] as u32) << 8) | (buffer[1] as u32);
205                let temp = ((temp_raw * 17572) / 65536) as i32 - 4685;
206
207                self.temp_callback.map(|cb| cb.callback(Ok(temp)));
208
209                match self.on_deck.get() {
210                    OnDeck::Humidity => {
211                        self.on_deck.set(OnDeck::Nothing);
212                        buffer[0] = Registers::MeasRelativeHumidityNoHoldMode as u8;
213                        // TODO verify errors
214                        let _ = self.i2c.write(buffer, 1);
215                        self.state.set(State::TakeRhMeasurementInit);
216                    }
217                    _ => {
218                        self.set_idle(buffer);
219                    }
220                }
221            }
222            State::GotRhMeasurement => {
223                // Humidity in hundredths of percent
224                let humidity_raw = ((buffer[0] as u32) << 8) | (buffer[1] as u32);
225                let humidity = (((humidity_raw * 125 * 100) / 65536) - 600) as u16;
226
227                self.humidity_callback
228                    .map(|cb| cb.callback(humidity as usize));
229                match self.on_deck.get() {
230                    OnDeck::Temperature => {
231                        self.on_deck.set(OnDeck::Nothing);
232                        buffer[0] = Registers::MeasTemperatureNoHoldMode as u8;
233                        // TODO verify errors
234                        let _ = self.i2c.write(buffer, 1);
235                        self.state.set(State::TakeTempMeasurementInit);
236                    }
237                    _ => {
238                        self.set_idle(buffer);
239                    }
240                }
241            }
242            _ => {}
243        }
244    }
245}
246
247impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::TemperatureDriver<'a>
248    for SI7021<'a, A, I>
249{
250    fn read_temperature(&self) -> Result<(), ErrorCode> {
251        // This chip handles both humidity and temperature measurements. We can
252        // only start a new measurement if the chip is idle. If it isn't then we
253        // can put this request "on deck" and it will happen after the
254        // temperature measurement has finished.
255        if self.state.get() == State::Idle {
256            self.buffer.take().map_or(Err(ErrorCode::BUSY), |buffer| {
257                // turn on i2c to send commands
258                self.i2c.enable();
259
260                buffer[0] = Registers::MeasTemperatureNoHoldMode as u8;
261                // TODO verify errors
262                let _ = self.i2c.write(buffer, 1);
263                self.state.set(State::TakeTempMeasurementInit);
264                Ok(())
265            })
266        } else {
267            // Queue this request if nothing else queued.
268            if self.on_deck.get() == OnDeck::Nothing {
269                self.on_deck.set(OnDeck::Temperature);
270                Ok(())
271            } else {
272                Err(ErrorCode::BUSY)
273            }
274        }
275    }
276
277    fn set_client(&self, client: &'a dyn kernel::hil::sensors::TemperatureClient) {
278        self.temp_callback.set(client);
279    }
280}
281
282impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::HumidityDriver<'a>
283    for SI7021<'a, A, I>
284{
285    fn read_humidity(&self) -> Result<(), ErrorCode> {
286        // This chip handles both humidity and temperature measurements. We can
287        // only start a new measurement if the chip is idle. If it isn't then we
288        // can put this request "on deck" and it will happen after the
289        // temperature measurement has finished.
290        if self.state.get() == State::Idle {
291            self.buffer.take().map_or(Err(ErrorCode::BUSY), |buffer| {
292                // turn on i2c to send commands
293                self.i2c.enable();
294
295                buffer[0] = Registers::MeasRelativeHumidityNoHoldMode as u8;
296                // TODO verify errors
297                let _ = self.i2c.write(buffer, 1);
298                self.state.set(State::TakeRhMeasurementInit);
299                Ok(())
300            })
301        } else {
302            // Not idle, so queue this request if nothing else is queued. If we have already
303            // queued a request return an error.
304            if self.on_deck.get() == OnDeck::Nothing {
305                self.on_deck.set(OnDeck::Humidity);
306                Ok(())
307            } else {
308                Err(ErrorCode::BUSY)
309            }
310        }
311    }
312
313    fn set_client(&self, client: &'a dyn kernel::hil::sensors::HumidityClient) {
314        self.humidity_callback.set(client);
315    }
316}
317
318impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> time::AlarmClient for SI7021<'a, A, I> {
319    fn alarm(&self) {
320        self.buffer.take().map(|buffer| {
321            // turn on i2c to send commands
322            self.i2c.enable();
323
324            // TODO verify errors
325            let _ = self.i2c.read(buffer, 2);
326            match self.state.get() {
327                State::WaitRh => self.state.set(State::ReadRhMeasurement),
328                State::WaitTemp => self.state.set(State::ReadTempMeasurement),
329                _ => (),
330            }
331        });
332    }
333}