diff --git a/src/co2_sensor.rs b/src/co2_sensor.rs new file mode 100644 index 0000000..9190dce --- /dev/null +++ b/src/co2_sensor.rs @@ -0,0 +1,71 @@ +use embassy_rp::{ + i2c::{Async, I2c}, + peripherals::I2C1, +}; + +/// I2C Address of the co2 sensor +const CCS811_I2C_ADDRESS: u16 = 0x5A; +/// Application I2C Register addresses of the co2 sensor +const CCS811_REGISTER_STATUS: u8 = 0x00; +const CCS811_REGISTER_MEAS_MODE: u8 = 0x01; +const CCS811_REGISTER_ALG_RESULT_DATA: u8 = 0x02; + +/// Bootloader I2C Register addresses of the co2 sensor +const CCS811_REGISTER_BOOTLOADER_APP_START: u8 = 0xF4; + +/// Read `buf.len()` bytes from register `register_address` over I2C +pub async fn read_register( + i2c: &mut I2c<'_, I2C1, Async>, + register_address: u8, + buf: &mut [u8], +) -> Result<(), embassy_rp::i2c::Error> { + i2c.write_async(CCS811_I2C_ADDRESS, [register_address]) + .await?; + i2c.read_async(CCS811_I2C_ADDRESS, buf).await +} + +/// Write all `bytes` in to register `register_address` over I2C +pub async fn write_register( + i2c: &mut I2c<'_, I2C1, Async>, + register_address: u8, + bytes: impl IntoIterator, +) -> Result<(), embassy_rp::i2c::Error> { + let write_buffer = core::iter::once(register_address).chain(bytes); + i2c.write_async(CCS811_I2C_ADDRESS, write_buffer).await +} + +/// Start the application on the sensor. The device can run custom applications. +/// However, there is no documentation about how these applications can be put together. +/// Therefore, we're just using whatever is already programmed on the chip. +pub async fn start_app(i2c: &mut I2c<'_, I2C1, Async>) -> Result<(), embassy_rp::i2c::Error> { + i2c.write_async(CCS811_I2C_ADDRESS, [CCS811_REGISTER_BOOTLOADER_APP_START]) + .await +} + +/// Read the status byte from the device +pub async fn get_status(i2c: &mut I2c<'_, I2C1, Async>) -> Result { + let mut res = [0]; + read_register(i2c, CCS811_REGISTER_STATUS, &mut res).await?; + Ok(res[0]) +} + +/// Read the eCO2 value from the `ALG_RESULT_DATA` register on the sensor +pub async fn get_measurement( + i2c: &mut I2c<'_, I2C1, Async>, +) -> Result { + let mut res = [0; 2]; + read_register(i2c, CCS811_REGISTER_ALG_RESULT_DATA, &mut res).await?; + Ok(u16::from_be_bytes(res)) +} + +/// Set the measurement mode register. +/// The measurement mode register is a bit field that also controls the +/// interrupt behavior. +/// By setting this register to `16`, you can select 1Hz constant power mode +/// without interrupts. +pub async fn set_measurement_mode( + i2c: &mut I2c<'_, I2C1, Async>, + mode: u8, +) -> Result<(), embassy_rp::i2c::Error> { + write_register(i2c, CCS811_REGISTER_MEAS_MODE, [mode]).await +} diff --git a/src/main.rs b/src/main.rs index 5647c0c..46d1ee2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,44 +6,33 @@ #![no_std] #![no_main] -use core::future::ready; +mod co2_sensor; use byteorder::ByteOrder; use defmt::*; use embassy_embedded_hal::SetConfig; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::{PIN_10, PIN_11, PIN_12, PIN_16, SPI1}; use embassy_rp::spi::Spi; -use embassy_rp::{bind_interrupts, interrupt}; use embassy_rp::{gpio, spi}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_sdmmc::sdcard::{DummyCsPin, SdCard}; use embedded_sdmmc::{Block, BlockDevice, BlockIdx, VolumeIdx}; -use futures::{FutureExt, TryFutureExt}; use gpio::{Level, Output}; -use usbd_hid::descriptor::MouseReport; use {defmt_rtt as _, panic_probe as _}; use embassy_rp::i2c::{self, Config}; use embassy_time::{Duration, Timer}; +use co2_sensor::{get_measurement, get_status, set_measurement_mode, start_app}; + bind_interrupts!(struct Irqs { I2C1_IRQ => i2c::InterruptHandler; }); struct DummyTimesource(); -/// I2C Address of the co2 sensor -const CCS811_I2C_ADDRESS: u16 = 0x5A; -/// Application I2C Register addresses of the co2 sensor -const CCS811_REGISTER_STATUS: u8 = 0x00; -const CCS811_REGISTER_MEAS_MODE: u8 = 0x01; -const CCS811_REGISTER_ALG_RESULT_DATA: u8 = 0x02; - -/// Bootloader I2C Register addresses of the co2 sensor -const CCS811_REGISTER_BOOTLOADER_APP_START: u8 = 0xF4; -const CCS811_REGISTER_BOOTLOADER_STATUS: u8 = CCS811_REGISTER_STATUS; - impl embedded_sdmmc::TimeSource for DummyTimesource { fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp { @@ -78,73 +67,32 @@ async fn main(spawner: Spawner) { // Wait for sensor to boot Timer::after(Duration::from_secs(1)).await; - debug!("Writing to I2C"); - let mut status = [42u8]; - i2c.write_async(CCS811_I2C_ADDRESS, [CCS811_REGISTER_STATUS]) - .await - .unwrap(); - debug!("Reading from I2C"); - i2c.read_async(CCS811_I2C_ADDRESS, &mut status) - .await - .unwrap(); - let status = status[0]; + let status = get_status(&mut i2c).await.unwrap(); - info!("Reported status: {}", status); - match status { - 16u8 => {} - 144u8 => { - warn!("Sensor already in APP mode! Configuration may come from previous boot."); - } - unexpected => { - warn!( - "Sensor reported unexpected state after boot: {}", - unexpected - ); + info!("CO2 sesor reported status on boot: {}", status); + + // Byte 7 is FW_MODE which indicates if the app is already running. + if status & 128u8 == 0 { + info!("App is not running yet. Booting sensor..."); + // App is not running + start_app(&mut i2c).await.unwrap(); + // After APP_START, we have to wait at least 1 ms (according to datasheet) + Timer::after(Duration::from_millis(2)).await; + + if get_status(&mut i2c).await.unwrap() & 128u8 == 0 { + error!("App still not running after boot! Terminating..."); + return; } + } else { + info!("App is already running. Skipping boot..."); } - // APP_START does not require to write data - i2c.write_async(CCS811_I2C_ADDRESS, [CCS811_REGISTER_BOOTLOADER_APP_START]) - .await - .unwrap(); - - // After APP_START, we have to wait 1 ms (according to datasheet) - Timer::after(Duration::from_millis(1000)).await; - - // Mode 1 is 1 measurement per second - i2c.write_async(CCS811_I2C_ADDRESS, [CCS811_REGISTER_MEAS_MODE, 16]) - .await - .unwrap(); + // 16 is measurement mode 1 => 1 measurement per second + set_measurement_mode(&mut i2c, 16).await.unwrap(); loop { - let mut status = [42u8]; - i2c.write_async(CCS811_I2C_ADDRESS, [CCS811_REGISTER_STATUS]) - .await; - // .unwrap(); - i2c.read_async(CCS811_I2C_ADDRESS, &mut status).await; - // .unwrap(); - let status = status[0]; - - let mut measured_value_buffer = [42u8; 2]; - let address_written = i2c - .write_async(CCS811_I2C_ADDRESS, [CCS811_REGISTER_ALG_RESULT_DATA]) - .await - .ok(); - - let data_written = if let Some(()) = address_written { - i2c.read_async(CCS811_I2C_ADDRESS, &mut measured_value_buffer) - .await - .ok() - } else { - None - }; - - let measured_value = if let Some(()) = data_written { - Some(u16::from_be_bytes(measured_value_buffer)) - } else { - None - }; - + let status = get_status(&mut i2c).await.unwrap(); + let measured_value = get_measurement(&mut i2c).await.unwrap(); info!( "Reported status: {}\tMeasured value: {}", status, measured_value