Proportional control
One gain, the whole trade-off, and the steady state error it cannot remove.
Do this lesson in the simulatorPrint it: worksheet (PDF) · answers (PDF) · as a web page
Guide: PID control explained
command = Kp * error
One line, one number to choose, and most of what a controller does.
What the gain means
Kp has units. Here the error is in centimetres and the command is in percent, so Kp is percent per centimetre. At Kp = 2, being 10 cm out asks for 20 percent. That is a physical statement, not a magic number, and it is why the same gain on a different robot means something different.
Turning it up
The robot starts 80 cm short of a point 30 cm from the wall. Each gain gets ten seconds, and the robot backs up to the start line between runs.
from bugbot import *
connect()
TARGET = 30.0
START = distance()
for KP in (0.5, 2.0, 6.0):
if distance() < START - 2:
backward(100, distance=START - distance()) # back to the start line
for tick in range(100):
error = distance() - TARGET
plot("kp %s" % KP, error)
drive(max(-70, min(70, KP * error)), 0, 0)
wait(0.1)
stop()
print("kp", KP, "ended", round(distance() - TARGET, 1), "cm short")
Three shapes on one chart, all coming down from the same side:
- 0.5: creeps in, and is still about 30 cm short when its ten seconds run out. Left for longer it gets no closer than about 28 cm.
- 2.0: faster, and stops about 6 cm short.
- 6.0: fastest, within 5 cm after about 6 s, and it stops on the target.
None of them swings past and back. This robot is slow for its lag: at 70 percent it moves at 14 cm/s, it answers a command within about a quarter of a second, and the loop looks ten times a second, so it can always brake in time. Push the gain far higher and the overshoot is still only a couple of centimetres. Above about 8 the robot starts to shuffle back and forth by a centimetre or two, but that is the sensor noise being amplified, not a swing.
Where the overshoot is
Overshoot needs a plant that is fast compared with how often you look. Turning on the spot is: up to 120 degrees a second. Here is the same controller on the heading, looking four times a second, with each gain asked for a quarter turn from wherever the last one stopped.
from bugbot import *
connect()
for KP in (0.5, 1.0, 4.0):
target = heading() + 90 # a quarter turn from where it is now
worst = 0.0
for tick in range(32): # 8 seconds, four looks a second
error = (target - heading() + 180) % 360 - 180
worst = min(worst, error)
plot("kp %s" % KP, error)
drive(0, 0, max(-100, min(100, KP * error)))
wait(0.25)
stop()
wait(0.5)
print("kp", KP, "overshoot", round(max(0.0, -worst), 1), "ended", round(error, 1), "degrees out")
Now the three textbook shapes are all there:
- 0.5: turns, then stops about 21 degrees short, and stays there.
- 1.0: arrives in about 1.5 s without swinging, within half a degree.
- 4.0: arrives fast, swings about 20 degrees past, comes back, crosses the target five times, and settles after about 4 s. It gets there in the end, and it looks awful doing it.
The difference is delay against speed. By the time the program looks again, a quarter of a second later, a robot turning at 100 degrees a second has already moved 25 degrees.
The steady state error
The low gain runs do not just look slow. They stop short, and stay short.
The reason is in the actuator. Below about 15 percent this drive does not move at all. At Kp = 0.5, any error under 30 cm asks for less than 15 percent, the motors do nothing, and the robot stops about 28 cm short for ever. The turn is the same arithmetic in degrees: 0.5 times 23 degrees is 11.5 percent. The controller is asking for a push too small for the machine to deliver.
That is steady state error, and it is not a fault in the arithmetic. Proportional control needs an error to produce an output. When the output it produces is too small to move anything, the error stops shrinking. The largest error it can leave is 15 divided by Kp: 30 cm at 0.5, 7.5 cm at 2, 2.5 cm at 6. The robot coasts a little, so it usually finishes inside that.
Three ways out, in increasing order of respectability:
- Raise the gain, and accept what comes with it: overshoot on a fast loop like the turn, a twitchy command on a noisy sensor.
- Add a dead band and declare that close enough is close enough.
- Add an integral term, which accumulates a small error until it becomes a large enough push. That is U5.4, and it is the real answer.
Choosing Kp by hand
- Start low, say 1.
- Double it until the robot overshoots or wobbles.
- Halve that.
Crude, effective, and it is roughly what most people actually do. On the distance loop the wobble is the shuffle on the noise, which starts at about 8, so this lands near 4. U5.5 is the version with a method behind it.
Task: find a gain that works
Settle 30 cm from the wall, within 5 cm, and stay there from ten seconds onwards. Print the kp: you chose.
from bugbot import *
connect()
KP = 1.0
TARGET = 30.0
Challenges
- In the turning cell, find the gain at which it first overshoots, to the nearest 0.5.
- Find the gain below which the distance loop never gets within 5 cm. Explain it with the dead band.
- Run the same gain from 100 cm away and from 5 cm away. Does the same number suit both?