Control: closing the loop
420-302-VA · WEEK 10 · FALL 2026

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

Three colleagues sharing one job. P, the present: output proportional to the error right now, with a sparkline showing a copy of the error. I, the past: output proportional to the accumulated area under the error, sparkline showing a rising sum. D, the future: output proportional to the error's slope, sparkline showing a spike at the moment of change. Their three outputs add into u. P · the present Kp·e(t) "How wrong are we right now? Push that hard." I · the past Ki·∫e dτ "How long have we been wrong? Keep leaning." D · the future Kd·de/dt "Which way is it heading? Brake before we arrive." u (sum)
Three views of one error. P answers the error's size, I its history, D its trend; the output is their sum. Each gain dials one colleague's voice up or down, and every classic loop misbehaviour is one voice too loud or too quiet.

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:

Three proportional-only responses toward a setpoint of 50. Low gain creeps up and settles far below the setpoint. Medium gain rises briskly and settles just below it with a little ringing. High gain overshoots and keeps ringing around the setpoint without settling.0255075100012345secondslight level (%)Kp = 0.6Kp = 2.5Kp = 8SP = 50
One knob, three personalities. The same plant under three proportional gains, u = Kp·e. Low gain (gray) is stable, slow, and parks far short; medium (green) is quick with a small miss and a ripple or two; high (copper) crosses the stability edge the last page warned about and rings on. Notice what no gain does: land on the setpoint and stay, the offset the next section proves.

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.

Two responses toward a setpoint of 50. The P-only curve settles visibly below the setpoint with the remaining steady-state error marked by an arrow. The PI curve rises the same way, then closes the gap completely and rests on the setpoint.02550751000123456secondslight level (%)SP = 50ess = 10 %P only (Kp = 1.6)PI (Kp = 1.6, Ki = 2)
The integral finishes what proportional starts. Same Kp in both runs. P alone (gray) settles where the formula says it must, with the marked error standing forever. Add integral action (green) and the accumulator keeps nudging until the gap is gone, then holds the standing duty out of its stored history, output with zero present error, which is exactly what P could not produce.

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:

Two responses to a setpoint step from 20 to 60. The PI curve rises fast, overshoots past the setpoint and rings before settling. The PID curve with the same P and I gains plus a derivative term rises nearly as fast but rounds off early and settles with far less overshoot.02550751000123456secondslight level (%)SP = 60PI: speed paid in overshootPID: same gains + Kd = 0.18, braked
D is the brake applied before arrival. Identical Kp and Ki; the green run adds a modest Kd. As PV races at the setpoint, the error's collapse makes the derivative large and opposing, easing the drive before the crossing, so the same speed arrives without the excursion. This damping is D's whole case, bought at the noise price the next section puts numbers on.

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

GainUnits (our loop)Reads asRaising it
Kp% duty per % errorStiffness: push per unit of gapFaster, smaller offset, then ringing
Kiper secondImpatience: how fast persistent error converts to pushOffset dies sooner; overdone, slow overshoot and windup risk
KdsecondsCaution: how far ahead the brake looksLess overshoot; overdone, noise jitter rules the output

Checklist for this stage

Check yourself

Trace it: SP = 50, Kp = 2, Ki = 0.5, Kd = 0, Δt = 0.1 s, I₀ = 0. PV reads 40, 44, 47 on three ticks. Compute u each tick.
Errors: 10, 6, 3. Integral: 0 + 10·0.1 = 1.0; then 1.0 + 0.6 = 1.6; then 1.6 + 0.3 = 1.9. Outputs: u₁ = 2·10 + 0.5·1.0 = 20.5; u₂ = 2·6 + 0.5·1.6 = 12.8; u₃ = 2·3 + 0.5·1.9 = 6.95. The P share collapses as the gap closes while the I share quietly grows: the handover that ends with I holding the fort alone.
Ambient light rises from 20 % to 35 % (a lamp turns on). What happens to a P-only loop's offset, per the formula?
ess = (SP − d)/(1 + K·Kp): d jumped, so the numerator shrank and the loop now parks closer to SP from above, with the LED dimmer. The offset's size depends on the disturbance, which is exactly why "just calibrate the offset away" fails: it moves. Only integral action tracks it to zero wherever d sits.
Why is PV = SP the only place a PI loop can come to rest?
Rest means u stops changing. P's contribution is frozen whenever e is constant, but I's contribution changes every tick that e ≠ 0, by Ki·e·Δt. So constancy of u forces e = 0; any other equilibrium contradicts the accumulator's definition. The integral then supplies the standing output from its stored history.
Measurement flicker: amplitude 0.3 % at 25 Hz. With Kd = 0.1 s, how big is the D term's jitter, and what would averaging that halves the flicker do?
Derivative amplitude = 2πf·A = 2π·25·0.3 ≈ 47 %/s; times Kd = 0.1 gives ±4.7 % of duty, visible shimmer from invisible noise. Halving A halves it linearly, ±2.4 %. The 2πf factor is why smoothing before D, and modest Kd, are both standard.
Derivative on error vs on measurement: when do they differ, and which do we ship?
de/dt = d(SP − PV)/dt = −dPV/dt whenever SP is constant, identical. They differ only at a setpoint step, where de/dt contains a one-tick spike (the "kick") that slams the actuator for no physical reason. Differentiating −PV skips the spike and is what the build page implements.
A classmate's loop uses sleep(0.1) and assumes Δt = 0.1 exactly. The node also publishes and checks messages each cycle. What corrupts, and how?
Network work makes real cycles run long and unevenly, so the true Δt wanders above 0.1 s. The integral under-accumulates relative to real elapsed time and the derivative divides by the wrong interval, spiking when a slow tick follows a fast one. Measuring Δt with a monotonic tick clock each cycle (build page) makes both terms honest regardless of jitter.