pid_control/lib.rs
1//! Software PID controller
2//!
3//! This crate implements a PID controller. It has seen some amount of
4//! real-world usage driving 100+ kW electrical motors, but has not been tested
5//! to death. Use with caution (but do use it and file bug reports!).
6//!
7//! Any change in behaviour that may make calculations behave differently will
8//! result in a major version upgrade; your tunings are safe as long as you
9//! stay on the same major version.
10//!
11//! Owes a great debt to:
12//!
13//! * https://en.wikipedia.org/wiki/PID_controller
14//! * http://www.embedded.com/design/prototyping-and-development/4211211/PID-without-a-PhD
15//! * http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/
16
17// FIXME: it may be worth to explore http://de.mathworks.com/help/simulink/slref/pidcontroller.html
18// for additional features/inspiration
19
20extern crate core;
21
22pub mod util;
23
24use std::f64;
25
26/// A generic controller interface.
27///
28/// A controller is fed timestamped values and calculates an adjusted value
29/// based on previous readings.
30///
31/// Many controllers possess a set of adjustable parameters as well as a set
32/// of input-value dependant state variables.
33pub trait Controller {
34 /// Record a measurement from the plant.
35 ///
36 /// Records a new values. `delta_t` is the time since the last update in
37 /// seconds.
38 fn update(&mut self, value: f64, delta_t: f64) -> f64;
39
40 /// Adjust set target for the plant.
41 ///
42 /// The controller will usually try to adjust its output (from `update`) in
43 /// a way that results in the plant approaching `target`.
44 fn set_target(&mut self, target: f64);
45
46 /// Retrieve target value.
47 fn target(&self) -> f64;
48
49 /// Reset internal state.
50 ///
51 /// Resets the internal state of the controller; not to be confused with
52 /// its parameters.
53 fn reset(&mut self);
54}
55
56
57/// PID controller derivative modes.
58///
59/// Two different ways of calculating the derivative can be used with the PID
60/// controller, allowing to avoid "derivative kick" if needed (see
61/// http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-derivative-kick/
62/// for details information on the implementation that inspired this one).
63///
64/// Choosing `OnMeasurement` will avoid large bumps in the controller output
65/// when changing the setpoint using `set_target()`.
66#[derive(Debug, Clone, Copy)]
67pub enum DerivativeMode {
68 /// Calculate derivative of error (classic PID-Controller)
69 OnError,
70 /// Calculate derivative of actual changes in value.
71 OnMeasurement,
72}
73
74/// PID Controller.
75///
76/// A PID controller, supporting the `Controller` interface. Any public values
77/// are safe to modify while in operation.
78///
79/// `p_gain`, `i_gain` and `d_gain` are the respective gain values. The
80/// controlller internally stores an already adjusted integral, making it safe
81/// to alter the `i_gain` - it will *not* result in an immediate large jump in
82/// controller output.
83///
84/// `i_min` and `i_max` are the limits for the internal integral storage.
85/// Similarly, `out_min` and `out_max` clip the output value to an acceptable
86/// range of values. By default, all limits are set to +/- infinity.
87///
88/// `d_mode` The `DerivativeMode`, the default is `OnMeasurement`.
89#[derive(Debug, Clone)]
90pub struct PIDController {
91 /// Proportional gain
92 pub p_gain: f64,
93
94 /// Integral gain
95 pub i_gain: f64,
96
97 /// Differential gain,
98 pub d_gain: f64,
99
100 target: f64,
101
102 // Integral range limits
103 pub i_min: f64,
104 pub i_max: f64,
105
106 // Output range limits
107 pub out_min: f64,
108 pub out_max: f64,
109
110 pub d_mode: DerivativeMode,
111
112 // The PIDs internal state. All other attributes are configuration values
113 err_sum: f64,
114 prev_value: f64,
115 prev_error: f64,
116}
117
118impl PIDController {
119 /// Creates a new PID Controller.
120 pub fn new(p_gain: f64, i_gain: f64, d_gain: f64) -> PIDController {
121 PIDController{
122 p_gain: p_gain,
123 i_gain: i_gain,
124 d_gain: d_gain,
125
126 target: 0.0,
127
128 err_sum: 0.0,
129 prev_value: f64::NAN,
130 prev_error: f64::NAN,
131
132 i_min: -f64::INFINITY,
133 i_max: f64::INFINITY,
134
135 out_min: -f64::INFINITY,
136 out_max: f64::INFINITY,
137
138 d_mode: DerivativeMode::OnMeasurement,
139 }
140 }
141
142 /// Convenience function to set `i_min`/`i_max` and `out_min`/`out_max`
143 /// to the same values simultaneously.
144 pub fn set_limits(&mut self, min: f64, max: f64) {
145 self.i_min = min;
146 self.i_max = max;
147 self.out_min = min;
148 self.out_max = max;
149 }
150}
151
152impl Controller for PIDController {
153 fn set_target(&mut self, target: f64) {
154 self.target = target;
155 }
156
157 fn target(&self) -> f64 {
158 self.target
159 }
160
161 fn update(&mut self, value: f64, delta_t: f64) -> f64 {
162 let error = self.target - value;
163
164 // PROPORTIONAL
165 let p_term = self.p_gain * error;
166
167 // INTEGRAL
168 self.err_sum = util::limit_range(
169 self.i_min, self.i_max,
170 self.err_sum + self.i_gain * error * delta_t
171 );
172 let i_term = self.err_sum;
173
174 // DIFFERENTIAL
175 let d_term = if self.prev_value.is_nan() || self.prev_error.is_nan() {
176 // we have no previous values, so skip the derivative calculation
177 0.0
178 } else {
179 match self.d_mode {
180 DerivativeMode::OnMeasurement => {
181 // we use -delta_v instead of delta_error to reduce "derivative kick",
182 // see http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-derivative-kick/
183 self.d_gain * (self.prev_value - value) / delta_t
184 },
185 DerivativeMode::OnError => {
186 self.d_gain * (error - self.prev_error) / delta_t
187 }
188 }
189 };
190
191 // store previous values
192 self.prev_value = value;
193 self.prev_error = error;
194
195 util::limit_range(
196 self.out_min, self.out_max,
197 p_term + d_term + i_term
198 )
199 }
200
201 fn reset(&mut self) {
202 self.prev_value = f64::NAN;
203 self.prev_error = f64::NAN;
204
205 // FIXME: http://brettbeauregard.com/blog/2011/04/improving-the-beginner
206 // %E2%80%99s-pid-initialization/
207 // suggests that this should not be there. however, it may miss
208 // the fact that input and output can be two completely
209 // different domains
210 self.err_sum = 0.0;
211 }
212}