use log::{debug, warn};
use embedded_hal::blocking::delay::DelayMs;
use embedded_hal::blocking::i2c::{Read, Write, WriteRead};
use crate::calibration;
use crate::{CalibrationData, Configuration, Status};
pub const DEFAULT_ADDRESS: u8 = 0x76;
pub const CHIP_ID: u8 = 0x60;
const BME280_REGISTER_CHIPID: u8 = 0xD0;
const BME280_REGISTER_SOFTRESET: u8 = 0xE0;
const BME280_REGISTER_CONTROLHUMID: u8 = 0xF2;
const BME280_REGISTER_STATUS: u8 = 0xF3;
const BME280_REGISTER_CONTROL: u8 = 0xF4;
const BME280_REGISTER_CONFIG: u8 = 0xF5;
const BME280_REGISTER_PRESSUREDATA: u8 = 0xF7;
const BME280_REGISTER_TEMPDATA: u8 = 0xFA;
const BME280_REGISTER_HUMIDDATA: u8 = 0xFD;
const BME280_COMMAND_SOFTRESET: u8 = 0xB6;
const MODE_SLEEP: u8 = 0b00;
pub type Sample = (Option<f32>, Option<f32>, Option<f32>);
type RawSample = (Option<u32>, Option<u32>, Option<u16>);
pub struct Bme280<I2c, Delay> {
i2c: I2c,
address: u8,
delay: Delay,
coefficients: CalibrationData,
configuration: Configuration,
}
impl<I2C, D, E> Bme280<I2C, D>
where
I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
D: DelayMs<u32>,
{
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: address as u8,
delay,
coefficients,
configuration: Configuration::default(),
}
}
pub fn init(&mut self) -> Result<(), E> {
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, E> {
debug!("Read chip id");
let chip_id = self.read_u8(BME280_REGISTER_CHIPID)?;
Ok(chip_id)
}
pub fn status(&mut self) -> Result<Status, E> {
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<(), E> {
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, E> {
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, E> {
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((
if adc_t == 0x80000 { None } else { Some(adc_t) },
if adc_p == 0x80000 { None } else { Some(adc_p) },
if adc_h == 0x8000 { None } else { Some(adc_h) },
))
}
pub fn read_sample(&mut self) -> Result<Sample, E> {
let (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);
#[allow(clippy::cast_precision_loss)] let humidity = h.map(|h| h as f32 / 1024.0);
Ok((temperature, pressure, humidity))
} else {
warn!("Temperature measurement is disabled");
Ok((None, None, None))
}
}
fn temperature_fine_to_temperature(t_fine: i32) -> f32 {
let t = (t_fine * 5 + 128) >> 8;
#[allow(clippy::cast_precision_loss)] let t = t as f32;
t / 100.0
}
pub fn read_temperature(&mut self) -> Result<Option<f32>, E> {
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>, E> {
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>, E> {
let adc_t = self.read_u24(BME280_REGISTER_TEMPDATA)?;
if adc_t == 0x80000 {
Ok(None)
} else {
Ok(Some(adc_t))
}
}
pub fn read_pressure(&mut self) -> Result<Option<f32>, E> {
if let Some(t_fine) = self.read_temperature_fine()? {
self.read_pressure_with_temperature_fine(t_fine)
} else {
warn!("Temperature measurement is disabled");
Ok(None)
}
}
pub fn read_pressure_with_temperature(&mut self, temperature: f32) -> Result<Option<f32>, E> {
#[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<f32>, E> {
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;
p / 256.0
});
Ok(p)
}
fn read_raw_pressure(&mut self) -> Result<Option<u32>, E> {
let adc_p = self.read_u24(BME280_REGISTER_PRESSUREDATA)?;
if adc_p == 0x80000 {
Ok(None)
} else {
Ok(Some(adc_p))
}
}
pub fn read_humidity(&mut self) -> Result<Option<f32>, E> {
if let Some(t_fine) = self.read_temperature_fine()? {
self.read_humidity_with_temperature_fine(t_fine)
} else {
warn!("Temperature measurement is disabled");
Ok(None)
}
}
pub fn read_humidity_with_temperature(&mut self, temperature: f32) -> Result<Option<f32>, E> {
#[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<f32>, E> {
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;
h / 1024.0
});
Ok(h)
}
fn read_raw_humidity(&mut self) -> Result<Option<u16>, E> {
let adc_h = self.read_u16(BME280_REGISTER_HUMIDDATA)?;
if adc_h == 0x8000 {
Ok(None)
} else {
Ok(Some(adc_h))
}
}
fn read_calibration_coefficients(&mut self) -> Result<(), E> {
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 as usize..calibration::TOTAL_LENGTH],
)?;
self.coefficients = (&out).into();
Ok(())
}
fn write_u8(&mut self, register: u8, value: u8) -> Result<(), E> {
let buffer: [u8; 2] = [register, value];
self.i2c.write(self.address, &buffer)?;
Ok(())
}
fn read_u8(&mut self, register: u8) -> Result<u8, E> {
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, E> {
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, E> {
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 {
use super::*;
use assert2::check;
use embedded_hal_mock::delay::MockNoop as DelayMock;
use embedded_hal_mock::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
use calibration::TEST_CALIBRATION_DATA;
#[test]
fn test_chip_id() -> Result<(), Box<dyn std::error::Error>> {
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()?;
check!(chip_id == CHIP_ID);
Ok(())
}
#[test]
fn test_chip_status() -> Result<(), Box<dyn std::error::Error>> {
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()?;
check!(!status.is_measuring());
check!(!status.is_calibrating());
Ok(())
}
#[test]
fn test_chip_status_measuring() -> Result<(), Box<dyn std::error::Error>> {
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()?;
check!(status.is_measuring());
check!(!status.is_calibrating());
Ok(())
}
#[test]
fn test_read_temperature_disabled() -> Result<(), Box<dyn std::error::Error>> {
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()?;
check!(temperature == expected);
Ok(())
}
#[test]
fn test_read_temperature() -> Result<(), Box<dyn std::error::Error>> {
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(27.33);
let temperature = bme280.read_temperature()?;
check!(temperature == expected);
Ok(())
}
#[test]
fn test_read_pressure_disabled() -> Result<(), Box<dyn std::error::Error>> {
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()?;
check!(pressure == expected);
Ok(())
}
#[test]
fn test_read_pressure() -> Result<(), Box<dyn std::error::Error>> {
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(101_233.016);
let pressure = bme280.read_pressure()?;
check!(pressure == expected);
Ok(())
}
#[test]
fn test_read_humidity_disabled() -> Result<(), Box<dyn std::error::Error>> {
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()?;
check!(humidity == expected);
Ok(())
}
#[test]
fn test_read_humidity() -> Result<(), Box<dyn std::error::Error>> {
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(34.854_492);
let humidity = bme280.read_humidity()?;
check!(humidity == expected);
Ok(())
}
}