Book contents
Contents
raw Math

Introduction to PID Controllers

Robert Eisele

A systematic introduction to proportional, integral, derivative, and combined PID control. Separate response diagrams show how each term reacts to error, while the continuous control law connects present, accumulated, and changing error. The sampled-time derivation introduces the sampling period, accumulated error state, and backward-difference derivative before moving to tuning, windup, derivative filtering, thermostat and steering examples, and a JavaScript implementation.

Read the story

A Proportional-Integral-Derivative (PID) controller is a control system mechanism that continuously calculates an error value as the difference between a desired setpoint and a measured process variable. It then applies corrections based on proportional, integral, and derivative terms, each multiplied by a tunable coefficient. PID controllers are crucial in systems where precise control is required, such as in robotics, industrial automation, and temperature regulation.

The key benefit of a PID controller is its ability to provide smooth, stable control by responding to the current error, the accumulation of past errors, and the rate of change of the error, ensuring that the system reaches and maintains the desired setpoint efficiently.

Continuous-Time PID Controller

Let \(r(t)\) be the setpoint and \(y(t)\) the measured process variable. Their signed difference

\[ e(t)=r(t)-y(t) \]

is the control error. A positive error asks the controller to move the process in the positive direction under this sign convention. The actuator command \(u(t)\) is built from three different views of that error: its present value, its accumulated history, and its current rate of change.

Proportional Controller

Proportional control reacts directly to the error that exists now:

\[ \boxed{u_P(t)=K_p\,e(t)}. \]

A step in the error therefore causes an immediate proportional step in the output:

e(t) uP(t) Kp

Increasing \(K_p\) usually makes the response faster and reduces the remaining error, but excessive proportional gain can produce overshoot or sustained oscillation. A proportional controller alone may retain a steady-state error when the plant needs a nonzero command merely to hold the setpoint.

Integral Controller

Integral control accumulates error over time:

\[ \boxed{u_I(t)=K_i\int_0^t e(t')\,dt'}. \]

A constant positive error makes the accumulated area, and therefore the integral output, rise as a ramp:

e(t) uI(t) Ki ∫

This term can eliminate steady-state error, but an actuator limit can let the integral continue growing while the requested command cannot be delivered. This integral windup causes overshoot and slow recovery. Practical controllers therefore clamp the integrator or feed the saturated output difference back into it.

Derivative Controller

Derivative control reacts to how rapidly the error changes:

\[ \boxed{u_D(t)=K_d\frac{de(t)}{dt}}. \]

An ideal step has an instantaneous derivative, so the corresponding derivative output is a narrow impulse:

e(t) uD(t) Kd d/dt

The derivative term anticipates motion and adds damping, but it also amplifies measurement noise. Real controllers use a filtered derivative. They often differentiate the measured process variable rather than the complete error so that a setpoint step does not create a large derivative kick.

Combining the Three Terms

The parallel PID controller adds the three contributions:

\[ \boxed{u(t) = K_p\,e(t) + K_i\int_{0}^{t} e(t')\,dt' + K_d\frac{d e(t)}{dt}}. \]

e(t) Kp e(t) Ki ∫ e(t') dt' Kd de(t)/dt + u(t) Problem

Proportional action addresses the present error, integral action addresses persistent past error, and derivative action addresses its trend. The three gains are not independent performance sliders: changing one term alters the closed-loop dynamics seen by the others.

PD Controller as a Practical Starting Point

In many practical stabilization tasks, a PD controller is already sufficient:

\[ u(t)=K_p\,e(t)+K_d\,\frac{de(t)}{dt}. \]

The proportional term reacts to the current error, while the derivative term adds damping by reacting to how fast the error changes. This combination is common in attitude stabilization, camera gimbals, and tracking controllers where integral action is not strictly required.

Discrete-Time PID Controller

In discrete-time systems, where control actions are computed at discrete and regular intervals (sampling period \( T_A=\Delta t \)), let \(e_k=e[k]\) be the current error, \(e_{k-1}\) the previous error, and \(e_{\Sigma,k}\) the accumulated error samples. A rectangular approximation gives the state update

\[ e_{\Sigma,k}=e_{\Sigma,k-1}+e_k. \]

The three digital contributions are then

\[ \boxed{P_k=K_p\,e_k}, \qquad \boxed{I_k=K_i\,T_A\,e_{\Sigma,k}}, \qquad \boxed{D_k=K_d\frac{e_k-e_{k-1}}{T_A}}. \]

Thus \(u_k=P_k+I_k+D_k\). This is the explicit digital form of the three equations: \(T_A\) is the sampling period, \(e_{\Sigma,k}\) stores the sum of all error samples through step \(k\), and the previous error is the one sample of memory required by the backward-difference derivative.

Equivalently, using array notation:

  1. Proportional Term: \[ P[k] = K_p \cdot e[k] \] where \( e[k] \) is the error at the \( k \)th time step.

  2. Integral Term: The integral is approximated by summing the errors over time: \[ I[k] = K_i \cdot\sum_{i=1}^{k} e[i] \cdot \Delta t \] where \( \Delta t \) is the time between steps.

  3. Derivative Term: The derivative is the change in error by the change in time: \[ D[k] = K_d \cdot \frac{e[k] - e[k-1]}{\Delta t} \]

The discrete control signal at step \( k \) is thus:

\[ u[k] = K_p \cdot e[k] + K_i \sum_{i=1}^{k} e[i] \cdot \Delta t + K_d \cdot \frac{e[k] - e[k-1]}{\Delta t} \]

Since \( \Delta t \) is constant in most cases, it can be incorporated into the coefficients for simplification:

\[ u[k] = K_p \cdot e[k] + K_i' \cdot \sum_{i=1}^{k} e[i] + K_d' \cdot (e[k] - e[k-1]) \]

where \( K_i' = K_i \cdot \Delta t \) and \( K_d' = \frac{K_d}{\Delta t} \) are tuning parameters that include constant \(\Delta t\).

Tuning the PID Parameters

To effectively tune a PID controller:

  1. Start with \( K_p \): Set \( K_p = 1 \) with \( K_i = 0 \) and \( K_d = 0 \). Adjust \( K_p \) to achieve a basic level of control where the system responds to errors.
  2. Introduce \( K_d \): Increase \( K_d \) to reduce oscillations and stabilize the system.
  3. Adjust \( K_i \): Finally, increase \( K_i \) to eliminate any steady-state errors.

Example: Thermostat

Suppose a thermostat is set to maintain a room at \( r[k] = 22^\circ \text{C} \). At time step \( k \), the current temperature is \( y[k] = 18^\circ \text{C} \). The error at this time step is:

\[ e[k] = r[k] - y[k] = 22 - 18 = 4^\circ \text{C} \]

The control input for the heater, \( u[k] \), is calculated using:

\[ u[k] = K_p \cdot e[k] + K_i \cdot \sum_{i=1}^{k} e[i] \cdot \Delta t + K_d \cdot \frac{e[k] - e[k-1]}{\Delta t} \]

where:

Example: Car Steering

Consider a car that needs to follow a line set at \( r[k] = 5 \) units from a reference point. If the car’s current position is \( y[k] = 3 \) units at time step \( k \), the error is:

\[ e[k] = r[k] - y[k] = 5 - 3 = 2 \text{ units} \]

The steering angle adjustment, \( u[k] \), is calculated as:

\[ u[k] = K_p \cdot e[k] + K_i \cdot \sum_{i=1}^{k} e[i] \cdot \Delta t + K_d \cdot \frac{e[k] - e[k-1]}{\Delta t} \]

where:

JavaScript Implementation

A small reusable step function is easier to maintain than a manual infinite loop. It keeps the state local, updates the integral and derivative terms in one place, and can be scheduled at the chosen sampling interval.

// PID parameters
const Kp = 1.0;
const Ki = 0.1;
const Kd = 0.05;
const dt = 0.1; // Sampling period in seconds

function createPidController(getGoal, getActual, logOutput = console.log) {
  let previousError = 0;
  let integral = 0;

  return function step() {
    const setpoint = getGoal();
    const actual = getActual();
    const error = setpoint - actual;

    integral += error * dt;
    const derivative = (error - previousError) / dt;

    const output = Kp * error + Ki * integral + Kd * derivative;
    logOutput(output);

    previousError = error;
    return output;
  };
}

const step = createPidController(getGoal, getActual);
setInterval(step, dt * 1000);