use crate::core::scalar::ControlScalar;
pub fn spwm_duties<S: ControlScalar>(theta: S, m: S) -> [S; 3] {
let m_clamped = m.clamp_val(S::ZERO, S::ONE);
let two_pi_3 = S::TWO * S::PI / S::from_f64(3.0);
let half = S::from_f64(0.5);
[
half + half * m_clamped * theta.cos(),
half + half * m_clamped * (theta - two_pi_3).cos(),
half + half * m_clamped * (theta - S::TWO * two_pi_3).cos(),
]
}
pub fn spwm_single<S: ControlScalar>(theta: S, m: S) -> S {
let m_clamped = m.clamp_val(S::ZERO, S::ONE);
let half = S::from_f64(0.5);
half + half * m_clamped * theta.cos()
}
pub fn spwm_with_third_harmonic<S: ControlScalar>(theta: S, m: S) -> [S; 3] {
let m_clamped = m.clamp_val(S::ZERO, S::from_f64(1.155));
let two_pi_3 = S::TWO * S::PI / S::from_f64(3.0);
let half = S::from_f64(0.5);
let v3h = -m_clamped * (S::from_f64(3.0) * theta).sin() / S::from_f64(6.0);
let raw = [
half + half * m_clamped * theta.cos() + v3h,
half + half * m_clamped * (theta - two_pi_3).cos() + v3h,
half + half * m_clamped * (theta - S::TWO * two_pi_3).cos() + v3h,
];
core::array::from_fn(|i| raw[i].clamp_val(S::ZERO, S::ONE))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spwm_sums_to_three_halves() {
let sum: f64 = spwm_duties(0.0_f64, 0.8).iter().sum();
assert!((sum - 1.5).abs() < 1e-10, "sum={sum:.6}");
}
#[test]
fn spwm_duties_in_range() {
for k in 0..36 {
let theta = k as f64 * core::f64::consts::PI / 18.0;
let duties = spwm_duties(theta, 0.9);
for (i, &d) in duties.iter().enumerate() {
assert!(
(0.0..=1.0).contains(&d),
"duties[{i}]={d:.4} out of [0,1] at theta={theta:.2}"
);
}
}
}
#[test]
fn zero_modulation_gives_half_duty() {
let duties = spwm_duties(0.5_f64, 0.0);
for &d in &duties {
assert!((d - 0.5).abs() < 1e-10);
}
}
#[test]
fn third_harmonic_duties_in_range() {
for k in 0..36 {
let theta = k as f64 * core::f64::consts::PI / 18.0;
let duties = spwm_with_third_harmonic(theta, 1.1);
for (i, &d) in duties.iter().enumerate() {
assert!(
(-0.01..=1.01).contains(&d),
"duties[{i}]={d:.4} out of range at theta={theta:.2}"
);
}
}
}
#[test]
fn spwm_120_degree_phase_offset() {
let theta = 0.0_f64;
let duties = spwm_duties(theta, 1.0);
assert!((duties[0] - 1.0).abs() < 1e-10, "d_a={:.4}", duties[0]);
let expected_b = 0.5 + 0.5 * (-2.0 * core::f64::consts::PI / 3.0_f64).cos();
assert!(
(duties[1] - expected_b).abs() < 1e-10,
"d_b={:.4}",
duties[1]
);
}
}