use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct CsiController<S: ControlScalar> {
pub kp: S,
pub ki: S,
pub l_filter: S,
pub omega: S,
int_d: S,
int_q: S,
pub int_limit: S,
pub v_limit: S,
}
impl<S: ControlScalar> CsiController<S> {
pub fn new(kp: S, ki: S, l_filter: S, omega: S, v_limit: S) -> Self {
Self {
kp,
ki,
l_filter,
omega,
int_d: S::ZERO,
int_q: S::ZERO,
int_limit: v_limit,
v_limit,
}
}
#[allow(clippy::too_many_arguments)]
pub fn update(
&mut self,
id_ref: S,
iq_ref: S,
id: S,
iq: S,
vd_ff: S,
vq_ff: S,
dt: S,
) -> (S, S) {
let ed = id_ref - id;
let eq = iq_ref - iq;
self.int_d = (self.int_d + self.ki * ed * dt).clamp_val(-self.int_limit, self.int_limit);
self.int_q = (self.int_q + self.ki * eq * dt).clamp_val(-self.int_limit, self.int_limit);
let cross_d = -self.omega * self.l_filter * iq;
let cross_q = self.omega * self.l_filter * id;
let vd = self.kp * ed + self.int_d + cross_d + vd_ff;
let vq = self.kp * eq + self.int_q + cross_q + vq_ff;
let amp = (vd * vd + vq * vq).sqrt();
if amp > self.v_limit {
let scale = self.v_limit / amp;
(vd * scale, vq * scale)
} else {
(vd, vq)
}
}
pub fn reset(&mut self) {
self.int_d = S::ZERO;
self.int_q = S::ZERO;
}
}
#[derive(Debug, Clone, Copy)]
pub struct SinglePhaseCsi<S: ControlScalar> {
pub kp: S,
pub ki: S,
int: S,
pub int_limit: S,
pub out_limit: S,
}
impl<S: ControlScalar> SinglePhaseCsi<S> {
pub fn new(kp: S, ki: S, out_limit: S) -> Self {
Self {
kp,
ki,
int: S::ZERO,
int_limit: out_limit,
out_limit,
}
}
pub fn update(&mut self, i_ref: S, i_meas: S, dt: S) -> S {
let err = i_ref - i_meas;
self.int = (self.int + self.ki * err * dt).clamp_val(-self.int_limit, self.int_limit);
let out = self.kp * err + self.int;
out.clamp_val(-self.out_limit, self.out_limit)
}
pub fn reset(&mut self) {
self.int = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::PI;
#[test]
fn csi_tracks_d_current() {
let omega = 2.0 * PI * 50.0;
let dt = 1e-4;
let mut csi = CsiController::new(10.0_f64, 200.0, 5e-3, omega, 400.0);
let mut id = 0.0f64;
let l = 5e-3f64;
for _ in 0..2000 {
let (vd, _vq) = csi.update(10.0, 0.0, id, 0.0, 0.0, 0.0, dt);
id += (vd / l) * dt;
id = id.clamp(-20.0, 20.0);
}
assert!((id - 10.0).abs() < 2.0, "id={id:.4}, expected ≈10A");
}
#[test]
fn output_voltage_bounded() {
let omega = 2.0 * PI * 50.0;
let mut csi = CsiController::new(100.0_f64, 5000.0, 1e-3, omega, 400.0);
let (vd, vq) = csi.update(1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e-4);
let amp = (vd * vd + vq * vq).sqrt();
assert!(amp <= 400.0 + 1e-6, "amp={amp:.2} exceeds limit");
}
#[test]
fn single_phase_csi_tracks() {
let mut csi = SinglePhaseCsi::new(5.0_f64, 100.0, 50.0);
let mut i = 0.0f64;
let l = 1e-3f64;
let dt = 1e-4;
for _ in 0..2000 {
let v = csi.update(5.0, i, dt);
i += (v / l) * dt;
i = i.clamp(-20.0, 20.0);
}
assert!((i - 5.0).abs() < 1.0, "i={i:.4}, expected ≈5A");
}
#[test]
fn csi_reset_clears_integrators() {
let mut csi = CsiController::new(10.0_f64, 200.0, 5e-3, 314.16, 400.0);
for _ in 0..100 {
csi.update(10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e-4);
}
csi.reset();
assert_eq!(csi.int_d, 0.0);
assert_eq!(csi.int_q, 0.0);
}
}