Keeping Things on Track: How PID Controllers Work
There is a moment, if you have ever tried to hold something steady, where you realize the problem is harder than it looks. You turn up the heat. It overshoots. You turn it down. It undershoots. You spend ten minutes chasing a number that keeps slipping away from you.
What I find remarkable is that this exact frustration, this feeling of always being one step behind a moving target, turned out to be one of the most universal problems in all of engineering. It shows up in ovens and oceans, in insulin pumps and fighter jets. And the solution that engineers converged on is, once you see it, almost suspiciously elegant. Three ideas. Three terms. A formula you could write in a single line.
The solution is called a PID controller. PID stands for Proportional-Integral-Derivative. Each of those three words describes one simple idea, and together they describe something you interact with probably dozens of times a day without knowing it.
But we are not going to start with all three. We are going to start with the problem, and let each piece of the solution earn its place. We will use a single running example throughout: a drone trying to hold a target altitude while gravity pulls it down. By the end, you will have built up the full controller one term at a time and seen exactly why each piece is necessary.
Imagine you are holding the drone's throttle by hand. You would do three things naturally: look at how far off you are, notice if you have been off for a while and feel how fast the gap is closing. P, I and D are those three instincts turned into math. We will get there one at a time.
The raw problem
First, let's get precise about what a controller is even trying to do.
You have some system with a value you want it to hold. Engineers call that the setpoint. You have what the system is actually at right now, the measured value. The difference between those two is called the error:
The controller's job is simple in principle: look at that error and decide how hard to push. That push is the output, written .
For our drone, the setpoint is 120 meters. Gravity is always pulling it down. Without any controller, the drone just falls.
The drone starts at 100m with a target of 120m. No controller is running. Gravity pulls it down and the error grows. The shaded region is the gap.
Watch the history line. The drone starts at 100m, already 20m below target, and it only gets worse. The line slopes downward and keeps going. The shaded region between the line and the target is the error, growing with every passing second. Nothing is fighting back. This is the raw problem: there is a gap, and something needs to close it.
When is positive the drone is below target. When negative, above. The sign tells the controller which direction to push. This will matter in a moment.
Now, if you were holding the throttle, what would you do? The most natural instinct: the further away you are, the harder you push.
Proportional: react to the present
That instinct has a name. It is called proportional control, and it is the P in PID. The idea is dead simple: make the thrust proportional to the error.
is the proportional gain. It is just a knob that controls how aggressively the controller responds. Big means a small error produces a lot of thrust. Small means the controller is gentle.
The drone is 100m below target. Proportional control will push it upward. Will it reach 120m? Think about what happens as it gets close: the error shrinks, so the thrust shrinks too. Meanwhile gravity never stops pulling. What do you think will happen?
P control is active. Watch the term breakdown bar: only the P bar is contributing. Try dragging Kp from 1 to 8. The drone gets closer but never arrives. Watch the history line flatten out below the target.
No matter what you set to, the drone never reaches 120m. It gets close and then stops, hovering at some altitude below the target. The history line flattens into a horizontal line that sits below the dashed target line. The shaded gap between them is the steady-state error, and it never closes.
Here is why, and this is the key insight of this section. The drone needs a specific amount of thrust just to hover: enough to exactly cancel gravity. Call that . Proportional control produces thrust of . The drone reaches equilibrium when those two are equal:
Rearranging:
This is the fundamental trap. The P controller needs error to produce thrust. If the error ever reached zero, thrust would be zero, and the drone would fall. So the system settles at exactly the error that produces exactly enough thrust to hover. Not at the target. At whatever altitude makes the math balance.
Gravity exerts 9.81 m/s² of acceleration. If : the drone hovers below target. If : the gap shrinks to . If : the gap is . Getting smaller, but never zero. The only that gives zero error is infinity, which is not a real option.
This is not a tuning problem. It is a structural limitation. The P controller is being asked to do two things at once: produce hovering thrust AND close the error to zero. But its only source of thrust IS the error. If it eliminates the error, it loses the ability to hover. It is like asking someone to push a heavy box while standing on it. The very thing they need (the box beneath them) disappears if they succeed.
You can see this in the widget. Watch the live equation at the bottom: the thrust output equals times the error. When the line flattens, the status reads "stuck: thrust = gravity." That is the equilibrium. The green thrust arrow on the drone exactly matches the gravity arrow. The system is balanced, but balanced at the wrong altitude.
Making enormous does shrink the steady-state error toward zero, but it creates a new problem. Try setting to 8 and watch the history line carefully: the drone oscillates wildly before settling. A huge gain turns every tiny wobble into a massive correction. The system becomes twitchy and unstable.
So we have a controller that gets close but can never arrive. It is reacting to the present, and the present alone does not contain enough information. We need something else. Something that notices the drone has been sitting 3 meters below target for the last ten seconds and thinks: "this is not working. Push harder."
Integral: remember the past
The fix is to give the controller a memory.
Instead of only reacting to the current error, track the accumulated error over time. If there has been a persistent small shortfall for ten seconds, that accumulated shortfall should itself drive additional thrust. The longer you have been off, the harder the extra push. This is the integral term, the I in PID.
That integral sign just means "add up all the error values from the beginning until now." If the drone has been 3m below target for 10 seconds, the integral has accumulated roughly units of pressure. And critically: the integral only stops growing when the error reaches zero. That is why it can eliminate steady-state error when P alone cannot. The P term was stuck because it needed error to produce thrust. The I term breaks that deadlock: it builds up a reservoir of thrust from past errors, so the system no longer depends on the current error to hover.
The I term will push the drone the rest of the way to 120m. But think about what happens at the moment of arrival. The integral has been accumulating for the entire approach. All that stored-up pressure is still there when the drone reaches the target. The error just hit zero, but the integral is at its maximum. What do you think happens next?
Ki is now active. Watch the I bar in the term breakdown grow over time. The drone reaches 120m... then look at the history line. It overshoots. The I bar was still full at arrival.
The drone reaches the target! The shaded error region closes to zero. But then it blows right past. Look at the history line: it crosses above the target, comes back, crosses again and oscillates before eventually settling.
The term breakdown bar tells the story. Watch the I bar: it builds steadily during the approach, getting larger and larger. At the exact moment the drone reaches 120m, the I bar is at its peak. All that accumulated pressure is still being applied as thrust. The drone does not stop because the I term does not know it has arrived. It only knows that for the past several seconds, there was a persistent error in one direction. So it keeps pushing.
Imagine pushing a stalled car. You push and push and push. By the time it starts rolling, you are pushing with everything you have. You cannot instantly stop. The car lurches forward. That is the integral term: it builds momentum from history and cannot react to the present moment.
Notice the overshoot region in the chart: when the line crosses above the target, the shaded area turns red. That is the I term overshooting. The live equation shows why: even when the error hits zero, the integral value is still large, so the total thrust remains well above what gravity requires. In real systems, unbounded accumulation like this is called "integral windup" and it can be dangerous. Engineers add clamps to limit it, but the fundamental oscillation problem remains.
So now we have a new problem. P alone could not reach the target. P + I reaches it but overshoots. The I term is reacting to the past, and the past is a blunt instrument. What we need is something that can feel the drone approaching the target quickly and say: "slow down."
Derivative: anticipate the future
This is where the real elegance shows up.
The fix for overshoot is to look at how fast the error is changing. If the drone is rocketing toward the target, the error is shrinking rapidly. The rate of change is large and negative. If we use that to reduce the thrust, it acts as a brake: the faster you approach, the harder the brake. This is the derivative term, the D in PID.
Think about driving toward a red light. You do not just look at how far away it is. You also feel how fast you are approaching. If you are 50 meters away and crawling, you do nothing. If you are 50 meters away and going 60 km/h, you brake hard. Same distance, different response, because the rate of change is different. The derivative term is that instinct.
The D term will oppose rapid motion. When the drone is climbing fast toward target, D will push back against the climb. This should soften the overshoot from the I term. But D does not know or care about the target itself. It only reacts to speed. What happens if you set Kd very high? The drone will resist ALL rapid motion, even the initial climb it needs to make.
All three terms active. Watch the term breakdown: P pushes, I builds, D brakes. The history line is smooth. Hit 'Wind Gust' and then try setting individual gains to zero to see what each term does alone.
Watch the term breakdown carefully. During the initial climb:
- P bar is large (the drone is far from target, so P pushes hard)
- I bar starts growing (error is accumulating)
- D bar is negative (the drone is approaching fast, so D pushes back)
As the drone nears the target, P shrinks (less error), I is still building, and D acts as a brake to prevent overshoot. The result: the history line approaches the target smoothly and stays there. No oscillation. The shaded error region shrinks to nothing and stays there.
- Set . The oscillation from the PI widget returns immediately.
- Set . Steady-state error comes back, the line flattens below target.
- Set . The drone barely moves. I builds too slowly, D has nothing to dampen.
- Hit "Wind Gust" with full PID, then try again with . The drone never fully recovers.
One thing to notice: the D term never moves the system toward the target on its own. It only resists rapid change. Set and to zero and the D term does nothing useful. Its job is to smooth what the other terms are already doing. It is a brake, not an engine. Watch the live equation when you zero out P and I: the D contribution is the only nonzero term, but the total thrust stays near zero. The drone just falls slowly instead of fast.
Each term handles a failure the others cannot. P reacts to the present: where am I right now? I reacts to the past: how long have I been off? D reacts to the future: how fast am I approaching? Remove any one of them and the system breaks in a specific, predictable way.
P provides the force. I eliminates the residual error. D prevents overshoot. That is the whole story.
The full formula
Put all three together and you have the full PID controller:
Three terms. Three gains to tune (, , ). Each one handling a different aspect of the problem.
This is exactly what a skilled human operator does. If you asked someone to manually hold a drone at 120 meters, they would:
- Look at the gap and push harder if the drone is far away (P)
- Notice persistence and push extra hard if the drone has been stuck below target for a long time (I)
- Feel the approach and ease off the throttle as the drone nears the target to avoid blowing past (D)
PID is those three instincts written as a formula. That is why it works so broadly. It is not a clever trick. It is a formalization of how thoughtful correction naturally works.
Choosing good values for , and is called "tuning" and it is genuinely hard. There are systematic methods (Ziegler-Nichols, Cohen-Coon, relay autotuning) but in practice a lot of tuning still happens by feel. The formulas help you start. Experience helps you finish.
Where PID lives
Where do PID controllers show up? Almost everywhere.
Adaptive cruise control in your car is the obvious one. You set 65 mph. A sensor measures your actual speed. The PID controller adjusts the throttle and sometimes the brakes to keep the two numbers matching, even as you go up hills or catch up to slower traffic. Without the I term, you would always drive slightly below your target on inclines (steady-state error from the hill acting like gravity on our drone). Without the D term, you would lurch past your target every time the road flattened.
But there are less obvious ones:
- The thermostat in a modern HVAC system
- The autopilot in a commercial airplane
- The motor controller in a hard drive that positions the read head over the right track in milliseconds
- The glucose regulation system in an insulin pump
- The flight controller in a drone, which runs three separate PID loops simultaneously: one for pitch, one for roll and one for yaw
A real drone does not run one PID controller. It runs at least three (pitch, roll, yaw) and often six or more (adding altitude, x-position, y-position). Each loop runs independently at hundreds of hertz, reading its own sensor and writing its own motor command. The simplicity of each individual PID loop is what makes this kind of parallelism practical.
Anywhere you have a system with a target and a way to measure the current state, PID is usually at least the starting point.
Why it survived
Here is what I keep coming back to when I think about this.
A formula invented around 1910 to steer ships through ocean currents is, right now, running inside the phone in your pocket, the car in your driveway and the aircraft somewhere overhead. It has survived a century of far more sophisticated alternatives not because engineers are lazy but because it keeps turning out to be right.
The reason, I think, is that P, I and D are not three tricks someone bolted together. They correspond to something genuinely true about how errors exist in time. The current error, the history of errors and the trend of the error are three fundamentally distinct pieces of information about a system. Any controller that ignores one of them is flying partially blind. PID is just the simplest formula that uses all three.
The best abstractions feel this way. They do not feel like someone's clever idea. They feel like something that was always there, waiting to be noticed. Present, past and future are not engineering concepts. They are how time works.
The best abstractions are like that. They do not feel invented. They feel discovered. And there is something quietly wonderful about the fact that a ship's engineer chasing a steering problem over a hundred years ago, and a firmware developer tuning a drone controller today, are both reaching for the same three numbers.