← Zinan Yang · lab

Interactive notes · from PID to MPC

Control Theory Bench

Everything here is a live simulation. Drag the sliders and watch the plant respond — control is learned in the fingers, not the eyes. Written for someone who has already tuned real drives and now needs the maths underneath.

Module 01

The plant, and why it rings

Nearly every mechanical axis you have commissioned is, to a first approximation, a second-order system: inertia, damping, and a spring-like restoring effect from the position loop.

J·θ̈ + b·θ̇ + k·θ = τ

→   θ̈ + 2ζωₙ·θ̇ + ωₙ²·θ = ωₙ²·r

Two numbers describe its whole character. ωₙ (natural frequency) is how fast it wants to move. ζ (damping ratio) is how much it resists overshooting.

Step response — drag ζ and ωₙ
setpointresponse
Overshoot
Settling 2%
Rise 10-90%
Regime
Your translation. ζ < 1 is the axis that rings after a move and needs the damping term raised. ζ = 1 is critically damped — the fastest approach with no overshoot, which is what you are aiming for when you tune out oscillation on a positioning axis. ζ > 1 is the sluggish, over-damped axis that never overshoots but takes forever to arrive.
Check yourself

Raising ωₙ makes the axis faster. Why can you not simply keep raising it on a real machine?

Because the second-order model is a lie above a certain frequency. A real axis has structural resonance, backlash, finite drive bandwidth and sampling delay, none of which appear in Jθ̈+bθ̇+kθ. Push ωₙ toward those unmodelled dynamics and the loop excites them — the axis screams, the coupling rings, the current draw climbs. The classical limit is the phase margin at the crossover frequency: you can go fast until the loop's own delays turn negative feedback into positive feedback.

Module 02

P, I and D — earn each one

PID stands for Proportional, Integral, Derivative — three terms summed, each acting on the error differently: on its present value, on its accumulated history, and on its rate of change.

Below is a real closed loop: the same second-order axis, now with a PID controller and a constant load torque pulling it off target. It is the classic case where pure PD leaves a steady droop that no amount of Kₚ removes.

Closed loop — tune it
setpoint position control effort saturated
Steady error
Overshoot
Settling 2%
Peak effort
Saturated

What each term actually does

Try this. Set Kᵢ to 0 and raise the load. Watch the droop appear. Now raise Kₚ alone and see the droop shrink but never reach zero, while the response starts to ring. Then add Kᵢ and watch the error walk to zero — slowly at first, then faster as you raise it, until the integrator itself causes overshoot.
Check yourself

Why is D usually implemented as −Kₔ·θ̇ (measured velocity) rather than Kₔ·d(error)/dt?

Two reasons. First, a step change in setpoint makes d(error)/dt infinite for one instant — the notorious "derivative kick" that slams the actuator. Using −θ̇ ignores setpoint jumps and responds only to actual motion. Second, error derivative and velocity differ only by the setpoint's own derivative, which is zero while the setpoint is constant — so you lose nothing in regulation and gain a great deal in behaviour at the step. This is why drive parameter sets almost always expose a velocity feedback gain rather than a true error-derivative gain.

Module 03

Feedforward — the term that prevents rather than corrects

Feedback is reactive by construction: it needs an error before it acts. Feedforward uses a model to command what you already know will be needed, so the error never appears.

τ = Kₚe + Kᵢ∫e + Kₔė  +  ̂  (the model's guess at the load)

Go back to Module 02 and tick load feedforward with Kᵢ at zero. The droop vanishes instantly — no integrator, no waiting, no wind-up risk. That is the whole argument for feedforward.

A MuJoCo test makes the point. Pure PD against gravity left +6.165° of steady droop; adding data.qfrc_bias — MuJoCo's computed gravity and Coriolis torque — took it to +0.000°. Nothing was tuned. The controller was simply told what the physics was about to do.

In your commissioning world this is torque feedforward, velocity feedforward, and the pre-control terms in a positioning axis. In robotics it is computed-torque control and the inverse-dynamics term in MR Ch 11. Same idea, three vocabularies.

The catch, and it is a real one. Feedforward is only as good as the model. Get the mass wrong and you inject the wrong torque — now you have a systematic error that feedback must clean up, and you have gained nothing. Feedback is robust and slow; feedforward is fast and brittle. Production systems use both, which is exactly why the equation above has two halves.
Check yourself

If feedforward removes the error without an integrator, why keep the integrator at all?

Because the model is never exactly right, and the plant drifts. Friction changes with temperature, a payload is added, a belt wears, the true mass differs from the CAD value by 8%. Feedforward cancels the part you predicted; the integrator mops up the residue you did not. Drop the integrator and every modelling error becomes permanent droop. Drop the feedforward and the integrator has to do all the work, slowly, with wind-up risk. Keep both, and each covers the other's weakness.

Module 04

Saturation and integrator wind-up

Every real actuator has a limit — current, torque, voltage. When the controller asks for more than the limit, the loop is effectively open, and the integrator keeps accumulating an error it is powerless to fix.

Reproduce it in Module 02. Set Kᵢ high (say 60), drop the actuator limit to about 6, and untick anti-wind-up. The response overshoots massively and takes an age to recover: the integrator charged up during saturation and must now discharge before the output can come back. Tick anti-wind-up and the same gains behave.

The fix in the simulation is the simplest of several: stop integrating while the output is saturated. Real implementations also use back-calculation, which bleeds the integrator down in proportion to how far the command exceeded the limit.

You have seen this on a machine. An axis blocked mid-move, or a conveyor started against a jam, then lurches violently when it frees. That lurch is the integrator discharging. It is also why a drive's ramp limiter and torque limit interact so badly with an untreated position loop.
Check yourself

Anti-wind-up stops integrating during saturation. What are you giving up?

Integral action exactly when the error is largest. If the plant is saturated because it genuinely needs sustained maximum effort — a heavy lift, a steep ramp — the frozen integrator will not help you get there faster, and on release the recovery is slower than an unclamped integrator would have been. That is the correct trade: you accept a slightly lazier recovery in exchange for never having a violent, uncontrolled discharge. In safety terms the choice makes itself.

Module 05

State space — the same system, written for more than one variable

PID thinks about one error signal. State space thinks about the whole state vector at once, which is what you need when the variables interact — a cart and its pendulum, a robot's joints coupling through inertia.

ẋ = A·x + B·u
y  = C·x + D·u
Controllability, in one sentence. A system is controllable if your actuator can reach every direction of the state space, possibly indirectly. A cart-pole is controllable: pushing the cart sideways does eventually rotate the pole. A pendulum with no motor is not — you can compute any K you like and none of it will help.

PID is a special case of this. A PD controller on a single axis is exactly u = −[Kₚ Kₔ]·[position error, velocity error]ᵀ — state feedback with a two-element state.

Module 06

LQR — stop guessing K, state the trade-off instead

LQR stands for Linear Quadratic Regulator. All three words are load-bearing:

Pole placement makes you choose where the poles go, which is hard to reason about physically. LQR asks a question you can actually answer: how much do you care about error versus effort?

minimise   J = ∫ ( xᵀQx + uᵀRu ) dt
Reading that equation. x is the state vector — everything you must know now to predict the future. u is the input vector — what your actuators are commanded to do. For a cart-pole:
x = [ cart position, cart velocity, pole angle, pole rate ]ᵀ  ← 4×1
u = [ force on the cart ]  ← 1×1
That raised ᵀ means transpose — flipping a column of numbers on its side into a row.
Why the transpose is there: the cost has to be a single number. You cannot minimise a vector — "smaller" is meaningless for four numbers at once. The transpose is what collapses them to one:
xᵀ · Q · x   =   (1×4) · (4×4) · (4×1)   =   1×1, a scalar
Written out with a diagonal Q, it is just a weighted sum of squares:
xᵀQx = q₁·position² + q₂·velocity² + q₃·angle² + q₄·rate²
So each q is simply "how much do I care about this state being wrong", and each entry of R is "how much do I care about spending this actuator". Squaring is also why sign does not matter — an error of −2° costs the same as +2°.

Q prices state error. R prices actuator effort. Give it those two and the Riccati equation hands back the optimal K. No trial and error.

Double integrator under LQR — slide the effort price
position control effort
K position
K velocity
Settling 2%
Peak effort
Notice what you are doing. You are not tuning gains — you are stating a preference, and the maths derives the gains. Cheap effort (low R) buys a fast, aggressive response. Expensive effort (high R) gives a gentle one. For the double integrator here the answer is analytic: K₁ = √(Q/R), K₂ = √(2K₁).
Check yourself

When would you reach for LQR over a well-tuned PID?

When the system is multivariable and the variables fight each other. One PID per axis works while the axes are independent; the moment moving joint 1 disturbs joint 2, per-axis loops are tuning against each other and you are trading one axis's performance for another's. LQR handles the coupling explicitly, because K is a matrix acting on the whole state. It also gives you a principled tuning knob — cost weights with physical meaning — instead of three gains and intuition. The price is that you need a model. No model, no A and B, no LQR: which is exactly why PID still runs most of the world's machinery.
The sentence to own

“Q prices state error, R prices actuator effort, and the Riccati equation returns the optimal gain matrix. I am specifying a trade-off rather than guessing gains.”

Module 07

MPC — conversation level, and that is enough

Model Predictive Control does at every timestep what LQR does once: it optimises. The difference is that it optimises over a finite horizon, subject to constraints, and then throws most of the answer away.

LQR vs MPC, side by side

LQRMPC
When it optimisesOnce, offline. You get a fixed gain matrix K.Every timestep, online.
HorizonInfiniteFinite (N steps), then slid forward
What runs on the machineu = −Kx — a matrix multiply. Microseconds.A full optimisation problem. Milliseconds, and it must finish before the next cycle.
ConstraintsNone. It will happily command 400% of your torque limit.Handled natively, and anticipated — it will brake early because it can see the limit coming.
Needs a modelYes (A, B)Yes, and a better one — errors compound over the horizon
Fails bySaturating, at which point its optimality guarantees are voidNot solving in time, or the model drifting from reality
Your analogyCommissioned gains in the drive: computed once, then it just runsA planner that re-plans the ramp every cycle against live limits
The one-sentence version. Unconstrained, with an infinite horizon and a linear plant, MPC converges to exactly the LQR solution — MPC is the generalisation, and LQR is the special case you can solve in closed form. You pay for the generality in compute.
What MPC buys that LQR cannot. Constraints, handled natively. LQR will happily hand you a gain that commands 400% of your available torque; you then saturate, and all its optimality guarantees evaporate. MPC knows the limit is 100% and plans a trajectory that respects it — including anticipating a limit several steps ahead and starting to brake early. That is why it dominates in process control, and increasingly in autonomous driving.
What it costs. You solve an optimisation problem every single cycle. At 1 kHz on an embedded target that is a serious engineering constraint, which is why linear MPC with a fast QP solver is common and full nonlinear MPC is still mostly a research or low-rate proposition.

At this level, what matters is the receding horizon and how constraints are handled — not the QP solver underneath.