A proportional-integral-derivative (PID) controller.
Inspired by pid-rs
With cleaner API and assumptions (constant time delta, symmetrical limits) dropped.
#![no_std]
Float::epsilon()
and Float::infinity()
cargo add pid-ctrl
```rust let mut pid = super::PidCtrl::newwithpid(3.0, 2.0, 1.0);
let setpoint = 5.0; let prevmeasurement = 0.0; // calling init optional. Setpoint and prevmeasurement set to 0.0 by default. // Recommended to avoid derivative kick on startup pid.init(setpoint, prev_measurement);
let measurement = 0.0; let timedelta = 1.0; asserteq!( pid.step(super::PidIn::new(measurement, time_delta)), super::PidOut::new(15.0, 10.0, 0.0, 25.0) );
// changing pid constants pid.kp.setscale(4.0); asserteq!( pid.step(super::PidIn::new(measurement, time_delta)), super::PidOut::new(20.0, 20.0, 0.0, 40.0) );
// setting symmetrical limits around zero pid.kp.limits.setlimit(10.0); asserteq!( pid.step(super::PidIn::new(measurement, time_delta)), super::PidOut::new(10.0, 30.0, 0.0, 40.0) );
let timedelta = 0.5; asserteq!( pid.step(super::PidIn::new(measurement, time_delta)), super::PidOut::new(10.0, 35.0, 0.0, 45.0) );
// setting upper limits returns error if new value conflicts with lower limit
pid.ki.limits.trysetupper(28.0).unwrap();
asserteq!(
pid.step(super::PidIn::new(measurement, timedelta)),
super::PidOut::new(10.0, 28.0, 0.0, 38.0)
);
// timedelta gets clamped to Float::epsilon() - Float::infinity() let measurement = 1.0; let timedelta = -7.0; pid.kd.setscale(numtraits::Float::epsilon()); asserteq!(pid.step( super::PidIn::new(measurement, timedelta)), super::PidOut::new(10.0, 28.0, -1.0, 37.0) );
// configure setpoint directly pid.setpoint = 1.0; asserteq!(pid.step( super::PidIn::new(measurement, timedelta)), super::PidOut::new(0.0, 28.0, 0.0, 28.0) ); ```
Feel free to raise issues.
Would like to make #![no_std]
optional so types can impl Display trait.