capsules_extra/
ccs811.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 AMS CCS811.
6//!
7//! The AMS CCS811 is an ultra-low power digital gas sensor solution
8//! which integrates a metal oxide (MOX) gas sensor to detect a wide
9//! range of Volatile Organic Compounds (VOCs) for indoor air quality
10//! monitoring using the I2C bus.
11//!
12//! <https://cdn.sparkfun.com/assets/learn_tutorials/1/4/3/CCS811_Datasheet-DS000459.pdf>
13//!
14
15use core::cell::Cell;
16use kernel::deferred_call::{DeferredCall, DeferredCallClient};
17use kernel::hil::i2c::{self, I2CClient, I2CDevice};
18use kernel::hil::sensors::{AirQualityClient, AirQualityDriver};
19use kernel::utilities::cells::{OptionalCell, TakeCell};
20use kernel::ErrorCode;
21
22const STATUS: u8 = 0x00;
23const MEAS_MODE: u8 = 0x01;
24const ALG_RESULT_DATA: u8 = 0x02;
25#[allow(dead_code)]
26const RAW_DATA: u8 = 0x03;
27#[allow(dead_code)]
28const ENV_DATA: u8 = 0x05;
29#[allow(dead_code)]
30const NTC: u8 = 0x06;
31#[allow(dead_code)]
32const THRESHOLDS: u8 = 0x10;
33#[allow(dead_code)]
34const BASELINE: u8 = 0x11;
35const HW_ID: u8 = 0x20;
36#[allow(dead_code)]
37const HW_VERSION: u8 = 0x21;
38#[allow(dead_code)]
39const FW_BOOT_VERSION: u8 = 0x23;
40#[allow(dead_code)]
41const FW_APP_VERSION: u8 = 0x24;
42#[allow(dead_code)]
43const ERROR_ID: u8 = 0xE0;
44const APP_START: u8 = 0xF4;
45const SW_RESET: u8 = 0xFF;
46
47#[derive(Clone, Copy, PartialEq)]
48enum DeviceState {
49    Identify,
50    Reset,
51    StatusCheck,
52    StartApp,
53    Normal,
54}
55
56#[allow(dead_code)]
57#[derive(Clone, Copy, PartialEq)]
58enum Operation {
59    None,
60    Setup,
61    SetEnv,
62    CO2,
63    TVOC,
64}
65
66pub struct Ccs811<'a> {
67    buffer: TakeCell<'static, [u8]>,
68    i2c: &'a dyn I2CDevice,
69    client: OptionalCell<&'a dyn AirQualityClient>,
70    state: Cell<DeviceState>,
71    op: Cell<Operation>,
72
73    /// Deferred caller for deferring client callbacks.
74    deferred_call: DeferredCall,
75    deferred_count: Cell<usize>,
76}
77
78impl<'a> Ccs811<'a> {
79    pub fn new(i2c: &'a dyn I2CDevice, buffer: &'static mut [u8]) -> Self {
80        Self {
81            buffer: TakeCell::new(buffer),
82            i2c,
83            client: OptionalCell::empty(),
84            state: Cell::new(DeviceState::Identify),
85            op: Cell::new(Operation::Setup),
86            deferred_call: DeferredCall::new(),
87            deferred_count: Cell::new(0),
88        }
89    }
90
91    pub fn startup(&self) {
92        self.buffer.take().map(|buffer| {
93            if self.state.get() == DeviceState::Identify {
94                // Read the ID buffer
95                buffer[0] = HW_ID;
96                self.i2c.write_read(buffer, 1, 1).unwrap();
97            }
98        });
99    }
100}
101
102impl<'a> AirQualityDriver<'a> for Ccs811<'a> {
103    fn set_client(&self, client: &'a dyn AirQualityClient) {
104        self.client.set(client);
105    }
106
107    fn specify_environment(
108        &self,
109        temp: Option<i32>,
110        humidity: Option<u32>,
111    ) -> Result<(), ErrorCode> {
112        if self.state.get() != DeviceState::Normal {
113            return Err(ErrorCode::BUSY);
114        }
115
116        if self.op.get() != Operation::None {
117            return Err(ErrorCode::BUSY);
118        }
119
120        self.buffer.take().map(|buffer| {
121            // Set the default values of 50% humidity and 25 degrees Celsius
122            buffer[0] = 0x05;
123            buffer[1] = 0x64;
124            buffer[2] = 0x00;
125            buffer[3] = 0x64;
126            buffer[4] = 0x00;
127
128            // Copy in our calibration data
129            if let Some(hum) = humidity {
130                buffer[1] = hum as u8 * 2;
131            }
132            if let Some(t) = temp {
133                if t < -25 {
134                    buffer[3] = 0;
135                } else {
136                    buffer[3] = (t as u8 + 25) * 2;
137                }
138            }
139
140            self.op.set(Operation::SetEnv);
141            self.i2c.write(buffer, 5).unwrap();
142        });
143
144        Ok(())
145    }
146
147    fn read_co2(&self) -> Result<(), ErrorCode> {
148        if self.state.get() != DeviceState::Normal {
149            return Err(ErrorCode::BUSY);
150        }
151
152        if self.op.get() != Operation::None {
153            return Err(ErrorCode::BUSY);
154        }
155
156        self.buffer.take().map(|buffer| {
157            buffer[0] = ALG_RESULT_DATA;
158
159            self.op.set(Operation::CO2);
160            self.i2c.write_read(buffer, 1, 6).unwrap();
161        });
162
163        Ok(())
164    }
165
166    fn read_tvoc(&self) -> Result<(), ErrorCode> {
167        if self.state.get() != DeviceState::Normal {
168            return Err(ErrorCode::BUSY);
169        }
170
171        if self.op.get() != Operation::None {
172            return Err(ErrorCode::BUSY);
173        }
174
175        self.buffer.take().map(|buffer| {
176            buffer[0] = ALG_RESULT_DATA;
177
178            self.op.set(Operation::TVOC);
179            self.i2c.write_read(buffer, 1, 6).unwrap();
180        });
181
182        Ok(())
183    }
184}
185
186impl I2CClient for Ccs811<'_> {
187    fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) {
188        if status.is_err() {
189            match self.op.get() {
190                Operation::None | Operation::Setup => (),
191                Operation::SetEnv => {
192                    self.client
193                        .map(|client| client.environment_specified(Err(ErrorCode::FAIL)));
194                }
195                Operation::CO2 => {
196                    self.client
197                        .map(|client| client.co2_data_available(Err(ErrorCode::FAIL)));
198                }
199                Operation::TVOC => {
200                    self.client
201                        .map(|client| client.tvoc_data_available(Err(ErrorCode::FAIL)));
202                }
203            }
204            self.buffer.replace(buffer);
205            self.op.set(Operation::None);
206            return;
207        }
208
209        match self.state.get() {
210            DeviceState::Identify => {
211                if buffer[0] != 0x81 {
212                    // We don't have the correct ID, this isn't the correct device
213                    // Just stop here
214                    self.buffer.replace(buffer);
215                    return;
216                }
217
218                buffer[0] = SW_RESET;
219                buffer[1] = 0x11;
220                buffer[2] = 0xE5;
221                buffer[3] = 0x72;
222                buffer[4] = 0x8A;
223                self.i2c.write(buffer, 5).unwrap();
224                self.state.set(DeviceState::Reset);
225            }
226            DeviceState::Reset => {
227                self.deferred_call.set();
228                self.buffer.replace(buffer);
229            }
230            DeviceState::StatusCheck => {
231                if buffer[0] & 0x01 == 0x01 {
232                    self.buffer.replace(buffer);
233                    return;
234                }
235
236                if buffer[0] & 0x04 == 0x04 {
237                    self.buffer.replace(buffer);
238                    return;
239                }
240
241                buffer[0] = APP_START;
242                self.i2c.write(buffer, 1).unwrap();
243                self.state.set(DeviceState::StartApp);
244            }
245            DeviceState::StartApp => {
246                buffer[0] = MEAS_MODE;
247                // Drive mode: 1 - Constant power mode, IAQ measurement every second
248                // Interrupt data ready: 0 - Interrupt generation is disabled
249                // Interrup Threshold: 0 - Interrupt mode operates normally
250                buffer[1] = (1 << 4) | (0 << 3) | (0 << 2);
251                self.i2c.write(buffer, 2).unwrap();
252
253                self.state.set(DeviceState::Normal);
254            }
255            DeviceState::Normal => {
256                match self.op.get() {
257                    Operation::None => (),
258                    Operation::Setup => {
259                        self.buffer.replace(buffer);
260                        self.deferred_call.set();
261                        return;
262                    }
263                    Operation::SetEnv => {
264                        self.client
265                            .map(|client| client.environment_specified(Ok(())));
266                    }
267                    Operation::CO2 => {
268                        let co2 = (buffer[0] as u32) << 8 | buffer[1] as u32;
269                        let status = buffer[4];
270                        let _error_id = buffer[5];
271
272                        if status & 0x01 == 0x01 {
273                            self.client
274                                .map(|client| client.co2_data_available(Err(ErrorCode::FAIL)));
275                        }
276
277                        self.client.map(|client| client.co2_data_available(Ok(co2)));
278                    }
279                    Operation::TVOC => {
280                        let tvoc = (buffer[2] as u32) << 8 | buffer[3] as u32;
281                        let status = buffer[4];
282                        let _error_id = buffer[5];
283
284                        if status & 0x01 == 0x01 {
285                            self.client
286                                .map(|client| client.tvoc_data_available(Err(ErrorCode::FAIL)));
287                        }
288
289                        self.client
290                            .map(|client| client.tvoc_data_available(Ok(tvoc)));
291                    }
292                }
293                self.buffer.replace(buffer);
294                self.op.set(Operation::None);
295            }
296        }
297    }
298}
299
300impl DeferredCallClient for Ccs811<'_> {
301    fn handle_deferred_call(&self) {
302        if self.deferred_count.get() > 1000 {
303            match self.state.get() {
304                DeviceState::Reset => {
305                    self.buffer.take().map(|buffer| {
306                        buffer[0] = STATUS;
307                        self.i2c.write_read(buffer, 1, 1).unwrap();
308
309                        self.state.set(DeviceState::StatusCheck);
310                    });
311                }
312                DeviceState::Normal => {
313                    self.op.set(Operation::None);
314                }
315                _ => unreachable!(),
316            }
317        } else {
318            self.deferred_count.set(self.deferred_count.get() + 1);
319            self.deferred_call.set();
320        }
321    }
322
323    fn register(&'static self) {
324        self.deferred_call.register(self);
325    }
326}