ccs811_rs/
lib.rs

1//! This is a platform agnostic Rust driver for the CCS881 indoor air quality
2//! sensor, based on the [`embedded-hal blocking i2c`] traits.
3//!
4//! [`embedded-hal blocking i2c`]: https://github.com/rust-embedded/embedded-hal
5//!
6//! This driver allows you to:
7//! - Start App mode.
8//! - Read all info available [eCO2, eTVOC, Status, ErrorId, RawData].
9//! - Reset device.
10//!
11//! ## The device
12//!
13//! The CCS811 The CCS811 is an ultra-low power digital gas sensor solution
14//! which integrates a metal oxide (MOX) gas sensor to detect a
15//! wide range of Volatile Organic Compounds (VOCs) for indoor
16//! air quality monitoring with a microcontroller unit (MCU), which
17//! includes an Analog-to-Digital converter (ADC), and an I²C
18//! interface.
19//!
20//! Datasheet:
21//! - [ccs811](https://ams.com/documents/20143/36005/CCS811_DS000459_7-00.pdf/3cfdaea5-b602-fe28-1a14-18776b61a35a)
22//!
23//!
24//! ## Usage examples (see also examples folder)
25//!
26//! To use this driver, import this crate and an `embedded-hal` implementation,
27//! then instantiate the device.
28
29#![deny(missing_docs, unsafe_code, warnings)]
30#![no_std]
31
32extern crate embedded_hal as hal;
33
34use hal::blocking::i2c::{Read, Write, WriteRead};
35
36/// All possible errors in this crate
37#[derive(Debug)]
38pub enum Error<E> {
39    /// I²C bus error
40    I2C(E),
41    /// Invalid input data
42    InvalidInputData,
43}
44
45/// Measurement Mode
46#[derive(Debug, Clone, Copy, PartialEq)]
47#[repr(u8)]
48pub enum DriveMode {
49    /// Idle Mode
50    DriveMode0Idle = 0b0u8,
51    /// Mode 1 : 1 read per second    
52    DriveMode1Sec = 0b1_0000u8,
53    /// Mode 2 : read each 10 seconds
54    DriveMode10Sec = 0b10_0000u8,
55    /// Mode 3 : read each 60 seconds
56    DriveMode60Sec = 0b11_0000u8,
57    /// Mode 4 : read every 250 ms, expose only raw data
58    DriveModeRawData = 0b100_0000u8,
59}
60
61/// Interrupt Mode
62#[derive(Debug, Clone, Copy, PartialEq)]
63#[repr(u8)]
64pub enum InterruptDataReady {
65    /// Assert nINT when data is available in ALG_RESULT_DATA
66    Enabled = 0b1000u8,
67    /// Interrupt generation is disabled
68    Disabled = 0b0u8,
69}
70
71/// Interrupt Mode
72#[derive(Debug, Clone, Copy, PartialEq)]
73#[repr(u8)]
74pub enum InterruptThreshold {
75    /// Assert nINT if ALG_RESULT_DATA crosses thresholds
76    Enabled = 0b100u8,
77    /// Interrupt mode, if asserted, operates normally
78    Disabled = 0x0,
79}
80
81/// ErrorId Codes
82#[derive(Debug, Clone, Copy, PartialEq)]
83#[repr(u8)]
84pub enum ErrorIdCodes {
85    /// WRITE_REG_INVALID
86    ///
87    /// "The CCS811 received an I2C write request addressed to this station but with
88    /// invalid register address ID"
89    WriteRegInvalid = 0b0u8,
90    /// READ_REG_INVALID
91    ///
92    /// "The CCS811 received an I2C read request to a mailbox ID that is invalid"
93    ReadRegInvalid = 0b10u8,
94    /// MEASMODE_INVALID
95    ///
96    /// "The CCS811 received an I2C request to write an unsupported mode to
97    /// MEAS_MODE"
98    MeasModeInvalid = 0b100u8,
99    /// MAX_RESISTANCE
100    ///
101    /// "The sensor resistance measurement has reached or exceeded the maximum
102    /// range"
103    MaxResistance = 0b1000u8,
104    /// HEATER_FAULT
105    ///
106    /// "The Heater current in the CCS811 is not in range"
107    HeaterFault = 0b1_0000u8,
108    /// HEATER_SUPPLY
109    ///
110    /// "The Heater voltage is not being applied correctly"
111    HeaterSupply = 0b10_0000u8,
112}
113
114/// Boot mode Register addresses
115/// Taken from the CCS811 data sheet (Figure 25, p.26)
116#[derive(Copy, Clone, Debug, Eq, PartialEq)]
117#[repr(u8)]
118pub enum BootRegister {
119    /// Status (RO)
120    ///
121    /// "Status register"
122    Status = 0x00,
123    /// HW_ID (RO)
124    ///
125    /// "Hardware ID. The value is 0x81"
126    HwId = 0x20,
127    /// HW Version (RO)
128    ///
129    /// "Hardware Version. The value is 0x1X"
130    HwVersion = 0x21,
131    /// FW_Boot_Version (RO)
132    ///
133    /// "Firmware Boot Version. The first 2 bytes contain the firmware version number for the boot code."
134    FwBootVersion = 0x23,
135    /// FW_App_Version (RO)
136    ///
137    /// "Firmware Application Version. The first 2 bytes contain the firmware version number for the application code"
138    FwAppVersion = 0x24,
139    /// Error Id
140    ///
141    /// "Error ID. When the status register reports an error its source is located in this register"
142    ErrorId = 0xE0,
143    /// APP_START (WO)
144    ///
145    /// "Status register"
146    AppStart = 0xF4,
147    /// SW_RESET
148    ///
149    /// "If the correct 4 bytes (0x11 0xE5 0x72 0x8A) are written to this register in a single
150    /// sequence the device will reset and return to BOOT mode."
151    SwReset = 0xFF,
152}
153
154/// Application mode Register addresses
155/// Taken from the CCS811 data sheet (Figure 14, p.17)
156#[derive(Copy, Clone, Debug, Eq, PartialEq)]
157#[repr(u8)]
158pub enum AppRegister {
159    /// Status (RO)
160    ///
161    /// "Status register"
162    Status = 0x00,
163    /// Measurement Mode (RW)
164    ///
165    /// "Measurement mode and conditions register"
166    MeasMode = 0x01,
167    /// ALG_RESULT_DATA (RO)
168    ///
169    /// "Algorithm result. The most significant 2 bytes contain a
170    /// ppm estimate of the equivalent CO 2 (eCO 2 ) level, and
171    /// the next two bytes contain a ppb estimate of the total
172    /// VOC level."
173    AlgResultData = 0x02,
174    /// RAW_DATA (RO)
175    ///
176    /// "Raw ADC data values for resistance and current source used."
177    RawData = 0x03,
178    /// ENV_DATA (WO)
179    ///
180    /// "Temperature and humidity data can be written to enable compensation"
181    EnvData = 0x05,
182    ///THRESHOLDS (WO)
183    ///
184    /// "Thresholds for operation when interrupts are only generated when eCO 2 ppm crosses a threshold"
185    Thresholds = 0x10,
186    /// BASELINE (R/W)
187    ///
188    /// "The encoded current baseline value can be read. A previously saved encoded baseline can be written."
189    Baseline = 0x11,
190    /// HW_ID (RO)
191    ///
192    /// "Hardware ID. The value is 0x81"
193    HwId = 0x20,
194    /// HW Version (RO)
195    ///
196    /// "Hardware Version. The value is 0x1X"
197    HwVersion = 0x21,
198    /// FW_Boot_Version (RO)
199    ///
200    /// "Firmware Boot Version. The first 2 bytes contain the firmware version number for the boot code."
201    FwBootVersion = 0x23,
202    /// FW_App_Version (RO)
203    ///
204    /// "Firmware Application Version. The first 2 bytes contain the firmware version number for the application code"
205    FwAppVersion = 0x24,
206    /// Internal_State (RO)
207    ///
208    /// "Internal Status register"
209    InternalStatus = 0xA0,
210    /// Error Id
211    ///
212    /// "Error ID. When the status register reports an error its source is located in this register"
213    ErrorId = 0xE0,
214    /// SW_RESET
215    ///
216    /// "If the correct 4 bytes (0x11 0xE5 0x72 0x8A) are written to this register in a single
217    /// sequence the device will reset and return to BOOT mode."
218    SwReset = 0xFF,
219}
220
221/// Result
222#[derive(Debug, Default)]
223pub struct SensorData {
224    /// eCO
225    pub e_co: u16,
226    /// eTVoc
227    pub e_tvoc: u16,
228    /// Status
229    pub status: u8,
230    /// Error Id
231    pub error_id: u8,
232    /// Raw data
233    pub raw: [u8; 2],
234}
235
236/// Ccs811 device driver.
237#[derive(Debug, Default)]
238pub struct Ccs811<I2C> {
239    /// The concrete I²C device implementation.
240    i2c: I2C,
241    /// i2c address
242    address: u8,
243}
244
245impl<I2C, E> Ccs811<I2C>
246where
247    I2C: Write<Error = E> + WriteRead<Error = E> + Read<Error = E>,
248{
249    /// Create new instance of the Ccs811 device.
250    pub fn new(i2c: I2C, address: u8) -> Self {
251        Ccs811 { i2c, address }
252    }
253
254    /// Destroy driver instance, return I²C bus instance.
255    pub fn destroy(self) -> I2C {
256        self.i2c
257    }
258
259    /// Enter App mode
260    pub fn app_start(&mut self) -> Result<(), E> {
261        self.i2c
262            .write(self.address, &[BootRegister::AppStart as u8])
263    }
264
265    /// Set MEAS_MODE Register
266    pub fn set_meas_mode(
267        &mut self,
268        drive_mode: DriveMode,
269        interrupt: InterruptDataReady,
270        threshold: InterruptThreshold,
271    ) -> Result<(), E> {
272        let mode: u8 = drive_mode as u8 | interrupt as u8 | threshold as u8;
273        self.i2c
274            .write(self.address, &[AppRegister::MeasMode as u8, mode])
275    }
276
277    /// Get MEAS_MODE Register
278    pub fn get_meas_mode(&mut self, data: &mut [u8; 1]) -> Result<(), E> {
279        self.i2c
280            .write_read(self.address, &[AppRegister::MeasMode as u8], data)
281    }
282
283    /// Perform a SwReset, which brings firmware in Boot Mode.
284    pub fn reset(&mut self) -> Result<(), E> {
285        self.i2c.write(
286            self.address,
287            &[AppRegister::SwReset as u8, 0x11, 0xe5, 0x72, 0x8a],
288        )
289    }
290
291    /// Returns [HwId;HwVersion]
292    pub fn hw_info(&mut self) -> Result<[u8; 2], E> {
293        let mut hw = [0u8; 2];
294        self.i2c
295            .write_read(self.address, &[AppRegister::HwId as u8], &mut hw[..1])?;
296        self.i2c
297            .write_read(self.address, &[AppRegister::HwVersion as u8], &mut hw[1..])?;
298        Ok(hw)
299    }
300
301    /// Returns [FwBootVersion;FwAppVersion]
302    pub fn fw_info(&mut self) -> Result<[u8; 4], E> {
303        let mut fw = [0u8; 4];
304        self.i2c.write_read(
305            self.address,
306            &[AppRegister::FwBootVersion as u8],
307            &mut fw[0..2],
308        )?;
309        self.i2c.write_read(
310            self.address,
311            &[AppRegister::FwAppVersion as u8],
312            &mut fw[2..],
313        )?;
314        Ok(fw)
315    }
316
317    /// Returns RAW_DATA
318    pub fn raw_data(&mut self) -> Result<[u8; 2], E> {
319        let mut data = [0u8; 2];
320        self.i2c
321            .write_read(self.address, &[AppRegister::RawData as u8], &mut data)?;
322        Ok(data)
323    }
324
325    /// Set a previosuly retrieved baseline
326    ///
327    /// "A previously stored value may be written back to this two byte
328    /// register and the Algorithms will use the new value in its
329    /// calculations (until it adjusts it as part of its internal Automatic
330    /// Baseline Correction). For more information, refer to ams
331    /// application note AN000370: CCS811 Clean Air Baseline Save and
332    /// Restore."
333    pub fn set_baseline(&mut self, baseline: [u8; 2]) -> Result<(), E> {
334        let data: [u8; 3] = [AppRegister::Baseline as u8, baseline[0], baseline[1]];
335        self.i2c.write(self.address, &data)
336    }
337
338    /// Retrieves Baseline
339    pub fn get_baseline(&mut self, baseline: &mut [u8; 2]) -> Result<(), E> {
340        self.i2c
341            .write_read(self.address, &[AppRegister::Baseline as u8], baseline)
342    }
343
344    /// Retrieves Status register
345    pub fn get_status(&mut self, status: &mut [u8; 1]) -> Result<(), E> {
346        self.i2c
347            .write_read(self.address, &[AppRegister::Status as u8], status)
348    }
349
350    /// Retrieves Error_Id register
351    pub fn get_error_id(&mut self) -> Result<u8, E> {
352        let mut data = [0u8; 1];
353        self.i2c
354            .write_read(self.address, &[AppRegister::ErrorId as u8], &mut data)?;
355        Ok(data[0])
356    }
357
358    /// Get result
359    pub fn get_results(&mut self) -> Result<SensorData, E> {
360        let mut data: [u8; 8] = [0u8; 8];
361        self.i2c
362            .write_read(self.address, &[AppRegister::AlgResultData as u8], &mut data)?;
363
364        let mut ret: SensorData = SensorData::default();
365        ret.e_co = (u16::from(data[0]) << 8) + u16::from(data[1]);
366        ret.e_tvoc = (u16::from(data[2]) << 8) + u16::from(data[3]);
367        ret.status = data[4];
368        ret.error_id = data[5];
369        ret.raw[0] = data[6];
370        ret.raw[1] = data[7];
371
372        Ok(ret)
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    #[test]
379    fn it_works() {
380        assert_eq!(2 + 2, 4);
381    }
382}