Proportional control

One gain, the whole trade-off, and the steady state error it cannot remove.

U5.2Feedback controlUniversity30 min

Do this lesson in the simulator
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")

Run this in the simulator

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.

The error of the distance loop over ten seconds at three gains, all coming down without swinging past0246810020406080time, secondserror, cmkp 0.5kp 2kp 6
The three runs from the cell. More gain comes down faster, and none of them goes below zero, which would be swinging past the target. At ten seconds kp 0.5 is still 34 cm out, kp 2 has stopped about 6 cm short, and kp 6 is on the target, wobbling a little with the sensor.

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")

Run this in the simulator

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 heading error over five seconds at three gains, the highest swinging past the target and back012345-300306090time, secondserror, degreeskp 0.5: stuck 21.5° shortkp 1kp 4: 20° past
The same controller on the turn. Each dot on the kp 4 line is one look, a quarter of a second apart, and between two looks it turns as much as 30 degrees. By the time it sees it has arrived it is already on its way past, and it goes 20 degrees beyond before it comes back.

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.

The push asked for against the error for three gains, with the drive's dead band below 15 percent051015202530350153045error, cmpush asked for, %dead band: no movementkp 0.530kp 27.5kp 62.5
Where each gain runs out of push. The line is kp × error, and the dot is where it falls below 15 percent: 30 cm at kp 0.5, 7.5 cm at kp 2, 2.5 cm at kp 6. Any error smaller than that asks for a push the drive cannot deliver, so it is where the robot can stop.

Three ways out, in increasing order of respectability:

  1. Raise the gain, and accept what comes with it: overshoot on a fast loop like the turn, a twitchy command on a noisy sensor.
  2. Add a dead band and declare that close enough is close enough.
  3. 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

  1. Start low, say 1.
  2. Double it until the robot overshoots or wobbles.
  3. 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

  1. In the turning cell, find the gain at which it first overshoots, to the nearest 0.5.
  2. Find the gain below which the distance loop never gets within 5 cm. Explain it with the dead band.
  3. Run the same gain from 100 cm away and from 5 cm away. Does the same number suit both?