PID: the 80% of industrial control
The oldest controller in the book and still the most used. How it works, how to tune it, and when it's the wrong tool.
More than 80% of deployed industrial controllers are still PID. It's not because engineers are lazy — it's because PID is genuinely the right answer for a surprising number of problems. Here's the mental model, the math, and the tuning procedure that gets you a working controller in an hour.
The scene
You have a system with a setpoint (target) and a measurement. You want the output (a motor command, heater power, valve position) to drive the measurement to the setpoint. PID does that with three ingredients.
The equation
e(t) = setpoint - measurement # the error
u(t) = Kp · e(t) # P: proportional
+ Ki · ∫ e(τ) dτ # I: integral
+ Kd · de/dt # D: derivative
Three gains. Three terms. That's the entire controller.
What each term actually does
P (proportional): fixes the now
"The bigger the error, the harder I push." A P-only controller is a linear spring: it pulls harder when further from the target. Downside: it always leaves a small steady-state error, because if the error goes to zero so does the control output — and the moment gravity or friction pushes back, you need some output to hold position.
I (integral): fixes the past
"I've been off for a while — keep increasing my push." The integral accumulates error over time. It eliminates steady-state error by ratcheting up command until the error actually goes to zero.
Downside: integral windup. If the actuator saturates and the error lingers, the integral balloons; when the error finally clears, the controller overshoots massively. Fix: cap the integral (integral clamping) or stop accumulating while the actuator is saturated (conditional integration).
D (derivative): fixes the future
"The error is decreasing fast — ease off before we overshoot." D is a damping term. It looks at the slope of the error and counteracts fast changes.
Downside: D amplifies noise. The derivative of a noisy signal is a much noisier signal. Always filter the measurement before differentiating, or compute the derivative of the measurement rather than the error (derivative-on-measurement).
A minimal Python implementation
class PID:
def __init__(self, kp, ki, kd, integral_limit=10.0):
self.kp, self.ki, self.kd = kp, ki, kd
self.integral_limit = integral_limit
self.integral = 0.0
self.prev_err = 0.0
def step(self, setpoint, measurement, dt):
err = setpoint - measurement
self.integral = max(-self.integral_limit,
min(self.integral_limit,
self.integral + err * dt))
deriv = (err - self.prev_err) / dt
self.prev_err = err
return self.kp * err + self.ki * self.integral + self.kd * deriv
That's a working PID. Twenty lines. In production you'd add output saturation, derivative-on-measurement, and anti-windup — but this gets you a running controller.
Tuning: the Ziegler-Nichols crank
The classic tuning method. Not the best tuning, but the fastest way to a working controller.
- Set Ki = 0, Kd = 0.
- Increase Kp until the output oscillates continuously with constant amplitude. Call that gain Ku and the oscillation period Tu.
- Set Kp = 0.6·Ku, Ki = 1.2·Ku/Tu, Kd = 0.075·Ku·Tu.
Done. Your controller is now stable, with modest overshoot and reasonable response time. You can squeeze more performance later.
When PID is the wrong tool
- Multi-input, multi-output systems. A quadrotor has three coupled rotational axes. You can stack three independent PIDs (and this is how quadcopters fly), but performance is limited. Consider LQR or MPC.
- Strong nonlinearities. A robot arm where gravity torque depends on configuration. PID works per joint, but compensating gravity explicitly via computed-torque control does better.
- Hard constraints. PID can't natively respect "joint must stay under 180°" or "motor current must stay under 5A." That's MPC's home turf.
- Very fast transients with known dynamics. Feedforward (not feedback) is the right tool when you know what's coming.
When PID is exactly the right tool
- Single-input, single-output. Motor speed. Heater temperature. Pressure regulator.
- Plant dynamics are roughly linear near the operating point.
- You don't need to squeeze the last 10% of performance.
- You need the controller to be trivially debuggable by anyone.
Real-robot gotchas
- Discrete time. All real PID controllers run at a fixed rate (100 Hz, 1 kHz). The math above is continuous. Your derivative term is actually a finite difference, and that's where noise lives. Filter.
- Sample rate. Control loop at 10× the bandwidth of the thing you're controlling, minimum. 100 Hz for mechanical systems. 1 kHz+ for direct-drive motors.
- Actuator saturation. Every motor has a max torque. Your controller output has to respect it, and your integral has to not wind up while it does.
- Units, always units. Kp has units of "output per error." If error is in radians and output is in volts, Kp is V/rad. Nothing will tell you when you mix them.
Exercise
Simulate a cart with mass 1 kg, Force = PID output, target position = 1.0 m. Start with Kp = 1, Ki = 0, Kd = 0 and watch the cart oscillate. Crank Kp until it oscillates at constant amplitude. Record Ku and Tu. Apply Ziegler-Nichols. Compare with PID-free (open-loop) behavior.
This exercise in a Jupyter notebook takes 30 minutes and teaches you more about control than a week of reading. Next lesson: LQR, the principled generalization of PID for multi-state systems.
Comments
Sign in to post a comment.