use core::time::Duration;
use crate::units::Hertz;
pub const CHIP_ID: u8 = 0x24;
pub const ADDRESSES: [u8; 2] = [0x68, 0x69];
pub mod register {
pub const CHIP_ID: u8 = 0x00;
pub const ACC_DATA: u8 = 0x0C;
pub const GYR_DATA: u8 = 0x12;
pub const INTERNAL_STATUS: u8 = 0x21;
pub const TEMPERATURE: u8 = 0x22;
pub const ACC_CONF: u8 = 0x40;
pub const ACC_RANGE: u8 = 0x41;
pub const GYR_CONF: u8 = 0x42;
pub const GYR_RANGE: u8 = 0x43;
pub const INIT_CTRL: u8 = 0x59;
pub const INIT_ADDR_0: u8 = 0x5B;
pub const INIT_DATA: u8 = 0x5E;
pub const PWR_CONF: u8 = 0x7C;
pub const PWR_CTRL: u8 = 0x7D;
pub const CMD: u8 = 0x7E;
}
pub const SOFT_RESET: u8 = 0xB6;
pub const ADVANCED_POWER_SAVE: u8 = 1 << 0;
pub const GYROSCOPE_ON: u8 = 1 << 1;
pub const ACCELEROMETER_ON: u8 = 1 << 2;
pub const INITIALISED: u8 = 1 << 0;
const OIS_RANGE_2000: u8 = 1 << 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum AccelerometerRange {
G2,
G4,
#[default]
G8,
G16,
}
impl AccelerometerRange {
pub(crate) const fn register(self) -> u8 {
match self {
Self::G2 => 0,
Self::G4 => 1,
Self::G8 => 2,
Self::G16 => 3,
}
}
pub(crate) const fn full_scale(self) -> i32 {
match self {
Self::G2 => 2,
Self::G4 => 4,
Self::G8 => 8,
Self::G16 => 16,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum GyroscopeRange {
Dps125,
Dps250,
Dps500,
Dps1000,
#[default]
Dps2000,
}
impl GyroscopeRange {
pub(crate) const fn register(self) -> u8 {
OIS_RANGE_2000
| match self {
Self::Dps2000 => 0,
Self::Dps1000 => 1,
Self::Dps500 => 2,
Self::Dps250 => 3,
Self::Dps125 => 4,
}
}
pub(crate) const fn full_scale(self) -> i32 {
match self {
Self::Dps125 => 125,
Self::Dps250 => 250,
Self::Dps500 => 500,
Self::Dps1000 => 1000,
Self::Dps2000 => 2000,
}
}
}
const BANDWIDTH: u8 = 0x02 << 4;
const FILTER_PERF: u8 = 1 << 7;
const NOISE_PERF: u8 = 1 << 6;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ImuMode {
LowPower,
Normal,
#[default]
Performance,
}
impl ImuMode {
pub(crate) const fn supports(self, rate: ImuRate) -> bool {
!matches!(self, Self::LowPower) || rate.hertz().0 <= 200
}
pub(crate) const fn acc_conf(self, rate: ImuRate) -> u8 {
let conf = rate.odr() | BANDWIDTH;
match self {
Self::LowPower => conf,
Self::Normal | Self::Performance => conf | FILTER_PERF,
}
}
pub(crate) const fn gyr_conf(self, rate: ImuRate) -> u8 {
let conf = rate.odr() | BANDWIDTH;
match self {
Self::LowPower => conf,
Self::Normal => conf | FILTER_PERF,
Self::Performance => conf | FILTER_PERF | NOISE_PERF,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ImuRate {
Hz25,
Hz50,
#[default]
Hz100,
Hz200,
Hz400,
Hz800,
Hz1600,
}
impl ImuRate {
#[must_use]
pub const fn hertz(self) -> Hertz {
Hertz(match self {
Self::Hz25 => 25,
Self::Hz50 => 50,
Self::Hz100 => 100,
Self::Hz200 => 200,
Self::Hz400 => 400,
Self::Hz800 => 800,
Self::Hz1600 => 1600,
})
}
#[must_use]
pub const fn period(self) -> Duration {
Duration::from_micros(1_000_000 / self.hertz().0 as u64)
}
const fn odr(self) -> u8 {
match self {
Self::Hz25 => 0x06,
Self::Hz50 => 0x07,
Self::Hz100 => 0x08,
Self::Hz200 => 0x09,
Self::Hz400 => 0x0A,
Self::Hz800 => 0x0B,
Self::Hz1600 => 0x0C,
}
}
}
pub const UPLOAD_CHUNK: usize = 126;
#[expect(
clippy::cast_possible_truncation,
reason = "the address is 12 bits: the configuration is 8 kB, 4096 words"
)]
pub fn upload_chunks(config: &[u8]) -> impl Iterator<Item = ([u8; 2], &[u8])> {
config
.chunks(UPLOAD_CHUNK)
.enumerate()
.map(|(index, chunk)| {
let word = index * UPLOAD_CHUNK / 2;
let address = [(word & 0x0F) as u8, (word >> 4) as u8];
(address, chunk)
})
}
pub fn axes(data: [u8; 6]) -> [i32; 3] {
let [x0, x1, y0, y1, z0, z1] = data;
[[x0, x1], [y0, y1], [z0, z1]].map(|bytes| i32::from(i16::from_le_bytes(bytes)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_configuration_goes_in_word_addressed_chunks() {
let config: [u8; 8192] = core::array::from_fn(|index| index.to_le_bytes()[0]);
let mut chunks = upload_chunks(&config);
assert_eq!(chunks.next(), Some(([0, 0], &config[..UPLOAD_CHUNK])));
assert_eq!(
chunks.next(),
Some(([0x0F, 0x03], &config[UPLOAD_CHUNK..2 * UPLOAD_CHUNK]))
);
assert_eq!(upload_chunks(&config).count(), 66);
assert_eq!(chunks.last(), Some(([0x0F, 0xFF], &config[8190..])));
}
#[test]
fn a_reading_is_three_little_endian_axes() {
assert_eq!(
axes([0x00, 0x10, 0xFF, 0xFF, 0x00, 0x80]),
[4096, -1, -32768]
);
}
#[test]
fn the_settings_are_the_datasheets() {
let rate = ImuRate::Hz100;
assert_eq!(ImuMode::Performance.acc_conf(rate), 0xA8);
assert_eq!(ImuMode::Performance.gyr_conf(rate), 0xE8);
assert_eq!(ImuMode::Normal.acc_conf(rate), 0xA8);
assert_eq!(ImuMode::Normal.gyr_conf(rate), 0xA8);
assert_eq!(ImuMode::LowPower.acc_conf(rate), 0x28);
assert_eq!(ImuMode::LowPower.gyr_conf(rate), 0x28);
}
#[test]
fn the_rates_are_the_datasheets() {
assert_eq!(ImuMode::Normal.acc_conf(ImuRate::Hz25), 0xA6);
assert_eq!(ImuMode::Performance.gyr_conf(ImuRate::Hz1600), 0xEC);
assert_eq!(ImuMode::LowPower.acc_conf(ImuRate::Hz200), 0x29);
assert_eq!(ImuRate::Hz200.period(), Duration::from_millis(5));
assert_eq!(ImuRate::Hz1600.hertz(), Hertz(1600));
}
#[test]
fn low_power_goes_up_to_200_hz() {
assert!(ImuMode::LowPower.supports(ImuRate::Hz200));
assert!(!ImuMode::LowPower.supports(ImuRate::Hz400));
assert!(!ImuMode::LowPower.supports(ImuRate::Hz800));
assert!(ImuMode::Normal.supports(ImuRate::Hz1600));
}
#[test]
fn the_ranges_registers_are_the_datasheets() {
assert_eq!(AccelerometerRange::default().register(), 0x02);
assert_eq!(AccelerometerRange::G16.register(), 0x03);
assert_eq!(GyroscopeRange::default().register(), 0x08);
assert_eq!(GyroscopeRange::Dps125.register(), 0x0C);
}
}