#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
use defmt::{debug, trace, Format};
use embedded_hal_async::{delay::DelayNs, i2c::I2c};
use libm::powf;
use uom::si::f32::{Length, Pressure, ThermodynamicTemperature};
use uom::si::length::{foot, meter};
use uom::si::pressure::{hectopascal, pascal};
use uom::si::thermodynamic_temperature::degree_celsius;
mod registers;
#[cfg(feature = "sync")]
pub mod sync;
pub use registers::*;
#[derive(Debug, Clone, Copy, Format)]
pub enum Error<E> {
I2c(E),
WrongChip(u8),
Fatal,
Command,
Configuration,
}
impl From<embedded_hal_async::i2c::ErrorKind> for Error<embedded_hal_async::i2c::ErrorKind> {
fn from(error: embedded_hal_async::i2c::ErrorKind) -> Self {
Error::I2c(error)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Measurement {
pub pressure: Pressure,
pub temperature: ThermodynamicTemperature,
pub altitude: Length,
}
impl Format for Measurement {
fn format(&self, f: defmt::Formatter) {
defmt::write!(
f,
"Pressure: {} Pa, Temperature: {} °C, Altitude: {} m",
self.pressure.get::<pascal>(),
self.temperature.get::<degree_celsius>(),
self.altitude.get::<meter>()
);
}
}
impl core::fmt::Display for Measurement {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Pressure: {} Pa, Temperature: {} °C, Altitude: {} m",
self.pressure.get::<pascal>(),
self.temperature.get::<degree_celsius>(),
self.altitude.get::<meter>(),
)
}
}
#[derive(Debug, Clone, Copy, Format)]
pub enum Address {
Down = 0x76,
Up = 0x77,
}
impl From<Address> for u8 {
fn from(address: Address) -> u8 {
address as u8
}
}
#[derive(Debug, Clone, Copy, Format)]
struct CalibrationCoefficients {
par_t1: f32,
par_t2: f32,
par_t3: f32,
par_p1: f32,
par_p2: f32,
par_p3: f32,
par_p4: f32,
par_p5: f32,
par_p6: f32,
par_p7: f32,
par_p8: f32,
par_p9: f32,
par_p10: f32,
par_p11: f32,
}
impl CalibrationCoefficients {
async fn try_from_i2c<I: I2c>(address: Address, i2c: &mut I) -> Result<Self, Error<I::Error>> {
let mut calibration_coefficient_regs = [0; 21];
i2c.write_read(
address.into(),
&Self::write_read_write_transaction(),
&mut calibration_coefficient_regs,
)
.await
.map_err(Error::I2c)?;
Ok(Self::from_registers(&calibration_coefficient_regs))
}
fn from_registers(data: &[u8; 21]) -> Self {
trace!("NVM_PAR: {=[u8]:#04x}", *data);
let nvm_par_t1: u16 = (data[1] as u16) << 8 | data[0] as u16;
let nvm_par_t2: u16 = (data[3] as u16) << 8 | data[2] as u16;
let nvm_par_t3: i8 = data[4] as i8;
let nvm_par_p1: i16 = (data[6] as i16) << 8 | data[5] as i16;
let nvm_par_p2: i16 = (data[8] as i16) << 8 | data[7] as i16;
let nvm_par_p3: i8 = data[9] as i8;
let nvm_par_p4: i8 = data[10] as i8;
let nvm_par_p5: u16 = (data[12] as u16) << 8 | data[11] as u16;
let nvm_par_p6: u16 = (data[14] as u16) << 8 | data[13] as u16;
let nvm_par_p7: i8 = data[15] as i8;
let nvm_par_p8: i8 = data[16] as i8;
let nvm_par_p9: i16 = (data[18] as i16) << 8 | data[17] as i16;
let nvm_par_p10: i8 = data[19] as i8;
let nvm_par_p11: i8 = data[20] as i8;
Self {
par_t1: (nvm_par_t1 as f32) / 0.003_906_25, par_t2: (nvm_par_t2 as f32) / 1_073_741_824.0, par_t3: (nvm_par_t3 as f32) / 281_474_976_710_656.0, par_p1: ((nvm_par_p1 as f32) - 16_384.0) / 1_048_576.0, par_p2: ((nvm_par_p2 as f32) - 16_384.0) / 536_870_912.0, par_p3: (nvm_par_p3 as f32) / 4_294_967_296.0, par_p4: (nvm_par_p4 as f32) / 137_438_953_472.0, par_p5: (nvm_par_p5 as f32) / 0.125, par_p6: (nvm_par_p6 as f32) / 64.0, par_p7: (nvm_par_p7 as f32) / 256.0, par_p8: (nvm_par_p8 as f32) / 32768.0, par_p9: (nvm_par_p9 as f32) / 281_474_976_710_656.0, par_p10: (nvm_par_p10 as f32) / 281_474_976_710_656.0, par_p11: (nvm_par_p11 as f32) / 36_893_488_147_419_103_232.0, }
}
fn compensate_temperature(&self, temperature_uncompensated: u32) -> ThermodynamicTemperature {
let uncompensated = temperature_uncompensated as f32;
let partial_data1 = uncompensated - self.par_t1;
let partial_data2 = partial_data1 * self.par_t2;
let temperature = partial_data2 + (partial_data1 * partial_data1) * self.par_t3;
ThermodynamicTemperature::new::<degree_celsius>(temperature)
}
fn compensate_pressure(
&self,
temperature: ThermodynamicTemperature,
pressure_uncompensated: u32,
) -> Pressure {
let uncompensated = pressure_uncompensated as f32;
let temperature = temperature.get::<degree_celsius>();
let partial_data1 = self.par_p6 * temperature;
let partial_data2 = self.par_p7 * temperature * temperature;
let partial_data3 = self.par_p8 * temperature * temperature * temperature;
let partial_out1 = self.par_p5 + partial_data1 + partial_data2 + partial_data3;
let partial_data1 = self.par_p2 * temperature;
let partial_data2 = self.par_p3 * temperature * temperature;
let partial_data3 = self.par_p4 * temperature * temperature * temperature;
let partial_out2 =
uncompensated * (self.par_p1 + partial_data1 + partial_data2 + partial_data3);
let partial_data1 = uncompensated * uncompensated;
let partial_data2 = self.par_p9 + self.par_p10 * temperature;
let partial_data3 = partial_data1 * partial_data2;
let partial_data4 =
partial_data3 + uncompensated * uncompensated * uncompensated * self.par_p11;
let pressure = partial_out1 + partial_out2 + partial_data4;
Pressure::new::<pascal>(pressure)
}
fn write_read_write_transaction() -> [u8; 1] {
[Register::NVM_PAR_T1_0.into()]
}
}
#[derive(Debug, Clone, Copy, Format)]
pub struct Configuration {
pub power_control: PowerControl,
pub oversampling: Osr,
pub output_data_rate: Odr,
pub iir_filter: Config,
}
impl Default for Configuration {
fn default() -> Self {
Self {
power_control: PowerControl {
enable_pressure: true,
enable_temperature: true,
mode: PowerMode::Normal,
},
oversampling: Osr {
pressure: Oversampling::X8,
temperature: Oversampling::X1,
},
output_data_rate: Odr {
odr_sel: OdrSel::ODR_50,
},
iir_filter: Config {
iir_filter: IirFilter::coef_15,
},
}
}
}
impl Configuration {
pub fn to_write_bytes(&self) -> [u8; 8] {
[
Register::PWR_CTRL.into(),
self.power_control.into(),
Register::OSR.into(),
self.oversampling.into(),
Register::ODR.into(),
self.output_data_rate.into(),
Register::CONFIG.into(),
self.iir_filter.into(),
]
}
}
pub struct Bmp390<I> {
i2c: I,
address: Address,
coefficients: CalibrationCoefficients,
altitude_reference: Length,
}
impl<I, E> Bmp390<I>
where
I: I2c<Error = E>,
{
pub async fn try_new<D: DelayNs>(
mut i2c: I,
address: Address,
mut delay: D,
config: &Configuration,
) -> Result<Self, Error<E>> {
delay.delay_ms(2).await;
let mut data = [0; 2];
i2c.write_read(address.into(), &[Register::CHIP_ID.into()], &mut data)
.await
.map_err(Error::I2c)?;
let chip_id = data[0];
let rev_id = data[1];
debug!("CHIP_ID = {=u8:#04x}; REV_ID = {=u8:#04x}", chip_id, rev_id);
if chip_id != 0x60 {
return Err(Error::WrongChip(chip_id));
}
let mut data = [0; 2];
i2c.write_read(address.into(), &[Register::EVENT.into()], &mut data)
.await
.map_err(Error::I2c)?;
i2c.write(address.into(), &config.to_write_bytes())
.await
.map_err(Error::I2c)?;
let mut err_reg = [0; 1];
i2c.write_read(address.into(), &[Register::ERR_REG.into()], &mut err_reg)
.await
.map_err(Error::I2c)
.and_then(move |_| {
let err_reg = ErrReg::from(err_reg[0]);
if err_reg.fatal_err {
Err(Error::<E>::Fatal)
} else if err_reg.cmd_err {
Err(Error::<E>::Command)
} else if err_reg.conf_err {
Err(Error::<E>::Configuration)
} else {
Ok(())
}
})?;
let coefficients = CalibrationCoefficients::try_from_i2c(address, &mut i2c).await?;
Ok(Self::new_with_coefficients(i2c, address, coefficients))
}
fn new_with_coefficients(
i2c: I,
address: Address,
coefficients: CalibrationCoefficients,
) -> Self {
Self {
i2c,
address,
coefficients,
altitude_reference: Length::new::<meter>(0.0),
}
}
pub async fn temperature(&mut self) -> Result<ThermodynamicTemperature, Error<E>> {
let write = &[Register::DATA_3.into()];
let mut read = [0; 3];
self.i2c
.write_read(self.address.into(), write, &mut read)
.await
.map_err(Error::I2c)?;
let temperature = u32::from(read[0]) | u32::from(read[1]) << 8 | u32::from(read[2]) << 16;
let temperature = self.coefficients.compensate_temperature(temperature);
Ok(temperature)
}
pub async fn temperature_pressure(
&mut self,
) -> Result<(ThermodynamicTemperature, Pressure), Error<E>> {
let write = &[Register::DATA_0.into()];
let mut read = [0; 6];
self.i2c
.write_read(self.address.into(), write, &mut read)
.await
.map_err(Error::I2c)?;
trace!("DATA = {=[u8]:#04x}", read);
let temperature = u32::from(read[3]) | u32::from(read[4]) << 8 | u32::from(read[5]) << 16;
let temperature = self.coefficients.compensate_temperature(temperature);
let pressure = u32::from(read[0]) | u32::from(read[1]) << 8 | u32::from(read[2]) << 16;
let pressure = self.coefficients.compensate_pressure(temperature, pressure);
Ok((temperature, pressure))
}
pub async fn pressure(&mut self) -> Result<Pressure, Error<E>> {
let (_, pressure) = self.temperature_pressure().await?;
Ok(pressure)
}
pub async fn measure(&mut self) -> Result<Measurement, Error<E>> {
let (temperature, pressure) = self.temperature_pressure().await?;
Ok(Measurement {
temperature,
pressure,
altitude: calculate_altitude(pressure, self.altitude_reference),
})
}
pub fn set_reference_altitude(&mut self, altitude: Length) {
self.altitude_reference = altitude;
}
pub async fn altitude(&mut self) -> Result<Length, Error<E>> {
let pressure = self.pressure().await?;
Ok(calculate_altitude(pressure, self.altitude_reference))
}
}
fn calculate_altitude(pressure: Pressure, altitude_reference: Length) -> Length {
let sea_level = Pressure::new::<hectopascal>(1013.25);
let above_sea_level =
Length::new::<foot>(145366.45 * (1.0 - powf((pressure / sea_level).value, 0.190284)));
above_sea_level - altitude_reference
}
#[cfg(test)]
mod tests {
extern crate std;
use embedded_hal_mock::eh1::delay::{CheckedDelay, NoopDelay, Transaction as DelayTransaction};
use embedded_hal_mock::eh1::i2c::{Mock, Transaction as I2cTransaction};
use std::prelude::rust_2021::*;
use std::vec;
use uom::ConstZero;
use super::*;
const PRESSURE_TEMPERATURE_BYTES: [u8; 6] = [0xcb, 0xb3, 0x6b, 0xd1, 0xba, 0x82];
fn expected_pressure() -> Pressure {
Pressure::new::<pascal>(98370.55)
}
const TEMPERATURE_BYTES: [u8; 3] = [0xd1, 0xba, 0x82];
fn expected_temperature() -> ThermodynamicTemperature {
ThermodynamicTemperature::new::<degree_celsius>(25.770_746)
}
fn expected_altitude() -> Length {
Length::new::<meter>(248.78754)
}
impl Default for CalibrationCoefficients {
fn default() -> Self {
Self::from_registers(&[
0x98, 0x6c, 0xa9, 0x4a, 0xf9, 0xe3, 0x1c, 0x61, 0x16, 0x06, 0x01, 0x51, 0x4a, 0xde,
0x5d, 0x03, 0xfa, 0xf9, 0x0e, 0x06, 0xf5,
])
}
}
fn get_try_new_transactions(
addr: Address,
configuration: &Configuration,
err_reg: &ErrReg,
event: &Event,
int_status: &IntStatus,
) -> [I2cTransaction; 5] {
[
I2cTransaction::write_read(
addr.into(),
vec![Register::CHIP_ID.into()],
vec![0x60, 0x01],
),
I2cTransaction::write_read(
addr.into(),
vec![Register::EVENT.into()],
vec![u8::from(*event), u8::from(*int_status)],
),
I2cTransaction::write(addr.into(), configuration.to_write_bytes().to_vec()),
I2cTransaction::write_read(
addr.into(),
vec![Register::ERR_REG.into()],
vec![u8::from(*err_reg)],
),
I2cTransaction::write_read(
addr.into(),
CalibrationCoefficients::write_read_write_transaction().to_vec(),
vec![0; 21],
),
]
}
#[tokio::test]
async fn test_try_new() {
let addr = Address::Up;
let config = Configuration::default();
let expectations = get_try_new_transactions(addr, &config, &0.into(), &0.into(), &0.into());
let mut i2c = Mock::new(&expectations);
let mut delay = CheckedDelay::new(&[
DelayTransaction::async_delay_ms(2), ]);
let _bmp390 = Bmp390::try_new(i2c.clone(), addr, delay.clone(), &config)
.await
.unwrap();
delay.done();
i2c.done();
}
#[tokio::test]
async fn test_reads_temperature_and_compensates() {
let addr = Address::Up;
let expectations = [I2cTransaction::write_read(
addr.into(),
vec![Register::DATA_3.into()],
TEMPERATURE_BYTES.to_vec(),
)];
let mut i2c = Mock::new(&expectations);
let mut bmp390 =
Bmp390::new_with_coefficients(i2c.clone(), addr, CalibrationCoefficients::default());
let temperature = bmp390.temperature().await.unwrap();
assert_eq!(temperature, expected_temperature());
i2c.done();
}
#[tokio::test]
async fn test_reads_pressure() {
let addr = Address::Up;
let expectations = [I2cTransaction::write_read(
addr.into(),
vec![Register::DATA_0.into()],
PRESSURE_TEMPERATURE_BYTES.to_vec(),
)];
let mut i2c = Mock::new(&expectations);
let mut bmp390 =
Bmp390::new_with_coefficients(i2c.clone(), addr, CalibrationCoefficients::default());
let pressure = bmp390.pressure().await.unwrap();
assert_eq!(pressure, expected_pressure());
i2c.done();
}
#[tokio::test]
async fn test_reads_temperature_pressure() {
let addr = Address::Up;
let expectations = [I2cTransaction::write_read(
addr.into(),
vec![Register::DATA_0.into()],
PRESSURE_TEMPERATURE_BYTES.to_vec(),
)];
let mut i2c = Mock::new(&expectations);
let mut bmp390 =
Bmp390::new_with_coefficients(i2c.clone(), addr, CalibrationCoefficients::default());
let measurement = bmp390.temperature_pressure().await.unwrap();
assert_eq!(measurement.0, expected_temperature());
assert_eq!(measurement.1, expected_pressure());
i2c.done();
}
#[tokio::test]
async fn test_altitude() {
let addr = Address::Up;
let expectations = [I2cTransaction::write_read(
addr.into(),
vec![Register::DATA_0.into()],
PRESSURE_TEMPERATURE_BYTES.to_vec(),
)];
let mut i2c = Mock::new(&expectations);
let mut bmp390 =
Bmp390::new_with_coefficients(i2c.clone(), addr, CalibrationCoefficients::default());
let altitude = bmp390.altitude().await.unwrap();
assert_eq!(altitude, expected_altitude());
i2c.done();
}
#[tokio::test]
async fn test_measure_reads_temperature_pressure_altitude() {
let addr = Address::Up;
let expectations = [I2cTransaction::write_read(
addr.into(),
vec![Register::DATA_0.into()],
PRESSURE_TEMPERATURE_BYTES.to_vec(),
)];
let mut i2c = Mock::new(&expectations);
let mut bmp390 =
Bmp390::new_with_coefficients(i2c.clone(), addr, CalibrationCoefficients::default());
let measurement = bmp390.measure().await.unwrap();
assert_eq!(measurement.temperature, expected_temperature());
assert_eq!(measurement.pressure, expected_pressure());
assert_eq!(measurement.altitude, expected_altitude());
i2c.done();
}
#[tokio::test]
async fn test_altitude_custom_reference() {
let addr = Address::Up;
let expectations = [I2cTransaction::write_read(
addr.into(),
vec![Register::DATA_0.into()],
PRESSURE_TEMPERATURE_BYTES.to_vec(),
)];
let mut i2c = Mock::new(&expectations);
let mut bmp390 =
Bmp390::new_with_coefficients(i2c.clone(), addr, CalibrationCoefficients::default());
bmp390.set_reference_altitude(expected_altitude());
let altitude = bmp390.altitude().await.unwrap();
assert_eq!(altitude, Length::ZERO);
i2c.done();
}
#[tokio::test]
async fn test_chip_id_incorrect() {
let addr = Address::Up;
let mut expectations = get_try_new_transactions(
addr,
&Configuration::default(),
&0.into(),
&0.into(),
&0.into(),
)
.into_iter()
.take(1)
.collect::<Vec<_>>();
expectations[0] = I2cTransaction::write_read(
addr.into(),
vec![Register::CHIP_ID.into()],
vec![0x42, 0x01],
);
let mut i2c = Mock::new(&expectations);
let delay = NoopDelay::new();
let result = Bmp390::try_new(i2c.clone(), addr, delay, &Configuration::default()).await;
assert!(matches!(result, Err(Error::WrongChip(0x42))));
i2c.done();
}
#[tokio::test]
async fn test_fatal_error() {
let addr = Address::Up;
let fatal_err = ErrReg {
fatal_err: true,
cmd_err: false,
conf_err: false,
};
let expectations = get_try_new_transactions(
addr,
&Configuration::default(),
&fatal_err.into(),
&0.into(),
&0.into(),
)
.into_iter()
.take(4)
.collect::<Vec<_>>();
let mut i2c = Mock::new(&expectations);
let delay = NoopDelay::new();
let result = Bmp390::try_new(i2c.clone(), addr, delay, &Configuration::default()).await;
assert!(matches!(result, Err(Error::Fatal)));
i2c.done();
}
#[tokio::test]
async fn test_command_error() {
let addr = Address::Up;
let cmd_err = ErrReg {
fatal_err: false,
cmd_err: true,
conf_err: false,
};
let expectations = get_try_new_transactions(
addr,
&Configuration::default(),
&cmd_err.into(),
&0.into(),
&0.into(),
)
.into_iter()
.take(4)
.collect::<Vec<_>>();
let mut i2c = Mock::new(&expectations);
let delay = NoopDelay::new();
let result = Bmp390::try_new(i2c.clone(), addr, delay, &Configuration::default()).await;
assert!(matches!(result, Err(Error::Command)));
i2c.done();
}
#[tokio::test]
async fn test_configuration_error() {
let addr = Address::Up;
let conf_err = ErrReg {
fatal_err: false,
cmd_err: false,
conf_err: true,
};
let expectations = get_try_new_transactions(
addr,
&Configuration::default(),
&conf_err.into(),
&0.into(),
&0.into(),
)
.into_iter()
.take(4)
.collect::<Vec<_>>();
let mut i2c = Mock::new(&expectations);
let delay = NoopDelay::new();
let result = Bmp390::try_new(i2c.clone(), addr, delay, &Configuration::default()).await;
assert!(matches!(result, Err(Error::Configuration)));
i2c.done();
}
#[tokio::test]
async fn test_any_other_error() {
let addr = Address::Up;
for err_reg_bits in 0..=7 {
let err_reg = ErrReg::from(err_reg_bits);
if err_reg.fatal_err || err_reg.cmd_err || err_reg.conf_err {
continue;
}
let mut expectations = get_try_new_transactions(
addr,
&Configuration::default(),
&0.into(),
&0.into(),
&0.into(),
);
expectations[3] = I2cTransaction::write_read(
addr.into(),
vec![Register::ERR_REG.into()],
vec![err_reg_bits],
);
let mut i2c = Mock::new(&expectations);
let delay = NoopDelay::new();
let result = Bmp390::try_new(i2c.clone(), addr, delay, &Configuration::default()).await;
assert!(
result.is_ok(),
"Unexpected error with ERR_REG = {:#010b}",
err_reg_bits
);
i2c.done();
}
}
}