embedded_ccs811/
lib.rs

1//! This is a platform agnostic Rust driver for the CCS811 ultra-low power
2//! digital VOC sensor for monitoring indoor air quality (IAQ) using
3//! the [`embedded-hal`] traits.
4//!
5//! [`embedded-hal`]: https://github.com/rust-embedded/embedded-hal
6//!
7//! This driver allows you to:
8//! - In application mode:
9//!     - Set the measurement mode. See: [`set_mode()`].
10//!     - Check if there is new data ready. See: [`has_data_ready()`].
11//!     - Get the algoritm and raw result data. See: [`data()`].
12//!     - Get the raw data. See: [`raw_data()`].
13//!     - Get the current baseline. See: [`baseline()`].
14//!     - Set the baseline. See: [`set_baseline()`].
15//!     - Set the environment temperature and relative humidity. See: [`set_environment()`].
16//!     - Set the interrupt mode. See: [`set_interrupt_mode()`].
17//!     - Set the eCO2 thresholds for interrupts. See: [`set_eco2_thresholds()`].
18//! - In boot mode:
19//!     - Start application. See: [`start_application()`].
20//!     - Reset, erase, download and verify new application. See: [`update_application()`].
21//!     - Erase application. See: [`erase_application()`].
22//!     - Verify application. See: [`verify_application()`].
23//!     - Download application. See: [`download_application()`].
24//! - In either mode:
25//!     - Get the firmware mode. See: [`firmware_mode()`].
26//!     - Check whether a valid application is loaded. See: [`has_valid_app()`].
27//!     - Get the hardware ID. See: [`hardware_id()`].
28//!     - Get the hardware version. See: [`hardware_version()`].
29//!     - Get the firmware bootloader version. See: [`firmware_bootloader_version()`].
30//!     - Get the firmware application version. See: [`firmware_application_version()`].
31//!     - Do a software reset. See: [`software_reset()`].
32//!
33//! [`set_mode()`]: trait.Ccs811AppMode.html#tymethod.set_mode
34//! [`has_data_ready()`]: trait.Ccs811AppMode.html#tymethod.has_data_ready
35//! [`data()`]: trait.Ccs811AppMode.html#tymethod.data
36//! [`raw_data()`]: trait.Ccs811AppMode.html#tymethod.raw_data
37//! [`baseline()`]: trait.Ccs811AppMode.html#tymethod.baseline
38//! [`set_baseline()`]: trait.Ccs811AppMode.html#tymethod.set_baseline
39//! [`set_environment()`]: trait.Ccs811AppMode.html#tymethod.set_environment
40//! [`set_interrupt_mode()`]: trait.Ccs811AppMode.html#tymethod.set_interrupt_mode
41//! [`set_eco2_thresholds()`]: trait.Ccs811AppMode.html#tymethod.set_eco2_thresholds
42//! [`start_application()`]: trait.Ccs811BootMode.html#tymethod.start_application
43//! [`update_application()`]: trait.Ccs811BootMode.html#tymethod.update_application
44//! [`erase_application()`]: trait.Ccs811BootMode.html#tymethod.erase_application
45//! [`verify_application()`]: trait.Ccs811BootMode.html#tymethod.verify_application
46//! [`download_application()`]: trait.Ccs811BootMode.html#tymethod.download_application
47//! [`firmware_mode()`]: trait.Ccs811Device.html#tymethod.firmware_mode
48//! [`has_valid_app()`]: trait.Ccs811Device.html#tymethod.has_valid_app
49//! [`hardware_id()`]: trait.Ccs811Device.html#tymethod.hardware_id
50//! [`hardware_version()`]: trait.Ccs811Device.html#tymethod.hardware_version
51//! [`firmware_bootloader_version()`]: trait.Ccs811Device.html#tymethod.firmware_bootloader_version
52//! [`firmware_application_version()`]: trait.Ccs811Device.html#tymethod.firmware_application_version
53//! [`software_reset()`]: trait.Ccs811Device.html#tymethod.software_reset
54//!
55//! [Introductory blog post](https://blog.eldruin.com/ccs811-indoor-air-quality-sensor-driver-in-rust)
56//!
57//! ## The device
58//!
59//! The CCS811 is an ultra-low power digital gas sensor solution which
60//! integrates a metal oxide (MOX) gas sensor to detect a wide range of
61//! Volatile Organic Compounds (VOCs) for indoor air quality monitoring
62//! with a microcontroller unit (MCU), which includes an Analog-to-Digital
63//! converter (ADC), and an I²C interface.
64//!
65//! CCS811 is based on ams unique micro-hotplate technology which enables a
66//! highly reliable solution for gas sensors, very fast cycle times and a
67//! significant reduction in average power consumption.
68//!
69//! The integrated MCU manages the sensor driver modes and measurements.
70//! The I²C digital interface significantly simplifies the hardware and
71//! software design, enabling a faster time to market.
72//!
73//! CCS811 supports intelligent algorithms to process raw sensor measurements
74//! to output equivalent total VOC (eTVOC) and equivalent CO2 (eCO2) values,
75//! where the main cause of VOCs is from humans.
76//!
77//! CCS811 supports multiple measurement modes that have been optimized for
78//! low-power consumption during an active sensor measurement and idle mode
79//! extending battery life in portable applications.
80//!
81//! Documentation:
82//! - [Datasheet](https://www.sciosense.com/wp-content/uploads/2020/01/CCS811-Datasheet.pdf)
83//! - [Programming and interfacing guide](https://www.sciosense.com/wp-content/uploads/2020/01/CCS811-Application-Note-Programming-and-interfacing-guide.pdf)
84//!
85//! ## Usage examples (see also examples folder)
86//!
87//! To use this driver, import this crate and an `embedded_hal` implementation,
88//! then instantiate the appropriate device.
89//!
90//! The CCS811 can be placed in sleep and woken up only for communication.
91//! This driver provides two structures: `Ccs811Awake` and `Ccs811` depeding
92//! on the waking state.
93//!
94//! The `Ccs811Awake` assumes an awake device and handles only the I2C communication.
95//! This can be used when the waking up and sleep of the device is handled
96//! manually.
97//! Additionally a wrapper `Ccs811` is provided, which handles waking up
98//! the device before each operation and putting it to sleep afterwards.
99//!
100//! Please find additional examples using hardware in this repository: [driver-examples]
101//!
102//! [driver-examples]: https://github.com/eldruin/driver-examples
103//!
104//! ### Start the application and take measurements
105//!
106//! ```no_run
107//! use linux_embedded_hal::{I2cdev, Pin, Delay};
108//! use embedded_ccs811::{prelude::*, Ccs811, SlaveAddr, MeasurementMode};
109//! use nb::block;
110//!
111//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
112//! let nwake = Pin::new(17);
113//! let delay = Delay {};
114//! let address = SlaveAddr::default();
115//! let sensor = Ccs811::new(dev, address, nwake, delay);
116//! let mut sensor = sensor.start_application().ok().unwrap();
117//! sensor.set_mode(MeasurementMode::ConstantPower1s).unwrap();
118//! loop {
119//!     let data = block!(sensor.data()).unwrap();
120//!     println!("eCO2: {}, eTVOC: {}", data.eco2, data.etvoc);
121//! }
122//! ```
123//!
124//! ### Save and restore the baseline
125//!
126//! ```no_run
127//! use linux_embedded_hal::I2cdev;
128//! use embedded_ccs811::{prelude::*, Ccs811Awake, SlaveAddr};
129//!
130//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
131//! let address = SlaveAddr::default();
132//! let sensor = Ccs811Awake::new(dev, address);
133//! let mut sensor = sensor.start_application().ok().unwrap();
134//! let baseline = sensor.baseline().unwrap();
135//! // ...
136//! sensor.set_baseline(baseline).unwrap();
137//! ```
138//!
139//! ### Set the environment temperature and relative humidity
140//!
141//! ```no_run
142//! use linux_embedded_hal::I2cdev;
143//! use embedded_ccs811::{prelude::*, Ccs811Awake, SlaveAddr};
144//!
145//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
146//! let address = SlaveAddr::default();
147//! let sensor = Ccs811Awake::new(dev, address);
148//! let mut sensor = sensor.start_application().ok().unwrap();
149//! let temp_c = 25.0;
150//! let rel_humidity = 50.0;
151//! sensor.set_environment(rel_humidity, temp_c).unwrap();
152//! ```
153//!
154//! ### Set the eCO2 thresholds and configure interrupts
155//!
156//! Only generate an interrupt when the thresholds are crossed.
157//!
158//! ```no_run
159//! use linux_embedded_hal::I2cdev;
160//! use embedded_ccs811::{prelude::*, Ccs811Awake, SlaveAddr, InterruptMode, MeasurementMode};
161//!
162//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
163//! let address = SlaveAddr::default();
164//! let sensor = Ccs811Awake::new(dev, address);
165//! let mut sensor = sensor.start_application().ok().unwrap();
166//! sensor.set_eco2_thresholds(1500, 2500).unwrap();
167//! sensor.set_interrupt_mode(InterruptMode::OnThresholdCrossed).unwrap();
168//! sensor.set_mode(MeasurementMode::ConstantPower1s).unwrap();
169//! ```
170//!
171//! ### Get hardware and firmware information
172//!
173//! ```no_run
174//! use linux_embedded_hal::I2cdev;
175//! use embedded_ccs811::{prelude::*, Ccs811Awake, SlaveAddr};
176//!
177//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
178//! let address = SlaveAddr::default();
179//! let mut sensor = Ccs811Awake::new(dev, address);
180//! let hw_id = sensor.hardware_id().unwrap();
181//! let hw_ver = sensor.hardware_version().unwrap();
182//! let fw_boot_ver = sensor.firmware_bootloader_version().unwrap();
183//! let fw_app_ver = sensor.firmware_application_version().unwrap();
184//! println!(
185//!     "HW ID: {}, HW version: {:#?}, FW bootloader version: {:#?}, FW app version: {:#?}",
186//!     hw_id, hw_ver, fw_boot_ver, fw_app_ver
187//! );
188//! ```
189
190#![deny(unsafe_code, missing_docs)]
191#![no_std]
192
193extern crate embedded_hal as hal;
194use core::marker::PhantomData;
195
196mod common_impl;
197pub mod prelude;
198mod register_access;
199use crate::register_access::{BitFlags, Register};
200mod app_mode;
201mod boot_mode;
202mod traits;
203pub use crate::traits::{Ccs811AppMode, Ccs811BootMode, Ccs811Device};
204mod types;
205pub use crate::types::{
206    AlgorithmResult, DeviceErrors, Error, ErrorAwake, FirmwareMode, InterruptMode, MeasurementMode,
207    ModeChangeError, SlaveAddr,
208};
209pub use nb;
210
211/// CCS811 device driver
212///
213/// Convenience wrapper arount `Ccs811Awake` which handles waking up the device on each operation.
214#[derive(Debug)]
215pub struct Ccs811<I2C, NWAKE, WAKEDELAY, MODE> {
216    dev: Ccs811Awake<I2C, MODE>,
217    n_wake_pin: NWAKE,
218    wake_delay: WAKEDELAY,
219    _mode: PhantomData<MODE>,
220}
221
222/// Already awake CCS811 device driver
223///
224/// This can be used when the nWAKE pin is connected directly to GND or when
225/// handling the device waking manually instead of using the `Ccs811` wrapper type.
226#[derive(Debug)]
227pub struct Ccs811Awake<I2C, MODE> {
228    /// The concrete I²C device implementation.
229    i2c: I2C,
230    address: u8,
231    meas_mode_reg: u8,
232    in_progress: ActionInProgress,
233    _mode: PhantomData<MODE>,
234}
235
236#[derive(Debug, PartialEq)]
237enum ActionInProgress {
238    None,
239    Verification,
240    Erase,
241}
242
243/// Mode marker
244pub mod mode {
245    /// Boot mode
246    pub struct Boot(());
247    /// App mode
248    pub struct App(());
249}
250
251mod private {
252    use super::{mode, Ccs811, Ccs811Awake};
253    pub trait Sealed {}
254
255    impl Sealed for mode::Boot {}
256    impl Sealed for mode::App {}
257    impl<I2C, NWAKE, WAKEDELAY, MODE> Sealed for Ccs811<I2C, NWAKE, WAKEDELAY, MODE> {}
258    impl<I2C, MODE> Sealed for Ccs811Awake<I2C, MODE> {}
259}