Skip to content

Commit b31ecd4

Browse files
committed
Add dt feature flag
1 parent c8b43eb commit b31ecd4

File tree

2 files changed

+132
-27
lines changed

2 files changed

+132
-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: 129 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,32 @@ 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+
#[cfg(feature = "dt")]
371+
$self.next_control_output($measurement, 1.0)
372+
};
373+
}
374+
375+
/// Macro to call Pid::next_control_output() with the correct signature
376+
#[cfg(not(feature = "dt"))]
377+
macro_rules! pid_next_control_output {
378+
($self:ident, $measurement:expr, $dt:expr) => {
379+
// If the dt feature is not enabled, we ignore the dt argument
380+
$self.next_control_output($measurement)
381+
};
382+
($self:ident, $measurement:expr) => {
383+
#[cfg(feature = "dt")]
384+
$self.next_control_output($measurement)
385+
};
386+
}
387+
286388
/// Proportional-only controller operation and limits
287389
#[test]
288390
fn proportional() {
@@ -291,11 +393,11 @@ mod tests {
291393
assert_eq!(pid.setpoint, 10.0);
292394

293395
// Test simple proportional
294-
assert_eq!(pid.next_control_output(0.0, 1.0).output, 20.0);
396+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 20.0);
295397

296398
// Test proportional limit
297399
pid.p_limit = 10.0;
298-
assert_eq!(pid.next_control_output(0.0, 1.0).output, 10.0);
400+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 10.0);
299401
}
300402

301403
/// Derivative-only controller operation and limits
@@ -305,14 +407,14 @@ mod tests {
305407
pid.p(0.0, 100.0).i(0.0, 100.0).d(2.0, 100.0);
306408

307409
// 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);
410+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 0.0);
309411

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

313415
// Test derivative limit
314416
pid.d_limit = 5.0;
315-
assert_eq!(pid.next_control_output(10.0, 1.0).output, -5.0);
417+
assert_eq!(pid_next_control_output!(pid, 10.0, 1.0).output, -5.0);
316418
}
317419

318420
/// Integral-only controller operation and limits
@@ -322,26 +424,26 @@ mod tests {
322424
pid.p(0.0, 100.0).i(2.0, 100.0).d(0.0, 100.0);
323425

324426
// 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);
427+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 20.0);
428+
assert_eq!(pid_next_control_output!(pid, 0.0, 1.0).output, 40.0);
429+
assert_eq!(pid_next_control_output!(pid, 5.0, 1.0).output, 50.0);
328430

329431
// Test limit
330432
pid.i_limit = 50.0;
331-
assert_eq!(pid.next_control_output(5.0, 1.0).output, 50.0);
433+
assert_eq!(pid_next_control_output!(pid, 5.0, 1.0).output, 50.0);
332434
// Test that limit doesn't impede reversal of error integral
333-
assert_eq!(pid.next_control_output(15.0, 1.0).output, 40.0);
435+
assert_eq!(pid_next_control_output!(pid, 15.0, 1.0).output, 40.0);
334436

335437
// Test that error integral accumulates negative values
336438
let mut pid2 = Pid::new(-10.0, 100.0);
337439
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);
440+
assert_eq!(pid_next_control_output!(pid2, 0.0, 1.0).output, -20.0);
441+
assert_eq!(pid_next_control_output!(pid2, 0.0, 1.0).output, -40.0);
340442

341443
pid2.i_limit = 50.0;
342-
assert_eq!(pid2.next_control_output(-5.0, 1.0).output, -50.0);
444+
assert_eq!(pid_next_control_output!(pid2, -5.0, 1.0).output, -50.0);
343445
// Test that limit doesn't impede reversal of error integral
344-
assert_eq!(pid2.next_control_output(-15.0, 1.0).output, -40.0);
446+
assert_eq!(pid_next_control_output!(pid2, -15.0, 1.0).output, -40.0);
345447
}
346448

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

353-
let out = pid.next_control_output(0.0, 1.0);
455+
let out = pid_next_control_output!(pid, 0.0, 1.0);
354456
assert_eq!(out.p, 10.0); // 1.0 * 10.0
355457
assert_eq!(out.output, 1.0);
356458

357-
let out = pid.next_control_output(20.0, 1.0);
459+
let out = pid_next_control_output!(pid, 20.0, 1.0);
358460
assert_eq!(out.p, -10.0); // 1.0 * (10.0 - 20.0)
359461
assert_eq!(out.output, -1.0);
360462
}
@@ -365,25 +467,25 @@ mod tests {
365467
let mut pid = Pid::new(10.0, 100.0);
366468
pid.p(1.0, 100.0).i(0.1, 100.0).d(1.0, 100.0);
367469

368-
let out = pid.next_control_output(0.0, 1.0);
470+
let out = pid_next_control_output!(pid, 0.0, 1.0);
369471
assert_eq!(out.p, 10.0); // 1.0 * 10.0
370472
assert_eq!(out.i, 1.0); // 0.1 * 10.0
371473
assert_eq!(out.d, 0.0); // -(1.0 * 0.0)
372474
assert_eq!(out.output, 11.0);
373475

374-
let out = pid.next_control_output(5.0, 1.0);
476+
let out = pid_next_control_output!(pid, 5.0, 1.0);
375477
assert_eq!(out.p, 5.0); // 1.0 * 5.0
376478
assert_eq!(out.i, 1.5); // 0.1 * (10.0 + 5.0)
377479
assert_eq!(out.d, -5.0); // -(1.0 * 5.0)
378480
assert_eq!(out.output, 1.5);
379481

380-
let out = pid.next_control_output(11.0, 1.0);
482+
let out = pid_next_control_output!(pid, 11.0, 1.0);
381483
assert_eq!(out.p, -1.0); // 1.0 * -1.0
382484
assert_eq!(out.i, 1.4); // 0.1 * (10.0 + 5.0 - 1)
383485
assert_eq!(out.d, -6.0); // -(1.0 * 6.0)
384486
assert_eq!(out.output, -5.6);
385487

386-
let out = pid.next_control_output(10.0, 1.0);
488+
let out = pid_next_control_output!(pid, 10.0, 1.0);
387489
assert_eq!(out.p, 0.0); // 1.0 * 0.0
388490
assert_eq!(out.i, 1.4); // 0.1 * (10.0 + 5.0 - 1.0 + 0.0)
389491
assert_eq!(out.d, 1.0); // -(1.0 * -1.0)
@@ -402,8 +504,8 @@ mod tests {
402504

403505
for _ in 0..5 {
404506
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
507+
pid_next_control_output!(pid_f32, 0.0, 1.0).output,
508+
pid_next_control_output!(pid_f64, 0.0, 1.0).output as f32
407509
);
408510
}
409511
}
@@ -420,8 +522,8 @@ mod tests {
420522

421523
for _ in 0..5 {
422524
assert_eq!(
423-
pid_i32.next_control_output(0, 1).output,
424-
pid_i8.next_control_output(0i8, 1i8).output as i32
525+
pid_next_control_output!(pid_i32, 0, 1).output,
526+
pid_next_control_output!(pid_i8, 0i8, 1i8).output as i32
425527
);
426528
}
427529
}
@@ -432,7 +534,7 @@ mod tests {
432534
let mut pid = Pid::new(10.0, 100.0);
433535
pid.p(1.0, 100.0).i(0.1, 100.0).d(1.0, 100.0);
434536

435-
let out = pid.next_control_output(0.0, 1.0);
537+
let out = pid_next_control_output!(pid, 0.0, 1.0);
436538
assert_eq!(out.p, 10.0); // 1.0 * 10.0
437539
assert_eq!(out.i, 1.0); // 0.1 * 10.0
438540
assert_eq!(out.d, 0.0); // -(1.0 * 0.0)
@@ -441,7 +543,7 @@ mod tests {
441543
pid.setpoint(0.0);
442544

443545
assert_eq!(
444-
pid.next_control_output(0.0, 1.0),
546+
pid_next_control_output!(pid, 0.0, 1.0),
445547
ControlOutput {
446548
p: 0.0,
447549
i: 1.0,
@@ -457,7 +559,7 @@ mod tests {
457559
let mut pid = Pid::new(10.0f32, -10.0);
458560
pid.p(1.0, -50.0).i(1.0, -50.0).d(1.0, -50.0);
459561

460-
let out = pid.next_control_output(0.0, 1.0);
562+
let out = pid_next_control_output!(pid, 0.0, 1.0);
461563
assert_eq!(out.p, 10.0);
462564
assert_eq!(out.i, 10.0);
463565
assert_eq!(out.d, 0.0);

0 commit comments

Comments
 (0)