capsules_extra/
sht4x.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 2023.
4
5//! Driver for SHT4x Temperature and Humidity Sensor
6
7use core::cell::Cell;
8use enum_primitive::cast::FromPrimitive;
9use enum_primitive::enum_from_primitive;
10use kernel::hil::i2c;
11use kernel::hil::time::{self, Alarm, ConvertTicks};
12use kernel::utilities::cells::{OptionalCell, TakeCell};
13use kernel::ErrorCode;
14
15pub static BASE_ADDR: u8 = 0x44;
16
17enum_from_primitive! {
18    enum Registers {
19        /// sht4x has no Clock Stretching
20        /// Measurement High Repeatability
21        MEASHIGHREP = 0xFD,
22        /// Measurement Medium Repeatability
23        MEASMEDREP = 0xF6,
24        /// Measurement Low Repeatability
25        MEASLOWREP = 0xE0,
26        /// Read Serial Number
27        READSERIALNUM = 0x89,
28        /// Soft Reset
29        SOFTRESET = 0x94,
30        /// Activate heater with 200mW for 1s
31        HEATER200MW1S = 0x39,
32        /// Activate heater with 200mW for 0.1s
33        HEATER200MW01S = 0x32,
34        /// Activate heater with 110mW for 1s
35        HEATER110MW1S = 0x2F,
36        /// Activate heater with 110mW for 0.1s
37        HEATER110MW01S = 0x24,
38        /// Activate heater with 20mW for 1s
39        HEATER20MW1S = 0x1E,
40        /// Activate heater with 20mW for 0.1s
41        HEATER20MW01S = 0x15,
42    }
43}
44
45#[derive(Clone, Copy, PartialEq)]
46enum State {
47    Idle,
48    Read,
49    ReadData,
50}
51
52fn crc8(data: &[u8]) -> u8 {
53    let polynomial = 0x31;
54    let mut crc = 0xff;
55
56    for x in 0..data.len() {
57        crc ^= data[x];
58        for _i in 0..8 {
59            if (crc & 0x80) != 0 {
60                crc = crc << 1 ^ polynomial;
61            } else {
62                crc <<= 1;
63            }
64        }
65    }
66    crc
67}
68
69pub struct SHT4x<'a, A: Alarm<'a>, I: i2c::I2CDevice> {
70    i2c: &'a I,
71    humidity_client: OptionalCell<&'a dyn kernel::hil::sensors::HumidityClient>,
72    temperature_client: OptionalCell<&'a dyn kernel::hil::sensors::TemperatureClient>,
73    state: Cell<State>,
74    buffer: TakeCell<'static, [u8]>,
75    read_temp: Cell<bool>,
76    read_hum: Cell<bool>,
77    alarm: &'a A,
78}
79
80impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> SHT4x<'a, A, I> {
81    pub fn new(i2c: &'a I, buffer: &'static mut [u8], alarm: &'a A) -> SHT4x<'a, A, I> {
82        SHT4x {
83            i2c,
84            humidity_client: OptionalCell::empty(),
85            temperature_client: OptionalCell::empty(),
86            state: Cell::new(State::Idle),
87            buffer: TakeCell::new(buffer),
88            read_temp: Cell::new(false),
89            read_hum: Cell::new(false),
90            alarm,
91        }
92    }
93
94    fn read_humidity(&self) -> Result<(), ErrorCode> {
95        if self.read_hum.get() {
96            Err(ErrorCode::BUSY)
97        } else {
98            if self.state.get() == State::Idle {
99                let result = self.read_temp_hum();
100                if result.is_ok() {
101                    self.read_hum.set(true);
102                }
103                result
104            } else {
105                self.read_hum.set(true);
106                Ok(())
107            }
108        }
109    }
110
111    fn read_temperature(&self) -> Result<(), ErrorCode> {
112        if self.read_temp.get() {
113            Err(ErrorCode::BUSY)
114        } else {
115            if self.state.get() == State::Idle {
116                let result = self.read_temp_hum();
117                if result.is_ok() {
118                    self.read_temp.set(true);
119                }
120                result
121            } else {
122                self.read_temp.set(true);
123                Ok(())
124            }
125        }
126    }
127
128    fn read_temp_hum(&self) -> Result<(), ErrorCode> {
129        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
130            self.state.set(State::Read);
131            self.i2c.enable();
132
133            buffer[0] = Registers::MEASHIGHREP as u8;
134
135            let _res = self.i2c.write(buffer, 1);
136            match _res {
137                Ok(()) => Ok(()),
138                Err((error, data)) => {
139                    self.buffer.replace(data);
140                    self.state.set(State::Idle);
141                    self.i2c.disable();
142                    Err(error.into())
143                }
144            }
145        })
146    }
147}
148
149impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> time::AlarmClient for SHT4x<'a, A, I> {
150    fn alarm(&self) {
151        let state = self.state.get();
152        match state {
153            State::Read => {
154                self.state.set(State::ReadData);
155                self.buffer.take().map(|buffer| {
156                    let _res = self.i2c.read(buffer, 6);
157                });
158            }
159            _ => {
160                // This should never happen
161                panic!("SHT4x Invalid alarm!");
162            }
163        }
164    }
165}
166
167impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> i2c::I2CClient for SHT4x<'a, A, I> {
168    fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) {
169        match status {
170            Ok(()) => {
171                let state = self.state.get();
172
173                match state {
174                    State::ReadData => {
175                        let read_temp_res = if self.read_temp.get() {
176                            self.read_temp.set(false);
177                            if crc8(&buffer[0..2]) == buffer[2] {
178                                let mut stemp = buffer[0] as u32;
179                                stemp <<= 8;
180                                stemp |= buffer[1] as u32;
181                                let stemp = ((4375 * stemp) >> 14) as i32 - 4500;
182                                Some(Ok(stemp))
183                            } else {
184                                Some(Err(ErrorCode::FAIL))
185                            }
186                        } else {
187                            None
188                        };
189
190                        let read_hum_res = if self.read_hum.get() {
191                            self.read_hum.set(false);
192                            if crc8(&buffer[3..5]) == buffer[5] {
193                                let mut shum = buffer[3] as u32;
194                                shum <<= 8;
195                                shum |= buffer[4] as u32;
196                                shum = (625 * shum) >> 12;
197                                Some(shum as usize)
198                            } else {
199                                Some(usize::MAX)
200                            }
201                        } else {
202                            None
203                        };
204
205                        self.buffer.replace(buffer);
206                        self.state.set(State::Idle);
207
208                        read_temp_res.map(|res| {
209                            self.temperature_client.map(|cb| cb.callback(res));
210                        });
211
212                        read_hum_res.map(|res| {
213                            self.humidity_client.map(|cb| cb.callback(res));
214                        });
215                    }
216                    State::Read => {
217                        self.buffer.replace(buffer);
218                        let interval = self.alarm.ticks_from_ms(20);
219                        self.alarm.set_alarm(self.alarm.now(), interval);
220                    }
221                    _ => {}
222                }
223            }
224            Err(i2c_err) => {
225                self.buffer.replace(buffer);
226                self.i2c.disable();
227                self.state.set(State::Idle);
228                if self.read_temp.get() {
229                    self.read_temp.set(false);
230                    self.temperature_client
231                        .map(|cb| cb.callback(Err(i2c_err.into())));
232                }
233                if self.read_hum.get() {
234                    self.read_hum.set(false);
235                    self.humidity_client.map(|cb| cb.callback(usize::MAX));
236                }
237            }
238        }
239    }
240}
241
242impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::HumidityDriver<'a>
243    for SHT4x<'a, A, I>
244{
245    fn set_client(&self, client: &'a dyn kernel::hil::sensors::HumidityClient) {
246        self.humidity_client.set(client);
247    }
248
249    fn read_humidity(&self) -> Result<(), ErrorCode> {
250        self.read_humidity()
251    }
252}
253
254impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::TemperatureDriver<'a>
255    for SHT4x<'a, A, I>
256{
257    fn set_client(&self, client: &'a dyn kernel::hil::sensors::TemperatureClient) {
258        self.temperature_client.set(client);
259    }
260
261    fn read_temperature(&self) -> Result<(), ErrorCode> {
262        self.read_temperature()
263    }
264}