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
setpointpositioncontrol effortsaturated
Steady error—
Overshoot—
Settling 2%—
Peak effort—
Saturated—
What each term actually does
P — the spring. Torque proportional to present error. Alone it always leaves droop against a constant load, because zero error would mean zero torque. Raising Kₚ shrinks the droop but never deletes it, and eventually causes overshoot and ringing.
D — the damper. Torque proportional to how fast the error is closing. Adds damping, kills overshoot, costs you noise sensitivity: differentiating a noisy encoder amplifies the noise, which is why real implementations filter D or use the measured velocity instead.
I — the memory. Accumulates error over time, so any persistent offset eventually generates enough torque to cancel it. It is the only term that removes steady-state error to a constant load — and the only one that can wind up.
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
x — the state vector. Everything you need to know now to predict the future: positions and velocities, stacked into one column of numbers.
A — the plant's own dynamics. Its eigenvalues are the poles: negative real parts mean stable, imaginary parts mean oscillation.
B — how your actuator pushes on the state.
u — the input vector: what the actuators are told to do. u = −K·x is full state feedback. Choose K and you move the eigenvalues of (A − BK) wherever you like, provided the system is controllable.
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:
Linear — it assumes the plant is linear, ẋ = Ax + Bu. For a nonlinear robot you linearise about an operating point first, which is why Brunton devotes a whole lecture to linearising around a fixed point.
Quadratic — the cost squares everything: xᵀQx and uᵀRu. Squaring is what makes the problem solvable in closed form (it yields the Riccati equation), and it penalises large deviations disproportionately, which is usually what you want.
Regulator — its job is to drive the state to zero and hold it there. Following a moving reference is a variant built on top, not the base case.
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:
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
positioncontrol 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.
Predict the plant's behaviour over the next N steps using a model.
Solve for the control sequence minimising a cost, subject to hard limits on states and inputs.
Apply only the first control of that sequence.
Measure again, and re-solve from scratch. This is the receding horizon.
LQR vs MPC, side by side
LQR
MPC
When it optimises
Once, offline. You get a fixed gain matrix K.
Every timestep, online.
Horizon
Infinite
Finite (N steps), then slid forward
What runs on the machine
u = −Kx — a matrix multiply. Microseconds.
A full optimisation problem. Milliseconds, and it must finish before the next cycle.
Constraints
None. 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 model
Yes (A, B)
Yes, and a better one — errors compound over the horizon
Fails by
Saturating, at which point its optimality guarantees are void
Not solving in time, or the model drifting from reality
Your analogy
Commissioned gains in the drive: computed once, then it just runs
A 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.