Line following robots explained
How a line following robot works: find the line, measure the error, turn to correct it. Run bang-bang, proportional and PD line follower code on a live robot, see why it zig-zags, how to make it faster, and what to do when it loses the line.
A line following robot drives along a dark line on a light floor. It is the first robot many people build, and the same idea still guides robots round factories and warehouses. On this page a small robot follows a taped line that jogs to the right and then back to the left, and each demo below is a real program you can change and run.
The chart under each robot shows the error: how far the line is from the middle of the camera picture, in pixels. The picture is 320 pixels wide, so 0 means the line is dead ahead and 160 means it is at the edge of the view. Plus is to the right, minus to the left. A good follower keeps that line close to zero, and only moves off it when the track bends.
How a line follower works
Every line follower runs the same loop, over and over:
- Sense where the line is.
- Work out the error: how far the line is from where you want it, which is straight ahead.
- Turn to make the error smaller, and keep driving forward.
error = where the line is - the middle
turn = Kp × error + Kd × (how fast the error is changing)
The robot on this page does that ten times a second. Everything else on this page is about the second line: how hard to turn for a given error.
Most hobby line followers sense the line with infrared reflectance sensors pointing at the floor. A dark line reflects less light than the floor around it. Two sensors, one each side of the line, can only say "left", "right" or "centred". A row of five or eight sensors can say how far off centre the line is, by taking an average of the sensor positions weighted by how dark each one reads. The robot here uses a camera instead: line() returns [cx, angle], where cx is where the line crosses the picture about 10 cm in front of the robot. Both kinds of sensor give you the same thing in the end, one number for where the line is.
Bang-bang: hard left or hard right
The simplest rule looks only at which side the line is on. Line to the right, turn right. Line to the left, turn left. Always by the same amount. This is bang-bang control, and it is what a robot with two sensors does.
The program
from bugbot import *
connect()
# change these two numbers and press Run
SPEED = 60 # forward, percent
TURN = 50 # always turn this hard
set_cv("line") # camera: find the line
while position()[1] < 85: # to the far end
seen = line() # [cx, angle] or []
if seen:
error = seen[0] - 160 # + means right
plot("error", error)
if error > 0:
drive(SPEED, 0, TURN)
else:
drive(SPEED, 0, -TURN)
wait(0.1) # 10 times a second
stop()
print("lap", clock(), "s")
It gets there, but watch the robot: it never drives straight, even on the straight parts, because it is always turning one way or the other. By the time it has turned back towards the line it has already turned too far, so it crosses over and has to turn back again. That is the zig-zag every bang-bang follower has.
Make TURN smaller and it cannot turn hard enough for the bends. Try SPEED = 100 and TURN = 20: it gets round the first bend, runs wide at the second, loses the line and never reaches the green zone.
Proportional: turn in proportion to the error
A better rule turns gently when the line is a little off centre and hard when it is a long way off. That is proportional control, the P in PID:
turn = Kp × error
Kp is the gain: how much turn you get for each pixel of error. Here it is 0.2, so a line 50 pixels to the right asks for a turn of 10.
The program
from bugbot import *
connect()
# change these three numbers and press Run
SPEED = 60
KP = 0.2
KD = 0.0
set_cv("line") # camera: find the line
last = 0
while position()[1] < 85: # to the far end
seen = line() # [cx, angle] or []
if seen:
error = seen[0] - 160 # + means right
change = (error - last) / 0.1
last = error
plot("error", error)
turn = KP * error + KD * change
drive(SPEED, 0, turn)
wait(0.1) # 10 times a second
stop()
print("lap", clock(), "s")
It is too gentle because of the motors. Below about 15 percent this robot does not turn at all, so with Kp = 0.2 nothing happens until the error reaches 75 pixels (0.2 × 75 = 15). By then the robot is well into the bend, and it swings wide. The PID guide shows the same dead band stopping a robot short of its target.
A gain that works
Raise Kp to 1. Now a 15 pixel error is already enough to turn, and a 50 pixel error turns at 50.
The program
from bugbot import *
connect()
# change these three numbers and press Run
SPEED = 60
KP = 1.0
KD = 0.0
set_cv("line") # camera: find the line
last = 0
while position()[1] < 85: # to the far end
seen = line() # [cx, angle] or []
if seen:
error = seen[0] - 160 # + means right
change = (error - last) / 0.1
last = error
plot("error", error)
turn = KP * error + KD * change
drive(SPEED, 0, turn)
wait(0.1) # 10 times a second
stop()
print("lap", clock(), "s")
Now try the other way: KP = 6. The largest error gets smaller, but the error flutters from side to side and crosses zero 19 times, nearly as many as bang-bang. With a gain of 6, an error of 17 pixels already asks for a full turn of 100, so almost every correction is a full one, and a proportional controller with too much gain behaves like bang-bang. Somewhere between too gentle and too keen is a gain that follows the bends without the wobble.
Going faster
Keep Kp = 1 and raise the speed to 100. The lap is quicker, but the bends now arrive faster than the robot turns into them.
The program
from bugbot import *
connect()
# change these three numbers and press Run
SPEED = 100
KP = 1.0
KD = 0.0
set_cv("line") # camera: find the line
last = 0
while position()[1] < 85: # to the far end
seen = line() # [cx, angle] or []
if seen:
error = seen[0] - 160 # + means right
change = (error - last) / 0.1
last = error
plot("error", error)
turn = KP * error + KD * change
drive(SPEED, 0, turn)
wait(0.1) # 10 times a second
stop()
print("lap", clock(), "s")
The robot falls behind because of delay. It only looks ten times a second, and the motors take a moment to change how fast it is turning. At a higher speed it travels further in that time, so the line has moved further across the picture before the turn takes hold. Try KP = 0.4 at this speed: it loses the line and strays from it for 40 percent of the run.
Add D: see the bend coming
Keep Kp = 1 at speed 100 and add Kd = 0.8. The D term looks at how fast the error is changing. As the robot reaches a bend, the line starts to slide across the picture, so the change is large while the error itself is still small. D turns the robot into the bend early, before P would.
The program
from bugbot import *
connect()
# change these three numbers and press Run
SPEED = 100
KP = 1.0
KD = 0.8
set_cv("line") # camera: find the line
last = 0
while position()[1] < 85: # to the far end
seen = line() # [cx, angle] or []
if seen:
error = seen[0] - 160 # + means right
change = (error - last) / 0.1
last = error
plot("error", error)
turn = KP * error + KD * change
drive(SPEED, 0, turn)
wait(0.1) # 10 times a second
stop()
print("lap", clock(), "s")
This is a PD controller, and it is what most competitive line followers use. The I term is usually left out, because a line that keeps bending rarely leaves a steady error for the integral to remove. On a robot whose sensor reading jumps about, a large Kd also makes it twitch, because D reacts to every jump.
What makes a line follower fast and stable
- Gain matched to speed. The faster the robot, the more it needs to turn for each pixel of error, or it falls behind in the bends.
- Some D. It lets the robot turn into a bend before the error has grown.
- A fast loop. The less time between looking and turning, the less the line moves in between. With
SPEED = 100andKP = 3on this mat, slowing the loop from ten times a second to about three raised the largest error from 41 to 133 pixels. - Looking ahead. A sensor that sees the line further in front gives the robot more warning of a bend, but it also makes the robot cut the corners. This camera looks about 10 cm ahead. The line detector also reports
angle, which way the line is heading, and the lesson Following a line uses it to turn earlier still. - Not turning too hard. Too much gain, or bang-bang, wastes time swinging from side to side.
When the robot loses the line
A robot that goes into a bend too fast can run off the outside of it, and then line() returns []. There is no error to work from, so the program has to decide what to do. The most useful thing it still knows is the last error it saw. The line left the picture on that side, so that is the way to turn.
Here the robot is at full speed with Kp = 0.4, a gain too low for that speed, so it overshoots the second bend.
The program
from bugbot import *
connect()
# change these two numbers and press Run
SEARCH = 100 # turn when lost (0 = none)
CREEP = 20 # forward speed when lost
SPEED = 100
KP = 0.4 # too low for this speed
KD = 0.0
set_cv("line")
last = 0
lost = 0 # ticks without a line
while position()[1] < 85:
seen = line()
if seen:
lost = 0
error = seen[0] - 160
change = (error - last) / 0.1
last = error
plot("error", error)
turn = KP * error + KD * change
drive(SPEED, 0, turn)
else:
lost += 1
# turn to the side it was last seen on
if last > 0:
drive(CREEP, 0, SEARCH)
else:
drive(CREEP, 0, -SEARCH)
plot("lost", lost)
wait(0.1)
stop()
print("lap", clock(), "s")
The second line on the chart counts the ticks since the line was last seen. Now try SEARCH = 0, so the robot just creeps straight on when it loses the line. The error on the chart stops at the last value it saw, the count keeps climbing, and the robot wanders off to 28 cm from the line without reaching the green zone.
Gaps in the line are the other common way to lose it. There, carrying straight on for a moment is usually right, because the line starts again ahead. Ten centimetres ahead this camera sees about 17 cm either side, so a line that starts again a few centimetres to one side is found by carrying straight on. On the gap in the lesson Losing the line, the line starts again 22 cm to one side, out of view, and the robot has to search for it. A robot with a narrow row of sensors would need to search for much smaller offsets.
Arduino, ESP32 and other robots
The same Python runs on the BugBot robot, whose camera finds the line in the same way. The only line that would change is while position()[1] < 85, which uses the simulator's overhead view of the mat to know when the run is over.
If you are building a line follower on an Arduino or an ESP32, the language is different but the idea and the maths are the same. Read the sensors, work out the error, and work out turn = Kp × error + Kd × change in a loop that runs at a steady rate. On a two-wheeled robot the turn becomes the difference between the wheels: left motor at speed + turn, right motor at speed - turn. The tuning steps below work the same way.
How to tune a line follower
- Start slowly, with
Kdat 0 and a smallKp. - Raise
Kpuntil the robot follows the bends without swinging wide. If it starts to wobble on the straights, back off a little. - Raise the speed until it starts to fall behind in the bends.
- Add
Kduntil it turns into the bends cleanly again. If it starts to twitch, you have gone too far. - Repeat 3 and 4 until it stops getting faster.
- Watch the chart of the error, not only the robot. A good tune stays close to zero on the straights and comes back to zero quickly after each bend.
Questions
How does a line following robot work?
It senses where the line is, works out how far that is from straight ahead (the error), and turns to make the error smaller while it keeps driving forward. It repeats this many times a second. How hard it turns for a given error is the part you tune.
What sensors does a line follower use?
Most use infrared reflectance sensors under the front of the robot, because a dark line reflects less light than the floor. Two sensors tell you which side the line is on. A row of five to eight tells you how far off centre it is. Some robots, including the BugBot, use a camera and find the line in the picture.
How does a line follower with two sensors work?
Each sensor sits just to one side of the line. When the left sensor sees the line, the robot turns left, and when the right one sees it, the robot turns right. That is bang-bang control, and it always zig-zags a little because it can only turn one amount.
How do you use PID in a line follower?
Use the line's distance from the centre as the error, and set the turn to Kp × error + Kd × (how fast the error is changing). Most line followers use only P and D. Kp sets how hard it steers back towards the line, and Kd makes it turn into bends early and damps the swinging. The I term is usually left at zero.
How do you make a line follower faster?
Raise the speed a step at a time and raise Kp or add Kd each time it starts to fall behind in the bends. Run the control loop as often as the sensors allow, and let the sensors see as far ahead as you can without the robot cutting corners.
Why does my line follower wobble or zig-zag?
Either it is bang-bang, so it can only turn fully one way or the other, or Kp is too high, which does much the same thing. A slow loop and slow motors make it worse, because the robot turns past the line before it sees that it has. Lower Kp, add Kd, or run the loop faster.
Why does my line follower run wide on bends?
Kp is too low for the speed, or the motors do not respond to small turns at all, so the robot only starts turning once the line is well off centre. Raise Kp, add Kd, or slow down.
What happens when a line follower loses the line?
The sensors have nothing to report, so the controller has no error to work from and the program has to decide what to do. A common plan is to carry on for a moment in case it is a gap, then search: turn towards the side where the line was last seen, or sweep from side to side, and go back to following as soon as the line reappears.
What is a good Kp and Kd for a line follower?
There is no single answer, because they depend on the robot's speed, its motors, its sensors and the units of the error. On this page Kp = 1 and Kd = 0.8 work at full speed, with the error in pixels. On your own robot, tune them with the steps above.
Can I write line follower code for Arduino from this?
Yes. The loop, the error and the PD formula are the same in any language. Read your sensors to get the error, work out the turn, and set the left motor to the speed plus the turn and the right motor to the speed minus the turn.
Learn it step by step
These lessons build the same ideas one at a time, each with tasks the simulator marks.
- 6.1 Lines Seeing more, Robot club
- 6.2 Following a line Seeing more, Robot club
- 6.3 Losing the line Seeing more, Robot club
- 6.8 Project: the delivery Seeing more, Robot club
- U10.3 Cross-track error Following a trajectory, University
- U10.4 Pure pursuit Following a trajectory, University