Skip to content
BetaDE
Lesson 4 of 8Robotics with LEGO SPIKE

Robotics with LEGO SPIKE · Lesson 4 · 30 minutes

Following lines with a P controller

What you'll learn

  • Understand why on/off steering jerks around
  • Build a P controller from error and gain
  • Tune KP and speed sensibly

The code in this lesson comes from the book “Python for Robots”, chapter 2.8, and runs unchanged in the simulator. German edition on Amazon ↗

The problem with on and off

A line follower should drive along a black line. The simplest idea: “If I see black, steer left, otherwise steer right.” That works – but the robot keeps jerking back and forth.

A controller is better: it steers hard when the robot is far off the line and gently when it’s almost right.

The P controller

The colour sensor sits exactly on the edge of the line. There it measures a medium brightness, the target value. The difference from it is called the error. The P controller simply calculates steering = KP * error.

from pybricks.hubs import PrimeHub
from pybricks.pupdevices import Motor, ColorSensor
from pybricks.parameters import Port, Direction
from pybricks.robotics import DriveBase
from pybricks.tools import wait

hub = PrimeHub()

left_motor = Motor(Port.A, positive_direction=Direction.COUNTERCLOCKWISE)
right_motor = Motor(Port.B, positive_direction=Direction.CLOCKWISE)
color_sensor = ColorSensor(Port.C)
drive_base = DriveBase(left_motor, right_motor,
                     wheel_diameter=56, axle_track=114)

TARGET_BRIGHTNESS = 50        # sensor value exactly at the line's edge (0-100)
KP = 1.2                      # the larger, the stronger the correction
BASE_SPEED = 120              # mm/s - better to start slowly!

while True:
    brightness = color_sensor.reflection()
    error_value = brightness - TARGET_BRIGHTNESS   # deviation from the line's edge
    steering = KP * error_value                    # only the P component
    drive_base.drive(BASE_SPEED, steering)
    wait(10)

Press Run and watch the robot follow the line. Then experiment:

ChangeWhat happens?
KP = 0.3The robot reacts too weakly and loses the line in the curve.
KP = 6It corrects very hard. On a real robot it often starts to wobble.
BASE_SPEED = 250Faster – KP = 1.2 still just about works. At 300 it doesn’t (see the task).

Remember: drive_base.drive(speed, turn_rate) keeps driving until a new command comes. That’s why it fits perfectly into a control loop.

See the readings: In the example above, add print("error:", error_value, "steering:", steering) below the steering line (same indentation). The simulator then draws the values as curves while the robot drives – so you can see how much it swings back and forth with KP = 6. With 💾 CSV you save the readings for a spreadsheet (the file stays on your device).

The book then continues with the PID controller, which also learns from the past (I) and from the change (D) – so the robot drives fast and cleanly even through tight curves.

Your task

Your turn

The line follower should get faster: set BASE_SPEED to 300. Then the old KP is no longer enough – find a value that keeps the robot on the line anyway. The check lets it drive for one minute.

from pybricks.hubs import PrimeHub
from pybricks.pupdevices import Motor, ColorSensor
from pybricks.parameters import Port, Direction
from pybricks.robotics import DriveBase
from pybricks.tools import wait

hub = PrimeHub()

left_motor = Motor(Port.A, positive_direction=Direction.COUNTERCLOCKWISE)
right_motor = Motor(Port.B, positive_direction=Direction.CLOCKWISE)
color_sensor = ColorSensor(Port.C)
drive_base = DriveBase(left_motor, right_motor,
                     wheel_diameter=56, axle_track=114)

TARGET_BRIGHTNESS = 50        # sensor value exactly at the line's edge (0-100)
KP = 1.2                      # the larger, the stronger the correction
BASE_SPEED = 120              # mm/s - better to start slowly!

while True:
    brightness = color_sensor.reflection()
    error_value = brightness - TARGET_BRIGHTNESS   # deviation from the line's edge
    steering = KP * error_value                    # only the P component
    drive_base.drive(BASE_SPEED, steering)
    wait(10)

Solve it without hints to earn a ⭐ on your progress page.

Show solution

Try it yourself first – mistakes teach you the most. Here is one possible solution:

from pybricks.hubs import PrimeHub
from pybricks.pupdevices import Motor, ColorSensor
from pybricks.parameters import Port, Direction
from pybricks.robotics import DriveBase
from pybricks.tools import wait

hub = PrimeHub()

left_motor = Motor(Port.A, positive_direction=Direction.COUNTERCLOCKWISE)
right_motor = Motor(Port.B, positive_direction=Direction.CLOCKWISE)
color_sensor = ColorSensor(Port.C)
drive_base = DriveBase(left_motor, right_motor,
                     wheel_diameter=56, axle_track=114)

TARGET_BRIGHTNESS = 50        # sensor value exactly at the line's edge (0-100)
KP = 3                        # the larger, the stronger the correction
BASE_SPEED = 300              # mm/s - better to start slowly!

while True:
    brightness = color_sensor.reflection()
    error_value = brightness - TARGET_BRIGHTNESS   # deviation from the line's edge
    steering = KP * error_value                    # only the P component
    drive_base.drive(BASE_SPEED, steering)
    wait(10)