use core::{array, fmt};
use bmi2::{
Bmi2, I2cAddr, config,
interface::I2cInterface,
types::{
AccBwp, AccConf, AccRange, Burst, GyrBwp, GyrConf, GyrRange, GyrRangeVal, Odr, OisRange,
PerfMode, PwrCtrl,
},
};
use embassy_time::{Duration, Timer};
use embedded_hal_bus::i2c::CriticalSectionDevice;
use esp_hal::delay::Delay;
use crate::{Error, I2cBus, I2cDevice};
const IMU_BURST: u16 = 128;
type Bmi270 = Bmi2<I2cInterface<I2cDevice>, Delay, { IMU_BURST as usize }>;
const GYROSCOPE_START: Duration = Duration::from_millis(50);
const CALIBRATION_SAMPLES: i32 = 32;
const SAMPLE_PERIOD: Duration = Duration::from_millis(10);
pub struct Imu {
bmi: Bmi270,
gyroscope_offset: [i32; 3],
}
impl Imu {
pub fn acceleration(&mut self) -> Result<[i32; 3], Error> {
let data = self.bmi.get_acc_data().map_err(|_| Error::Imu)?;
let mg = |raw: i16| i32::from(raw) * 1000 / 4096;
Ok([mg(data.x), mg(data.y), mg(data.z)])
}
pub fn set_gyroscope(&mut self, on: bool) -> Result<(), Error> {
if on {
self.bmi
.set_gyr_range(GyrRange {
range: GyrRangeVal::Range2000,
ois_range: OisRange::Range2000,
})
.map_err(|_| Error::Imu)?;
self.bmi
.set_gyr_conf(GyrConf {
odr: Odr::Odr100,
bwp: GyrBwp::Norm,
noise_perf: PerfMode::Perf,
filter_perf: PerfMode::Perf,
})
.map_err(|_| Error::Imu)?;
}
self.set_power(on)
}
pub fn rotation(&mut self) -> Result<[i32; 3], Error> {
let raw = self.gyroscope_raw()?;
Ok(array::from_fn(|axis| {
(raw[axis] - self.gyroscope_offset[axis]) * 15_625 / 256
}))
}
pub async fn calibrate_gyroscope(&mut self) -> Result<(), Error> {
self.set_gyroscope(true)?;
Timer::after(GYROSCOPE_START).await;
let mut sum = [0; 3];
for _ in 0..CALIBRATION_SAMPLES {
Timer::after(SAMPLE_PERIOD).await;
let raw = self.gyroscope_raw()?;
for (total, value) in sum.iter_mut().zip(raw) {
*total += value;
}
}
self.gyroscope_offset = sum.map(|total| total / CALIBRATION_SAMPLES);
Ok(())
}
fn gyroscope_raw(&mut self) -> Result<[i32; 3], Error> {
let data = self.bmi.get_gyr_data().map_err(|_| Error::Imu)?;
Ok([data.x, data.y, data.z].map(i32::from))
}
fn set_power(&mut self, gyroscope: bool) -> Result<(), Error> {
self.bmi
.set_pwr_ctrl(PwrCtrl {
aux_en: false,
gyr_en: gyroscope,
acc_en: true,
temp_en: false,
})
.map_err(|_| Error::Imu)
}
}
pub fn imu(bus: &'static I2cBus) -> Result<Imu, Error> {
let mut bmi = [I2cAddr::Default, I2cAddr::Alternative]
.into_iter()
.find_map(|address| {
let mut bmi = Bmi270::new_i2c(
CriticalSectionDevice::new(bus),
Delay::new(),
address,
Burst::new(IMU_BURST),
);
bmi.get_chip_id().is_ok_and(|id| id == 0x24).then_some(bmi)
})
.ok_or(Error::Imu)?;
bmi.init(&config::BMI270_CONFIG_FILE)
.map_err(|_| Error::Imu)?;
bmi.set_acc_range(AccRange::Range8g)
.map_err(|_| Error::Imu)?;
bmi.set_acc_conf(AccConf {
odr: Odr::Odr100,
bwp: AccBwp::NormAvg4,
filter_perf: PerfMode::Perf,
})
.map_err(|_| Error::Imu)?;
let mut imu = Imu {
bmi,
gyroscope_offset: [0; 3],
};
imu.set_power(false)?;
Ok(imu)
}
impl fmt::Debug for Imu {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Imu")
.field("gyroscope_offset", &self.gyroscope_offset)
.finish_non_exhaustive()
}
}