#![doc = include_str!("../README.md")]
#![deny(unsafe_code, missing_docs)]
#![no_std]
#[allow(unused_imports)]
use micromath::F32Ext;
#[cfg(not(feature = "async"))]
use embedded_hal as hal;
#[cfg(feature = "async")]
use embedded_hal_async as hal;
use hal::i2c::{Operation, SevenBitAddress};
pub use weather_utils::Temperature;
use weather_utils::{BarometricPressure, Celsius, TemperatureAndBarometricPressure};
pub const I2C_ADDRESS_LOGIC_LOW: SevenBitAddress = 0x70;
pub const I2C_ADDRESS_LOGIC_HIGH: SevenBitAddress = 0x56;
pub const DEFAULT_I2C_ADDRESS: SevenBitAddress = I2C_ADDRESS_LOGIC_LOW;
const CHIP_ID_REGISTER: u8 = 0xd1;
const COE_B00_1_REGISTER: u8 = 0xa0;
const CTRL_MEAS_REGISTER: u8 = 0xf4;
const IIR_CNT_REGISTER: u8 = 0xf1;
const PRESS_TXD2: u8 = 0xf7;
const RESET_REGISTER: u8 = 0xe0;
#[derive(Debug)]
pub enum Error<I2cE>
where
I2cE: hal::i2c::Error,
{
I2c(I2cE),
ChipNotDetected,
BadCrc,
}
impl<I2cE> From<I2cE> for Error<I2cE>
where
I2cE: hal::i2c::Error,
{
fn from(value: I2cE) -> Self {
Error::I2c(value)
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(u8)]
pub enum IirFilter {
Off = 0x00,
Coeff2 = 0x01,
#[default]
Coeff4 = 0x02,
Coeff8 = 0x03,
Coeff16 = 0x04,
Coeff32 = 0x05,
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(u8)]
pub enum OverSamplingSetting {
HighSpeed,
LowPower,
#[default]
Standard,
HighAccuracy,
UltraHighAccuracy,
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(u8)]
enum PowerMode {
#[default]
Sleep = 0x00,
Forced = 0x01,
#[allow(dead_code)]
Normal = 0x03,
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(u8)]
enum OverSampling {
#[default]
X1 = 0x01,
X2 = 0x02,
X4 = 0x03,
X8 = 0x04,
X16 = 0x05,
X32 = 0x06,
}
#[derive(Debug, Default)]
struct Coe {
a0: i32,
a1: i16,
a2: i16,
b00: i32,
bt1: i16,
bt2: i16,
bp1: i16,
b11: i16,
bp2: i16,
b12: i16,
b21: i16,
bp3: i16,
}
impl From<&[u8; 25]> for Coe {
fn from(value: &[u8; 25]) -> Self {
Coe {
a0: ((((value[18] as u32) << 12 | (value[19] as u32) << 4 | (value[24] as u32) & 0x0f)
<< 12) as i32)
>> 12,
a1: ((value[20] as u16) << 8 | (value[21] as u16)) as i16,
a2: ((value[22] as u16) << 8 | (value[23] as u16)) as i16,
b00: ((((value[0] as u32) << 12
| (value[1] as u32) << 4
| ((value[24] as u32) & 0xf0) >> 4)
<< 12) as i32)
>> 12,
bt1: ((value[2] as u16) << 8 | (value[3] as u16)) as i16,
bt2: ((value[4] as u16) << 8 | (value[5] as u16)) as i16,
bp1: ((value[6] as u16) << 8 | (value[7] as u16)) as i16,
b11: ((value[8] as u16) << 8 | (value[9] as u16)) as i16,
bp2: ((value[10] as u16) << 8 | (value[11] as u16)) as i16,
b12: ((value[12] as u16) << 8 | (value[13] as u16)) as i16,
b21: ((value[14] as u16) << 8 | (value[15] as u16)) as i16,
bp3: ((value[16] as u16) << 8 | (value[17] as u16)) as i16,
}
}
}
#[derive(Debug, Default)]
struct K {
a0: f32,
a1: f32,
a2: f32,
b00: f32,
bt1: f32,
bt2: f32,
bp1: f32,
b11: f32,
bp2: f32,
b12: f32,
b21: f32,
bp3: f32,
}
impl From<&Coe> for K {
fn from(value: &Coe) -> Self {
K {
a0: value.a0 as f32 / 16.0,
a1: -6.30E-03 + ((4.30E-04 * value.a1 as f32) / 32_767.0),
a2: -1.90E-11 + ((1.20E-10 * value.a2 as f32) / 32_767.0),
b00: value.b00 as f32 / 16.0,
bt1: 1.00E-01 + ((9.10E-02 * value.bt1 as f32) / 32_767.0),
bt2: 1.20E-08 + ((1.20E-06 * value.bt2 as f32) / 32_767.0),
bp1: 3.30E-02 + ((1.90E-02 * value.bp1 as f32) / 32_767.0),
b11: 2.10E-07 + ((1.40E-07 * value.b11 as f32) / 32_767.0),
bp2: -6.30E-10 + ((3.50E-10 * value.bp2 as f32) / 32_767.0),
b12: 2.90E-13 + ((7.60E-13 * value.b12 as f32) / 32_767.0),
b21: 2.10E-15 + ((1.20E-14 * value.b21 as f32) / 32_767.0),
bp3: 1.30E-16 + ((7.90E-17 * value.bp3 as f32) / 32_767.0),
}
}
}
#[derive(Debug)]
pub struct Qmp6988<I2C, D> {
address: SevenBitAddress,
coe: Coe,
delay: D,
filter: IirFilter,
i2c: I2C,
k: K,
oversampling_setting: OverSamplingSetting,
}
impl<I2C, D> Qmp6988<I2C, D>
where
I2C: hal::i2c::I2c,
D: hal::delay::DelayNs,
{
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
pub async fn measure(
&mut self,
) -> Result<TemperatureAndBarometricPressure<Celsius>, Error<I2C::Error>> {
self.apply_power_mode(PowerMode::Forced).await?;
self.delay.delay_ms(self.get_measurement_duration()).await;
let mut data = [0u8; 6];
let mut operations = [Operation::Write(&[PRESS_TXD2]), Operation::Read(&mut data)];
self.i2c.transaction(self.address, &mut operations).await?;
let dp: &[u8; 3] = &data[0..3].try_into().unwrap();
let dt: &[u8; 3] = &data[3..6].try_into().unwrap();
let dp = Self::get_i32_value(dp) - 8_388_608;
let dt = Self::get_i32_value(dt) - 8_388_608;
let temperature = self.compensate_temperature(dt);
let pressure = self.compensate_pressure(dp, temperature);
Ok(TemperatureAndBarometricPressure {
temperature: Celsius(temperature / 256.0),
barometric_pressure: BarometricPressure(pressure / 100.0),
})
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
pub async fn new(
i2c: I2C,
address: SevenBitAddress,
delay: D,
) -> Result<Self, Error<I2C::Error>> {
let mut device = Self {
address,
coe: Coe::default(),
delay,
filter: IirFilter::default(),
i2c,
k: K::default(),
oversampling_setting: OverSamplingSetting::default(),
};
device.check_device().await?;
device.get_calibration_data().await?;
device.apply_filter().await?;
device.apply_measure_control_parameters().await?;
Ok(device)
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
pub async fn reset(&mut self) -> Result<(), Error<I2C::Error>> {
self.i2c.write(self.address, &[RESET_REGISTER]).await?;
self.delay.delay_ms(10).await;
Ok(())
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
pub async fn set_filter(&mut self, filter: IirFilter) -> Result<(), Error<I2C::Error>> {
self.filter = filter;
self.apply_filter().await
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
pub async fn set_oversampling_setting(
&mut self,
oversampling_setting: OverSamplingSetting,
) -> Result<(), Error<I2C::Error>> {
self.oversampling_setting = oversampling_setting;
self.apply_measure_control_parameters().await
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
async fn apply_filter(&mut self) -> Result<(), Error<I2C::Error>> {
let data = [IIR_CNT_REGISTER, self.filter as u8];
self.i2c.write(self.address, &data).await?;
self.delay.delay_ms(20).await;
Ok(())
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
async fn apply_measure_control_parameters(&mut self) -> Result<(), Error<I2C::Error>> {
let (pressure_oversampling, temperature_oversampling) = self.get_oversamplings();
let data = [
CTRL_MEAS_REGISTER,
(temperature_oversampling as u8) << 5
| (pressure_oversampling as u8) << 2
| (PowerMode::Sleep as u8),
];
self.i2c.write(self.address, &data).await?;
self.delay.delay_ms(20).await;
Ok(())
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
async fn apply_power_mode(&mut self, power_mode: PowerMode) -> Result<(), Error<I2C::Error>> {
let mut data = [0u8; 1];
let mut operations = [
Operation::Write(&[CTRL_MEAS_REGISTER]),
Operation::Read(&mut data),
];
self.i2c.transaction(self.address, &mut operations).await?;
let data = [CTRL_MEAS_REGISTER, (data[0] & 0xfc) | power_mode as u8];
self.i2c.write(self.address, &data).await?;
self.delay.delay_ms(20).await;
Ok(())
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
async fn check_device(&mut self) -> Result<(), Error<I2C::Error>> {
let mut chip_id = [0u8; 1];
let mut operations = [
Operation::Write(&[CHIP_ID_REGISTER]),
Operation::Read(&mut chip_id),
];
self.i2c.transaction(self.address, &mut operations).await?;
if chip_id[0] == 0x5c {
Ok(())
} else {
Err(Error::ChipNotDetected)
}
}
fn compensate_pressure(&self, dp: i32, temperature: f32) -> f32 {
let dp = dp as f32;
self.k.b00
+ self.k.bt1 * temperature
+ self.k.bp1 * dp
+ self.k.b11 * temperature * dp
+ self.k.bt2 * temperature.powf(2.0)
+ self.k.bp2 * dp.powf(2.0)
+ self.k.b12 * dp * temperature.powf(2.0)
+ self.k.b21 * dp.powf(2.0) * temperature
+ self.k.bp3 * dp.powf(3.0)
}
fn compensate_temperature(&self, dt: i32) -> f32 {
let dt = dt as f32;
self.k.a0 + self.k.a1 * dt + self.k.a2 * dt.powf(2.0)
}
#[maybe_async_cfg::maybe(
sync(not(feature = "async"), keep_self),
async(feature = "async", keep_self)
)]
async fn get_calibration_data(&mut self) -> Result<(), Error<I2C::Error>> {
let mut coe = [0u8; 25];
let mut operations = [
Operation::Write(&[COE_B00_1_REGISTER]),
Operation::Read(&mut coe),
];
self.i2c.transaction(self.address, &mut operations).await?;
self.coe = (&coe).into();
self.k = (&self.coe).into();
Ok(())
}
fn get_measurement_duration(&self) -> u32 {
match self.oversampling_setting {
OverSamplingSetting::HighSpeed => 6,
OverSamplingSetting::LowPower => 8,
OverSamplingSetting::Standard => 11,
OverSamplingSetting::HighAccuracy => 19,
OverSamplingSetting::UltraHighAccuracy => 34,
}
}
fn get_oversamplings(&self) -> (OverSampling, OverSampling) {
match self.oversampling_setting {
OverSamplingSetting::HighSpeed => (OverSampling::X2, OverSampling::X1),
OverSamplingSetting::LowPower => (OverSampling::X4, OverSampling::X1),
OverSamplingSetting::Standard => (OverSampling::X8, OverSampling::X1),
OverSamplingSetting::HighAccuracy => (OverSampling::X16, OverSampling::X2),
OverSamplingSetting::UltraHighAccuracy => (OverSampling::X32, OverSampling::X4),
}
}
#[inline]
fn get_i32_value(data: &[u8; 3]) -> i32 {
((data[0] as u32) << 16 | (data[1] as u32) << 8 | (data[2] as u32)) as i32
}
}
#[cfg(test)]
mod tests {
use embedded_hal::i2c::ErrorKind;
use embedded_hal_mock::eh1::delay::StdSleep as Delay;
use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
use rstest::rstest;
use weather_utils::Altitude;
use crate::*;
fn create_device() -> Qmp6988<I2cMock, Delay> {
let expectations = [
I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CHIP_ID_REGISTER].to_vec()),
I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x5c].to_vec()),
I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [COE_B00_1_REGISTER].to_vec()),
I2cTransaction::read(
DEFAULT_I2C_ADDRESS,
[
0x48, 0xE3, 0xF7, 0xD5, 0x04, 0x50, 0xFD, 0x02, 0xF3, 0xCB, 0x0A, 0x5D, 0x1F,
0x8C, 0x09, 0x13, 0xF9, 0xB6, 0xF7, 0x68, 0xD1, 0x62, 0xEB, 0xF2, 0x4E,
]
.to_vec(),
),
I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [IIR_CNT_REGISTER, 0x02].to_vec()),
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CTRL_MEAS_REGISTER, 0x30].to_vec()),
];
let i2c = I2cMock::new(&expectations);
let mut device = Qmp6988::new(i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
device.i2c.done();
device
}
#[test]
fn chip_not_detected() {
let expectations = [
I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CHIP_ID_REGISTER].to_vec()),
I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x2a].to_vec()),
I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
];
let mut i2c = I2cMock::new(&expectations);
assert!(matches!(
Qmp6988::new(i2c.by_ref(), DEFAULT_I2C_ADDRESS, Delay {}),
Err(Error::ChipNotDetected)
));
i2c.done();
}
#[test]
fn measure() {
let expectations = [
I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CTRL_MEAS_REGISTER].to_vec()),
I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x30].to_vec()),
I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [CTRL_MEAS_REGISTER, 0x31].to_vec()),
I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [PRESS_TXD2].to_vec()),
I2cTransaction::read(
DEFAULT_I2C_ADDRESS,
[0xA4, 0x92, 0xF1, 0x6E, 0x0D, 0x98].to_vec(),
),
I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
];
let mut device = create_device();
device.i2c.update_expectations(&expectations);
let measure = device.measure();
assert!(matches!(measure, Ok(_)));
let measure = measure.unwrap();
assert_eq!(
measure,
TemperatureAndBarometricPressure {
temperature: Celsius(20.87),
barometric_pressure: BarometricPressure(981.19),
}
);
assert_eq!(measure.altitude(), Altitude(277.31));
device.i2c.done();
}
#[test]
fn reset() {
let expectations = [I2cTransaction::write(
DEFAULT_I2C_ADDRESS,
[RESET_REGISTER].to_vec(),
)];
let mut device = create_device();
device.i2c.update_expectations(&expectations);
assert!(matches!(device.reset(), Ok(())));
device.i2c.done();
}
#[test]
fn reset_with_arbitration_loss_error() {
let expectations = [
I2cTransaction::write(DEFAULT_I2C_ADDRESS, [RESET_REGISTER].to_vec())
.with_error(ErrorKind::ArbitrationLoss),
];
let mut device = create_device();
device.i2c.update_expectations(&expectations);
assert!(matches!(device.reset(), Err(Error::I2c(_))));
device.i2c.done();
}
#[test]
fn set_filter() {
let expectations = [I2cTransaction::write(
DEFAULT_I2C_ADDRESS,
[IIR_CNT_REGISTER, 0x05].to_vec(),
)];
let mut device = create_device();
device.i2c.update_expectations(&expectations);
assert!(matches!(device.set_filter(IirFilter::Coeff32), Ok(())));
device.i2c.done();
}
#[rstest]
#[case(OverSamplingSetting::HighSpeed, 0x28, 6)]
#[case(OverSamplingSetting::LowPower, 0x2C, 8)]
#[case(OverSamplingSetting::Standard, 0x30, 11)]
#[case(OverSamplingSetting::HighAccuracy, 0x54, 19)]
#[case(OverSamplingSetting::UltraHighAccuracy, 0x78, 34)]
fn set_oversampling_setting(
#[case] oversampling_setting: OverSamplingSetting,
#[case] expected_ctrl_meas_value: u8,
#[case] expected_measurement_duration: u32,
) {
let expectations = [I2cTransaction::write(
DEFAULT_I2C_ADDRESS,
[CTRL_MEAS_REGISTER, expected_ctrl_meas_value].to_vec(),
)];
let mut device = create_device();
device.i2c.update_expectations(&expectations);
assert!(matches!(
device.set_oversampling_setting(oversampling_setting),
Ok(())
));
assert_eq!(
device.get_measurement_duration(),
expected_measurement_duration
);
device.i2c.done();
}
}