PID control explained

What a PID controller does, what Kp, Ki and Kd each change, and how to tune one, shown on a robot turning to face a direction. Change the numbers, press Run and watch it undershoot, overshoot and settle.

Guidefree, runs in your browser

A PID controller looks at how far something is from where you want it, and works out how hard to push. It is in cruise control, drones, ovens, 3D printers and nearly every robot. On this page a small robot turns on the spot to face 90 degrees, and each demo below is a real program you can change and run.

The chart under each robot shows the error: how many degrees the robot still has to turn. A good controller takes that line down to zero quickly and leaves it there.

The idea in one line

push = Kp × error
     + Ki × (the error added up over time)
     + Kd × (how fast the error is changing)
  • P (proportional) pushes harder the further away you are.
  • I (integral) adds up the error that is left over, and pushes harder the longer it stays.
  • D (derivative) watches how fast the error is shrinking, and brakes before you arrive.

Kp, Ki and Kd are the three numbers you choose. Choosing them is called tuning.

P on its own: too gentle

The robot starts facing 0 degrees and has to turn to 90. Here Kp is 0.5, so 90 degrees of error asks for a push of 45 percent.

Kp = 0.5: the robot turns, then stops about 23 degrees short and stays there.
The program
from bugbot import *
connect()

# change these three numbers and press Run
KP = 0.5
KI = 0.0
KD = 0.0

TARGET = 90          # degrees
integral = 0.0
last = 90.0          # the error at the start
for tick in range(32):             # 8 seconds
    # degrees still to turn, -180 to 180
    error = (TARGET - heading() + 180) % 360 - 180
    integral = integral + error * 0.25
    change = (error - last) / 0.25
    last = error
    plot("error", error)
    push = KP * error + KI * integral + KD * change
    drive(0, 0, push)
    wait(0.25)
stop()

It stops short because of the motors. Below about 15 percent this robot does not move at all. When 23 degrees are left, 0.5 × 23 asks for 11.5 percent, nothing happens, and the error stays at 23 for ever. This is called steady state error, and every real machine has some version of it: friction, a dead band, a load it has to hold.

P on its own: too keen

Now Kp is 4. The robot turns much faster, but it arrives with so much speed that it swings past 90, comes back, and swings past again.

Kp = 4: fast, but it overshoots by about 22 degrees and swings back and forth before it settles.
The program
from bugbot import *
connect()

# change these three numbers and press Run
KP = 4.0
KI = 0.0
KD = 0.0

TARGET = 90          # degrees
integral = 0.0
last = 90.0          # the error at the start
for tick in range(32):             # 8 seconds
    # degrees still to turn, -180 to 180
    error = (TARGET - heading() + 180) % 360 - 180
    integral = integral + error * 0.25
    change = (error - last) / 0.25
    last = error
    plot("error", error)
    push = KP * error + KI * integral + KD * change
    drive(0, 0, push)
    wait(0.25)
stop()

The overshoot comes from delay. The robot only checks its heading four times a second, and the motors take a moment to slow down, so by the time the program sees it has arrived, it has already gone past. More gain means more speed at the moment it arrives, and a bigger overshoot. Try KP = 1 for a gain in between: slower, but it arrives without swinging.

Add D: brake before you arrive

Keep Kp at 4 and add Kd = 0.8. As the robot swings towards 90, the error is falling fast, so the D term is large and negative. It takes away push exactly when the robot is about to overshoot.

Kp = 4, Kd = 0.8: just as fast, and it stops on 90 degrees with almost no overshoot.
The program
from bugbot import *
connect()

# change these three numbers and press Run
KP = 4.0
KI = 0.0
KD = 0.8

TARGET = 90          # degrees
integral = 0.0
last = 90.0          # the error at the start
for tick in range(32):             # 8 seconds
    # degrees still to turn, -180 to 180
    error = (TARGET - heading() + 180) % 360 - 180
    integral = integral + error * 0.25
    change = (error - last) / 0.25
    last = error
    plot("error", error)
    push = KP * error + KI * integral + KD * change
    drive(0, 0, push)
    wait(0.25)
stop()

D is the term that lets you use a high gain without the swinging. Too much of it and the robot brakes too early and creeps in, or stops a little short. Try KD = 2. On a robot with noisy sensors, a large D also makes it twitch, because D reacts to every small jump in the reading.

Add I: close the last gap

Back to the gentle Kp = 0.5 that stopped 23 degrees short, and add a little I. While the robot sits short of the target, the integral keeps adding up, the push grows, and eventually it is big enough to move the robot.

Kp = 0.5, Ki = 0.1: the integral builds up and pushes the robot the rest of the way to 90 degrees.
The program
from bugbot import *
connect()

# change these three numbers and press Run
KP = 0.5
KI = 0.1
KD = 0.0

TARGET = 90          # degrees
integral = 0.0
last = 90.0          # the error at the start
for tick in range(32):             # 8 seconds
    # degrees still to turn, -180 to 180
    error = (TARGET - heading() + 180) % 360 - 180
    integral = integral + error * 0.25
    change = (error - last) / 0.25
    last = error
    plot("error", error)
    push = KP * error + KI * integral + KD * change
    drive(0, 0, push)
    wait(0.25)
stop()

Too much I: windup

I is powerful and easy to overdo. With Ki = 0.4 the integral grows so large during the turn that the robot is still being pushed hard when it reaches 90, and it shoots past.

Kp = 1, Ki = 0.4: the integral has wound up during the turn and carries the robot about 26 degrees past the target.
The program
from bugbot import *
connect()

# change these three numbers and press Run
KP = 1.0
KI = 0.4
KD = 0.0

TARGET = 90          # degrees
integral = 0.0
last = 90.0          # the error at the start
for tick in range(32):             # 8 seconds
    # degrees still to turn, -180 to 180
    error = (TARGET - heading() + 180) % 360 - 180
    integral = integral + error * 0.25
    change = (error - last) / 0.25
    last = error
    plot("error", error)
    push = KP * error + KI * integral + KD * change
    drive(0, 0, push)
    wait(0.25)
stop()

This is integral windup. The usual fixes are to cap the integral at a sensible size, or to only start adding it up once the robot is close.

How to tune a PID controller by hand

  1. Set Ki and Kd to 0. Start with a small Kp.
  2. Raise Kp until the robot arrives quickly but starts to overshoot.
  3. Add Kd until the overshoot goes. If the robot starts to creep in slowly or twitch, you have gone too far.
  4. If it still stops short, add a small Ki, and raise it slowly. If it starts to overshoot again, you have too much.
  5. Watch the chart, not the robot. A good tune is a line that drops fast and stays flat on zero.

This is roughly what engineers do on a new machine. There are methods with more theory behind them (Ziegler and Nichols is the famous one), and the University lessons below work through one, but the steps above get most robots most of the way.

Questions

What is a PID controller in simple terms?

It is a rule for how hard to push, worked out from how far you are from where you want to be. P pushes in proportion to the distance left, I pushes harder the longer an error lasts, and D eases off as you close in quickly. The output is the sum of the three.

What do Kp, Ki and Kd do?

Kp sets how strongly the controller reacts to the error now: higher is faster but overshoots more. Ki sets how quickly left-over error builds into extra push: it removes steady state error but causes windup if too big. Kd sets how strongly it reacts to the error changing: it damps overshoot but amplifies noise.

What is the PID formula?

The output is Kp × e + Ki × ∫e dt + Kd × de/dt, where e is the error (target minus measurement). In a program that runs in a loop, the integral becomes a running total of error × dt and the derivative becomes (error − last error) / dt, which is exactly what the demos on this page do.

What does a PID graph show?

Usually the error, or the measured value against the target, over time. Too little gain shows as a line that levels off above zero. Too much gain shows as a line that crosses zero and swings. A good tune drops quickly and flattens on zero with little or no swing.

Why does my robot overshoot and oscillate?

The gain is too high for how fast the robot and the loop respond. Every system has some delay, and a strong push arrives late enough to carry the robot past the target. Lower Kp, add Kd, or run the control loop faster.

Why does my robot stop short of the target?

Close to the target the push Kp × error becomes smaller than the smallest push that moves the robot, because of friction or the motors' dead band. Adding a small integral term fixes it, because the integral keeps growing until the push is enough.

What is integral windup?

The integral keeps adding up error for as long as the robot is away from the target. During a long move it can become so large that it keeps pushing after the robot arrives, and the robot overshoots. Cap the integral, reset it when the robot crosses the target, or only start it near the target.

Do I always need all three terms?

No. Many real controllers are P or PI. Use P when a little steady state error does not matter, PI when it does, PD when you need speed without overshoot, and PID when you need all of it.

What are the main types of controller?

On-off (bang-bang), which pushes fully one way or the other; P; PI; PD; and PID. On-off is the simplest and always wobbles around the target. The others add the terms described on this page.

Learn it step by step

These lessons build the same ideas one at a time, each with tasks the simulator marks.

  1. 3.1 What a controller is Control, Robot club
  2. 3.2 Proportional control Control, Robot club
  3. 3.3 Tuning the gain Control, Robot club
  4. U5.1 The feedback loop Feedback control, University
  5. U5.2 Proportional control Feedback control, University
  6. U5.3 The derivative term Feedback control, University
  7. U5.4 The integral term Feedback control, University
  8. U5.5 Tuning Feedback control, University
  9. U5.6 Saturation and windup Feedback control, University
  10. U5.7 Project: park it Feedback control, University
Open the lessons