use core::{array, fmt};
use bmi2::config::BMI270_CONFIG_FILE;
use embassy_time::{Duration, Timer};
use embedded_hal_async::i2c::I2c as _;
use crate::{
Acceleration, Error, I2cBus, I2cDevice, Millicelsius, Motion, Rotation,
bmi270::{
self, ACC_RANGE_8G, ACCELEROMETER_ON, ADVANCED_POWER_SAVE, GYR_RANGE_2000, GYROSCOPE_ON,
INITIALISED, ImuMode, SOFT_RESET, UPLOAD_CHUNK, register,
},
convert::{milli_g, millicelsius, millidegrees_per_second},
};
const RESET_TIME: Duration = Duration::from_millis(2);
const WRITE_GAP: Duration = Duration::from_micros(450);
const INITIALISATION_TIME: Duration = Duration::from_millis(20);
const GYROSCOPE_START: Duration = Duration::from_millis(50);
const CALIBRATION_SAMPLES: i32 = 32;
const SAMPLE_PERIOD: Duration = Duration::from_millis(10);
pub struct Imu {
device: I2cDevice,
address: u8,
mode: ImuMode,
gyroscope_offset: [i32; 3],
}
impl Imu {
pub async fn acceleration(&mut self) -> Result<Acceleration, Error> {
let raw = self.axes(register::ACC_DATA).await?;
Ok(acceleration(raw))
}
pub async fn motion(&mut self) -> Result<Motion, Error> {
let mut data = [0; 12];
self.device
.write_read(self.address, &[register::ACC_DATA], &mut data)
.await
.map_err(|_| Error::Imu)?;
let [a0, a1, a2, a3, a4, a5, g0, g1, g2, g3, g4, g5] = data;
Ok(Motion {
acceleration: acceleration(bmi270::axes([a0, a1, a2, a3, a4, a5])),
rotation: self.rotation_from(bmi270::axes([g0, g1, g2, g3, g4, g5])),
})
}
pub async fn set_mode(&mut self, mode: ImuMode) -> Result<(), Error> {
self.write(register::ACC_CONF, mode.acc_conf()).await?;
self.write(register::GYR_CONF, mode.gyr_conf()).await?;
self.mode = mode;
Ok(())
}
#[must_use]
pub const fn mode(&self) -> ImuMode {
self.mode
}
pub async fn set_gyroscope(&mut self, on: bool) -> Result<(), Error> {
if on {
self.write(register::GYR_RANGE, GYR_RANGE_2000).await?;
self.write(register::GYR_CONF, self.mode.gyr_conf()).await?;
}
self.set_power(on).await
}
pub async fn rotation(&mut self) -> Result<Rotation, Error> {
let raw = self.axes(register::GYR_DATA).await?;
Ok(self.rotation_from(raw))
}
pub async fn temperature(&mut self) -> Result<Millicelsius, Error> {
let mut data = [0; 2];
self.device
.write_read(self.address, &[register::TEMPERATURE], &mut data)
.await
.map_err(|_| Error::Imu)?;
millicelsius(i16::from_le_bytes(data)).ok_or(Error::Imu)
}
pub async fn calibrate_gyroscope(&mut self) -> Result<(), Error> {
self.set_gyroscope(true).await?;
Timer::after(GYROSCOPE_START).await;
let mut sum = [0; 3];
for _ in 0..CALIBRATION_SAMPLES {
Timer::after(SAMPLE_PERIOD).await;
let raw = self.axes(register::GYR_DATA).await?;
for (total, value) in sum.iter_mut().zip(raw) {
*total += value;
}
}
self.gyroscope_offset = sum.map(|total| total / CALIBRATION_SAMPLES);
Ok(())
}
async fn set_power(&mut self, gyroscope: bool) -> Result<(), Error> {
let gyroscope = if gyroscope { GYROSCOPE_ON } else { 0 };
self.write(register::PWR_CTRL, ACCELEROMETER_ON | gyroscope)
.await
}
async fn set_up(&mut self) -> Result<(), Error> {
self.write(register::CMD, SOFT_RESET).await?;
Timer::after(RESET_TIME).await;
self.set_power_save(false).await?;
let control = self.read(register::INIT_CTRL).await?;
self.write(register::INIT_CTRL, control & !1).await?;
let mut buffer = [0; UPLOAD_CHUNK + 1];
buffer[0] = register::INIT_DATA;
for ([address_low, address_high], chunk) in bmi270::upload_chunks(&BMI270_CONFIG_FILE) {
self.write_all(&[register::INIT_ADDR_0, address_low, address_high])
.await?;
buffer[1..=chunk.len()].copy_from_slice(chunk);
self.write_all(&buffer[..=chunk.len()]).await?;
}
self.write(register::INIT_CTRL, 1).await?;
self.set_power_save(true).await?;
Timer::after(INITIALISATION_TIME).await;
if self.read(register::INTERNAL_STATUS).await? & INITIALISED == 0 {
return Err(Error::Imu);
}
self.write(register::ACC_RANGE, ACC_RANGE_8G).await?;
self.write(register::ACC_CONF, self.mode.acc_conf()).await?;
self.set_power(false).await
}
async fn set_power_save(&mut self, on: bool) -> Result<(), Error> {
let config = self.read(register::PWR_CONF).await?;
let config = if on {
config | ADVANCED_POWER_SAVE
} else {
config & !ADVANCED_POWER_SAVE
};
self.write(register::PWR_CONF, config).await
}
fn rotation_from(&self, raw: [i32; 3]) -> Rotation {
let [x, y, z] =
array::from_fn(|axis| millidegrees_per_second(raw[axis] - self.gyroscope_offset[axis]));
Rotation { x, y, z }
}
async fn axes(&mut self, register: u8) -> Result<[i32; 3], Error> {
let mut data = [0; 6];
self.device
.write_read(self.address, &[register], &mut data)
.await
.map_err(|_| Error::Imu)?;
Ok(bmi270::axes(data))
}
async fn read(&mut self, register: u8) -> Result<u8, Error> {
let mut value = [0];
self.device
.write_read(self.address, &[register], &mut value)
.await
.map_err(|_| Error::Imu)?;
Ok(value[0])
}
async fn write(&mut self, register: u8, value: u8) -> Result<(), Error> {
self.write_all(&[register, value]).await?;
Timer::after(WRITE_GAP).await;
Ok(())
}
async fn write_all(&mut self, bytes: &[u8]) -> Result<(), Error> {
self.device
.write(self.address, bytes)
.await
.map_err(|_| Error::Imu)
}
}
fn acceleration(raw: [i32; 3]) -> Acceleration {
let [x, y, z] = raw.map(milli_g);
Acceleration { x, y, z }
}
pub async fn imu(bus: &'static I2cBus) -> Result<Imu, Error> {
for address in bmi270::ADDRESSES {
let mut imu = Imu {
device: I2cDevice::new(bus),
address,
mode: ImuMode::default(),
gyroscope_offset: [0; 3],
};
if imu
.read(register::CHIP_ID)
.await
.is_ok_and(|id| id == bmi270::CHIP_ID)
{
imu.set_up().await?;
return Ok(imu);
}
}
Err(Error::Imu)
}
impl fmt::Debug for Imu {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Imu")
.field("address", &self.address)
.field("mode", &self.mode)
.field("gyroscope_offset", &self.gyroscope_offset)
.finish_non_exhaustive()
}
}