#![allow(clippy::excessive_precision)]
use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct TorqueBand<S: ControlScalar> {
pub upper: S,
pub lower: S,
}
impl<S: ControlScalar> TorqueBand<S> {
pub fn new(hysteresis: S) -> Self {
Self {
upper: hysteresis,
lower: -hysteresis,
}
}
pub fn compare(&self, error: S) -> i8 {
if error > self.upper {
1
} else if error < self.lower {
-1
} else {
0
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct FluxBand<S: ControlScalar> {
pub upper: S,
pub lower: S,
}
impl<S: ControlScalar> FluxBand<S> {
pub fn new(hysteresis: S) -> Self {
Self {
upper: hysteresis,
lower: -hysteresis,
}
}
pub fn compare(&self, error: S) -> i8 {
if error > self.upper {
1
} else if error < self.lower {
0
} else {
1 }
}
}
const SWITCHING_TABLE: [[[u8; 2]; 3]; 6] = [
[
[5, 3], [7, 0], [2, 6], ],
[[6, 4], [0, 7], [3, 1]],
[[1, 5], [7, 0], [4, 2]],
[[2, 6], [0, 7], [5, 3]],
[[3, 1], [7, 0], [6, 4]],
[[4, 2], [0, 7], [1, 5]],
];
pub fn select_voltage_vector(flux_sector: usize, dtau: i8, dpsi: i8) -> u8 {
let sector = flux_sector.saturating_sub(1).min(5);
let tau_idx = match dtau {
i8::MIN..=-1 => 0,
0 => 1,
_ => 2,
};
let psi_idx = if dpsi <= 0 { 0usize } else { 1usize };
SWITCHING_TABLE[sector][tau_idx][psi_idx]
}
#[derive(Debug, Clone, Copy)]
pub struct FluxEstimator<S: ControlScalar> {
pub psi_alpha: S,
pub psi_beta: S,
pub drift_reset_gain: S,
}
impl<S: ControlScalar> FluxEstimator<S> {
pub fn new(drift_reset_gain: S) -> Self {
Self {
psi_alpha: S::ZERO,
psi_beta: S::ZERO,
drift_reset_gain,
}
}
pub fn update(&mut self, v_alpha: S, v_beta: S, i_alpha: S, i_beta: S, r_s: S, dt: S) {
let e_alpha = v_alpha - r_s * i_alpha - self.drift_reset_gain * self.psi_alpha;
let e_beta = v_beta - r_s * i_beta - self.drift_reset_gain * self.psi_beta;
self.psi_alpha += e_alpha * dt;
self.psi_beta += e_beta * dt;
}
pub fn flux_magnitude(&self) -> S {
(self.psi_alpha * self.psi_alpha + self.psi_beta * self.psi_beta).sqrt()
}
pub fn flux_angle(&self) -> S {
let angle = self.psi_beta.atan2(self.psi_alpha);
if angle < S::ZERO {
angle + S::PI * S::TWO
} else {
angle
}
}
pub fn flux_sector(&self) -> usize {
let psi_a = self.psi_alpha.to_f64();
let psi_b = self.psi_beta.to_f64();
let angle = psi_b.atan2(psi_a);
let angle = if angle < 0.0 {
angle + 2.0 * core::f64::consts::PI
} else {
angle
};
let sector_width = core::f64::consts::FRAC_PI_3;
let sector = (angle / sector_width + 1e-12).floor() as usize;
(sector % 6) + 1
}
}
#[derive(Debug, Clone, Copy)]
pub struct DtcController<S: ControlScalar> {
pub flux_est: FluxEstimator<S>,
pub torque_band: TorqueBand<S>,
pub flux_band: FluxBand<S>,
pub torque_est: S,
pub poles: u8,
}
impl<S: ControlScalar> DtcController<S> {
pub fn new(_r_s: S, torque_hysteresis: S, flux_hysteresis: S, _flux_ref: S, poles: u8) -> Self {
Self {
flux_est: FluxEstimator::new(S::from_f64(0.01)),
torque_band: TorqueBand::new(torque_hysteresis),
flux_band: FluxBand::new(flux_hysteresis),
torque_est: S::ZERO,
poles,
}
}
#[allow(clippy::too_many_arguments)]
pub fn update(
&mut self,
v_alpha: S,
v_beta: S,
i_alpha: S,
i_beta: S,
torque_ref: S,
flux_ref: S,
r_s: S,
dt: S,
) -> u8 {
self.flux_est
.update(v_alpha, v_beta, i_alpha, i_beta, r_s, dt);
let p = S::from_f64(self.poles as f64);
let three_halves = S::from_f64(1.5);
self.torque_est = three_halves
* p
* (self.flux_est.psi_alpha * i_beta - self.flux_est.psi_beta * i_alpha);
let torque_error = torque_ref - self.torque_est;
let dtau = self.torque_band.compare(torque_error);
let flux_mag = self.flux_est.flux_magnitude();
let flux_error = flux_ref - flux_mag;
let dpsi = self.flux_band.compare(flux_error);
let sector = self.flux_est.flux_sector();
select_voltage_vector(sector, dtau, dpsi)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_switching_table_active_vectors() {
let v = select_voltage_vector(1, 1, 1);
assert_eq!(v, 6);
let v = select_voltage_vector(1, -1, 0);
assert_eq!(v, 5);
let v = select_voltage_vector(3, 1, 0);
assert_eq!(v, 4);
}
#[test]
fn test_switching_table_zero_vectors() {
for sector in 1..=6 {
let v0 = select_voltage_vector(sector, 0, 0);
let v1 = select_voltage_vector(sector, 0, 1);
assert!(
v0 == 0 || v0 == 7,
"sector {sector}: dtau=0 dpsi=0 should be zero vector, got {v0}"
);
assert!(
v1 == 0 || v1 == 7,
"sector {sector}: dtau=0 dpsi=1 should be zero vector, got {v1}"
);
}
}
#[test]
fn test_flux_estimator_integration() {
let mut est = FluxEstimator::<f32>::new(0.0);
for _ in 0..10 {
est.update(10.0, 5.0, 0.0, 0.0, 0.1, 0.001);
}
assert!((est.psi_alpha - 0.1_f32).abs() < 1e-5);
assert!((est.psi_beta - 0.05_f32).abs() < 1e-5);
}
#[test]
fn test_flux_sector_boundaries() {
let mut est = FluxEstimator::<f32>::new(0.0);
est.psi_alpha = 1.0;
est.psi_beta = 0.0;
assert_eq!(est.flux_sector(), 1);
est.psi_alpha = 0.0;
est.psi_beta = 1.0;
assert_eq!(est.flux_sector(), 2);
est.psi_alpha = -1.0;
est.psi_beta = 0.0;
assert_eq!(est.flux_sector(), 4);
}
#[test]
fn test_dtc_controller_returns_valid_vector() {
let mut dtc = DtcController::<f32>::new(0.1, 0.5, 0.05, 0.5, 2);
for _ in 0..50 {
let v = dtc.update(10.0, 5.0, 1.0, 0.5, 1.0, 0.5, 0.1, 0.001);
assert!(v <= 7, "Voltage vector index must be 0..=7, got {v}");
}
}
}