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.

GuideG3.1free, runs in your browser

Open this guide in the app

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 PID simulator: 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.

The PID loop: the error goes to three terms, their sum drives the robot, and the heading is measured againtarget 90°+−errorP: Kp × errornowI: Ki × totalthe pastD: Kd × changewhat comes next+pushrobotheading(): measured every 0.25 s
The whole controller is one loop. The error is split three ways, the three pushes are added, and the robot's new heading is measured and taken from the target again, 4 times a second.

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 21 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()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

It stops short because of the motors. Below about 15 percent this robot does not move at all. When 21 degrees are left, 0.5 × 21 asks for 10.5 percent, nothing happens, and the error stays at 21 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.

The push asked for by Kp 0.5 against the error, with the motors' dead band below 15 percent01530456075900153045error, degrees still to turnpush asked for, %dead band: the robot does not movepush = 0.5 × errorstuck at 21.5°
Any error under 30 degrees asks for less than 15 percent, which does not move the robot. It coasts a little way into that band and stops for good, 21.5 degrees short, asking for 10.8 percent.

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 20 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()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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 within 2 degrees of 90 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()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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.

The P push, the D push and their sum over the first two and a half seconds with Kp 4 and Kd 0.800.511.522.5-1000100200300time, secondspush asked for, %motors top out at 100PDsumsum -13 at 0.76 s
At 0.76 s the robot still has 21 degrees to go, so P asks for 85. But the error is falling fast, D asks for -98, and the sum is -13: the controller is already braking before the robot arrives.

Add I: close the last gap

Back to the gentle Kp = 0.5 that stopped 21 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()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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 22 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()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

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.

The P push, the I push and their sum over five seconds with Kp 1 and Ki 0.4, with the dead band shaded012345-250255075100time, secondspush asked for, %dead bandreaches 90° at 1.1 sPIsum
When the robot reaches 90 degrees P has fallen to 0, but the I push, wound up during the turn, is still 25. That carries the robot 22 degrees past, where the sum is inside the dead band. It only moves back at 3.5 s, once the integral has unwound enough for the sum to leave the band.

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