use embedded_hal::delay::DelayNs;
use embedded_hal::i2c::I2c;
use crate::calibration;
use crate::constants::BME280_COMMAND_SOFTRESET;
use crate::constants::BME280_REGISTER_CHIPID;
use crate::constants::BME280_REGISTER_CONFIG;
use crate::constants::BME280_REGISTER_CONTROL;
use crate::constants::BME280_REGISTER_CONTROLHUMID;
use crate::constants::BME280_REGISTER_HUMIDDATA;
use crate::constants::BME280_REGISTER_PRESSUREDATA;
use crate::constants::BME280_REGISTER_SOFTRESET;
use crate::constants::BME280_REGISTER_STATUS;
use crate::constants::BME280_REGISTER_TEMPDATA;
use crate::constants::DEFAULT_ADDRESS;
use crate::constants::MODE_SLEEP;
use crate::constants::SKIPPED_HUMIDITY_OUTPUT;
use crate::constants::SKIPPED_PRESSURE_OUTPUT;
use crate::constants::SKIPPED_TEMPERATURE_OUTPUT;
use crate::macros::debug;
use crate::macros::warn;
use crate::sample::humidity_from_number;
use crate::sample::pressure_from_pascal;
use crate::sample::temperature_from_celsius;
use crate::sample::Humidity;
use crate::sample::Pressure;
use crate::sample::RawSample;
use crate::sample::Sample;
use crate::sample::Temperature;
use crate::CalibrationData;
use crate::Configuration;
use crate::Status;
pub struct Bme280<I2c, Delay> {
i2c: I2c,
address: u8,
delay: Delay,
coefficients: CalibrationData,
configuration: Configuration,
}
impl<I2C, D> Bme280<I2C, D>
where
I2C: I2c,
D: DelayNs,
{
pub fn new(i2c: I2C, delay: D) -> Self {
Self::new_with_address(i2c, DEFAULT_ADDRESS, delay)
}
pub fn release(self) -> I2C {
self.i2c
}
pub fn new_with_address(i2c: I2C, address: u8, delay: D) -> Self {
Self::new_with_coefficients(i2c, address, delay, CalibrationData::default())
}
fn new_with_coefficients(
i2c: I2C,
address: u8,
delay: D,
coefficients: CalibrationData,
) -> Self {
debug!("Creating new BME280 device at address 0x{:x}", address);
Self {
i2c,
address,
delay,
coefficients,
configuration: Configuration::default(),
}
}
pub fn init(&mut self) -> Result<(), I2C::Error> {
debug!("Sending soft-reset signal");
self.write_u8(BME280_REGISTER_SOFTRESET, BME280_COMMAND_SOFTRESET)?;
debug!("Waiting 10 ms");
self.delay.delay_ms(10);
while self.status()?.is_calibrating() {
debug!("Calibration not complete, waiting 10 ms");
self.delay.delay_ms(10);
}
debug!("Reading coefficients");
self.read_calibration_coefficients()?;
debug!("Set sampling");
let configuration = Configuration::default();
self.set_sampling_configuration(configuration)?;
debug!("Waiting 100 ms");
self.delay.delay_ms(100);
Ok(())
}
pub fn chip_id(&mut self) -> Result<u8, I2C::Error> {
debug!("Read chip id");
let chip_id = self.read_u8(BME280_REGISTER_CHIPID)?;
Ok(chip_id)
}
pub fn status(&mut self) -> Result<Status, I2C::Error> {
debug!("Read chip status");
let status = self.read_u8(BME280_REGISTER_STATUS)?.into();
Ok(status)
}
pub fn set_sampling_configuration(
&mut self,
configuration: Configuration,
) -> Result<(), I2C::Error> {
self.configuration = configuration;
let (config, ctrl_meas, ctrl_hum) = self.configuration.to_lowlevel_configuration();
self.write_u8(BME280_REGISTER_CONTROL, MODE_SLEEP)?;
self.write_u8(BME280_REGISTER_CONTROLHUMID, ctrl_hum.into())?;
self.write_u8(BME280_REGISTER_CONFIG, config.into())?;
self.write_u8(BME280_REGISTER_CONTROL, ctrl_meas.into())?;
Ok(())
}
pub fn take_forced_measurement(&mut self) -> Result<bool, I2C::Error> {
if self.configuration.is_forced() {
debug!("Forcing taking a measurement");
let (_config, ctrl_meas, _ctrl_hum) = self.configuration.to_lowlevel_configuration();
self.write_u8(BME280_REGISTER_CONTROL, ctrl_meas.into())?;
for _ in 0..10 {
if !self.status()?.is_measuring() {
break;
}
debug!("Measuring not complete, waiting 10 ms");
self.delay.delay_ms(10);
}
Ok(true)
} else {
Ok(false)
}
}
fn read_raw_sample(&mut self) -> Result<RawSample, I2C::Error> {
let buffer: [u8; 1] = [BME280_REGISTER_PRESSUREDATA];
let mut buf: [u8; 8] = [0; 8];
self.i2c.write_read(self.address, &buffer, &mut buf)?;
let adc_p: u32 =
(u32::from(buf[0]) << 12) | (u32::from(buf[1]) << 4) | (u32::from(buf[2]) >> 4);
let adc_t: u32 =
(u32::from(buf[3]) << 12) | (u32::from(buf[4]) << 4) | (u32::from(buf[5]) >> 4);
let adc_h: u16 = (u16::from(buf[6]) << 8) | u16::from(buf[7]);
Ok(RawSample {
adc_t: if adc_t == SKIPPED_TEMPERATURE_OUTPUT {
None
} else {
Some(adc_t)
},
adc_p: if adc_p == SKIPPED_PRESSURE_OUTPUT {
None
} else {
Some(adc_p)
},
adc_h: if adc_h == SKIPPED_HUMIDITY_OUTPUT {
None
} else {
Some(adc_h)
},
})
}
pub fn read_sample(&mut self) -> Result<Sample, I2C::Error> {
let RawSample {
adc_t,
adc_p,
adc_h,
} = self.read_raw_sample()?;
if let Some(adc_t) = adc_t {
let t_fine = self.coefficients.compensate_temperature(adc_t);
let t = Some(Self::temperature_fine_to_temperature(t_fine));
let p = adc_p.map(|adc_p| self.coefficients.compensate_pressure(adc_p, t_fine));
let h = adc_h.map(|adc_h| self.coefficients.compensate_humidity(adc_h, t_fine));
let temperature = t;
#[allow(clippy::cast_precision_loss)] let pressure = p.map(|p| p as f32 / 256.0);
let pressure = pressure.map(pressure_from_pascal);
#[allow(clippy::cast_precision_loss)] let humidity = h.map(|h| h as f32 / 1024.0);
let humidity = humidity.map(humidity_from_number);
Ok(Sample {
temperature,
pressure,
humidity,
})
} else {
warn!("Temperature measurement is disabled");
Ok(Sample::default())
}
}
fn temperature_fine_to_temperature(t_fine: i32) -> Temperature {
let t = (t_fine * 5 + 128) >> 8;
#[allow(clippy::cast_precision_loss)] let t = t as f32;
temperature_from_celsius(t / 100.0)
}
pub fn read_temperature(&mut self) -> Result<Option<Temperature>, I2C::Error> {
if let Some(t_fine) = self.read_temperature_fine()? {
Ok(Some(Self::temperature_fine_to_temperature(t_fine)))
} else {
Ok(None)
}
}
fn read_temperature_fine(&mut self) -> Result<Option<i32>, I2C::Error> {
let adc_t = self.read_raw_temperature()?;
let t_fine = adc_t.map(|adc_t| self.coefficients.compensate_temperature(adc_t));
Ok(t_fine)
}
fn read_raw_temperature(&mut self) -> Result<Option<u32>, I2C::Error> {
let adc_t = self.read_u24(BME280_REGISTER_TEMPDATA)?;
if adc_t == SKIPPED_TEMPERATURE_OUTPUT {
Ok(None)
} else {
Ok(Some(adc_t))
}
}
pub fn read_pressure(&mut self) -> Result<Option<Pressure>, I2C::Error> {
if let Some(t_fine) = self.read_temperature_fine()? {
self.read_pressure_with_temperature_fine(t_fine)
} else {
warn!("Pressure measurement is disabled");
Ok(None)
}
}
pub fn read_pressure_with_temperature(
&mut self,
temperature: f32,
) -> Result<Option<Pressure>, I2C::Error> {
#[allow(clippy::cast_possible_truncation)] let t = (temperature * 100.0) as i32;
let t_fine = ((t << 8) - 128) / 5;
self.read_pressure_with_temperature_fine(t_fine)
}
fn read_pressure_with_temperature_fine(
&mut self,
t_fine: i32,
) -> Result<Option<Pressure>, I2C::Error> {
let adc_p = self.read_raw_pressure()?;
let p = adc_p.map(|adc_p| {
let p = self.coefficients.compensate_pressure(adc_p, t_fine);
#[allow(clippy::cast_precision_loss)] let p = p as f32;
pressure_from_pascal(p / 256.0)
});
Ok(p)
}
fn read_raw_pressure(&mut self) -> Result<Option<u32>, I2C::Error> {
let adc_p = self.read_u24(BME280_REGISTER_PRESSUREDATA)?;
if adc_p == SKIPPED_PRESSURE_OUTPUT {
Ok(None)
} else {
Ok(Some(adc_p))
}
}
pub fn read_humidity(&mut self) -> Result<Option<Humidity>, I2C::Error> {
if let Some(t_fine) = self.read_temperature_fine()? {
self.read_humidity_with_temperature_fine(t_fine)
} else {
warn!("Humidity measurement is disabled");
Ok(None)
}
}
pub fn read_humidity_with_temperature(
&mut self,
temperature: f32,
) -> Result<Option<Humidity>, I2C::Error> {
#[allow(clippy::cast_possible_truncation)] let t = (temperature * 100.0) as i32;
let t_fine = ((t << 8) - 128) / 5;
self.read_humidity_with_temperature_fine(t_fine)
}
fn read_humidity_with_temperature_fine(
&mut self,
t_fine: i32,
) -> Result<Option<Humidity>, I2C::Error> {
let adc_h = self.read_raw_humidity()?;
let h = adc_h.map(|adc_h| {
let h = self.coefficients.compensate_humidity(adc_h, t_fine);
#[allow(clippy::cast_precision_loss)] let h = h as f32;
humidity_from_number(h / 1024.0)
});
Ok(h)
}
fn read_raw_humidity(&mut self) -> Result<Option<u16>, I2C::Error> {
let adc_h = self.read_u16(BME280_REGISTER_HUMIDDATA)?;
if adc_h == SKIPPED_HUMIDITY_OUTPUT {
Ok(None)
} else {
Ok(Some(adc_h))
}
}
fn read_calibration_coefficients(&mut self) -> Result<(), I2C::Error> {
let buffer: [u8; 1] = [calibration::FIRST_REGISTER];
let mut out: [u8; calibration::TOTAL_LENGTH] = [0; calibration::TOTAL_LENGTH];
self.i2c.write_read(
self.address,
&buffer,
&mut out[0..calibration::FIRST_LENGTH],
)?;
let buffer: [u8; 1] = [calibration::SECOND_REGISTER];
self.i2c.write_read(
self.address,
&buffer,
&mut out[calibration::FIRST_LENGTH..calibration::TOTAL_LENGTH],
)?;
self.coefficients = (&out).into();
Ok(())
}
fn write_u8(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {
let buffer: [u8; 2] = [register, value];
self.i2c.write(self.address, &buffer)?;
Ok(())
}
fn read_u8(&mut self, register: u8) -> Result<u8, I2C::Error> {
let buffer: [u8; 1] = [register];
let mut output_buffer: [u8; 1] = [0];
self.i2c
.write_read(self.address, &buffer, &mut output_buffer)?;
Ok(output_buffer[0])
}
fn read_u16(&mut self, register: u8) -> Result<u16, I2C::Error> {
let buffer: [u8; 1] = [register];
let mut output_buffer: [u8; 2] = [0, 0];
self.i2c
.write_read(self.address, &buffer, &mut output_buffer)?;
Ok((u16::from(output_buffer[0]) << 8) | u16::from(output_buffer[1]))
}
fn read_u24(&mut self, register: u8) -> Result<u32, I2C::Error> {
let buffer: [u8; 1] = [register];
let mut output_buffer: [u8; 3] = [0, 0, 0];
self.i2c
.write_read(self.address, &buffer, &mut output_buffer)?;
Ok((u32::from(output_buffer[0]) << 12)
| (u32::from(output_buffer[1]) << 4)
| (u32::from(output_buffer[2]) >> 4))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::panic_in_result_fn)]
use alloc::vec;
use embedded_hal::i2c::ErrorKind;
use embedded_hal_mock::eh1::delay::NoopDelay as DelayMock;
use embedded_hal_mock::eh1::i2c::Mock as I2cMock;
use embedded_hal_mock::eh1::i2c::Transaction as I2cTransaction;
use crate::calibration::TEST_CALIBRATION_DATA;
use crate::constants::CHIP_ID;
use super::*;
#[test]
fn test_init() -> Result<(), ErrorKind> {
let expectations = [
I2cTransaction::write(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_SOFTRESET, BME280_COMMAND_SOFTRESET],
),
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_STATUS],
vec![0b0000_0001],
),
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_STATUS],
vec![0b0000_0000],
),
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![calibration::FIRST_REGISTER],
vec![0; 26],
),
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![calibration::SECOND_REGISTER],
vec![0; 7],
),
I2cTransaction::write(DEFAULT_ADDRESS, vec![BME280_REGISTER_CONTROL, MODE_SLEEP]),
I2cTransaction::write(DEFAULT_ADDRESS, vec![BME280_REGISTER_CONTROLHUMID, 0]),
I2cTransaction::write(DEFAULT_ADDRESS, vec![BME280_REGISTER_CONFIG, 0]),
I2cTransaction::write(DEFAULT_ADDRESS, vec![BME280_REGISTER_CONTROL, 0]),
];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new(i2c, DelayMock);
bme280.init()?;
bme280.release().done();
Ok(())
}
#[test]
fn test_chip_id() -> Result<(), ErrorKind> {
let expectations = [I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_CHIPID],
vec![CHIP_ID],
)];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new(i2c, DelayMock);
let chip_id = bme280.chip_id()?;
assert_eq!(chip_id, CHIP_ID);
bme280.release().done();
Ok(())
}
#[test]
fn test_chip_status() -> Result<(), ErrorKind> {
let expectations = [I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_STATUS],
vec![0x00],
)];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new(i2c, DelayMock);
let status = bme280.status()?;
assert!(!status.is_measuring());
assert!(!status.is_calibrating());
bme280.release().done();
Ok(())
}
#[test]
fn test_chip_status_measuring() -> Result<(), ErrorKind> {
let expectations = [I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_STATUS],
vec![0b0000_0100],
)];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new(i2c, DelayMock);
let status = bme280.status()?;
assert!(status.is_measuring());
assert!(!status.is_calibrating());
bme280.release().done();
Ok(())
}
#[test]
fn test_read_temperature_disabled() -> Result<(), ErrorKind> {
let expectations = [I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_TEMPDATA],
vec![0x80, 0x00, 0x00],
)];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new_with_coefficients(
i2c,
DEFAULT_ADDRESS,
DelayMock,
TEST_CALIBRATION_DATA.clone(),
);
let expected = None;
let temperature = bme280.read_temperature()?;
assert_eq!(temperature, expected);
bme280.release().done();
Ok(())
}
#[test]
fn test_read_temperature() -> Result<(), ErrorKind> {
let expectations = [I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_TEMPDATA],
vec![0x84, 0x47, 0x00],
)];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new_with_coefficients(
i2c,
DEFAULT_ADDRESS,
DelayMock,
TEST_CALIBRATION_DATA.clone(),
);
let expected = Some(temperature_from_celsius(27.33));
let temperature = bme280.read_temperature()?;
assert_eq!(temperature, expected);
bme280.release().done();
Ok(())
}
#[test]
fn test_read_pressure_disabled() -> Result<(), ErrorKind> {
let expectations = [
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_TEMPDATA],
vec![0x84, 0x47, 0x00],
),
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_PRESSUREDATA],
vec![0x80, 0x00, 0x00],
),
];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new_with_coefficients(
i2c,
DEFAULT_ADDRESS,
DelayMock,
TEST_CALIBRATION_DATA.clone(),
);
let expected = None;
let pressure = bme280.read_pressure()?;
assert_eq!(pressure, expected);
bme280.release().done();
Ok(())
}
#[test]
fn test_read_pressure() -> Result<(), ErrorKind> {
let expectations = [
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_TEMPDATA],
vec![0x84, 0x47, 0x00],
),
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_PRESSUREDATA],
vec![0x4f, 0x50, 0x00],
),
];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new_with_coefficients(
i2c,
DEFAULT_ADDRESS,
DelayMock,
TEST_CALIBRATION_DATA.clone(),
);
let expected = Some(pressure_from_pascal(101_233.016));
let pressure = bme280.read_pressure()?;
assert_eq!(pressure, expected);
bme280.release().done();
Ok(())
}
#[test]
fn test_read_humidity_disabled() -> Result<(), ErrorKind> {
let expectations = [
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_TEMPDATA],
vec![0x84, 0x47, 0x00],
),
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_HUMIDDATA],
vec![0x80, 0x00],
),
];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new_with_coefficients(
i2c,
DEFAULT_ADDRESS,
DelayMock,
TEST_CALIBRATION_DATA.clone(),
);
let expected = None;
let humidity = bme280.read_humidity()?;
assert_eq!(humidity, expected);
bme280.release().done();
Ok(())
}
#[test]
fn test_read_humidity() -> Result<(), ErrorKind> {
let expectations = [
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_TEMPDATA],
vec![0x84, 0x47, 0x00],
),
I2cTransaction::write_read(
DEFAULT_ADDRESS,
vec![BME280_REGISTER_HUMIDDATA],
vec![0x60, 0x02],
),
];
let i2c = I2cMock::new(&expectations);
let mut bme280 = Bme280::new_with_coefficients(
i2c,
DEFAULT_ADDRESS,
DelayMock,
TEST_CALIBRATION_DATA.clone(),
);
let expected = Some(humidity_from_number(34.854_492));
let humidity = bme280.read_humidity()?;
assert_eq!(humidity, expected);
bme280.release().done();
Ok(())
}
}