What a controller is

The error and its sign, wrapping headings, bang-bang control, overshoot.

3.1ControlRobot club15 min

Do this lesson in the simulator

Module 2 ended with the robot correcting its drift: look, compare, adjust. That loop has a name, a controller, and this module is about doing it properly. It starts with the one number every controller is built on: the error.

The error

Error means "how far from what I want", with a sign:

error = target - measured

Positive means you are short of the target; negative means you are past it. The sign tells you which way to go, the size tells you how far.

The robot in this module starts facing 200 degrees and the target is 0. Careful: on a compass, 200 is not 200 away from 0, it is 160 the other way. Headings wrap, so the error has to wrap too:

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

def wrapped(h):
    # 200 becomes -160, 350 becomes -10
    return (h + 180) % 360 - 180

print("heading:", round(heading()))
print("error:", round(wrapped(0 - heading())))

Run this in the simulator

Run it. The error is 160: the shortest way to 0 is clockwise, to the right, 160 degrees. A positive error means turn right. Keep wrapped around; every heading controller needs it.

The simplest controller: bang-bang

Look at the sign and go that way at a fixed speed. That is called bang-bang control, and it is how a thermostat works:

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

def wrapped(h):
    return (h + 180) % 360 - 180

error = wrapped(0 - heading())
while abs(error) > 2:
    if error > 0:
        # spin clockwise on the spot at 40
        turn_right(40)
    else:
        # spin anticlockwise on the spot at 40
        turn_left(40)
    # pause 0.1 s (the robot keeps doing what it was told)
    wait(0.1)
    error = wrapped(0 - heading())
# all motors off
stop()
# pause 0.5 s (the robot keeps doing what it was told)
wait(0.5)
print("stopped facing", round(heading(), 1))

Run this in the simulator

Overshoot

It stopped, but not at 0. The loop says stop() at the moment the error is small, and the robot keeps turning for a moment after that. Coasting again, now in rotation. Faster spin, bigger overshoot.

The robot did exactly what you said. The controller is the problem: it does not know the robot coasts.

Fixing it the crude way

Go fast when far away, and when close go slowly, in short bursts, letting the robot settle before looking again:

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

def wrapped(h):
    return (h + 180) % 360 - 180

# again and again, for ever
while True:
    error = wrapped(0 - heading())
    if abs(error) <= 2:
        # leave the loop
        break
    speed = 60 if abs(error) > 25 else 20
    if error > 0:
        turn_right(speed)
    else:
        turn_left(speed)
    # pause 0.1 s (the robot keeps doing what it was told)
    wait(0.1)
    if abs(error) <= 25:
        # all motors off
        stop()
        # settle, then look again
        wait(0.3)
print("stopped with error", round(wrapped(0 - heading()), 1))

Run this in the simulator

It works, and it is slow at the end. Lesson 3.2 does it properly.

Task: face north

Spin until the robot faces 0 within 3 degrees, printing error: and the error every time round the loop. Do not drive anywhere.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

print("heading:", heading())

Challenges

  1. Count how many times the loop ran and print it at the end.
  2. Face 90, then 180, then 270, then 0 with the same loop and a for over the targets.
  3. Find the fastest spin speed that still stops within 3 degrees using the crude fix.