use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct Svpwm3LevelDuty<S: ControlScalar> {
pub ta: S,
pub tb: S,
pub tc: S,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VectorType {
Zero,
SmallP,
SmallN,
Medium,
Large,
}
pub struct Svpwm3Level<S: ControlScalar> {
pub v_dc: S,
pub neutral_balance: S,
}
impl<S: ControlScalar> Svpwm3Level<S> {
pub fn new(v_dc: S) -> Self {
Self {
v_dc,
neutral_balance: S::ZERO,
}
}
pub fn modulate(&self, v_alpha: S, v_beta: S) -> Svpwm3LevelDuty<S> {
let v_ref = (v_alpha * v_alpha + v_beta * v_beta).sqrt();
let v_half_dc = self.v_dc * S::HALF;
let m = (v_ref / v_half_dc).clamp_val(S::ZERO, S::ONE);
let sector = Self::compute_sector(v_alpha, v_beta);
self.sector_duties(sector, m, v_alpha, v_beta)
}
fn sector_duties(&self, sector: u8, m: S, v_alpha: S, v_beta: S) -> Svpwm3LevelDuty<S> {
let half = S::HALF;
let zero = S::ZERO;
let v_half_dc = self.v_dc * half;
let va_n = if v_half_dc > S::EPSILON {
v_alpha / v_half_dc
} else {
zero
};
let vb_n = if v_half_dc > S::EPSILON {
v_beta / v_half_dc
} else {
zero
};
let sqrt3_2 = S::from_f64(0.866_025_403_784);
let va = va_n;
let vb = -va_n * half + sqrt3_2 * vb_n;
let vc = -va_n * half - sqrt3_2 * vb_n;
let duty_a = Self::map_to_level(va, m);
let duty_b = Self::map_to_level(vb, m);
let duty_c = Self::map_to_level(vc, m);
let _ = sector; Svpwm3LevelDuty {
ta: duty_a,
tb: duty_b,
tc: duty_c,
}
}
fn map_to_level(v_norm: S, _m: S) -> S {
let quarter = S::from_f64(0.25);
let three_quarter = S::from_f64(0.75);
let half = S::HALF;
let duty = (v_norm + S::ONE) * half;
if duty >= three_quarter {
S::ONE
} else if duty >= quarter {
half
} else {
S::ZERO
}
}
pub fn vector_type(&self, sector: u8, vector_idx: u8) -> VectorType {
match vector_idx {
0 => VectorType::Zero,
1 | 3 | 5 => {
if sector % 2 == 1 {
VectorType::SmallP
} else {
VectorType::SmallN
}
}
2 | 4 | 6 => VectorType::Medium,
7..=12 => VectorType::Large,
_ => VectorType::Zero,
}
}
pub fn select_small_vector(&self, sector: u8, np_imbalance: S) -> u8 {
let prefer_p = np_imbalance < S::ZERO;
let base = (sector - 1) * 2 + 1;
if prefer_p {
base
} else {
base + 1
}
}
fn compute_sector(v_alpha: S, v_beta: S) -> u8 {
let angle = v_beta.atan2(v_alpha);
let pi = S::PI;
let two_pi = pi * S::TWO;
let angle = if angle < S::ZERO {
angle + two_pi
} else {
angle
};
let sector_width = pi / S::from_f64(3.0); let sector = (angle / sector_width).floor();
let s = sector.to_f64() as u8;
(s % 6) + 1
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::PI;
#[test]
fn zero_reference_gives_midpoint_duties() {
let svpwm = Svpwm3Level::new(800.0_f64);
let duty = svpwm.modulate(0.0, 0.0);
assert_eq!(duty.ta, 0.5);
assert_eq!(duty.tb, 0.5);
assert_eq!(duty.tc, 0.5);
}
#[test]
fn full_positive_alpha_gives_high_a_duty() {
let svpwm = Svpwm3Level::new(800.0_f64);
let duty = svpwm.modulate(400.0, 0.0);
assert!(duty.ta >= 0.5, "ta={}", duty.ta);
}
#[test]
fn sector_computation_covers_all_sectors() {
let mut sectors = [0u8; 6];
for (i, slot) in sectors.iter_mut().enumerate() {
let angle = (i as f64) * PI / 3.0 + PI / 6.0;
let va = angle.cos();
let vb = angle.sin();
let s = Svpwm3Level::<f64>::compute_sector(va, vb);
assert!((1..=6).contains(&s), "sector out of range: {}", s);
*slot = s;
}
let mut sorted = sectors;
sorted.sort_unstable();
for (i, &s) in sorted.iter().enumerate() {
assert_eq!(s, (i + 1) as u8, "sector {} missing", i + 1);
}
}
#[test]
fn vector_type_classification() {
let svpwm = Svpwm3Level::new(800.0_f64);
assert_eq!(svpwm.vector_type(1, 0), VectorType::Zero);
assert_eq!(svpwm.vector_type(1, 7), VectorType::Large);
assert_eq!(svpwm.vector_type(1, 2), VectorType::Medium);
}
}