Skip to content

Commit 6a22a47

Browse files
committed
Add dt feature flag
1 parent c8b43eb commit 6a22a47

File tree

2 files changed

+130
-27
lines changed

2 files changed

+130
-27
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ keywords = ["pid"]
1313
categories = ["no-std", "embedded", "algorithms"]
1414
readme = "README.md"
1515

16+
[features]
17+
dt = []
18+
1619
[dependencies.num-traits]
1720
version = "0.2"
1821
default-features = false

src/lib.rs

Lines changed: 127 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,33 @@
1212
//! pid.p(10.0, 100.0);
1313
//!
1414
//! // Input a measurement with an error of 5.0 from our setpoint
15+
//!
16+
//! #[cfg(feature = "dt")]
1517
//! let output = pid.next_control_output(10.0, 1.0);
18+
//! #[cfg(not(feature = "dt"))]
19+
//! let output = pid.next_control_output(10.0);
1620
//!
1721
//! // Show that the error is correct by multiplying by our kp
1822
//! assert_eq!(output.output, 50.0); // <--
1923
//! assert_eq!(output.p, 50.0);
2024
//!
2125
//! // It won't change on repeat; the controller is proportional-only
26+
//!
27+
//! #[cfg(feature = "dt")]
2228
//! let output = pid.next_control_output(10.0, 1.0);
29+
//! #[cfg(not(feature = "dt"))]
30+
//! let output = pid.next_control_output(10.0);
31+
//!
2332
//! assert_eq!(output.output, 50.0); // <--
2433
//! assert_eq!(output.p, 50.0);
2534
//!
2635
//! // Add a new integral term to the controller and input again
2736
//! pid.i(1.0, 100.0);
37+
//!
38+
//! #[cfg(feature = "dt")]
2839
//! let output = pid.next_control_output(10.0, 1.0);
40+
//! #[cfg(not(feature = "dt"))]
41+
//! let output = pid.next_control_output(10.0);
2942
//!
3043
//! // Now that the integral makes the controller stateful, it will change
3144
//! assert_eq!(output.output, 55.0); // <--
@@ -34,7 +47,10 @@
3447
//!
3548
//! // Add our final derivative term and match our setpoint target
3649
//! pid.d(2.0, 100.0);
50+
//! #[cfg(feature = "dt")]
3751
//! let output = pid.next_control_output(15.0, 1.0);
52+
//! #[cfg(not(feature = "dt"))]
53+
//! let output = pid.next_control_output(15.0);
3854
//!
3955
//! // The output will now say to go down due to the derivative
4056
//! assert_eq!(output.output, -5.0); // <--
@@ -79,7 +95,10 @@ impl<T: PartialOrd + num_traits::Signed + Copy> Number for T {}
7995
/// p_controller.p(10.0, 100.0);
8096
///
8197
/// // Get first output
98+
/// #[cfg(feature = "dt")]
8299
/// let p_output = p_controller.next_control_output(400.0, 1.0);
100+
/// #[cfg(not(feature = "dt"))]
101+
/// let p_output = p_controller.next_control_output(400.0);
83102
/// ```
84103
///
85104
/// 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:
@@ -92,7 +111,10 @@ impl<T: PartialOrd + num_traits::Signed + Copy> Number for T {}
92111
/// full_controller.p(10.0, 100.0).i(4.5, 100.0).d(0.25, 100.0);
93112
///
94113
/// // Get first output
114+
/// #[cfg(feature = "dt")]
95115
/// let full_output = full_controller.next_control_output(400.0, 1.0);
116+
/// #[cfg(not(feature = "dt"))]
117+
/// let full_output = full_controller.next_control_output(400.0);
96118
/// ```
97119
///
98120
/// 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.
@@ -141,7 +163,11 @@ pub struct Pid<T: Number> {
141163
/// pid.p(10.0, 100.0).i(1.0, 100.0).d(2.0, 100.0);
142164
///
143165
/// // Input an example value and get a report for an output iteration
166+
/// #[cfg(feature = "dt")]
144167
/// let output = pid.next_control_output(26.2456, 1.0);
168+
/// #[cfg(not(feature = "dt"))]
169+
/// let output = pid.next_control_output(26.2456);
170+
///
145171
/// println!("P: {}\nI: {}\nD: {}\nFinal Output: {}", output.p, output.i, output.d, output.output);
146172
/// ```
147173
#[derive(Debug, PartialEq, Eq)]
@@ -210,6 +236,7 @@ where
210236
self
211237
}
212238

239+
#[cfg(feature = "dt")]
213240
/// Given a new measurement and dt, calculates the next [control output](ControlOutput).
214241
///
215242
/// # Panics
@@ -259,6 +286,55 @@ where
259286
}
260287
}
261288

289+
#[cfg(not(feature = "dt"))]
290+
/// Given a new measurement, calculates the next [control output](ControlOutput).
291+
///
292+
/// # Panics
293+
///
294+
/// - If a setpoint has not been set via `update_setpoint()`.
295+
pub fn next_control_output(&mut self, measurement: T) -> ControlOutput<T> {
296+
// Calculate the error between the ideal setpoint and the current
297+
// measurement to compare against
298+
let error = self.setpoint - measurement;
299+
300+
// Calculate the proportional term and limit to it's individual limit
301+
let p_unbounded = error * self.kp;
302+
let p = apply_limit(self.p_limit, p_unbounded);
303+
304+
// Mitigate output jumps when ki(t) != ki(t-1).
305+
// While it's standard to use an error_integral that's a running sum of
306+
// just the error (no ki), because we support ki changing dynamically,
307+
// we store the entire term so that we don't need to remember previous
308+
// ki values.
309+
self.integral_term = self.integral_term + error * self.ki;
310+
311+
// Mitigate integral windup: Don't want to keep building up error
312+
// beyond what i_limit will allow.
313+
self.integral_term = apply_limit(self.i_limit, self.integral_term);
314+
315+
// Mitigate derivative kick: Use the derivative of the measurement
316+
// rather than the derivative of the error.
317+
let d_unbounded = -match self.prev_measurement.as_ref() {
318+
Some(prev_measurement) => measurement - *prev_measurement,
319+
None => T::zero(),
320+
} * self.kd;
321+
self.prev_measurement = Some(measurement);
322+
let d = apply_limit(self.d_limit, d_unbounded);
323+
324+
// Calculate the final output by adding together the PID terms, then
325+
// apply the final defined output limit
326+
let output = p + self.integral_term + d;
327+
let output = apply_limit(self.output_limit, output);
328+
329+
// Return the individual term's contributions and the final output
330+
ControlOutput {
331+
p,
332+
i: self.integral_term,
333+
d,
334+
output,
335+
}
336+
}
337+
262338
/// Resets the integral term back to zero, this may drastically change the
263339
/// control output.
264340
pub fn reset_integral_term(&mut self) {
@@ -283,6 +359,30 @@ mod tests {
283359
use super::Pid;
284360
use crate::ControlOutput;
285361

362+
/// Macro to call Pid::next_control_output() with the correct signature
363+
#[cfg(feature = "dt")]
364+
macro_rules! pid_next_control_output {
365+
($self:ident, $measurement:expr, $dt:expr) => {
366+
$self.next_control_output($measurement, $dt)
367+
};
368+
($self:ident, $measurement:expr) => {
369+
// If the dt feature is enabled, we call the function with dt = 1.0
370+
$self.next_control_output($measurement, 1.0)
371+
};
372+
}
373+
374+
/// Macro to call Pid::next_control_output() with the correct signature
375+
#[cfg(not(feature = "dt"))]
376+
macro_rules! pid_next_control_output {
377+
($self:ident, $measurement:expr, $dt:expr) => {
378+
// If the dt feature is not enabled, we ignore the dt argument
379+
$self.next_control_output($measurement)
380+
};
381+
($self:ident, $measurement:expr) => {
382+
$self.next_control_output($measurement)
383+
};
384+
}
385+
286386
/// Proportional-only controller operation and limits
287387
#[test]
288388
fn proportional() {
@@ -291,11 +391,11 @@ mod tests {
291391
assert_eq!(pid.setpoint, 10.0);
292392

293393
// Test simple proportional
294-
assert_eq!(pid.next_control_output(0.0, 1.0).output, 20.0);
394+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 20.0);
295395

296396
// Test proportional limit
297397
pid.p_limit = 10.0;
298-
assert_eq!(pid.next_control_output(0.0, 1.0).output, 10.0);
398+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 10.0);
299399
}
300400

301401
/// Derivative-only controller operation and limits
@@ -305,14 +405,14 @@ mod tests {
305405
pid.p(0.0, 100.0).i(0.0, 100.0).d(2.0, 100.0);
306406

307407
// Test that there's no derivative since it's the first measurement
308-
assert_eq!(pid.next_control_output(0.0, 1.0).output, 0.0);
408+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 0.0);
309409

310410
// Test that there's now a derivative
311-
assert_eq!(pid.next_control_output(5.0, 1.0).output, -10.0);
411+
assert_eq!(pid_next_control_output!(pid, 5.0, 1.0).output, -10.0);
312412

313413
// Test derivative limit
314414
pid.d_limit = 5.0;
315-
assert_eq!(pid.next_control_output(10.0, 1.0).output, -5.0);
415+
assert_eq!(pid_next_control_output!(pid, 10.0, 1.0).output, -5.0);
316416
}
317417

318418
/// Integral-only controller operation and limits
@@ -322,26 +422,26 @@ mod tests {
322422
pid.p(0.0, 100.0).i(2.0, 100.0).d(0.0, 100.0);
323423

324424
// Test basic integration
325-
assert_eq!(pid.next_control_output(0.0, 1.0).output, 20.0);
326-
assert_eq!(pid.next_control_output(0.0, 1.0).output, 40.0);
327-
assert_eq!(pid.next_control_output(5.0, 1.0).output, 50.0);
425+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 20.0);
426+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 40.0);
427+
assert_eq!(pid_next_control_output!(pid, 5.0, 1.0).output, 50.0);
328428

329429
// Test limit
330430
pid.i_limit = 50.0;
331-
assert_eq!(pid.next_control_output(5.0, 1.0).output, 50.0);
431+
assert_eq!(pid_next_control_output!(pid, 5.0, 1.0).output, 50.0);
332432
// Test that limit doesn't impede reversal of error integral
333-
assert_eq!(pid.next_control_output(15.0, 1.0).output, 40.0);
433+
assert_eq!(pid_next_control_output!(pid, 15.0, 1.0).output, 40.0);
334434

335435
// Test that error integral accumulates negative values
336436
let mut pid2 = Pid::new(-10.0, 100.0);
337437
pid2.p(0.0, 100.0).i(2.0, 100.0).d(0.0, 100.0);
338-
assert_eq!(pid2.next_control_output(0.0, 1.0).output, -20.0);
339-
assert_eq!(pid2.next_control_output(0.0, 1.0).output, -40.0);
438+
assert_eq!(pid_next_control_output!(pid2, 0.0, 1.0).output, -20.0);
439+
assert_eq!(pid_next_control_output!(pid2, 0.0, 1.0).output, -40.0);
340440

341441
pid2.i_limit = 50.0;
342-
assert_eq!(pid2.next_control_output(-5.0, 1.0).output, -50.0);
442+
assert_eq!(pid_next_control_output!(pid2, -5.0, 1.0).output, -50.0);
343443
// Test that limit doesn't impede reversal of error integral
344-
assert_eq!(pid2.next_control_output(-15.0, 1.0).output, -40.0);
444+
assert_eq!(pid_next_control_output!(pid2, -15.0, 1.0).output, -40.0);
345445
}
346446

347447
/// Checks that a full PID controller's limits work properly through multiple output iterations
@@ -350,11 +450,11 @@ mod tests {
350450
let mut pid = Pid::new(10.0, 1.0);
351451
pid.p(1.0, 100.0).i(0.0, 100.0).d(0.0, 100.0);
352452

353-
let out = pid.next_control_output(0.0, 1.0);
453+
let out = pid_next_control_output!(pid, 0.0, 1.0);
354454
assert_eq!(out.p, 10.0); // 1.0 * 10.0
355455
assert_eq!(out.output, 1.0);
356456

357-
let out = pid.next_control_output(20.0, 1.0);
457+
let out = pid_next_control_output!(pid, 20.0, 1.0);
358458
assert_eq!(out.p, -10.0); // 1.0 * (10.0 - 20.0)
359459
assert_eq!(out.output, -1.0);
360460
}
@@ -365,25 +465,25 @@ mod tests {
365465
let mut pid = Pid::new(10.0, 100.0);
366466
pid.p(1.0, 100.0).i(0.1, 100.0).d(1.0, 100.0);
367467

368-
let out = pid.next_control_output(0.0, 1.0);
468+
let out = pid_next_control_output!(pid, 0.0, 1.0);
369469
assert_eq!(out.p, 10.0); // 1.0 * 10.0
370470
assert_eq!(out.i, 1.0); // 0.1 * 10.0
371471
assert_eq!(out.d, 0.0); // -(1.0 * 0.0)
372472
assert_eq!(out.output, 11.0);
373473

374-
let out = pid.next_control_output(5.0, 1.0);
474+
let out = pid_next_control_output!(pid, 5.0, 1.0);
375475
assert_eq!(out.p, 5.0); // 1.0 * 5.0
376476
assert_eq!(out.i, 1.5); // 0.1 * (10.0 + 5.0)
377477
assert_eq!(out.d, -5.0); // -(1.0 * 5.0)
378478
assert_eq!(out.output, 1.5);
379479

380-
let out = pid.next_control_output(11.0, 1.0);
480+
let out = pid_next_control_output!(pid, 11.0, 1.0);
381481
assert_eq!(out.p, -1.0); // 1.0 * -1.0
382482
assert_eq!(out.i, 1.4); // 0.1 * (10.0 + 5.0 - 1)
383483
assert_eq!(out.d, -6.0); // -(1.0 * 6.0)
384484
assert_eq!(out.output, -5.6);
385485

386-
let out = pid.next_control_output(10.0, 1.0);
486+
let out = pid_next_control_output!(pid, 10.0, 1.0);
387487
assert_eq!(out.p, 0.0); // 1.0 * 0.0
388488
assert_eq!(out.i, 1.4); // 0.1 * (10.0 + 5.0 - 1.0 + 0.0)
389489
assert_eq!(out.d, 1.0); // -(1.0 * -1.0)
@@ -402,8 +502,8 @@ mod tests {
402502

403503
for _ in 0..5 {
404504
assert_eq!(
405-
pid_f32.next_control_output(0.0, 1.0).output,
406-
pid_f64.next_control_output(0.0, 1.0).output as f32
505+
pid_next_control_output!(pid_f32, 0.0, 1.0).output,
506+
pid_next_control_output!(pid_f64, 0.0, 1.0).output as f32
407507
);
408508
}
409509
}
@@ -420,8 +520,8 @@ mod tests {
420520

421521
for _ in 0..5 {
422522
assert_eq!(
423-
pid_i32.next_control_output(0, 1).output,
424-
pid_i8.next_control_output(0i8, 1i8).output as i32
523+
pid_next_control_output!(pid_i32, 0, 1).output,
524+
pid_next_control_output!(pid_i8, 0i8, 1i8).output as i32
425525
);
426526
}
427527
}
@@ -432,7 +532,7 @@ mod tests {
432532
let mut pid = Pid::new(10.0, 100.0);
433533
pid.p(1.0, 100.0).i(0.1, 100.0).d(1.0, 100.0);
434534

435-
let out = pid.next_control_output(0.0, 1.0);
535+
let out = pid_next_control_output!(pid, 0.0, 1.0);
436536
assert_eq!(out.p, 10.0); // 1.0 * 10.0
437537
assert_eq!(out.i, 1.0); // 0.1 * 10.0
438538
assert_eq!(out.d, 0.0); // -(1.0 * 0.0)
@@ -441,7 +541,7 @@ mod tests {
441541
pid.setpoint(0.0);
442542

443543
assert_eq!(
444-
pid.next_control_output(0.0, 1.0),
544+
pid_next_control_output!(pid, 0.0, 1.0),
445545
ControlOutput {
446546
p: 0.0,
447547
i: 1.0,
@@ -457,7 +557,7 @@ mod tests {
457557
let mut pid = Pid::new(10.0f32, -10.0);
458558
pid.p(1.0, -50.0).i(1.0, -50.0).d(1.0, -50.0);
459559

460-
let out = pid.next_control_output(0.0, 1.0);
560+
let out = pid_next_control_output!(pid, 0.0, 1.0);
461561
assert_eq!(out.p, 10.0);
462562
assert_eq!(out.i, 10.0);
463563
assert_eq!(out.d, 0.0);

0 commit comments

Comments
 (0)