pid/lib.rs
1//! A proportional-integral-derivative (PID) controller library.
2//!
3//! See [Pid] for the adjustable controller itself, as well as [ControlOutput] for the outputs and weights which you can use after setting up your controller. Follow the complete example below to setup your first controller!
4//!
5//! # Example
6//!
7//! ```rust
8//! use pid::Pid;
9//!
10//! // Create a new proportional-only PID controller with a setpoint of 15
11//! let mut pid = Pid::new(15.0, 100.0);
12//! pid.p(10.0, 100.0);
13//!
14//! // Input a measurement with an error of 5.0 from our setpoint
15//! let output = pid.next_control_output(10.0);
16//!
17//! // Show that the error is correct by multiplying by our kp
18//! assert_eq!(output.output, 50.0); // <--
19//! assert_eq!(output.p, 50.0);
20//!
21//! // It won't change on repeat; the controller is proportional-only
22//! let output = pid.next_control_output(10.0);
23//! assert_eq!(output.output, 50.0); // <--
24//! assert_eq!(output.p, 50.0);
25//!
26//! // Add a new integral term to the controller and input again
27//! pid.i(1.0, 100.0);
28//! let output = pid.next_control_output(10.0);
29//!
30//! // Now that the integral makes the controller stateful, it will change
31//! assert_eq!(output.output, 55.0); // <--
32//! assert_eq!(output.p, 50.0);
33//! assert_eq!(output.i, 5.0);
34//!
35//! // Add our final derivative term and match our setpoint target
36//! pid.d(2.0, 100.0);
37//! let output = pid.next_control_output(15.0);
38//!
39//! // The output will now say to go down due to the derivative
40//! assert_eq!(output.output, -5.0); // <--
41//! assert_eq!(output.p, 0.0);
42//! assert_eq!(output.i, 5.0);
43//! assert_eq!(output.d, -10.0);
44//! ```
45#![no_std]
46
47use num_traits::float::FloatCore;
48#[cfg(feature = "serde")]
49use serde::{Deserialize, Serialize};
50
51/// Adjustable proportional-integral-derivative (PID) controller.
52///
53/// # Examples
54///
55/// This controller provides a builder pattern interface which allows you to pick-and-choose which PID inputs you'd like to use during operation. Here's what a basic proportional-only controller could look like:
56///
57/// ```rust
58/// use pid::Pid;
59///
60/// // Create limited controller
61/// let mut p_controller = Pid::new(15.0, 100.0);
62/// p_controller.p(10.0, 100.0);
63///
64/// // Get first output
65/// let p_output = p_controller.next_control_output(400.0);
66/// ```
67///
68/// This controller would give you set a proportional controller to `10.0` with a target of `15.0` and an output limit of `100.0` per [output](Self::next_control_output) iteration. The same controller with a full PID system built in looks like:
69///
70/// ```rust
71/// use pid::Pid;
72///
73/// // Create full PID controller
74/// let mut full_controller = Pid::new(15.0, 100.0);
75/// full_controller.p(10.0, 100.0).i(4.5, 100.0).d(0.25, 100.0);
76///
77/// // Get first output
78/// let full_output = full_controller.next_control_output(400.0);
79/// ```
80///
81/// This [`next_control_output`](Self::next_control_output) method is what's used to input new values into the controller to tell it what the current state of the system is. In the examples above it's only being used once, but realistically this will be a hot method. Please see [ControlOutput] for examples of how to handle these outputs; it's quite straight forward and mirrors the values of this structure in some ways.
82///
83/// The last item of note is that these [`p`](Self::p()), [`i`](Self::i()), and [`d`](Self::d()) methods can be used *during* operation which lets you add and/or modify these controller values if need be.
84#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
85#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
86pub struct Pid<T: FloatCore> {
87 /// Ideal setpoint to strive for.
88 pub setpoint: T,
89 /// Defines the overall output filter limit.
90 pub output_limit: T,
91 /// Proportional gain.
92 pub kp: T,
93 /// Integral gain.
94 pub ki: T,
95 /// Derivative gain.
96 pub kd: T,
97 /// Limiter for the proportional term: `-p_limit <= P <= p_limit`.
98 pub p_limit: T,
99 /// Limiter for the integral term: `-i_limit <= I <= i_limit`.
100 pub i_limit: T,
101 /// Limiter for the derivative term: `-d_limit <= D <= d_limit`.
102 pub d_limit: T,
103 /// Last calculated integral value if [Pid::ki] is used.
104 integral_term: T,
105 /// Previously found measurement whilst using the [Pid::next_control_output] method.
106 prev_measurement: Option<T>,
107}
108
109/// Output of [controller iterations](Pid::next_control_output) with weights
110///
111/// # Example
112///
113/// This structure is simple to use and features three weights: [p](Self::p), [i](Self::i), and [d](Self::d). These can be used to figure out how much each term from [Pid] contributed to the final [output](Self::output) value which should be taken as the final controller output for this iteration:
114///
115/// ```rust
116/// use pid::{Pid, ControlOutput};
117///
118/// // Setup controller
119/// let mut pid = Pid::new(15.0, 100.0);
120/// pid.p(10.0, 100.0).i(1.0, 100.0).d(2.0, 100.0);
121///
122/// // Input an example value and get a report for an output iteration
123/// let output = pid.next_control_output(26.2456);
124/// println!("P: {}\nI: {}\nD: {}\nFinal Output: {}", output.p, output.i, output.d, output.output);
125/// ```
126#[derive(Debug, PartialEq, Eq)]
127pub struct ControlOutput<T: FloatCore> {
128 /// Contribution of the P term to the output.
129 pub p: T,
130 /// Contribution of the I term to the output.
131 ///
132 /// This integral term is equal to `sum[error(t) * ki(t)] (for all t)`
133 pub i: T,
134 /// Contribution of the D term to the output.
135 pub d: T,
136 /// Output of the PID controller.
137 pub output: T,
138}
139
140impl<T> Pid<T>
141where
142 T: FloatCore,
143{
144 /// Creates a new controller with the target setpoint and the output limit
145 ///
146 /// To set your P, I, and D terms into this controller, please use the following builder methods:
147 /// - [Self::p()]: Proportional term setting
148 /// - [Self::i()]: Integral term setting
149 /// - [Self::d()]: Derivative term setting
150 pub fn new(setpoint: impl Into<T>, output_limit: impl Into<T>) -> Self {
151 Self {
152 setpoint: setpoint.into(),
153 output_limit: output_limit.into(),
154 kp: T::zero(),
155 ki: T::zero(),
156 kd: T::zero(),
157 p_limit: T::zero(),
158 i_limit: T::zero(),
159 d_limit: T::zero(),
160 integral_term: T::zero(),
161 prev_measurement: None,
162 }
163 }
164
165 /// Sets the [Self::p] term for this controller.
166 pub fn p(&mut self, gain: impl Into<T>, limit: impl Into<T>) -> &mut Self {
167 self.kp = gain.into();
168 self.p_limit = limit.into();
169 self
170 }
171
172 /// Sets the [Self::i] term for this controller.
173 pub fn i(&mut self, gain: impl Into<T>, limit: impl Into<T>) -> &mut Self {
174 self.ki = gain.into();
175 self.i_limit = limit.into();
176 self
177 }
178
179 /// Sets the [Self::d] term for this controller.
180 pub fn d(&mut self, gain: impl Into<T>, limit: impl Into<T>) -> &mut Self {
181 self.kd = gain.into();
182 self.d_limit = limit.into();
183 self
184 }
185
186 /// Sets the [Pid::setpoint] to target for this controller.
187 pub fn setpoint(&mut self, setpoint: impl Into<T>) -> &mut Self {
188 self.setpoint = setpoint.into();
189 self
190 }
191
192 /// Given a new measurement, calculates the next [control output](ControlOutput).
193 ///
194 /// # Panics
195 ///
196 /// - If a setpoint has not been set via `update_setpoint()`.
197 pub fn next_control_output(&mut self, measurement: T) -> ControlOutput<T> {
198 // Calculate the error between the ideal setpoint and the current
199 // measurement to compare against
200 let error = self.setpoint - measurement;
201
202 // Calculate the proportional term and limit to it's individual limit
203 let p_unbounded = error * self.kp;
204 let p = apply_limit(self.p_limit, p_unbounded);
205
206 // Mitigate output jumps when ki(t) != ki(t-1).
207 // While it's standard to use an error_integral that's a running sum of
208 // just the error (no ki), because we support ki changing dynamically,
209 // we store the entire term so that we don't need to remember previous
210 // ki values.
211 self.integral_term = self.integral_term + error * self.ki;
212
213 // Mitigate integral windup: Don't want to keep building up error
214 // beyond what i_limit will allow.
215 self.integral_term = apply_limit(self.i_limit, self.integral_term);
216
217 // Mitigate derivative kick: Use the derivative of the measurement
218 // rather than the derivative of the error.
219 let d_unbounded = -match self.prev_measurement.as_ref() {
220 Some(prev_measurement) => measurement - *prev_measurement,
221 None => T::zero(),
222 } * self.kd;
223 self.prev_measurement = Some(measurement);
224 let d = apply_limit(self.d_limit, d_unbounded);
225
226 // Calculate the final output by adding together the PID terms, then
227 // apply the final defined output limit
228 let output = p + self.integral_term + d;
229 let output = apply_limit(self.output_limit, output);
230
231 // Return the individual term's contributions and the final output
232 ControlOutput {
233 p,
234 i: self.integral_term,
235 d,
236 output: output,
237 }
238 }
239
240 /// Resets the integral term back to zero, this may drastically change the
241 /// control output.
242 pub fn reset_integral_term(&mut self) {
243 self.integral_term = T::zero();
244 }
245}
246
247/// Saturating the input `value` according the absolute `limit` (`-limit <= output <= limit`).
248fn apply_limit<T: FloatCore>(limit: T, value: T) -> T {
249 limit.min(value.abs()) * value.signum()
250}
251
252#[cfg(test)]
253mod tests {
254 use super::Pid;
255 use crate::ControlOutput;
256
257 /// Proportional-only controller operation and limits
258 #[test]
259 fn proportional() {
260 let mut pid = Pid::new(10.0, 100.0);
261 pid.p(2.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
262 assert_eq!(pid.setpoint, 10.0);
263
264 // Test simple proportional
265 assert_eq!(pid.next_control_output(0.0).output, 20.0);
266
267 // Test proportional limit
268 pid.p_limit = 10.0;
269 assert_eq!(pid.next_control_output(0.0).output, 10.0);
270 }
271
272 /// Derivative-only controller operation and limits
273 #[test]
274 fn derivative() {
275 let mut pid = Pid::new(10.0, 100.0);
276 pid.p(0.0, 100.0).i(0.0, 100.0).d(2.0, 100.0);
277
278 // Test that there's no derivative since it's the first measurement
279 assert_eq!(pid.next_control_output(0.0).output, 0.0);
280
281 // Test that there's now a derivative
282 assert_eq!(pid.next_control_output(5.0).output, -10.0);
283
284 // Test derivative limit
285 pid.d_limit = 5.0;
286 assert_eq!(pid.next_control_output(10.0).output, -5.0);
287 }
288
289 /// Integral-only controller operation and limits
290 #[test]
291 fn integral() {
292 let mut pid = Pid::new(10.0, 100.0);
293 pid.p(0.0, 100.0).i(2.0, 100.0).d(0.0, 100.0);
294
295 // Test basic integration
296 assert_eq!(pid.next_control_output(0.0).output, 20.0);
297 assert_eq!(pid.next_control_output(0.0).output, 40.0);
298 assert_eq!(pid.next_control_output(5.0).output, 50.0);
299
300 // Test limit
301 pid.i_limit = 50.0;
302 assert_eq!(pid.next_control_output(5.0).output, 50.0);
303 // Test that limit doesn't impede reversal of error integral
304 assert_eq!(pid.next_control_output(15.0).output, 40.0);
305
306 // Test that error integral accumulates negative values
307 let mut pid2 = Pid::new(-10.0, 100.0);
308 pid2.p(0.0, 100.0).i(2.0, 100.0).d(0.0, 100.0);
309 assert_eq!(pid2.next_control_output(0.0).output, -20.0);
310 assert_eq!(pid2.next_control_output(0.0).output, -40.0);
311
312 pid2.i_limit = 50.0;
313 assert_eq!(pid2.next_control_output(-5.0).output, -50.0);
314 // Test that limit doesn't impede reversal of error integral
315 assert_eq!(pid2.next_control_output(-15.0).output, -40.0);
316 }
317
318 /// Checks that a full PID controller's limits work properly through multiple output iterations
319 #[test]
320 fn output_limit() {
321 let mut pid = Pid::new(10.0, 1.0);
322 pid.p(1.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
323
324 let out = pid.next_control_output(0.0);
325 assert_eq!(out.p, 10.0); // 1.0 * 10.0
326 assert_eq!(out.output, 1.0);
327
328 let out = pid.next_control_output(20.0);
329 assert_eq!(out.p, -10.0); // 1.0 * (10.0 - 20.0)
330 assert_eq!(out.output, -1.0);
331 }
332
333 /// Combined PID operation
334 #[test]
335 fn pid() {
336 let mut pid = Pid::new(10.0, 100.0);
337 pid.p(1.0, 100.0).i(0.1, 100.0).d(1.0, 100.0);
338
339 let out = pid.next_control_output(0.0);
340 assert_eq!(out.p, 10.0); // 1.0 * 10.0
341 assert_eq!(out.i, 1.0); // 0.1 * 10.0
342 assert_eq!(out.d, 0.0); // -(1.0 * 0.0)
343 assert_eq!(out.output, 11.0);
344
345 let out = pid.next_control_output(5.0);
346 assert_eq!(out.p, 5.0); // 1.0 * 5.0
347 assert_eq!(out.i, 1.5); // 0.1 * (10.0 + 5.0)
348 assert_eq!(out.d, -5.0); // -(1.0 * 5.0)
349 assert_eq!(out.output, 1.5);
350
351 let out = pid.next_control_output(11.0);
352 assert_eq!(out.p, -1.0); // 1.0 * -1.0
353 assert_eq!(out.i, 1.4); // 0.1 * (10.0 + 5.0 - 1)
354 assert_eq!(out.d, -6.0); // -(1.0 * 6.0)
355 assert_eq!(out.output, -5.6);
356
357 let out = pid.next_control_output(10.0);
358 assert_eq!(out.p, 0.0); // 1.0 * 0.0
359 assert_eq!(out.i, 1.4); // 0.1 * (10.0 + 5.0 - 1.0 + 0.0)
360 assert_eq!(out.d, 1.0); // -(1.0 * -1.0)
361 assert_eq!(out.output, 2.4);
362 }
363
364 /// Full PID operation with mixed f32/f64 checking to make sure they're equal
365 #[test]
366 fn f32_and_f64() {
367 let mut pid32 = Pid::new(10.0f32, 100.0);
368 pid32.p(0.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
369
370 let mut pid64 = Pid::new(10.0, 100.0f64);
371 pid64.p(0.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
372
373 assert_eq!(
374 pid32.next_control_output(0.0).output,
375 pid64.next_control_output(0.0).output as f32
376 );
377 assert_eq!(
378 pid32.next_control_output(0.0).output as f64,
379 pid64.next_control_output(0.0).output
380 );
381 }
382
383 /// See if the controller can properly target to the setpoint after 2 output iterations
384 #[test]
385 fn setpoint() {
386 let mut pid = Pid::new(10.0, 100.0);
387 pid.p(1.0, 100.0).i(0.1, 100.0).d(1.0, 100.0);
388
389 let out = pid.next_control_output(0.0);
390 assert_eq!(out.p, 10.0); // 1.0 * 10.0
391 assert_eq!(out.i, 1.0); // 0.1 * 10.0
392 assert_eq!(out.d, 0.0); // -(1.0 * 0.0)
393 assert_eq!(out.output, 11.0);
394
395 pid.setpoint(0.0);
396
397 assert_eq!(
398 pid.next_control_output(0.0),
399 ControlOutput {
400 p: 0.0,
401 i: 1.0,
402 d: -0.0,
403 output: 1.0
404 }
405 );
406 }
407}