#![cfg_attr(not(feature = "std"), no_std)]
use core::{fmt::Debug, ops::Neg, result::Result};
pub trait Number
where
Self: Copy + PartialOrd + Neg<Output = Self>,
{
fn zero() -> Self;
fn one() -> Self;
fn epsilon() -> Self;
fn max_value() -> Self;
fn pi() -> Self;
fn abs(&self) -> Self;
fn clamp(&self, min: Self, max: Self) -> Self;
fn safe_add(&self, rhs: Self) -> Self;
fn safe_sub(&self, rhs: Self) -> Self;
fn safe_mul(&self, rhs: Self) -> Self;
fn safe_div(&self, rhs: Self) -> Self;
fn sin(&self) -> Self;
fn cos(&self) -> Self;
}
#[cfg(all(feature = "float", not(feature = "fixed")))]
impl<T> Number for T
where
T: num_traits::float::Float + num_traits::FloatConst,
{
fn zero() -> T {
T::zero()
}
fn one() -> T {
T::one()
}
fn epsilon() -> T {
T::epsilon()
}
fn max_value() -> T {
Self::one().safe_div(Self::epsilon())
}
fn pi() -> T {
T::PI()
}
fn abs(&self) -> T {
T::abs(*self)
}
fn clamp(&self, min: T, max: T) -> T {
if self < &min {
min
} else if self > &max {
max
} else {
*self
}
}
fn safe_add(&self, rhs: Self) -> Self {
let max = Self::max_value();
let res = *self + rhs;
if <Self as num_traits::float::Float>::is_finite(res) {
res.clamp(-max, max)
} else {
max
}
}
fn safe_sub(&self, rhs: Self) -> Self {
let max = Self::max_value();
let res = *self - rhs;
if <Self as num_traits::float::Float>::is_finite(res) {
res.clamp(-max, max)
} else {
-max
}
}
fn safe_mul(&self, rhs: Self) -> Self {
let max = Self::max_value();
let res = *self * rhs;
if <Self as num_traits::float::Float>::is_finite(res) {
res.clamp(-max, max)
} else {
max
}
}
fn safe_div(&self, rhs: Self) -> Self {
let max = Self::max_value();
if rhs == <Self as Number>::zero() {
max
} else {
let res = *self / rhs;
if <Self as num_traits::float::Float>::is_finite(res) {
res.clamp(-max, max)
} else if self.safe_mul(rhs) > <Self as Number>::zero() {
max
} else {
-max
}
}
}
fn sin(&self) -> T {
T::sin(*self)
}
fn cos(&self) -> T {
T::cos(*self)
}
}
#[cfg(all(feature = "fixed", not(feature = "float")))]
impl<T> Number for T
where
T: fixed::traits::FixedSigned
+ cordic::CordicNumber
+ num_traits::SaturatingAdd
+ num_traits::SaturatingSub
+ num_traits::SaturatingMul
+ num_traits::CheckedDiv,
{
fn zero() -> T {
cordic::CordicNumber::zero()
}
fn one() -> T {
cordic::CordicNumber::one()
}
fn epsilon() -> T {
T::DELTA
}
fn max_value() -> T {
T::saturating_recip(Self::epsilon())
}
fn pi() -> T {
cordic::CordicNumber::pi()
}
fn abs(&self) -> T {
T::abs(*self)
}
fn clamp(&self, min: T, max: T) -> T {
if self < &min {
min
} else if self > &max {
max
} else {
*self
}
}
fn safe_add(&self, rhs: Self) -> Self {
let max = Self::max_value();
self.saturating_add(&rhs).clamp(-max, max)
}
fn safe_sub(&self, rhs: Self) -> Self {
let max = Self::max_value();
self.saturating_sub(&rhs).clamp(-max, max)
}
fn safe_mul(&self, rhs: Self) -> Self {
let max = Self::max_value();
self.saturating_mul(&rhs).clamp(-max, max)
}
fn safe_div(&self, rhs: Self) -> Self {
let max = Self::max_value();
if let Some(retval) = self.checked_div(&rhs) {
retval.clamp(-max, max)
} else if self.safe_mul(rhs) >= <Self as Number>::zero() {
max
} else {
-max
}
}
fn sin(&self) -> T {
cordic::sin(*self)
}
fn cos(&self) -> T {
cordic::cos(*self)
}
}
#[cfg(all(features = "fixed", feature = "float"))]
impl<T> Number for T
where
T: fixed::traits::FixedSigned + num_traits::float::Float,
{
fn zero() -> T {
unimplemented!()
}
fn one() -> T {
unimplemented!()
}
fn epsilon() -> T {
unimplemented!()
}
fn max_value() -> T {
unimplemented!()
}
fn pi() -> T {
unimplemented!()
}
fn abs(&self) -> T {
unimplemented!()
}
fn clamp(&self, min: T, max: T) -> T {
unimplemented!()
}
fn safe_add(&self, rhs: Self) -> Self {
unimplemented!()
}
fn safe_sub(&self, rhs: Self) -> Self {
unimplemented!()
}
fn safe_mul(&self, rhs: Self) -> Self {
unimplemented!()
}
fn safe_div(&self, rhs: Self) -> Self {
unimplemented!()
}
fn sin(&self) -> T {
unimplemented!()
}
fn cos(&self) -> T {
unimplemented!()
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum Parameter {
ProportionalGain,
IntegralTimeConstant,
DerivativeTimeConstant,
SetPointCoefficient,
InitialControllerOutput,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum PidControllerError<T>
where
T: Number,
{
UnrepresentableNumber,
ParameterOutOfBounds {
parameter: Parameter,
value: T,
lower_bound: T,
upper_bound: T,
},
InvalidTimestamp,
}
fn check_constants<T>(
proportional_gain: T,
integral_time_constant: T,
derivative_time_constant: T,
set_point_coefficient: T,
initial_controller_output: Option<T>,
) -> Result<(), PidControllerError<T>>
where
T: Number,
{
let zero = T::zero();
let eps = T::epsilon();
let max = T::max_value();
if proportional_gain < zero || proportional_gain > max {
return Err(PidControllerError::ParameterOutOfBounds {
parameter: Parameter::ProportionalGain,
value: proportional_gain,
lower_bound: zero,
upper_bound: max,
});
}
if integral_time_constant < eps || integral_time_constant > max {
return Err(PidControllerError::ParameterOutOfBounds {
parameter: Parameter::IntegralTimeConstant,
value: integral_time_constant,
lower_bound: eps,
upper_bound: max,
});
}
if derivative_time_constant < eps || derivative_time_constant > max {
return Err(PidControllerError::ParameterOutOfBounds {
parameter: Parameter::DerivativeTimeConstant,
value: derivative_time_constant,
lower_bound: eps,
upper_bound: max,
});
}
if set_point_coefficient < zero || set_point_coefficient > max {
return Err(PidControllerError::ParameterOutOfBounds {
parameter: Parameter::SetPointCoefficient,
value: set_point_coefficient,
lower_bound: zero,
upper_bound: max,
});
}
if let Some(u0) = initial_controller_output {
if u0 < -max || u0 > max {
return Err(PidControllerError::ParameterOutOfBounds {
parameter: Parameter::InitialControllerOutput,
value: u0,
lower_bound: -max,
upper_bound: max,
});
}
}
Ok(())
}
#[allow(non_snake_case)]
#[derive(Clone, Copy, Debug)]
pub struct PidController<T>
where
T: Number + Debug,
{
K: T,
T_i: T,
T_d: T,
b: T,
u: T,
r_1: T,
y_1: T,
y_2: T,
}
#[allow(non_snake_case)]
impl<T> PidController<T>
where
T: Number + Debug,
{
pub fn new(
proportional_gain: T,
integral_time_constant: T,
derivative_time_constant: T,
set_point_coefficient: T,
initial_controller_output: T,
) -> Result<Self, PidControllerError<T>> {
check_constants(
proportional_gain,
integral_time_constant,
derivative_time_constant,
set_point_coefficient,
Some(initial_controller_output),
)?;
let zero = T::zero();
Ok(Self {
K: proportional_gain,
T_i: integral_time_constant,
T_d: derivative_time_constant,
b: set_point_coefficient,
u: initial_controller_output,
r_1: zero,
y_1: zero,
y_2: zero,
})
}
pub fn update_constants(
&self,
proportional_gain: T,
integral_time_constant: T,
derivative_time_constant: T,
set_point_coefficient: T,
) -> Result<Self, PidControllerError<T>> {
check_constants(
proportional_gain,
integral_time_constant,
derivative_time_constant,
set_point_coefficient,
None,
)?;
Ok(Self {
K: proportional_gain,
T_i: integral_time_constant,
T_d: derivative_time_constant,
b: set_point_coefficient,
u: self.u,
r_1: self.r_1,
y_1: self.y_1,
y_2: self.y_2,
})
}
pub fn update_constants_mut(
&mut self,
proportional_gain: T,
integral_time_constant: T,
derivative_time_constant: T,
set_point_coefficient: T,
) -> Result<(), PidControllerError<T>> {
check_constants(
proportional_gain,
integral_time_constant,
derivative_time_constant,
set_point_coefficient,
None,
)?;
self.K = proportional_gain;
self.T_i = integral_time_constant;
self.T_d = derivative_time_constant;
self.b = set_point_coefficient;
Ok(())
}
pub fn control_output(&self) -> T {
self.u
}
pub fn update(
&mut self,
set_point: T,
process_measurement: T,
measurement_time_interval: T,
lower_saturation_limit: T,
upper_saturation_limit: T,
) -> T {
self.update_state(
measurement_time_interval,
set_point,
process_measurement,
lower_saturation_limit,
upper_saturation_limit,
)
}
fn delta_P(&self, r: T, y: T) -> T {
self.K.safe_mul(
self.b
.safe_mul(r)
.safe_sub(y)
.safe_sub(self.b.safe_mul(self.r_1))
.safe_add(self.y_1),
)
}
fn delta_I(&self, h: T, r: T, y: T) -> T {
self.K
.safe_mul(h)
.safe_div(self.T_i)
.safe_mul(r.safe_sub(y))
}
fn delta_D(&self, h: T, y: T) -> T {
let two = T::one().safe_add(T::one());
-self
.K
.safe_mul(self.T_d)
.safe_div(h)
.safe_mul(y.safe_sub(two.safe_mul(self.y_1)).safe_add(self.y_2))
}
fn update_state(&mut self, h: T, r: T, y: T, u_low: T, u_high: T) -> T {
let delta_P = self.delta_P(r, y);
let delta_I = self.delta_I(h, r, y);
let delta_D = self.delta_D(h, y);
let delta_v = delta_P.safe_add(delta_I).safe_add(delta_D);
self.u = self.u.safe_add(delta_v).clamp(u_low, u_high);
self.y_2 = self.y_1;
self.r_1 = r;
self.y_1 = y;
self.u
}
}
#[cfg(feature = "std")]
pub mod std {
use crate::{Number, PidControllerError};
use std::time::SystemTimeError;
impl<T> From<SystemTimeError> for PidControllerError<T>
where
T: Number,
{
fn from(_: SystemTimeError) -> Self {
Self::InvalidTimestamp
}
}
#[cfg(feature = "fixed")]
pub mod fixed {
use super::*;
use ::fixed::traits::{FixedSigned, LosslessTryFrom};
use std::time::SystemTime;
pub fn calculate_h<T>(
measurement_time: SystemTime,
last_update_time: SystemTime,
) -> Result<T, PidControllerError<T>>
where
T: Number
+ FixedSigned
+ LosslessTryFrom<u64>
+ LosslessTryFrom<u32>
+ cordic::CordicNumber,
{
let duration = measurement_time.duration_since(last_update_time)?;
Ok(T::lossless_try_from(duration.as_secs())
.ok_or(PidControllerError::UnrepresentableNumber)?
.safe_add(
T::lossless_try_from(duration.subsec_nanos())
.ok_or(PidControllerError::UnrepresentableNumber)?
.safe_div(
T::lossless_try_from(1_000_000_000_u32)
.ok_or(PidControllerError::UnrepresentableNumber)?,
),
))
}
}
#[cfg(feature = "float")]
pub mod float {
use super::*;
use std::time::SystemTime;
pub fn calculate_h<T>(
measurement_time: SystemTime,
last_update_time: SystemTime,
) -> Result<T, PidControllerError<T>>
where
T: Number + num_traits::cast::FromPrimitive,
{
let duration = measurement_time.duration_since(last_update_time)?;
Ok(T::from_u64(duration.as_secs())
.ok_or(PidControllerError::UnrepresentableNumber)?
.safe_add(
T::from_u32(duration.subsec_nanos())
.ok_or(PidControllerError::UnrepresentableNumber)?
.safe_div(
T::from_u32(1_000_000_000_u32)
.ok_or(PidControllerError::UnrepresentableNumber)?,
),
))
}
}
}