1#![cfg_attr(not(feature = "std"), no_std)]
18
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22use core::fmt;
23use num_traits::Float;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
28pub enum SafetyStatus {
29 Normal,
31 OutputSaturated,
33 IntegralWindup,
35 DerivativeNoisy,
37 TimeStepExcessive,
39 MultipleFaults,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
46pub enum AntiWindupMode {
47 Clamping,
49 BackCalculation,
51 Conditional,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
58pub enum DerivativeMode {
59 None,
61 LowPass,
63 SimpleMovingAverage,
65}
66
67#[derive(Debug, Clone, Copy)]
69#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
70pub struct PidConfig<T: Float> {
71 pub kp: T,
73 pub ki: T,
75 pub kd: T,
77
78 pub output_max: T,
80 pub output_min: T,
82
83 pub output_rate_limit: Option<T>,
85
86 pub p_limit: T,
88 pub i_limit: T,
90 pub d_limit: T,
92
93 pub antiwindup_mode: AntiWindupMode,
95 pub antiwindup_gain: T,
97
98 pub derivative_mode: DerivativeMode,
100 pub derivative_filter_coeff: T,
102
103 pub max_dt: T,
105 pub min_dt: T,
107
108 pub setpoint_ramping: bool,
110 pub setpoint_ramp_rate: T,
112}
113
114impl<T: Float> Default for PidConfig<T> {
115 fn default() -> Self {
116 Self {
117 kp: T::one(),
118 ki: T::zero(),
119 kd: T::zero(),
120 output_max: T::from(100.0).unwrap(),
121 output_min: T::from(-100.0).unwrap(),
122 output_rate_limit: None,
123 p_limit: T::infinity(),
124 i_limit: T::from(100.0).unwrap(),
125 d_limit: T::infinity(),
126 antiwindup_mode: AntiWindupMode::BackCalculation,
127 antiwindup_gain: T::one(),
128 derivative_mode: DerivativeMode::LowPass,
129 derivative_filter_coeff: T::from(0.2).unwrap(),
130 max_dt: T::from(1.0).unwrap(),
131 min_dt: T::from(0.0001).unwrap(),
132 setpoint_ramping: false,
133 setpoint_ramp_rate: T::from(10.0).unwrap(),
134 }
135 }
136}
137
138#[derive(Debug, Clone, Copy)]
140#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
141pub struct ControlOutput<T: Float> {
142 pub output: T,
144 pub p: T,
146 pub i: T,
148 pub d: T,
150 pub error: T,
152 pub effective_setpoint: T,
154 pub safety_status: SafetyStatus,
156 pub dt: T,
158}
159
160#[derive(Clone)]
162#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
163pub struct RobustPid<T: Float> {
164 config: PidConfig<T>,
166 setpoint: T,
168 internal_setpoint: T,
170
171 integral: T,
174 prev_measurement: Option<T>,
176 filtered_derivative: T,
178 prev_output: T,
180 prev_error: Option<T>,
182
183 update_count: u64,
186 saturation_count: u64,
188 max_error: T,
190}
191
192impl<T: Float> RobustPid<T> {
193 pub fn new(config: PidConfig<T>) -> Self {
195 Self {
196 internal_setpoint: T::zero(),
197 setpoint: T::zero(),
198 config,
199 integral: T::zero(),
200 prev_measurement: None,
201 filtered_derivative: T::zero(),
202 prev_output: T::zero(),
203 prev_error: None,
204 update_count: 0,
205 saturation_count: 0,
206 max_error: T::zero(),
207 }
208 }
209
210 pub fn with_gains(kp: T, ki: T, kd: T) -> Self {
212 let mut config = PidConfig::default();
213 config.kp = kp;
214 config.ki = ki;
215 config.kd = kd;
216 Self::new(config)
217 }
218
219 pub fn set_setpoint(&mut self, setpoint: T) {
221 self.setpoint = setpoint;
222 if !self.config.setpoint_ramping {
223 self.internal_setpoint = setpoint;
224 }
225 }
226
227 pub fn update_config(&mut self, config: PidConfig<T>) {
229 if config.ki != self.config.ki && config.ki != T::zero() {
231 self.integral = self.integral * (config.ki / self.config.ki);
232 }
233 self.config = config;
234 }
235
236 pub fn config(&self) -> &PidConfig<T> {
238 &self.config
239 }
240
241 pub fn update(&mut self, measurement: T, dt: T) -> ControlOutput<T> {
245 self.update_count += 1;
246
247 let dt = self.clamp_dt(dt);
249
250 if self.config.setpoint_ramping {
252 self.update_setpoint_ramp(dt);
253 }
254
255 let error = self.internal_setpoint - measurement;
257
258 self.update_with_error_and_measurement(error, measurement, dt)
259 }
260
261 pub fn update_from_error(&mut self, error: T, dt: T) -> ControlOutput<T> {
276 self.update_count += 1;
277
278 let dt = self.clamp_dt(dt);
280
281 let calculated_measurement = match self.prev_error {
284 Some(prev_err) => {
285 match self.prev_measurement {
288 Some(prev_meas) => prev_meas - (error - prev_err),
289 None => T::zero(), }
291 }
292 None => T::zero(), };
294
295 self.update_with_error_and_measurement(error, calculated_measurement, dt)
296 }
297
298 fn update_with_error_and_measurement(
300 &mut self,
301 error: T,
302 measurement: T,
303 dt: T,
304 ) -> ControlOutput<T> {
305 if error.abs() > self.max_error {
307 self.max_error = error.abs();
308 }
309
310 let p_unbounded = error * self.config.kp;
312 let p = self.clamp(p_unbounded, self.config.p_limit);
313
314 let d = self.calculate_derivative(measurement, dt);
316
317 let tentative_output = p + self.integral + d;
319
320 let i = self.update_integral(error, dt, tentative_output);
322
323 let mut output = p + i + d;
325 output = self.clamp_output(output);
326
327 output = self.apply_rate_limit(output, dt);
329
330 let safety_status = self.determine_safety_status(output, error, dt);
332
333 self.prev_measurement = Some(measurement);
335 self.prev_error = Some(error);
336 self.prev_output = output;
337
338 ControlOutput {
339 output,
340 p,
341 i,
342 d,
343 error,
344 effective_setpoint: self.internal_setpoint,
345 safety_status,
346 dt,
347 }
348 }
349
350 fn calculate_derivative(&mut self, measurement: T, dt: T) -> T {
352 let raw_derivative = match self.prev_measurement {
353 Some(prev_measurement) => {
354 -(measurement - prev_measurement) / dt
356 }
357 None => T::zero(),
358 };
359
360 let derivative = match self.config.derivative_mode {
362 DerivativeMode::None => raw_derivative,
363 DerivativeMode::LowPass => {
364 let alpha = self.config.derivative_filter_coeff;
366 alpha * raw_derivative + (T::one() - alpha) * self.filtered_derivative
367 }
368 DerivativeMode::SimpleMovingAverage => {
369 let alpha = self.config.derivative_filter_coeff;
371 alpha * raw_derivative + (T::one() - alpha) * self.filtered_derivative
372 }
373 };
374
375 self.filtered_derivative = derivative;
376
377 let d_unbounded = derivative * self.config.kd;
378 self.clamp(d_unbounded, self.config.d_limit)
379 }
380
381 fn update_integral(&mut self, error: T, dt: T, tentative_output: T) -> T {
383 let error_contribution = error * self.config.ki * dt;
384
385 match self.config.antiwindup_mode {
386 AntiWindupMode::Clamping => {
387 let tentative_integral = self.integral + error_contribution;
389 self.integral = self.clamp(tentative_integral, self.config.i_limit);
390 }
391 AntiWindupMode::BackCalculation => {
392 let clamped_output = self.clamp_output(tentative_output);
394 let output_error = clamped_output - tentative_output;
395
396 let integral_correction = output_error * self.config.antiwindup_gain * dt;
398 let tentative_integral = self.integral + error_contribution + integral_correction;
399 self.integral = self.clamp(tentative_integral, self.config.i_limit);
400 }
401 AntiWindupMode::Conditional => {
402 let clamped_output = self.clamp_output(tentative_output);
404 if (tentative_output - clamped_output).abs() < T::epsilon() {
405 let tentative_integral = self.integral + error_contribution;
407 self.integral = self.clamp(tentative_integral, self.config.i_limit);
408 }
409 }
411 }
412
413 self.integral
414 }
415
416 fn update_setpoint_ramp(&mut self, dt: T) {
418 let error = self.setpoint - self.internal_setpoint;
419 let max_change = self.config.setpoint_ramp_rate * dt;
420
421 if error.abs() <= max_change {
422 self.internal_setpoint = self.setpoint;
423 } else {
424 let sign = if error > T::zero() {
425 T::one()
426 } else {
427 -T::one()
428 };
429 self.internal_setpoint = self.internal_setpoint + sign * max_change;
430 }
431 }
432
433 fn apply_rate_limit(&self, output: T, dt: T) -> T {
435 if let Some(rate_limit) = self.config.output_rate_limit {
436 let max_change = rate_limit * dt;
437 let change = output - self.prev_output;
438
439 if change.abs() > max_change {
440 let sign = if change > T::zero() {
441 T::one()
442 } else {
443 -T::one()
444 };
445 self.prev_output + sign * max_change
446 } else {
447 output
448 }
449 } else {
450 output
451 }
452 }
453
454 fn clamp(&self, value: T, limit: T) -> T {
456 let limit_abs = limit.abs();
457 if value > limit_abs {
458 limit_abs
459 } else if value < -limit_abs {
460 -limit_abs
461 } else {
462 value
463 }
464 }
465
466 fn clamp_output(&mut self, value: T) -> T {
468 let clamped = if value > self.config.output_max {
469 self.saturation_count += 1;
470 self.config.output_max
471 } else if value < self.config.output_min {
472 self.saturation_count += 1;
473 self.config.output_min
474 } else {
475 value
476 };
477 clamped
478 }
479
480 fn clamp_dt(&self, dt: T) -> T {
482 if dt < self.config.min_dt {
483 self.config.min_dt
484 } else if dt > self.config.max_dt {
485 self.config.max_dt
486 } else {
487 dt
488 }
489 }
490
491 fn determine_safety_status(&self, output: T, error: T, dt: T) -> SafetyStatus {
493 let mut fault_count = 0;
494 let mut status = SafetyStatus::Normal;
495
496 if (output - self.config.output_max).abs() < T::epsilon()
498 || (output - self.config.output_min).abs() < T::epsilon()
499 {
500 status = SafetyStatus::OutputSaturated;
501 fault_count += 1;
502 }
503
504 if (self.integral.abs() - self.config.i_limit).abs() < T::epsilon() {
506 status = SafetyStatus::IntegralWindup;
507 fault_count += 1;
508 }
509
510 if error != T::zero()
512 && self.filtered_derivative.abs() > error.abs() * T::from(10.0).unwrap()
513 {
514 status = SafetyStatus::DerivativeNoisy;
515 fault_count += 1;
516 }
517
518 if dt >= self.config.max_dt {
520 status = SafetyStatus::TimeStepExcessive;
521 fault_count += 1;
522 }
523
524 if fault_count > 1 {
525 SafetyStatus::MultipleFaults
526 } else {
527 status
528 }
529 }
530
531 pub fn reset(&mut self) {
533 self.integral = T::zero();
534 self.prev_measurement = None;
535 self.filtered_derivative = T::zero();
536 self.prev_output = T::zero();
537 self.prev_error = None;
538 self.internal_setpoint = self.setpoint;
539 }
540
541 pub fn reset_integral(&mut self) {
543 self.integral = T::zero();
544 }
545
546 pub fn preload_integral(&mut self, value: T) {
548 self.integral = self.clamp(value, self.config.i_limit);
549 }
550
551 pub fn get_integral_term(&self) -> T {
553 self.integral
554 }
555
556 pub fn diagnostics(&self) -> PidDiagnostics<T> {
558 PidDiagnostics {
559 update_count: self.update_count,
560 saturation_count: self.saturation_count,
561 saturation_ratio: if self.update_count > 0 {
562 T::from(self.saturation_count).unwrap() / T::from(self.update_count).unwrap()
563 } else {
564 T::zero()
565 },
566 max_error: self.max_error,
567 current_integral: self.integral,
568 current_derivative: self.filtered_derivative,
569 }
570 }
571}
572
573#[derive(Debug, Clone, Copy)]
575pub struct PidDiagnostics<T: Float> {
576 pub update_count: u64,
578 pub saturation_count: u64,
580 pub saturation_ratio: T,
582 pub max_error: T,
584 pub current_integral: T,
586 pub current_derivative: T,
588}
589
590impl<T: Float + fmt::Display> fmt::Display for PidDiagnostics<T> {
591 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
592 write!(
593 f,
594 "PID Diagnostics:\n\
595 Updates: {}\n\
596 Saturations: {} ({:.2}%)\n\
597 Max Error: {}\n\
598 Current Integral: {}\n\
599 Current Derivative: {}",
600 self.update_count,
601 self.saturation_count,
602 self.saturation_ratio * T::from(100.0).unwrap(),
603 self.max_error,
604 self.current_integral,
605 self.current_derivative
606 )
607 }
608}
609
610pub struct PidBuilder<T: Float> {
615 config: PidConfig<T>,
616}
617
618impl<T: Float> PidBuilder<T> {
619 pub fn new() -> Self {
620 Self {
621 config: PidConfig::default(),
622 }
623 }
624
625 pub fn gains(mut self, kp: T, ki: T, kd: T) -> Self {
626 self.config.kp = kp;
627 self.config.ki = ki;
628 self.config.kd = kd;
629 self
630 }
631
632 pub fn output_limits(mut self, min: T, max: T) -> Self {
633 self.config.output_min = min;
634 self.config.output_max = max;
635 self
636 }
637
638 pub fn term_limits(mut self, p_limit: T, i_limit: T, d_limit: T) -> Self {
639 self.config.p_limit = p_limit;
640 self.config.i_limit = i_limit;
641 self.config.d_limit = d_limit;
642 self
643 }
644
645 pub fn output_rate_limit(mut self, rate: T) -> Self {
646 self.config.output_rate_limit = Some(rate);
647 self
648 }
649
650 pub fn antiwindup(mut self, mode: AntiWindupMode, gain: T) -> Self {
651 self.config.antiwindup_mode = mode;
652 self.config.antiwindup_gain = gain;
653 self
654 }
655
656 pub fn derivative_filter(mut self, mode: DerivativeMode, coeff: T) -> Self {
657 self.config.derivative_mode = mode;
658 self.config.derivative_filter_coeff = coeff;
659 self
660 }
661
662 pub fn setpoint_ramping(mut self, enabled: bool, rate: T) -> Self {
663 self.config.setpoint_ramping = enabled;
664 self.config.setpoint_ramp_rate = rate;
665 self
666 }
667
668 pub fn time_limits(mut self, min_dt: T, max_dt: T) -> Self {
669 self.config.min_dt = min_dt;
670 self.config.max_dt = max_dt;
671 self
672 }
673
674 pub fn build(self) -> RobustPid<T> {
675 RobustPid::new(self.config)
676 }
677}
678
679impl<T: Float> Default for PidBuilder<T> {
680 fn default() -> Self {
681 Self::new()
682 }
683}
684
685#[cfg(test)]
690mod tests {
691 use super::*;
692
693 #[test]
694 fn test_proportional_only() {
695 let mut pid = PidBuilder::new()
696 .gains(2.0, 0.0, 0.0)
697 .output_limits(-100.0, 100.0)
698 .build();
699
700 pid.set_setpoint(10.0);
701
702 let output = pid.update(5.0, 1.0);
703 assert_eq!(output.p, 10.0); assert_eq!(output.i, 0.0);
705 assert_eq!(output.d, 0.0);
706 assert_eq!(output.output, 10.0);
707 }
708
709 #[test]
710 fn test_integral_accumulation() {
711 let mut pid = PidBuilder::new()
712 .gains(0.0, 1.0, 0.0)
713 .output_limits(-100.0, 100.0)
714 .build();
715
716 pid.set_setpoint(10.0);
717
718 let output1 = pid.update(8.0, 1.0); assert_eq!(output1.i, 2.0);
720
721 let output2 = pid.update(8.0, 1.0); assert_eq!(output2.i, 4.0);
723 }
724
725 #[test]
726 fn test_output_saturation() {
727 let mut pid = PidBuilder::new()
728 .gains(10.0, 0.0, 0.0)
729 .output_limits(-50.0, 50.0)
730 .build();
731
732 pid.set_setpoint(100.0);
733
734 let output = pid.update(0.0, 1.0);
735 assert_eq!(output.output, 50.0); assert!(matches!(
737 output.safety_status,
738 SafetyStatus::OutputSaturated | SafetyStatus::MultipleFaults
739 ));
740 }
741}