Struct Pid

Source
pub struct Pid { /* private fields */ }
Expand description

PID controller.

The PID controller is one of the most widely used feedback control algorithms in industrial control systems, robotics, and process control. It computes a control signal based on three terms: proportional, integral, and derivative.

§Components

  • The proportional term produces an output proportional to the current error, multiplied by the gain constant kp. This provides the main control action: larger errors result in larger corrective responses and vice versa.

  • The integral term sums the error over time, multiplied by the gain constant ki. This term eliminates steady-state error by ensuring that even small errors will eventually accumulate a large enough response to reach the setpoint. Without integral action, a system might stabilize with a small, persistent error if the control signal becomes too small to overcome system friction, gravity, or other external factors on the system. For example, a motor might need some minimum voltage to start moving, or a drone might need extra thrust to hover against gravity. The integral term accumulates over time to provide this additional correction.

  • The derivative term measures the error’s change over time, multiplied by the gain constant kd. This provides a damping effect that reduces overshoot and oscillation by counteracting rapid changes in error. The derivative term helps anticipate and smooth out the system’s response, preventing sudden changes resulting from large proportional or integral gains.

§Tuning

Tuning a PID controller requires adjusting kp, ki, and kd to allow the system to reach a setpoint in a reasonable amount of time without oscillations (rapid, unpredictable changes in output).

Tuning methods are typically dependent on the application that the PID controller is used in, but a common method is as follows:

  1. Start with all gains at zero (kp = 0.0, ki = 0.0, kd = 0.0).

  2. Tune proportional gain first:

    • Gradually increase kp until the system starts to oscillate around the setpoint.
    • Oscillation occurs when the system reaches and overshoots the setpoint, then repeatedly overadjusts itself around the setpoint, resulting in a “back-and-fourth” motion around the setpoint.
  3. Tune the derivative gain:

    • Start with a very small kd gain (0.05 × kp or less is a safe bet to start with).
    • Gradually increase by small increments until oscillations from the proportional term stop occurring.
  4. Add integral gain if necessary:

    • Integral gain is only necessary if your controller’s proportional and derivative terms become small enough to where they can no longer overcome some external factor (such as friction) of the system, resulting in what’s called steady-state error.
    • Start with a very small ki gain (such as 0.01 × kp).
    • Increase ki slowly until steady-state errors are eliminated within an acceptable time.
    • If oscillation occurs, reduce both ki and kp slightly.

Common signs of poor tuning:

  • Slow response: kp is too low.
  • Excessive overshoot: kd is too low or ki is too high.
  • Oscillation: kp is too high or kd is too low.
  • Noisy, unpredictable response: kd is too high.

§Integral Windup (and Mitigations)

In some scenarios, a PID controller may be prone to integral windup, where a controlled system reaches a saturation point preventing the error from decreasing. In this case, integral will rapidly accumulate, causing large and unpredictable control signals. This specific implementation provides two mitigations for integral windup:

  1. Sign-based reset: When the sign of error changes (in other words, when the controller has crossed/overshot its target), the integral value is reset to prevent overshoot of the target.
  2. Integration bounds: An optional integration_range value can be passed to the controller, which defines a range of error where integration will occur. When |error| > integration_range, no integration will occur if used.

Implementations§

Source§

impl Pid

Source

pub const fn new( kp: f64, ki: f64, kd: f64, integration_range: Option<f64>, ) -> Self

Construct a new PID controller from gain constants and an optional integration range.

Source

pub const fn gains(&self) -> (f64, f64, f64)

Get the current PID gains as a tuple (kp, ki, kd).

Source

pub const fn kp(&self) -> f64

Returns the controller’s proportional gain (kp).

Source

pub const fn ki(&self) -> f64

Returns the controller’s integral gain (ki).

Source

pub const fn kd(&self) -> f64

Returns the controller’s derivative gain (kd).

Source

pub const fn integration_range(&self) -> Option<f64>

Returns the controller’s integration range.

Integration range is the minimum error range required to start integrating error. This is optionally applied to the controller as a mitigation for integral windup.

Source

pub const fn output_limit(&self) -> Option<f64>

Returns the controller’s output limit, or None if there is no limit applied.

Source

pub const fn set_gains(&mut self, kp: f64, ki: f64, kd: f64)

Sets the PID gains to provided values.

Source

pub const fn set_kp(&mut self, kp: f64)

Sets the controller’s proportional gain (kp).

Source

pub const fn set_ki(&mut self, ki: f64)

Sets the controller’s integral gain (ki).

Source

pub const fn set_kd(&mut self, kd: f64)

Sets the controller’s derivative gain (kd).

Source

pub const fn set_integration_range(&mut self, range: Option<f64>)

Sets the controller’s integration range.

Integration range is the minimum error range required to start integrating error. This is optionally applied to the controller as a mitigation for integral windup.

Source

pub const fn set_output_limit(&mut self, range: Option<f64>)

Sets the controller’s output limit.

This sets a maximum range for the controller’s output signal. It will effectively limit how fast the controller is able to drive the system, which may be desirable in some cases (e.g. limiting the maximum speed of a robot’s motion).

Trait Implementations§

Source§

impl Clone for Pid

Source§

fn clone(&self) -> Pid

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl ControlLoop for Pid

Source§

type Input = f64

The type of input measurements and setpoints that this controller takes.
Source§

type Output = f64

The type of output (control signal) this controller produces.
Source§

fn update(&mut self, measurement: f64, setpoint: f64, dt: Duration) -> f64

Updates the control loop with the latest measurement and setpoint for the system, producing a corresponding control signal.
Source§

impl Debug for Pid

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Pid

Source§

fn eq(&self, other: &Pid) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for Pid

Source§

impl StructuralPartialEq for Pid

Auto Trait Implementations§

§

impl Freeze for Pid

§

impl RefUnwindSafe for Pid

§

impl Send for Pid

§

impl Sync for Pid

§

impl Unpin for Pid

§

impl UnwindSafe for Pid

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.