use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct ResolverDecoder<S: ControlScalar> {
pub kp: S,
pub ki: S,
theta: S,
omega: S,
integrator: S,
}
impl<S: ControlScalar> ResolverDecoder<S> {
pub fn new(kp: S, ki: S) -> Self {
Self {
kp,
ki,
theta: S::ZERO,
omega: S::ZERO,
integrator: S::ZERO,
}
}
pub fn update(&mut self, sin_sig: S, cos_sig: S, dt: S) {
let sin_hat = self.theta.sin();
let cos_hat = self.theta.cos();
let error = cos_hat * sin_sig - sin_hat * cos_sig;
self.integrator += error * dt;
let omega_corr = self.kp * error + self.ki * self.integrator;
self.omega = omega_corr;
self.theta += self.omega * dt;
while self.theta > S::PI {
self.theta -= S::TWO * S::PI;
}
while self.theta < -S::PI {
self.theta += S::TWO * S::PI;
}
}
pub fn theta(&self) -> S {
self.theta
}
pub fn omega(&self) -> S {
self.omega
}
pub fn reset(&mut self) {
self.theta = S::ZERO;
self.omega = S::ZERO;
self.integrator = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tracks_constant_angle() {
let target = 1.0_f64;
let amp = 1.0_f64;
let sin_sig = amp * target.sin();
let cos_sig = amp * target.cos();
let mut dec = ResolverDecoder::new(200.0_f64, 10_000.0);
let dt = 1e-4_f64;
for _ in 0..50_000 {
dec.update(sin_sig, cos_sig, dt);
}
assert!(
(dec.theta() - target).abs() < 0.01,
"theta={:.4} (expected {:.4})",
dec.theta(),
target
);
}
#[test]
fn tracks_rotating_resolver() {
let omega_true = 100.0_f64;
let amp = 1.0_f64;
let mut dec = ResolverDecoder::new(2000.0_f64, 100_000.0);
let dt = 1e-5_f64;
let mut theta_true = 0.0_f64;
for _ in 0..100_000 {
theta_true += omega_true * dt;
let sin_sig = amp * theta_true.sin();
let cos_sig = amp * theta_true.cos();
dec.update(sin_sig, cos_sig, dt);
}
assert!(
(dec.omega() - omega_true).abs() < 10.0,
"omega_est={:.2} (true={:.2})",
dec.omega(),
omega_true
);
}
#[test]
fn reset_clears_state() {
let mut dec = ResolverDecoder::new(200.0_f64, 10_000.0);
dec.update(0.5, 0.866, 1e-4);
dec.reset();
assert_eq!(dec.theta(), 0.0);
assert_eq!(dec.omega(), 0.0);
}
}