Where you are. You can write the sense-compare-correct loop and you know why an on-off rule buzzes. This lesson answers the question that rule dodged: given the error, how hard should you actually push?
A bag on a rubber band
Hang a rubber band from a hook at shoulder height, tie a shopping bag to the bottom of it, and let go.
The bag drops, the band stretches, and the bag comes to rest somewhere well below the hook. It has to. The band only pulls when it is stretched, and it is only stretched because the bag is below where you wanted it. Zero gap means zero pull means the bag falls. The gap is not a defect in the band; it is how the band produces any force at all.
Now pull the bag down and let go. It bounces, up past its resting point and back down, for several seconds before it gives up. Nothing is wrong. It is a mass on a spring, and that is what a mass on a spring does.
Close your hand loosely around the band and repeat. The bouncing stops almost immediately. Your hand did not move the bag anywhere; it only resisted the band moving quickly. It removed the bouncing and left the sag completely untouched.
So how do you get the bag actually up to the hook? Somebody has to keep shortening the band. Not once, in proportion to the gap; continuously, for as long as any gap remains, until the pull is finally enough to hold the bag at the hook with no gap left at all.
Three interventions. The band, your hand, and the shortening. Those are the three terms of the controller running inside almost every robot joint on earth, and they are not variations on one idea. They answer three different questions.
The idea in one paragraph
A control law has to turn one number, the error, into one number, the torque. There are exactly three useful things you can ask about an error: how big it is now, how long it has been there, and how fast it is changing. Multiply each answer by a gain and add them up, and you have PID. The proportional term does the moving and behaves exactly like a spring, which is why it both overshoots and settles short of the target under a constant load. The derivative term behaves exactly like a damper and cancels the overshoot without touching the shortfall. The integral term is the only one of the three that can produce a push while the error is zero, which is precisely what holding something up requires, so it is the term that closes the last gap. Three lines of code, sixty years of industrial dominance, and each line earns its place for a different reason.
One error, three questions
Wider than the screen; scroll it sideways.
In words: push in proportion to how wrong you are now, plus in proportion to the total wrongness accumulated so far, plus in proportion to how fast the wrongness is changing. The three gains , and are what you tune. The integral sign is a running sum and the derivative is a difference between consecutive ticks, so in code all three terms are arithmetic on the previous error and a stored total.
Everything below runs on the same plant as the last lesson: a 0.4 m link with 0.5 kg on the end, told to hold horizontal, where gravity is asking for 1.962 N·m and the motor can produce at most 3 N·m.
P, and the sag you cannot tune away
Start with and nothing else. The buzzing is gone immediately, because the torque now shrinks as the joint approaches the target instead of staying at maximum until it crosses.
Two problems replace it.
The joint overshoots to 115.7° and then rings. Four seconds in it is still swinging between 60 and 89 degrees. That is the bag bouncing on the band, and it is not a coincidence: is , the same equation. A proportional controller does not merely resemble a spring; the closed loop is a mass on a spring, and springs ring.
The second problem is the one that matters more. Let it run long enough to settle and the joint comes to rest at 76.35°, not 90. And it stays there.
You can find that resting place with a calculator. The controller supplies and gravity asks for ; set them equal and the answer is 76.35 degrees, exactly what the simulation gives.
D, the hand on the band
The derivative term multiplies . When the setpoint is fixed, the error only changes because the joint moves, so is just the negative of the joint’s speed, and is a torque that always opposes motion. It is a viscous damper, written in software.
Add to the same and the difference is total. The joint rises, approaches 76 degrees, and stops. No overshoot at all; it never goes above 78.3°. No ringing.
And the droop is still 76.35°. Not approximately: to two decimal places, exactly what P alone settled at.
Wider than the screen; scroll it sideways.
That is the cleanest possible demonstration that these terms are not interchangeable knobs. D removed the transient behaviour and had no effect whatsoever on the resting place, because at rest the joint is not moving, so is zero, so the D term contributes nothing at all.
I, the one term that remembers
The integral keeps a running total of error over time. Its usefulness comes from one property nothing else has: can be large while itself is zero, because the total does not disappear when the error does.
That is exactly what holding a load requires. The band has to end up permanently shortened.
Add and the joint reaches 90.02°, a residual error of two hundredths of a degree, and it is inside two degrees of target within 0.71 s. The 13.65 degree droop is simply gone.
Windup, the way integrals go wrong
Give the same controller a motor that can only manage 1.7 N·m, below the 1.962 needed to hold horizontal. The joint climbs to about 61 degrees and sticks there, permanently short, with the motor already flat out.
The integral has no idea. It keeps adding roughly 0.5 radian-seconds of backlog every second. By twelve seconds in it has reached 6.04, demanding a torque several times what the motor could ever deliver.
Now change the target to 30 degrees, which the motor can easily hold. The clamped controller is within 0.03° of 30 degrees twelve seconds later. The unclamped one is still 6.3° high at that point, and was 29.8° high six seconds in, because before it can come down it has to burn off all that accumulated backlog by integrating negative error for just as long.
The standard fix is one line: clamp the accumulator.
self.integ = np.clip(self.integ + err * self.dt, -self.i_clamp, self.i_clamp)
Feedforward: stop making the loop guess
There is something faintly ridiculous about the integral’s job here. Gravity’s torque is . You know , you know , and the encoder is telling you . The controller is patiently rediscovering, by accumulating error, a quantity you could have simply calculated.
So calculate it.
Wider than the screen; scroll it sideways.
tau = pid.update(target - theta) + M * G * L * np.sin(theta)
The second term is feedforward: a command computed from a model rather than from an error. Add it, and PD alone with , and no integral at all reaches 90.00°, is inside two degrees within 0.45 s, and overshoots by 1.44°. That is better than the full PID managed without it on every count, from a controller with one fewer term.
This principle scales far beyond gravity. In the milestone project, and in every serious arm controller, the feedforward term is the full inverse dynamics of the whole mechanism, and the feedback loop exists only to mop up the difference between the model and reality.
Tuning, and where PID runs out
The ritual is short and you now know why each step is there. Raise until the response is quick but visibly ringing. Raise until the ringing stops. Add just enough to erase whatever offset is left, and no more, because too much of it overshoots: at on this plant the joint sails to 121.5° before coming back.
PID knows nothing about the plant it is controlling. That is its great strength, since it works on a joint, a heater, a fluid level and a network queue without modification, and it is why it still runs most of the world’s industrial control. It is also its ceiling: it cannot anticipate, it cannot respect a limit it has not hit yet, and it treats every axis of a coupled system as if it were alone. The controllers that fix those things, LQR and MPC, need a model of the plant and a great deal more compute, and this course does not teach them: they belong to legged balance, and the locomotion track names where to go and read them if you take it. For manipulation, PID plus feedforward is not a stepping stone; it is the answer.
Review
One error, three questions
A control law has to turn one number, the error, into one number, the torque. There are exactly three useful things you can ask about an error: how big it is now, how long it has been there, and how fast it is changing. Multiply each answer by a gain and add them up and you have PID. The proportional term does the moving and behaves exactly like a spring, which is why it both overshoots and settles short under a constant load. The derivative term behaves exactly like a damper and cancels the overshoot without touching the shortfall. The integral term is the only one of the three that can produce a push while the error is zero, which is precisely what holding something up requires, so it is the term that closes the last gap.
The sag you cannot tune away
Holding a link up needs sustained torque, and proportional action produces torque only out of error. So the only place a proportional controller can rest is somewhere with enough error left to generate the torque that holds it there. You can find that resting place with a calculator: set the controller’s push equal to what gravity asks for, and on the worked example the answer is seventy-six point three degrees against a target of ninety, exactly what the simulation gives. Raising the gain shrinks the droop and never removes it. Each doubling roughly halves the sag, from thirteen point six degrees to seven to three and a half, and each doubling also doubles the torque demanded for a given error, so more of the move is spent with the motor flat out. You are buying a smaller constant offset with a more violent machine, and you never reach zero.
Windup, and why the clamp is insurance
Give the controller a motor too weak to hold the load and the joint sticks short with the motor already flat out. The integral has no idea. It keeps adding backlog every second, and by twelve seconds it is demanding several times what the motor could ever deliver. Now ask for a target the motor can easily hold: the clamped controller settles within three hundredths of a degree, while the unclamped one is still six degrees high, because before it can come down it has to burn off all that accumulated backlog by integrating negative error for just as long. The fix is one line, clamping the accumulator. Be honest about it though: on a well-sized joint the clamp never fires. You will see it the first time somebody bolts a heavier gripper on, or a cable snags, or the arm is commanded somewhere it cannot reach and sits there straining.
Check yourself
1. Explain the P droop without equations, then name two fixes that work in completely different ways.
Holding the link up requires continuous torque, and P generates torque only from present error, so it can only rest somewhere that still has error. The gap is not a bug, it is the mechanism. Fix one is integral action: feedback that accumulates the persistent error and keeps pushing harder until it is gone. It needs no model, but it adds lag and the risk of windup. Fix two is gravity feedforward: compute and add it, cancelling the load outright. It is instant and free of lag, but it is only as good as your knowledge of and . Real systems use both.
2. PD settled at exactly the same angle as P alone, to two decimal places. Why is that not a coincidence?
At rest the joint is not moving, so is zero, so the D term contributes exactly zero torque. The equilibrium is therefore determined entirely by the P term against gravity: , the same equation with or without D. Derivative action shapes how you get somewhere; it has no vote on where you stop.
3. Your arm holds position fine on the bench and chatters audibly the moment you mount a higher-resolution encoder read at a faster rate. What is the likely cause?
The derivative term. divides by the sample interval, so shrinking scales up the torque produced by any given measurement jitter, and finer encoder counts arriving faster means more jitter per second, not less. One count of 0.0879° through at 1 kHz already asks for 1.841 N·m. The fix is to low-pass filter the derivative, or to differentiate the measured angle rather than the error, or both.
4. Give a scenario where integral windup does real damage, and say why the anti-windup clamp is worth writing even though it never fires in the exercise.
Any long period where the error cannot shrink: the arm commanded past a joint limit, a gripper closed on something solid, a payload heavier than the motor can lift, a cable snagged. Throughout, the integral accumulates a backlog that no achievable torque could justify. When the obstruction clears or the target changes, the controller has to spend that backlog before it can respond, so it drives hard in the wrong direction for a period proportional to how long it was stuck. On this plant with a 1.7 N·m motor, an unclamped controller was still 6.3 degrees off target twelve seconds after the command changed, and nearly 30 degrees off six seconds in. The clamp costs one line and bounds the damage.
5. PD with gravity feedforward beat full PID without it on settling time, overshoot and final error. So why does anyone keep the integral term?
Because feedforward only cancels what the model knows. It has the mass of the link, not the mass of the object the gripper just picked up; it has no term for friction, for a stiff cable, or for the operator resting a hand on the arm. Anything the model omits shows up as a constant offset, and the integral is the only term that removes a constant offset. In practice you run both: feedforward carries the large predictable load so the loop can use gentle gains, and a small integral cleans up the remainder.
6. Which term of a PID controller would you expect to dominate during the first few milliseconds of a large step command, and what does that tell you about commanding steps?
The derivative. The error jumps by the full size of the step in a single tick, so is enormous: a 90 degree step with at 1 kHz asks for 1885 N·m on that one tick, against a 3 N·m motor. The P term contributes a comparatively modest 12.6 N·m and the integral has had no time to accumulate anything. It tells you that a step is a violent thing to ask for, that the D term should differentiate the measurement rather than the error so a setpoint jump does not kick it, and that the real fix is to stop commanding steps at all, which is the next lesson.
Do this
Implement the controller. About forty minutes, and it is the most reusable forty minutes in the module.
Open code/pid_pendulum.py. The plant and the plotting are complete; PID.update is yours. Write the three terms plus the anti-windup clamp, remembering that the derivative has no previous error to work with on the very first tick.
Then run the staged experiment and match your numbers against the lesson:
- Plain
python pid_pendulum.py. The three curves should show P ringing and low, PD steady and low, PID on target. The script prints where each one is over the last half second:72.47°,76.35°and90.02°. P prints lower than PD only because it is still swinging at five seconds; setT_END = 40.0and P settles on the same76.35°as PD, which is the droop both share. python pid_pendulum.py --gravity-comp. Same gains, one extra term. Watch all three curves reach the target, including the one with no integral at all.python pid_pendulum.py --disturbshoves the joint mid-hold. PID recovers; PD returns to its droop and sits there. Retune if the recovery looks ugly to you, and notice what you had to trade to get it.- Set
TAU_MAX = 1.7, below the1.962 N·mthe joint needs to hold horizontal, and printctrl.integeach second. Watch the backlog grow on a job that cannot be finished. Then seti_clampto a huge number and compare.
Solution: solutions/pid_pendulum.py. Your update should be about eight lines.
What you can now do
You can write a PID controller from scratch, including the anti-windup clamp, and say what each of the three terms contributes without reciting the acronym. You can predict a proportional controller’s resting place with a calculator, explain why derivative action changes the journey and never the destination, and name the exact measurement problem that makes real D terms filtered. You can spot integral windup by its signature, and you can move predictable load out of the loop with a feedforward term so the feedback controller has less to do and can do it more gently.