use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct TrapezoidalCommutator<S: ControlScalar> {
pub rise_angle: S,
pub i_ref: S,
}
impl<S: ControlScalar> TrapezoidalCommutator<S> {
pub fn new(i_ref: S) -> Self {
Self {
rise_angle: S::PI / S::from_f64(3.0),
i_ref,
}
}
pub fn current_references(&self, theta_e: S) -> [S; 3] {
core::array::from_fn(|i| {
let offset = S::from_f64(i as f64) * S::TWO * S::PI / S::from_f64(3.0);
let angle = theta_e - offset;
self.i_ref * trapezoidal_profile(angle, self.rise_angle)
})
}
pub fn duties(&self, theta_e: S, i_measured: &[S; 3], kp: S) -> [S; 3] {
let i_ref = self.current_references(theta_e);
core::array::from_fn(|i| {
let duty = kp * (i_ref[i] - i_measured[i]);
duty.clamp_val(-S::ONE, S::ONE)
})
}
}
fn trapezoidal_profile<S: ControlScalar>(angle: S, rise: S) -> S {
let two_pi = S::TWO * S::PI;
let a = normalize_angle(angle, two_pi);
let flat = S::PI - rise;
if a < rise {
a / rise
} else if a < flat {
S::ONE
} else if a < S::PI {
(S::PI - a) / rise
} else {
let a2 = a - S::PI;
if a2 < rise {
-(a2 / rise)
} else if a2 < flat {
-S::ONE
} else if a2 < S::PI {
-(S::PI - a2) / rise
} else {
S::ZERO
}
}
}
fn normalize_angle<S: ControlScalar>(angle: S, period: S) -> S {
let mut a = angle;
while a < S::ZERO {
a += period;
}
while a >= period {
a -= period;
}
a
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_refs_sum_to_zero() {
let comm = TrapezoidalCommutator::new(10.0_f64);
for k in 0..12 {
let theta = k as f64 * core::f64::consts::PI / 6.0;
let refs = comm.current_references(theta);
let sum = refs[0] + refs[1] + refs[2];
assert!(sum.abs() < 1e-10, "sum={:.6} at theta={:.2}", sum, theta);
}
}
#[test]
fn peak_magnitude_is_i_ref() {
let i_ref = 5.0_f64;
let comm = TrapezoidalCommutator::new(i_ref);
let half_pi = core::f64::consts::PI / 2.0;
let refs = comm.current_references(half_pi);
assert!(refs[0].abs() <= i_ref + 1e-10, "ia={:.4}", refs[0]);
}
#[test]
fn trapezoidal_profile_symmetric() {
let rise = core::f64::consts::PI / 3.0;
let pos = trapezoidal_profile(core::f64::consts::PI / 2.0, rise);
let neg = trapezoidal_profile(3.0 * core::f64::consts::PI / 2.0, rise);
assert!(
(pos + neg).abs() < 1e-10,
"not anti-symmetric: {pos:.4} {neg:.4}"
);
}
#[test]
fn flat_section_is_one() {
let rise = core::f64::consts::PI / 3.0;
let v = trapezoidal_profile(core::f64::consts::PI / 2.0, rise);
assert!((v - 1.0).abs() < 1e-10, "v={v:.6}");
}
}