bme680-rust/src/lib.rs
2019-11-27 11:07:15 +01:00

953 lines
32 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! This crate is a pure Rust implementation for the BME680 environmental sensor.
//! The library can be used to read the gas, pressure, humidity and temperature sensors via I²C.
//!
//! The library uses the embedded-hal crate to abstract reading and writing via I²C.
//! In the examples you can find a demo how to use the library in Linux using the linux-embedded-hal crate (e.g. on a RPI).
//! ```no_run
//! extern crate bme680;
//! extern crate embedded_hal;
//! extern crate linux_embedded_hal as hal;
//!
//! use bme680::*;
//! use embedded_hal::blocking::i2c;
//! use hal::*;
//! use std::result;
//! use std::time::Duration;
//!
//! fn main() -> result::Result<(), Error<<hal::I2cdev as i2c::Read>::Error, <hal::I2cdev as i2c::Write>::Error>>
//! {
//! // Initialize device
//! let i2c = I2cdev::new("/dev/i2c-1").unwrap();
//! let mut dev = Bme680::init(i2c, Delay {}, I2CAddress::Primary)?;
//! let settings = SettingsBuilder::new()
//! .with_humidity_oversampling(OversamplingSetting::OS2x)
//! .with_pressure_oversampling(OversamplingSetting::OS4x)
//! .with_temperature_oversampling(OversamplingSetting::OS8x)
//! .with_temperature_filter(IIRFilterSize::Size3)
//! .with_gas_measurement(Duration::from_millis(1500), 320, 25)
//! .with_run_gas(true)
//! .build();
//! dev.set_sensor_settings(settings)?;
//!
//! // Read sensor data
//! dev.set_sensor_mode(PowerMode::ForcedMode)?;
//! let (data, _state) = dev.get_sensor_data()?;
//!
//! println!("Temperature {}°C", data.temperature_celsius());
//! println!("Pressure {}hPa", data.pressure_hpa());
//! println!("Humidity {}%", data.humidity_percent());
//! println!("Gas Resistence {}Ω", data.gas_resistance_ohm());
//!
//! Ok(())
//! }
//! ```
#![no_std]
#![forbid(unsafe_code)]
pub use self::settings::{
DesiredSensorSettings, GasSett, IIRFilterSize, OversamplingSetting, SensorSettings, Settings,
SettingsBuilder, TphSett,
};
mod calc;
mod settings;
use crate::calc::Calc;
use crate::hal::blocking::delay::DelayMs;
use crate::hal::blocking::i2c::{Read, Write};
use core::result;
use core::time::Duration;
use embedded_hal as hal;
use log::{debug, error, info};
/// BME680 General config
pub const BME680_POLL_PERIOD_MS: u8 = 10;
/// BME680 unique chip identifier
pub const BME680_CHIP_ID: u8 = 0x61;
/// BME680 field_x related defines
const BME680_FIELD_LENGTH: usize = 15;
/// BME680 coefficients related defines
const BME680_COEFF_ADDR1_LEN: usize = 25;
const BME680_COEFF_ADDR2_LEN: usize = 16;
const BME680_SOFT_RESET_CMD: u8 = 0xb6;
/// Register map
/// Other coefficient's address
const BME680_ADDR_RES_HEAT_VAL_ADDR: u8 = 0x00;
const BME680_ADDR_RES_HEAT_RANGE_ADDR: u8 = 0x02;
const BME680_ADDR_RANGE_SW_ERR_ADDR: u8 = 0x04;
const BME680_ADDR_SENS_CONF_START: u8 = 0x5A;
const BME680_ADDR_GAS_CONF_START: u8 = 0x64;
const BME680_SOFT_RESET_ADDR: u8 = 0xe0;
/// Field settings
const BME680_FIELD0_ADDR: u8 = 0x1d;
/// Heater settings
const BME680_RES_HEAT0_ADDR: u8 = 0x5a;
const BME680_GAS_WAIT0_ADDR: u8 = 0x64;
/// Sensor configuration registers
const BME680_CONF_HEAT_CTRL_ADDR: u8 = 0x70;
const BME680_CONF_ODR_RUN_GAS_NBC_ADDR: u8 = 0x71;
const BME680_CONF_OS_H_ADDR: u8 = 0x72;
const BME680_CONF_T_P_MODE_ADDR: u8 = 0x74;
const BME680_CONF_ODR_FILT_ADDR: u8 = 0x75;
/// Coefficient's address
const BME680_COEFF_ADDR1: u8 = 0x89;
const BME680_COEFF_ADDR2: u8 = 0xe1;
/// Chip identifier
const BME680_CHIP_ID_ADDR: u8 = 0xd0;
const BME680_SLEEP_MODE: u8 = 0;
const BME680_FORCED_MODE: u8 = 1;
const BME680_RESET_PERIOD: u8 = 10;
const BME680_MODE_MSK: u8 = 0x03;
const BME680_RSERROR_MSK: u8 = 0xf0;
const BME680_NEW_DATA_MSK: u8 = 0x80;
const BME680_GAS_INDEX_MSK: u8 = 0x0f;
const BME680_GAS_RANGE_MSK: u8 = 0x0f;
const BME680_GASM_VALID_MSK: u8 = 0x20;
const BME680_HEAT_STAB_MSK: u8 = 0x10;
/// Buffer length macro declaration
const BME680_TMP_BUFFER_LENGTH: usize = 40;
const BME680_REG_BUFFER_LENGTH: usize = 6;
/// All possible errors in this crate
#[derive(Debug)]
pub enum Error<R, W> {
///
/// aka BME680_E_COM_FAIL
///
I2CWrite(W),
I2CRead(R),
///
/// aka BME680_E_DEV_NOT_FOUND
///
DeviceNotFound,
///
/// aka BME680_E_INVALID_LENGTH
///
InvalidLength,
///
/// Warning aka BME680_W_DEFINE_PWR_MODE
///
DefinePwrMode,
///
/// Warning aka BME680_W_DEFINE_PWR_MODE
///
NoNewData,
///
/// Warning Boundary Check
///
BoundaryCheckFailure(&'static str),
}
/// Abbreviates `std::result::Result` type
pub type Result<T, R, W> = result::Result<T, Error<R, W>>;
///
/// Power mode settings
///
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PowerMode {
SleepMode,
ForcedMode,
}
impl PowerMode {
// TODO replace with TryFrom once stabilized
fn from(power_mode: u8) -> Self {
match power_mode {
BME680_SLEEP_MODE => PowerMode::SleepMode,
BME680_FORCED_MODE => PowerMode::ForcedMode,
_ => panic!("Unknown power mode: {}", power_mode),
}
}
fn value(&self) -> u8 {
match self {
PowerMode::SleepMode => BME680_SLEEP_MODE,
PowerMode::ForcedMode => BME680_FORCED_MODE,
}
}
}
///
/// I2C Slave Address
/// To determine the slave address of your device you can use `i2cdetect -y 1` on linux.
/// The 7-bit device address is 111011x. The 6 MSB bits are fixed.
/// The last bit is changeable by SDO value and can be changed during operation.
/// Connecting SDO to GND results in slave address 1110110 (0x76); connection it to V DDIO results in slave
/// address 1110111 (0x77), which is the same as BMP280s I2C address.
///
#[derive(Debug, Clone, Copy)]
pub enum I2CAddress {
/// Primary Slave Address 0x77
Primary,
/// Secondary Slave Address 0x77
Secondary,
/// Alternative address
Other(u8),
}
impl I2CAddress {
pub fn addr(&self) -> u8 {
match &self {
I2CAddress::Primary => 0x76u8,
I2CAddress::Secondary => 0x77u8,
I2CAddress::Other(addr) => addr.clone(),
}
}
}
impl Default for I2CAddress {
fn default() -> I2CAddress {
I2CAddress::Primary
}
}
/// Calibration data used during initalization
#[derive(Debug, Default, Copy)]
#[repr(C)]
pub struct CalibData {
pub par_h1: u16,
pub par_h2: u16,
pub par_h3: i8,
pub par_h4: i8,
pub par_h5: i8,
pub par_h6: u8,
pub par_h7: i8,
pub par_gh1: i8,
pub par_gh2: i16,
pub par_gh3: i8,
pub par_t1: u16,
pub par_t2: i16,
pub par_t3: i8,
pub par_p1: u16,
pub par_p2: i16,
pub par_p3: i8,
pub par_p4: i16,
pub par_p5: i16,
pub par_p6: i8,
pub par_p7: i8,
pub par_p8: i16,
pub par_p9: i16,
pub par_p10: u8,
pub res_heat_range: u8,
pub res_heat_val: i8,
pub range_sw_err: u8,
}
impl Clone for CalibData {
fn clone(&self) -> Self {
*self
}
}
/// Contains read sensors values e.g. temperature, pressure, humidity etc.
#[derive(Debug, Default, Copy)]
#[repr(C)]
pub struct FieldData {
/// Contains new_data, gasm_valid & heat_stab
status: u8,
/// Index of heater profile used
gas_index: u8,
/// Measurement index
meas_index: u8,
temperature: i16,
pressure: u32,
humidity: u32,
gas_resistance: u32,
}
impl Clone for FieldData {
fn clone(&self) -> Self {
*self
}
}
impl FieldData {
/// Temperature in degree celsius (°C)
pub fn temperature_celsius(&self) -> f32 {
self.temperature as f32 / 100f32
}
/// Pressure in hectopascal (hPA)
pub fn pressure_hpa(&self) -> f32 {
self.pressure as f32 / 100f32
}
/// Humidity in % relative humidity
pub fn humidity_percent(&self) -> f32 {
self.humidity as f32 / 1000f32
}
pub fn gas_resistance_ohm(&self) -> u32 {
self.gas_resistance
}
}
/// Shows if new data is available
#[derive(PartialEq, Debug)]
pub enum FieldDataCondition {
///
/// Data changed since last read
///
NewData,
///
/// Data has not changed since last read
///
Unchanged,
}
struct I2CUtil {}
impl I2CUtil {
pub fn read_byte<I2C>(
i2c: &mut I2C,
dev_id: u8,
reg_addr: u8,
) -> Result<u8, <I2C as Read>::Error, <I2C as Write>::Error>
where
I2C: Read + Write,
{
let mut buf = [0; 1];
i2c.write(dev_id, &mut [reg_addr])
.map_err(|e| Error::I2CWrite(e))?;
match i2c.read(dev_id, &mut buf) {
Ok(()) => Ok(buf[0]),
Err(e) => Err(Error::I2CRead(e)),
}
}
pub fn read_bytes<I2C>(
i2c: &mut I2C,
dev_id: u8,
reg_addr: u8,
buf: &mut [u8],
) -> Result<(), <I2C as Read>::Error, <I2C as Write>::Error>
where
I2C: Read + Write,
{
i2c.write(dev_id, &mut [reg_addr])
.map_err(|e| Error::I2CWrite(e))?;
match i2c.read(dev_id, buf) {
Ok(()) => Ok(()),
Err(e) => Err(Error::I2CRead(e)),
}
}
}
/// Driver for the BME680 environmental sensor
#[repr(C)]
pub struct Bme680<I2C, D> {
i2c: I2C,
delay: D,
dev_id: I2CAddress,
calib: CalibData,
// TODO remove ? as it may not reflect the state of the device
tph_sett: TphSett,
// TODO remove ? as it may not reflect the state of the device
gas_sett: GasSett,
// TODO remove ? as it may not reflect the state of the device
power_mode: PowerMode,
}
fn boundary_check<I2C>(
value: Option<u8>,
value_name: &'static str,
min: u8,
max: u8,
) -> Result<u8, <I2C as Read>::Error, <I2C as Write>::Error>
where
I2C: Read + Write,
{
let value = value.ok_or(Error::BoundaryCheckFailure(value_name))?;
if value < min {
const MIN: &str = "Boundary check failure, value exceeds maximum";
error!("{}, value name: {}", MIN, value_name);
return Err(Error::BoundaryCheckFailure(MIN));
}
if value > max {
const MAX: &str = "Boundary check, value exceeds minimum";
error!("{}, value name: {}", MAX, value_name);
return Err(Error::BoundaryCheckFailure(MAX));
}
Ok(value)
}
impl<I2C, D> Bme680<I2C, D>
where
D: DelayMs<u8>,
I2C: Read + Write,
{
pub fn soft_reset(
i2c: &mut I2C,
delay: &mut D,
dev_id: I2CAddress,
) -> Result<(), <I2C as Read>::Error, <I2C as Write>::Error> {
let tmp_buff: [u8; 2] = [BME680_SOFT_RESET_ADDR, BME680_SOFT_RESET_CMD];
i2c.write(dev_id.addr(), &tmp_buff)
.map_err(|e| Error::I2CWrite(e))?;
delay.delay_ms(BME680_RESET_PERIOD);
Ok(())
}
pub fn init(
mut i2c: I2C,
mut delay: D,
dev_id: I2CAddress,
) -> Result<Bme680<I2C, D>, <I2C as Read>::Error, <I2C as Write>::Error> {
Bme680::soft_reset(&mut i2c, &mut delay, dev_id)?;
debug!("Reading chip id");
/* Soft reset to restore it to default values*/
let chip_id = I2CUtil::read_byte::<I2C>(&mut i2c, dev_id.addr(), BME680_CHIP_ID_ADDR)?;
debug!("Chip id: {}", chip_id);
if chip_id == BME680_CHIP_ID {
debug!("Reading calib data");
let calib = Bme680::<I2C, D>::get_calib_data::<I2C>(&mut i2c, dev_id)?;
debug!("Calib data {:?}", calib);
let dev = Bme680 {
i2c: i2c,
delay: delay,
dev_id: dev_id,
calib: calib,
power_mode: PowerMode::ForcedMode,
tph_sett: Default::default(),
gas_sett: Default::default(),
};
info!("Finished device init");
Ok(dev)
} else {
error!("Device does not match chip id {}", BME680_CHIP_ID);
Err(Error::DeviceNotFound)
}
}
fn bme680_set_regs(
&mut self,
reg: &[(u8, u8)],
) -> Result<(), <I2C as Read>::Error, <I2C as Write>::Error> {
if reg.is_empty() || reg.len() > (BME680_TMP_BUFFER_LENGTH / 2) as usize {
return Err(Error::InvalidLength);
}
for (reg_addr, reg_data) in reg {
let tmp_buff: [u8; 2] = [reg_addr.clone(), reg_data.clone()];
debug!(
"Setting register reg: {:?} tmp_buf: {:?}",
reg_addr, tmp_buff
);
self.i2c
.write(self.dev_id.addr(), &tmp_buff)
.map_err(|e| Error::I2CWrite(e))?;
}
Ok(())
}
/// Set the settings to be used during the sensor measurements
pub fn set_sensor_settings(
&mut self,
settings: Settings,
) -> Result<(), <I2C as Read>::Error, <I2C as Write>::Error> {
let (sensor_settings, desired_settings) = settings;
let tph_sett = sensor_settings.tph_sett;
let gas_sett = sensor_settings.gas_sett;
let mut reg: [(u8, u8); BME680_REG_BUFFER_LENGTH] = [(0, 0); BME680_REG_BUFFER_LENGTH];
let intended_power_mode = self.power_mode;
if desired_settings.contains(DesiredSensorSettings::GAS_MEAS_SEL) {
debug!("GAS_MEAS_SEL: true");
self.set_gas_config(gas_sett)?;
}
let power_mode = self.power_mode;
self.set_sensor_mode(power_mode)?;
let mut element_index = 0;
// Selecting the filter
if desired_settings.contains(DesiredSensorSettings::FILTER_SEL) {
let mut data =
I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_ODR_FILT_ADDR)?;
debug!("FILTER_SEL: true");
data = (data as (i32) & !0x1ci32
| tph_sett.filter.unwrap_or(IIRFilterSize::Size0) as (i32) << 2i32 & 0x1ci32)
as (u8);
reg[element_index] = (BME680_CONF_ODR_FILT_ADDR, data);
element_index += 1;
}
if desired_settings.contains(DesiredSensorSettings::HCNTRL_SEL) {
debug!("HCNTRL_SEL: true");
let gas_sett_heatr_ctrl =
boundary_check::<I2C>(gas_sett.heatr_ctrl, "GasSett.heatr_ctrl", 0x0u8, 0x8u8)?;
let mut data = I2CUtil::read_byte(
&mut self.i2c,
self.dev_id.addr(),
BME680_CONF_HEAT_CTRL_ADDR,
)?;
data = (data as (i32) & !0x8i32 | gas_sett_heatr_ctrl as (i32) & 0x8) as (u8);
reg[element_index] = (BME680_CONF_HEAT_CTRL_ADDR, data);
element_index += 1;
}
// Selecting heater T,P oversampling for the sensor
if desired_settings
.contains(DesiredSensorSettings::OST_SEL | DesiredSensorSettings::OSP_SEL)
{
let mut data =
I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_T_P_MODE_ADDR)?;
if desired_settings.contains(DesiredSensorSettings::OST_SEL) {
debug!("OST_SEL: true");
let tph_sett_os_temp = boundary_check::<I2C>(
tph_sett.os_temp.map(|x| x as u8),
"TphSett.os_temp",
0,
5,
)?;
data = (data as (i32) & !0xe0i32 | tph_sett_os_temp as (i32) << 5i32 & 0xe0i32)
as (u8);
}
if desired_settings.contains(DesiredSensorSettings::OSP_SEL) {
debug!("OSP_SEL: true");
let tph_sett_os_pres = tph_sett.os_temp.expect("OS TEMP");
data = (data as (i32) & !0x1ci32 | tph_sett_os_pres as (i32) << 2i32 & 0x1ci32)
as (u8);
}
reg[element_index] = (BME680_CONF_T_P_MODE_ADDR, data);
element_index += 1;
}
// Selecting humidity oversampling for the sensor
if desired_settings.contains(DesiredSensorSettings::OSH_SEL) {
debug!("OSH_SEL: true");
let tph_sett_os_hum =
boundary_check::<I2C>(tph_sett.os_hum.map(|x| x as u8), "TphSett.os_hum", 0, 5)?;
let mut data =
I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_OS_H_ADDR)?;
data = (data as (i32) & !0x7i32 | tph_sett_os_hum as (i32) & 0x7i32) as (u8);
reg[element_index] = (BME680_CONF_OS_H_ADDR, data);
element_index += 1;
}
// Selecting the runGas and NB conversion settings for the sensor
if desired_settings
.contains(DesiredSensorSettings::RUN_GAS_SEL | DesiredSensorSettings::NBCONV_SEL)
{
let mut data = I2CUtil::read_byte(
&mut self.i2c,
self.dev_id.addr(),
BME680_CONF_ODR_RUN_GAS_NBC_ADDR,
)?;
if desired_settings.contains(DesiredSensorSettings::RUN_GAS_SEL) {
debug!("RUN_GAS_SEL: true");
data = (data as (i32) & !0x10i32
| gas_sett.run_gas_measurement as (i32) << 4i32 & 0x10i32)
as (u8);
}
if desired_settings.contains(DesiredSensorSettings::NBCONV_SEL) {
debug!("NBCONV_SEL: true");
let gas_sett_nb_conv =
boundary_check::<I2C>(Some(gas_sett.nb_conv), "GasSett.nb_conv", 0, 10)?;
data = (data as (i32) & !0xfi32 | gas_sett_nb_conv as (i32) & 0xfi32) as (u8);
}
reg[element_index] = (BME680_CONF_ODR_RUN_GAS_NBC_ADDR, data);
element_index += 1;
}
self.bme680_set_regs(&reg[0..element_index])?;
// Restore previous intended power mode
self.power_mode = intended_power_mode;
self.tph_sett = tph_sett;
Ok(())
}
/// Retrieve settings from sensor registers
///
/// # Arguments
///
/// * `desired_settings` - Settings to be retrieved. Setting values may stay `None` if not retrieved.
pub fn get_sensor_settings(
&mut self,
desired_settings: DesiredSensorSettings,
) -> Result<SensorSettings, <I2C as Read>::Error, <I2C as Write>::Error> {
let reg_addr: u8 = 0x70u8;
let mut data_array: [u8; BME680_REG_BUFFER_LENGTH] = [0; BME680_REG_BUFFER_LENGTH];
let mut sensor_settings: SensorSettings = Default::default();
sensor_settings.tph_sett.temperature_offset = self.tph_sett.temperature_offset;
I2CUtil::read_bytes(&mut self.i2c, self.dev_id.addr(), reg_addr, &mut data_array)?;
if desired_settings.contains(DesiredSensorSettings::GAS_MEAS_SEL) {
sensor_settings.gas_sett = self.get_gas_config()?;
}
if desired_settings.contains(DesiredSensorSettings::FILTER_SEL) {
sensor_settings.tph_sett.filter = Some(IIRFilterSize::from_u8(
((data_array[5usize] as (i32) & 0x1ci32) >> 2i32) as (u8),
));
}
if desired_settings
.contains(DesiredSensorSettings::OST_SEL | DesiredSensorSettings::OSP_SEL)
{
let os_temp: u8 = ((data_array[4usize] as (i32) & 0xe0i32) >> 5i32) as (u8);
let os_pres: u8 = ((data_array[4usize] as (i32) & 0x1ci32) >> 2i32) as (u8);
sensor_settings.tph_sett.os_temp = Some(OversamplingSetting::from_u8(os_temp));
sensor_settings.tph_sett.os_pres = Some(OversamplingSetting::from_u8(os_pres));
}
if desired_settings.contains(DesiredSensorSettings::OSH_SEL) {
let os_hum: u8 = (data_array[2usize] as (i32) & 0x7i32) as (u8);
sensor_settings.tph_sett.os_hum = Some(OversamplingSetting::from_u8(os_hum));
}
if desired_settings.contains(DesiredSensorSettings::HCNTRL_SEL) {
sensor_settings.gas_sett.heatr_ctrl =
Some((data_array[0usize] as (i32) & 0x8i32) as (u8));
}
if desired_settings
.contains(DesiredSensorSettings::RUN_GAS_SEL | DesiredSensorSettings::NBCONV_SEL)
{
sensor_settings.gas_sett.nb_conv = (data_array[1usize] as (i32) & 0xfi32) as (u8);
sensor_settings.gas_sett.run_gas_measurement =
((data_array[1usize] as (i32) & 0x10i32) >> 4i32) == 0;
}
Ok(sensor_settings)
}
/// Set the sensor into a certain power mode
///
/// # Arguments
///
/// * `target_power_mode` - Desired target power mode
pub fn set_sensor_mode(
&mut self,
target_power_mode: PowerMode,
) -> Result<(), <I2C as Read>::Error, <I2C as Write>::Error> {
let mut tmp_pow_mode: u8;
let mut current_power_mode: PowerMode;
// Call repeatedly until in sleep
loop {
tmp_pow_mode =
I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_T_P_MODE_ADDR)?;
// Put to sleep before changing mode
current_power_mode = PowerMode::from(tmp_pow_mode & BME680_MODE_MSK);
debug!("Current power mode: {:?}", current_power_mode);
if current_power_mode != PowerMode::SleepMode {
// Set to sleep
tmp_pow_mode = tmp_pow_mode & !BME680_MODE_MSK;
debug!("Setting to sleep tmp_pow_mode: {}", tmp_pow_mode);
self.bme680_set_regs(&[(BME680_CONF_T_P_MODE_ADDR, tmp_pow_mode)])?;
self.delay.delay_ms(BME680_POLL_PERIOD_MS);
} else {
// TODO do while in Rust?
break;
}
}
// Already in sleep
if target_power_mode != PowerMode::SleepMode {
tmp_pow_mode = tmp_pow_mode & !BME680_MODE_MSK | target_power_mode.value();
debug!("Already in sleep Target power mode: {}", tmp_pow_mode);
self.bme680_set_regs(&[(BME680_CONF_T_P_MODE_ADDR, tmp_pow_mode)])?;
}
Ok(())
}
/// Retrieve current sensor power mode via registers
pub fn get_sensor_mode(
&mut self,
) -> Result<PowerMode, <I2C as Read>::Error, <I2C as Write>::Error> {
let regs =
I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_T_P_MODE_ADDR)?;
let mode = regs & BME680_MODE_MSK;
Ok(PowerMode::from(mode))
}
pub fn bme680_set_profile_dur(&mut self, tph_sett: TphSett, duration: Duration) {
let os_to_meas_cycles: [u8; 6] = [0u8, 1u8, 2u8, 4u8, 8u8, 16u8];
// TODO check if the following unwrap_ors do not change behaviour
// TODO replace once https://github.com/rust-lang/rust/pull/50167 has been merged
const MILLIS_PER_SEC: u64 = 1_000;
const NANOS_PER_MILLI: u64 = 1_000_000;
let millis = (duration.as_secs() as u64 * MILLIS_PER_SEC)
+ (duration.subsec_nanos() as u64 / NANOS_PER_MILLI);
let mut meas_cycles = os_to_meas_cycles
[tph_sett.os_temp.unwrap_or(OversamplingSetting::OSNone) as (usize)]
as (u64);
meas_cycles = meas_cycles.wrapping_add(
os_to_meas_cycles[tph_sett.os_pres.unwrap_or(OversamplingSetting::OSNone) as (usize)]
as (u64),
);
meas_cycles = meas_cycles.wrapping_add(
os_to_meas_cycles[tph_sett.os_hum.unwrap_or(OversamplingSetting::OSNone) as (usize)]
as (u64),
);
let mut tph_dur = meas_cycles.wrapping_mul(1963u64);
tph_dur = tph_dur.wrapping_add(477u64.wrapping_mul(4u64));
tph_dur = tph_dur.wrapping_add(477u64.wrapping_mul(5u64));
tph_dur = tph_dur.wrapping_add(500u64);
tph_dur = tph_dur.wrapping_div(1000u64);
tph_dur = tph_dur.wrapping_add(1u64);
self.gas_sett.heatr_dur = Some(Duration::from_millis(millis - tph_dur));
}
pub fn get_profile_dur(
&self,
sensor_settings: &SensorSettings,
) -> Result<Duration, <I2C as Read>::Error, <I2C as Write>::Error> {
let os_to_meas_cycles: [u8; 6] = [0u8, 1u8, 2u8, 4u8, 8u8, 16u8];
// TODO check if the following unwrap_ors do not change behaviour
let mut meas_cycles = os_to_meas_cycles[sensor_settings
.tph_sett
.os_temp
.unwrap_or(OversamplingSetting::OSNone)
as (usize)] as (u32);
meas_cycles = meas_cycles.wrapping_add(
os_to_meas_cycles[sensor_settings
.tph_sett
.os_pres
.unwrap_or(OversamplingSetting::OSNone) as (usize)] as (u32),
);
meas_cycles = meas_cycles.wrapping_add(
os_to_meas_cycles[sensor_settings
.tph_sett
.os_hum
.unwrap_or(OversamplingSetting::OSNone) as (usize)] as (u32),
);
let mut tph_dur = meas_cycles.wrapping_mul(1963u32);
tph_dur = tph_dur.wrapping_add(477u32.wrapping_mul(4u32));
tph_dur = tph_dur.wrapping_add(477u32.wrapping_mul(5u32));
tph_dur = tph_dur.wrapping_add(500u32);
tph_dur = tph_dur.wrapping_div(1000u32);
tph_dur = tph_dur.wrapping_add(1u32);
let mut duration = Duration::from_millis(tph_dur as u64);
if sensor_settings.gas_sett.run_gas_measurement {
duration = duration + sensor_settings.gas_sett.heatr_dur.expect("Heatrdur");
}
Ok(duration)
}
fn get_calib_data<I2CX>(
i2c: &mut I2CX,
dev_id: I2CAddress,
) -> Result<CalibData, <I2CX as Read>::Error, <I2CX as Write>::Error>
where
I2CX: Read + Write,
{
let mut calib: CalibData = Default::default();
let mut coeff_array: [u8; (BME680_COEFF_ADDR1_LEN + BME680_COEFF_ADDR2_LEN)] =
[0; (BME680_COEFF_ADDR1_LEN + BME680_COEFF_ADDR2_LEN)];
I2CUtil::read_bytes::<I2CX>(
i2c,
dev_id.addr(),
BME680_COEFF_ADDR1,
&mut coeff_array[0..(BME680_COEFF_ADDR1_LEN - 1)],
)?;
I2CUtil::read_bytes::<I2CX>(
i2c,
dev_id.addr(),
BME680_COEFF_ADDR2,
&mut coeff_array
[BME680_COEFF_ADDR1_LEN..(BME680_COEFF_ADDR1_LEN + BME680_COEFF_ADDR2_LEN - 1)],
)?;
calib.par_t1 = (coeff_array[34usize] as (u16) as (i32) << 8i32
| coeff_array[33usize] as (u16) as (i32)) as (u16);
calib.par_t2 = (coeff_array[2usize] as (u16) as (i32) << 8i32
| coeff_array[1usize] as (u16) as (i32)) as (i16);
calib.par_t3 = coeff_array[3usize] as (i8);
calib.par_p1 = (coeff_array[6usize] as (u16) as (i32) << 8i32
| coeff_array[5usize] as (u16) as (i32)) as (u16);
calib.par_p2 = (coeff_array[8usize] as (u16) as (i32) << 8i32
| coeff_array[7usize] as (u16) as (i32)) as (i16);
calib.par_p3 = coeff_array[9usize] as (i8);
calib.par_p4 = (coeff_array[12usize] as (u16) as (i32) << 8i32
| coeff_array[11usize] as (u16) as (i32)) as (i16);
calib.par_p5 = (coeff_array[14usize] as (u16) as (i32) << 8i32
| coeff_array[13usize] as (u16) as (i32)) as (i16);
calib.par_p6 = coeff_array[16usize] as (i8);
calib.par_p7 = coeff_array[15usize] as (i8);
calib.par_p8 = (coeff_array[20usize] as (u16) as (i32) << 8i32
| coeff_array[19usize] as (u16) as (i32)) as (i16);
calib.par_p9 = (coeff_array[22usize] as (u16) as (i32) << 8i32
| coeff_array[21usize] as (u16) as (i32)) as (i16);
calib.par_p10 = coeff_array[23usize];
calib.par_h1 = (coeff_array[27usize] as (u16) as (i32) << 4i32
| coeff_array[26usize] as (i32) & 0xfi32) as (u16);
calib.par_h2 = (coeff_array[25usize] as (u16) as (i32) << 4i32
| coeff_array[26usize] as (i32) >> 4i32) as (u16);
calib.par_h3 = coeff_array[28usize] as (i8);
calib.par_h4 = coeff_array[29usize] as (i8);
calib.par_h5 = coeff_array[30usize] as (i8);
calib.par_h6 = coeff_array[31usize];
calib.par_h7 = coeff_array[32usize] as (i8);
calib.par_gh1 = coeff_array[37usize] as (i8);
calib.par_gh2 = (coeff_array[36usize] as (u16) as (i32) << 8i32
| coeff_array[35usize] as (u16) as (i32)) as (i16);
calib.par_gh3 = coeff_array[38usize] as (i8);
calib.res_heat_range =
(I2CUtil::read_byte::<I2CX>(i2c, dev_id.addr(), BME680_ADDR_RES_HEAT_RANGE_ADDR)?
& 0x30)
/ 16;
calib.res_heat_val =
I2CUtil::read_byte::<I2CX>(i2c, dev_id.addr(), BME680_ADDR_RES_HEAT_VAL_ADDR)? as i8;
calib.range_sw_err =
(I2CUtil::read_byte::<I2CX>(i2c, dev_id.addr(), BME680_ADDR_RANGE_SW_ERR_ADDR)?
& BME680_RSERROR_MSK)
/ 16;
Ok(calib)
}
fn set_gas_config(
&mut self,
gas_sett: GasSett,
) -> Result<(), <I2C as Read>::Error, <I2C as Write>::Error> {
if self.power_mode != PowerMode::ForcedMode {
return Err(Error::DefinePwrMode);
}
// TODO check whether unwrap_or changes behaviour
let reg: [(u8, u8); 2] = [
(
BME680_RES_HEAT0_ADDR,
Calc::calc_heater_res(
&self.calib,
gas_sett.ambient_temperature,
gas_sett.heatr_temp.unwrap_or(0),
),
),
(
BME680_GAS_WAIT0_ADDR,
Calc::calc_heater_dur(gas_sett.heatr_dur.unwrap_or(Duration::from_secs(0))),
),
];
self.gas_sett.nb_conv = 0;
self.bme680_set_regs(&reg)
}
fn get_gas_config(&mut self) -> Result<GasSett, <I2C as Read>::Error, <I2C as Write>::Error> {
let mut gas_sett: GasSett = Default::default();
gas_sett.heatr_temp = Some(I2CUtil::read_byte(
&mut self.i2c,
self.dev_id.addr(),
BME680_ADDR_SENS_CONF_START,
)? as u16);
let heatr_dur_ms = I2CUtil::read_byte(
&mut self.i2c,
self.dev_id.addr(),
BME680_ADDR_GAS_CONF_START,
)? as u64;
gas_sett.heatr_dur = Some(Duration::from_millis(heatr_dur_ms));
Ok(gas_sett)
}
/// Retrieve the current sensor informations
pub fn get_sensor_data(
&mut self,
) -> Result<(FieldData, FieldDataCondition), <I2C as Read>::Error, <I2C as Write>::Error> {
let mut buff: [u8; BME680_FIELD_LENGTH] = [0; BME680_FIELD_LENGTH];
debug!("Buf {:?}, len: {}", buff, buff.len());
let mut data: FieldData = Default::default();
const TRIES: u8 = 10;
for _ in 0..TRIES {
I2CUtil::read_bytes(
&mut self.i2c,
self.dev_id.addr(),
BME680_FIELD0_ADDR,
&mut buff,
)?;
debug!("Field data read {:?}, len: {}", buff, buff.len());
data.status = buff[0] & BME680_NEW_DATA_MSK;
data.gas_index = buff[0] & BME680_GAS_INDEX_MSK;
data.meas_index = buff[1];
let adc_pres = (buff[2] as (u32)).wrapping_mul(4096)
| (buff[3] as (u32)).wrapping_mul(16)
| (buff[4] as (u32)).wrapping_div(16);
let adc_temp = (buff[5] as (u32)).wrapping_mul(4096)
| (buff[6] as (u32)).wrapping_mul(16)
| (buff[7] as (u32)).wrapping_div(16);
let adc_hum = ((buff[8] as (u32)).wrapping_mul(256) | buff[9] as (u32)) as (u16);
let adc_gas_res = ((buff[13] as (u32)).wrapping_mul(4)
| (buff[14] as (u32)).wrapping_div(64)) as (u16);
let gas_range = buff[14] & BME680_GAS_RANGE_MSK;
data.status = data.status | buff[14] & BME680_GASM_VALID_MSK;
data.status = data.status | buff[14] & BME680_HEAT_STAB_MSK;
if data.status & BME680_NEW_DATA_MSK != 0 {
let (temp, t_fine) =
Calc::calc_temperature(&self.calib, adc_temp, self.tph_sett.temperature_offset);
debug!(
"adc_temp: {} adc_pres: {} adc_hum: {} adc_gas_res: {}, t_fine: {}",
adc_temp, adc_pres, adc_hum, adc_gas_res, t_fine
);
data.temperature = temp;
data.pressure = Calc::calc_pressure(&self.calib, t_fine, adc_pres);
data.humidity = Calc::calc_humidity(&self.calib, t_fine, adc_hum);
data.gas_resistance =
Calc::calc_gas_resistance(&self.calib, adc_gas_res, gas_range);
return Ok((data, FieldDataCondition::NewData));
}
self.delay.delay_ms(BME680_POLL_PERIOD_MS);
}
Ok((data, FieldDataCondition::Unchanged))
}
}