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}