Stage 3 of 6 · Theory · about 40 minutes
P, I, D
The PID controller is one equation with three terms, and each term answers a different failure of the one before it. This page builds the law term by term, with the math that explains each behaviour and computed response curves from a simulated bench plant, the same shapes you will produce live in the lab.
One law, three tenses
Every cycle, the controller computes the error and responds with a weighted sum of three views of it:
e(t) = SP − PV(t)
u(t) = Kp·e(t) + Ki·∫e(τ) dτ + Kd·de/dt
P: proportional, the present
Proportional control is bang-bang grown up: instead of all-or-nothing, push in proportion to the error, u = Kp·e. Far from target: push hard. Close: ease off. The gain Kp sets the temperament, and the computed sweep shows the whole personality range on one plant:
Low gain is safe and sleepy, and stops visibly short of the setpoint. Raise the gain and the loop gets faster and the miss smaller, until, past a point, the corrections start arriving too hard for the loop's delay and the response rings, then oscillates outright. Two facts to carry out of this figure: gain trades speed against stability (last page's warning, now visible), and no gain reaches the setpoint exactly. That second fact is not bad luck; it is provable.
Why P alone always misses: the math
The steady-state offset, derived
At rest, our plant's light level is the LED's contribution plus ambient: PV = K·u + d, with K the plant gain and d the ambient light. The controller holds u = Kp·e. Substitute into e = SP − PV:
e = SP − (K·Kp·e + d) ⇒ e·(1 + K·Kp) = SP − d ⇒ ess = (SP − d) / (1 + K·Kp)
Numbers: SP = 50 %, ambient d = 20 %, plant gain K = 1.2. Then Kp = 1 leaves ess ≈ 13.6 %; Kp = 5 leaves ≈ 4.3 %; Kp = 20 leaves ≈ 1.2 %, if the loop still stood at 20, which it will not. The offset shrinks with gain but the denominator never makes it zero.
The intuition under the algebra: holding the light at 50 % needs a standing, non-zero duty. P-only can produce output only from error, so zero error would mean zero output, the LED off, and the light falling, contradiction. The loop therefore parks exactly where the leftover error, times the gain, equals the duty the plant needs. To hold a setpoint with no error, the controller needs a term that can produce output without present error. It needs memory.
I: integral, the past
The integral term accumulates the error over time, Ki·∫e dτ, and its logic kills the offset by construction: as long as any error persists, the accumulator keeps growing, so u keeps creeping, so PV keeps moving toward SP. The only value of PV at which the controller stops adjusting is PV = SP exactly. The integral then holds, at precisely the standing duty the plant needs, output with zero present error, which is the thing P could not do.
The price of the new power is twofold. The accumulated push takes time to build and time to unwind, so integral action adds sluggishness and, overdone, its own slow overshoot: the accumulator keeps pushing after PV reaches SP, because it is still unwinding history. And when the actuator saturates, duty pinned at 100 % while the error persists, the accumulator inflates toward infinity with nothing to show for it, then takes seconds to deflate after the error reverses: integral windup, the classic PID disease. The build page provokes it on purpose and installs the cure.
D: derivative, the future
P and I both react to error that already exists. The derivative term reads the error's slope, Kd·de/dt, and pushes against change: when PV is racing toward the setpoint, the error is collapsing fast, the derivative is large and negative, and the term brakes before arrival. It is anticipation, and its visible effect is damping, the same rise with the overshoot shaved off:
D's price is written in its own calculus. Feed it a clean sine of measurement noise, PVnoise = A·sin(2πf t), and differentiate:
d/dt [A·sin(2πf t)] = 2πf·A·cos(2πf t)
The derivative multiplies amplitude by 2πf: a wiggle too small to see at 10 Hz comes out of the D term thirty times larger, and faster noise is amplified more, in proportion to its frequency. The derivative is a high-frequency amplifier pointed at your noisiest signal, which is why real controllers pair D with smoothing (your Week 8 read_avg earns a new job) and why many industrial loops run PI only. One refinement the build page implements: differentiate the measurement (−dPV/dt) instead of the error, which is identical between setpoint changes but avoids the one-cycle output spike ("derivative kick") when SP steps.
From calculus to code
Your loop ticks at a measured interval Δt, so the calculus becomes arithmetic, Week 5's trace-table style:
Ik = Ik−1 + ek·Δt (a running sum: the area under the error)
Dk = (ek − ek−1) / Δt (a difference: the slope since last tick)
uk = Kp·ek + Ki·Ik + Kd·Dk
Both discrete forms divide or multiply by Δt, which is why the build page insists on measuring the interval each cycle rather than assuming the loop ran on time: a lying Δt scales I and D by the size of the lie. This is Week 5's monotonic-clock rule promoted from etiquette to correctness.
What the gains mean
| Gain | Units (our loop) | Reads as | Raising it |
|---|---|---|---|
| Kp | % duty per % error | Stiffness: push per unit of gap | Faster, smaller offset, then ringing |
| Ki | per second | Impatience: how fast persistent error converts to push | Offset dies sooner; overdone, slow overshoot and windup risk |
| Kd | seconds | Caution: how far ahead the brake looks | Less overshoot; overdone, noise jitter rules the output |