PID Control Theory
Embedded → Closed-Loop Control + PID on Embedded
Why closed-loop control, and why PID
Open-loop control means you tell an actuator what to do and hope — set a heater to 60% duty cycle and walk away, and if the room is cold or the heating element ages, the temperature drifts uncorrected. Closed-loop control measures the actual output, compares it to a setpoint, and continuously adjusts the actuator based on the error between the two. PID (Proportional-Integral-Derivative) is the most common way to turn that error into a correction — simple, well understood, tunable without a control-theory degree, and good enough for most real embedded control problems (motor speed, temperature, position loops).
The three terms, in plain terms
At every control-loop tick, you compute one number: error = setpoint − measurement. PID turns that single number into an actuator command using three independent terms, added together:
| Term | Reacts to | Effect |
|---|---|---|
| Proportional (P) | current error | bigger error, bigger correction now — alone it always leaves some steady-state error (settles just short of setpoint) |
| Integral (I) | accumulated error over time | keeps growing while error persists, driving it to zero — eliminates P's offset, but too much gain causes overshoot/oscillation |
| Derivative (D) | rate of change of error | pulls the output back before P+I overshoot — damps oscillation, but is the most noise-sensitive term |
From the continuous formula to code you can actually run
The textbook continuous-time PID equation integrates and differentiates over continuous time — not something a microcontroller sampling at a fixed rate can compute directly. Every real embedded PID runs the discrete-time difference-equation form instead, evaluated once per fixed sample interval Ts:
// One control-loop tick, called every Ts seconds (e.g. every 10 ms) error = setpoint - measurement; integral += error * Ts; // accumulate area under error(t) derivative = (error - prev_error) / Ts; // rate of change of error output = Kp * error + Ki * integral + Kd * derivative; prev_error = error;
Kp, Ki, Kdare the three tuning gains — the whole job of “tuning a PID loop” is finding values for these three numbers that give a fast, stable, non-oscillating response for your specific plant (motor, heater, whatever you're controlling).
Integral windup and output clamping
A common bug in a first PID implementation: the integral term keeps accumulating even while the actuator output is already saturated (e.g. PWM duty cycle pinned at 100%). Once the error finally starts shrinking, the accumulated integral is enormous and takes a long time to unwind, causing a large, slow overshoot. This is integral windup, fixed by anti-windup clamping: clamp the output to the actuator's real range, and stop accumulating the integral once the output is already saturated in the direction that would make it worse.
output = Kp * error + Ki * integral + Kd * derivative;
if (output > OUT_MAX) {
output = OUT_MAX;
if (error > 0) integral -= error * Ts; // undo this tick's windup
} else if (output < OUT_MIN) {
output = OUT_MIN;
if (error < 0) integral -= error * Ts;
}(The exact clamping scheme varies — clamp-and-hold, back-calculation, conditional integration — but the principle is the same in all of them: never let the integral term keep growing past what the actuator can physically deliver. The sibling “Discrete PID in C” lesson goes deeper into a complete, ship-able anti-windup implementation.)
Try it — tune P, I, D and watch the step response
A simulated first-order plant (think: a motor spinning up, or a heater warming a room) is given a step setpoint. Drag the three gain sliders and watch how the response changes — this is the same intuition you build by tuning a real loop, just without needing real hardware:
Practice
pms67/PID(linked in this topic's hands-on resource) and integrate it into a task triggered every 10 ms. Feed it a step setpoint and log the output — first with only P active (Ki = Kd = 0), then add I, then add D, confirming for yourself that each term changes the response in exactly the way described above before moving on to the discrete-PID-in-C lesson.Completion saves automatically after you read through to the end.
Next lesson →