Robotics, Control and Autonomy

PID control, and when to use it instead

A PID controller pushes a system toward a target using three simple signals — how far off it is, how long it has been off, and how fast that is changing.

Read these first

On this page 7
  1. Why it exists
  2. How it works
  3. Where you have already seen it
  4. When PID is not enough
  5. An honest note
  6. Remember this
  7. What to learn next

One lesson, three depths. Pick the one that fits you today — you can switch any time.

Beginner — No maths. Plain English.

A PID controller steers a system toward a target using three simple signals, added together.

Think about an old water heater with a slow, laggy knob. Turn it a little when the water is only slightly cold. Turn it a lot when it is freezing. If it has stayed cold for a while despite your last turn, you nudge the knob further still. And if the water starts heating up fast, you ease off early, before it scalds you.

Without knowing it, you ran a PID controller — Proportional, Integral, Derivative. Three habits, combined.

Why it exists

Before feedback control, machines ran open-loop: do a fixed thing, and hope. A heater set to "medium" stays at medium whether the room is warm or freezing, with no way to correct itself.

PID fixes this using only what a machine can measure right now: the gap between where it is and where it should be. It does not need a full physics model of the system it is steering. That is a large part of why it has stayed useful for a century. It is an old, unglamorous idea, and a huge share of industrial control loops still run on it today.

How it works

target: 40°C          current: 25°C          error = 15°C
                                                  |
        P: turn the knob a lot RIGHT NOW    (big error, big response)
        I: error has lingered for a while,  nudge further still
        D: temperature is rising fast,      ease off before it overshoots
                                                  |
                                                  v
                                    knob position (the control output)

Each of the three terms answers a different question. P asks "how wrong am I right now?" I asks "how wrong have I been, added up over time?" D asks "how fast is that wrongness changing?" Add the three together, and you get the next control signal.

Where you have already seen it

  • A car's cruise control, holding speed steady up and down small hills.
  • A room air conditioner's thermostat, cycling to hold temperature.
  • A drone's altitude hold, fighting gusts of wind many times a second.
  • A 3D printer's hotend, holding a precise melting temperature.

When PID is not enough

PID assumes a system that responds to one dial with roughly one, fairly steady, effect. A humanoid robot balancing on two legs has dozens of joints that all affect each other at once. Moving one arm shifts the load on every other joint. A robot arm's effective mass even changes with its own pose. These are exactly the cases where classical control hits a wall. Later lessons in this section — imitation learning, and vision-language-action models — take over from here.

An honest note

A PID controller with badly chosen gains does not fail quietly. It oscillates, and on a real machine that means visible shaking, a drone that won't hold altitude, or worse. Every real robot's control gains are tuned on a bench, tested under load, and reviewed before that robot ever operates near a person. Nothing in this lesson is tuned to that standard.

Remember this

  • PID reacts to the current error, its history, and its rate of change — nothing more.
  • It needs no model of the machine's physics, which is why it spread everywhere.
  • It struggles once a system has many interacting parts, or changes its own behaviour as it moves.

What to learn next

Developer — Code and libraries.

Setup

No installs needed — the whole example is plain Python.

Minimal runnable code

A simulated heater with real heat loss to the room, so a controller that ignores accumulated error settles below target and stays there.

pid_heater.py
def simulate(kp, ki, kd, steps=150, dt=0.1, target=40.0, start=20.0, ambient=18.0):
    temp = start
    heat_rate = 0.6   # how much one unit of power raises temperature per second
    loss_rate = 0.05  # how fast heat leaks back out to the room
    integral = 0.0
    prev_error = target - temp
    for _ in range(steps):
        error = target - temp
        integral += error * dt
        derivative = (error - prev_error) / dt
        power = kp * error + ki * integral + kd * derivative
        power = max(0, min(100, power))  # a real heater can't cool, and has a max power
        temp += (heat_rate * power - loss_rate * (temp - ambient)) * dt
        prev_error = error
    return temp

p_only = simulate(kp=4.0, ki=0.0, kd=0.0)
full_pid = simulate(kp=4.0, ki=0.5, kd=0.8)

print(f"P-only  -> settles at {p_only:.2f}C   (target was 40.00C)")
print(f"PID     -> settles at {full_pid:.2f}C   (target was 40.00C)")
Output
P-only  -> settles at 39.55C   (target was 40.00C)
PID     -> settles at 40.18C   (target was 40.00C)

What actually happened

P-only never reaches 40°C. It settles at the exact power level where heat gained equals heat lost to the room — 0.45°C short of target, forever. Running it for ten times as long changes nothing; this is not slowness, it is a genuine steady-state error built into proportional-only control on a system with loss.

Adding the I term fixes it. Accumulated past error keeps nudging the output up even after the instantaneous error shrinks, until the offset is gone. The small overshoot to 40.18°C is the price paid for that. ki and kd need balancing against each other — that balancing act is what "tuning" a PID controller means in practice.

Common mistakes

Forgetting integral windup. If the target is unreachable for a while — a heater fighting an open window — integral grows without bound. The controller then overshoots hugely once the disturbance clears. Real controllers clamp the integral term to prevent this.

Tuning derivative on noisy sensors. derivative is computed from the change in error, so it amplifies sensor noise. A jittery raw sensor reading produces a wildly jittery D term unless it is filtered first.

One set of gains for every operating point. The kp=4.0 used here might be too aggressive for a small error and too weak for a large one, on a real nonlinear machine. This is a genuinely common source of production tuning bugs, not an edge case.

Try it yourself

Set kd = 0.0 while keeping ki = 0.5. Compare the overshoot to the version above. The derivative term exists mainly to fight overshoot — remove it, and watch the peak temperature rise before it settles.

What to learn next

Researcher — Mathematics and papers.

The control law

u(t) = Kp * e(t)  +  Ki * INTEGRAL_0^t e(tau) dtau  +  Kd * d/dt e(t)

e(t) = r(t) - y(t) is the error between reference r(t) and measured output y(t). u(t) is the control signal applied to the plant. Kp, Ki, Kd are the three tuning gains.

Why proportional-only leaves an offset

For a first-order plant with a constant disturbance d — heat loss, gravity, friction — the closed-loop steady state under P-only control satisfies:

Kp * (r - y_ss) = d  /  gain_of_plant

Since Kp is finite, closing this equation requires y_ss != r whenever d != 0. The I term removes this by definition: at steady state, a nonzero constant error would make the integral grow without bound, which is inconsistent with a steady state at all. So the only steady state with integral action is e = 0.

Frequency-domain view and tuning

In the Laplace domain, the PID transfer function is:

C(s) = Kp + Ki/s + Kd*s

This adds a pole at the origin (from Ki/s, which forces zero steady-state error to a step) and a zero (from the combination with Kd*s, which improves phase margin and damps overshoot). Classical tuning methods — Ziegler-Nichols (1942) being the oldest — pick gains from the plant's open-loop step response or its critical oscillation point, targeting a specified gain and phase margin for stability robustness.

Complexity and cost

PID is O(1) per control step: three multiplications, one running sum, one finite difference. This is a large part of why it runs on microcontrollers costing a few dollars, at kilohertz update rates, decades after more expressive alternatives existed.

Limits, and what replaces it

PID controls one output from one error signal. Systems with strongly coupled multi-input, multi-output dynamics — a legged robot, a multi-joint arm near its dynamic limits — need either:

  • Model Predictive Control (MPC). Optimises a sequence of future control actions against an explicit dynamics model, subject to constraints, re-solved every step. Handles coupling and constraints that PID cannot express, at real computational cost.
  • Learned control policies. A neural network trained by reinforcement learning or imitation learning, covered later in this section, can capture nonlinear, coupled dynamics that would need an intractable number of hand-tuned PID loops to approximate.

PID and these more complex approaches are not always alternatives. Cascaded architectures often use MPC or a learned policy at an outer loop, generating setpoints for the inner loop. That inner loop stays fast, cheap, well-understood PID, close to the actuators.

Key references

  • Åström, K. J., Hägglund, T. (2006). Advanced PID Control. ISA — the standard modern reference, including anti-windup and tuning.
  • Ziegler, J. G., Nichols, N. B. (1942). Optimum Settings for Automatic Controllers. Transactions of the ASME.
  • Camacho, E. F., Bordons, C. (2013). Model Predictive Control. Springer.

What to learn next