#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![deny(missing_debug_implementations, nonstandard_style)]
#![warn(missing_docs, future_incompatible, unreachable_pub, rust_2018_idioms)]
use core::time::Duration;
#[cfg(feature = "std")]
use std::time::Instant;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Controller {
target: f64,
proportional_gain: f64,
integral_gain: f64,
derivative_gain: f64,
error_sum: f64,
last_error: f64,
#[cfg(feature = "std")]
last_instant: Option<Instant>,
}
impl Controller {
pub const fn new(
target: f64,
proportional_gain: f64,
integral_gain: f64,
derivative_gain: f64,
) -> Self {
Self {
target,
proportional_gain,
integral_gain,
derivative_gain,
error_sum: 0.0,
last_error: 0.0,
#[cfg(feature = "std")]
last_instant: None,
}
}
pub const fn target(&self) -> f64 {
self.target
}
pub fn set_target(&mut self, target: f64) {
self.target = target;
}
pub fn set_proportional_gain(&mut self, proportional_gain: f64) {
self.proportional_gain = proportional_gain;
}
pub fn set_integral_gain(&mut self, integral_gain: f64) {
self.integral_gain = integral_gain;
}
pub fn set_derivative_gain(&mut self, derivative_gain: f64) {
self.derivative_gain = derivative_gain;
}
#[cfg(feature = "std")]
#[must_use = "A PID controller does nothing if the correction is not applied"]
pub fn update(&mut self, current_value: f64) -> f64 {
let now = Instant::now();
let elapsed = match self.last_instant {
Some(last_time) => now.duration_since(last_time),
None => Duration::from_millis(1),
};
self.last_instant = Some(now);
self.update_elapsed(current_value, elapsed)
}
#[must_use = "A PID controller does nothing if the correction is not applied"]
pub fn update_elapsed(&mut self, current_value: f64, elapsed: Duration) -> f64 {
let elapsed = (elapsed.as_millis() as f64).max(1.0);
let error = self.target - current_value;
let error_delta = (error - self.last_error) / elapsed;
self.error_sum += error * elapsed;
self.last_error = error;
let p = self.proportional_gain * error;
let i = self.integral_gain * self.error_sum;
let d = self.derivative_gain * error_delta;
p + i + d
}
pub fn reset(&mut self) {
self.reset_inner();
}
#[cfg(feature = "std")]
fn reset_inner(&mut self) {
self.error_sum = 0.0;
self.last_error = 0.0;
self.last_instant = None;
}
#[cfg(not(feature = "std"))]
pub fn reset_inner(&mut self) {
self.error_sum = 0.0;
self.last_error = 0.0;
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn base_correction() {
let target = 80.0;
let mut controller = Controller::new(target, 0.5, 0.1, 0.2);
let dur = Duration::from_millis(4);
assert_eq!(controller.update_elapsed(60.0, dur), 19.0);
}
#[test]
#[cfg(feature = "std")]
fn no_correction() {
let target = 80.0;
let mut controller = Controller::new(target, 0.0, 0.0, 0.0);
assert_eq!(controller.update(60.0), 0.0);
}
#[test]
#[cfg(feature = "std")]
fn updating_values() {
let target = 80.0;
let mut controller = Controller::new(target, 0.5, 0.1, 0.2);
let dur = Duration::from_millis(4);
assert_eq!(controller.update_elapsed(60.0, dur), 19.0);
controller.set_proportional_gain(0.8);
controller.set_derivative_gain(0.5);
assert_eq!(controller.update_elapsed(60.0, dur), 32.0);
}
}