use crate::{
accel::{Accel, AccelFullScale},
gyro::{Gyro, GyroFullScale},
};
use core::fmt::Debug;
pub(crate) const WARMUP_ITERATIONS: usize = 30;
pub(crate) const ITERATIONS: usize = 200;
pub(crate) const DELAY_MS: u32 = 2;
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CalibrationThreshold {
value: i16,
}
impl CalibrationThreshold {
pub const fn from_accel_scale(scale: AccelFullScale) -> Self {
Self {
value: match scale {
AccelFullScale::G2 => 8,
AccelFullScale::G4 => 4,
AccelFullScale::G8 => 2,
AccelFullScale::G16 => 1,
},
}
}
pub const fn from_gyro_scale(scale: GyroFullScale) -> Self {
Self {
value: match scale {
_ => 1,
},
}
}
pub const fn value(&self) -> i16 {
self.value
}
pub(crate) const fn is_value_within(self, value: i16) -> bool {
value.abs() <= self.value
}
pub const fn is_accel_within(self, accel: &Accel) -> bool {
self.is_value_within(accel.x())
&& self.is_value_within(accel.y())
&& self.is_value_within(accel.z())
}
pub const fn is_gyro_within(self, gyro: &Gyro) -> bool {
self.is_value_within(gyro.x())
&& self.is_value_within(gyro.y())
&& self.is_value_within(gyro.z())
}
pub const fn next_offset(self, current_mean: i16, current_offset: i16) -> i16 {
if self.is_value_within(current_mean) {
current_offset
} else {
current_offset.saturating_sub((current_mean / 10) + current_mean.signum())
}
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ReferenceGravity {
Zero,
XN,
XP,
YN,
YP,
ZN,
ZP,
}
impl ReferenceGravity {
const fn gravity_value(scale: AccelFullScale) -> i16 {
match scale {
AccelFullScale::G2 => 16384,
AccelFullScale::G4 => 8192,
AccelFullScale::G8 => 4096,
AccelFullScale::G16 => 2048,
}
}
pub const fn gravity_compensation(self, scale: AccelFullScale) -> Accel {
match self {
Self::Zero => Accel::new(0, 0, 0),
Self::XN => Accel::new(-Self::gravity_value(scale), 0, 0),
Self::XP => Accel::new(Self::gravity_value(scale), 0, 0),
Self::YN => Accel::new(0, -Self::gravity_value(scale), 0),
Self::YP => Accel::new(0, Self::gravity_value(scale), 0),
Self::ZN => Accel::new(0, 0, -Self::gravity_value(scale)),
Self::ZP => Accel::new(0, 0, Self::gravity_value(scale)),
}
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CalibrationActions {
flags: u8,
}
impl CalibrationActions {
const ACCEL_X: u8 = 1 << 0;
const ACCEL_Y: u8 = 1 << 1;
const ACCEL_Z: u8 = 1 << 2;
const GYRO_X: u8 = 1 << 3;
const GYRO_Y: u8 = 1 << 4;
const GYRO_Z: u8 = 1 << 5;
pub const fn empty() -> Self {
Self { flags: 0 }
}
pub const fn all() -> Self {
Self { flags: 0x3f }
}
pub const fn is_empty(self) -> bool {
self.flags == 0
}
pub const fn accel_x(self) -> bool {
self.flags & Self::ACCEL_X != 0
}
pub const fn accel_y(self) -> bool {
self.flags & Self::ACCEL_Y != 0
}
pub const fn accel_z(self) -> bool {
self.flags & Self::ACCEL_Z != 0
}
pub const fn gyro_x(self) -> bool {
self.flags & Self::GYRO_X != 0
}
pub const fn gyro_y(self) -> bool {
self.flags & Self::GYRO_Y != 0
}
pub const fn gyro_z(self) -> bool {
self.flags & Self::GYRO_Z != 0
}
const fn with_flag(self, value: bool, flag: u8) -> Self {
Self {
flags: if value {
self.flags | flag
} else {
self.flags & !flag
},
}
}
pub const fn with_accel_x(self, value: bool) -> Self {
self.with_flag(value, Self::ACCEL_X)
}
pub const fn with_accel_y(self, value: bool) -> Self {
self.with_flag(value, Self::ACCEL_Y)
}
pub const fn with_accel_z(self, value: bool) -> Self {
self.with_flag(value, Self::ACCEL_Z)
}
pub const fn with_gyro_x(self, value: bool) -> Self {
self.with_flag(value, Self::GYRO_X)
}
pub const fn with_gyro_y(self, value: bool) -> Self {
self.with_flag(value, Self::GYRO_Y)
}
pub const fn with_gyro_z(self, value: bool) -> Self {
self.with_flag(value, Self::GYRO_Z)
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CalibrationParameters {
pub accel_scale: AccelFullScale,
pub accel_threshold: CalibrationThreshold,
pub gyro_scale: GyroFullScale,
pub gyro_threshold: CalibrationThreshold,
pub warmup_iterations: usize,
pub iterations: usize,
pub gravity: ReferenceGravity,
}
impl CalibrationParameters {
pub const fn new(
accel_scale: AccelFullScale,
gyro_scale: GyroFullScale,
gravity: ReferenceGravity,
) -> Self {
Self {
accel_scale,
accel_threshold: CalibrationThreshold::from_accel_scale(accel_scale),
gyro_scale,
gyro_threshold: CalibrationThreshold::from_gyro_scale(gyro_scale),
warmup_iterations: WARMUP_ITERATIONS,
iterations: ITERATIONS,
gravity,
}
}
pub const fn with_accel_threshold(self, threshold: i16) -> Self {
Self {
accel_threshold: CalibrationThreshold { value: threshold },
..self
}
}
pub const fn with_gyro_threshold(self, threshold: i16) -> Self {
Self {
gyro_threshold: CalibrationThreshold { value: threshold },
..self
}
}
pub const fn with_warmup_iterations(self, warmup_iterations: usize) -> Self {
Self {
warmup_iterations,
..self
}
}
pub const fn with_iterations(self, iterations: usize) -> Self {
Self { iterations, ..self }
}
}
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MeanAccumulator {
pub ax: i32,
pub ay: i32,
pub az: i32,
pub gx: i32,
pub gy: i32,
pub gz: i32,
pub gravity_compensation: Accel,
}
impl MeanAccumulator {
pub const fn new(accel_scale: AccelFullScale, gravity: ReferenceGravity) -> Self {
Self {
ax: 0,
ay: 0,
az: 0,
gx: 0,
gy: 0,
gz: 0,
gravity_compensation: gravity.gravity_compensation(accel_scale),
}
}
pub const fn add(&mut self, accel: &Accel, gyro: &Gyro) {
self.ax += (accel.x() as i32) - (self.gravity_compensation.x() as i32);
self.ay += (accel.y() as i32) - (self.gravity_compensation.y() as i32);
self.az += (accel.z() as i32) - (self.gravity_compensation.z() as i32);
self.gx += gyro.x() as i32;
self.gy += gyro.y() as i32;
self.gz += gyro.z() as i32;
}
pub const fn means(mut self) -> (Accel, Gyro) {
self.ax /= ITERATIONS as i32;
self.ay /= ITERATIONS as i32;
self.az /= ITERATIONS as i32;
self.gx /= ITERATIONS as i32;
self.gy /= ITERATIONS as i32;
self.gz /= ITERATIONS as i32;
(
Accel::new(self.ax as i16, self.ay as i16, self.az as i16),
Gyro::new(self.gx as i16, self.gy as i16, self.gz as i16),
)
}
}
#[cfg(test)]
mod tests {
use crate::{
accel::{Accel, AccelFullScale},
gyro::Gyro,
};
use super::{CalibrationThreshold, MeanAccumulator, ReferenceGravity};
#[test]
fn test_mean_accumulator_with_compensation() {
{
let accel = Accel {
x: -1180,
y: -32768,
z: 32767,
};
let gyro = Gyro {
x: -3,
y: -7,
z: -10,
};
let mut mean_acc = MeanAccumulator::new(AccelFullScale::G2, ReferenceGravity::ZN);
mean_acc.add(&accel, &gyro);
assert_eq!(mean_acc.az, 49151);
}
}
#[test]
fn test_mean_accumulator_with_compensation_negate_panic() {
{
let mut mean_acc = MeanAccumulator {
ax: -700924,
ay: -6520832,
az: 3260217,
gx: -3345,
gy: 770,
gz: -7648,
gravity_compensation: Accel {
x: 0,
y: 0,
z: -16384,
},
};
let accel = Accel {
x: -3536,
y: -32768,
z: 32767,
};
let gyro = Gyro {
x: -105,
y: 100,
z: -36,
};
mean_acc.add(&accel, &gyro);
}
}
#[test]
fn test_next_offset_overflow_protection() {
let threshold = CalibrationThreshold { value: 8 };
let result = threshold.next_offset(30000, -32000);
assert!(result >= i16::MIN && result <= i16::MAX);
assert_eq!(result, i16::MIN);
let result = threshold.next_offset(-30000, 32000);
assert!(result >= i16::MIN && result <= i16::MAX);
assert_eq!(result, i16::MAX);
let result = threshold.next_offset(100, 1000);
assert_eq!(result, 1000 - (10 + 1));
let result = threshold.next_offset(5, 100);
assert_eq!(result, 100);
let result = threshold.next_offset(8, 100);
assert_eq!(result, 100);
let result = threshold.next_offset(9, 100);
assert_eq!(result, 100 - (0 + 1)); assert_eq!(result, 99);
}
}