use oxictl::core::fixed_point::convert::{fixed_from_f32_saturating, fixed_to_f32};
use oxictl::core::fixed_point::types::Q15_16;
use oxictl::core::scalar::PidScalar;
use oxictl::core::signal::{Feedback, Setpoint};
use oxictl::core::traits::Controller;
use oxictl::pid::anti_windup::AntiWindupMethod;
use oxictl::pid::standard::PidConfig;
#[test]
fn pi_pid_drives_first_order_plant_to_setpoint_q15_16() {
let kp = fixed_from_f32_saturating(0.5);
let ki = fixed_from_f32_saturating(1.0);
let kd = Q15_16::ZERO;
let config = PidConfig {
kp,
ki,
kd,
beta: Q15_16::ONE,
gamma: Q15_16::ZERO,
output_limiter: None,
anti_windup: AntiWindupMethod::Clamping,
derivative_filter_tau: None,
};
let mut pid = config.build();
let mut y = Q15_16::ZERO;
let setpoint = fixed_from_f32_saturating(1.0);
let dt = fixed_from_f32_saturating(0.01);
for _ in 0..300_usize {
let sp = Setpoint::new(setpoint);
let fb = Feedback::new(y);
let u = pid.update(&sp, &fb, dt);
y = y + dt * ((-y) + u.value());
}
let y_f32 = fixed_to_f32(y);
assert!(
(y_f32 - 1.0_f32).abs() < 0.05,
"PI controller should drive plant near setpoint 1.0, got y={}",
y_f32
);
}