Line-following cart on an Arduino Uno
A five-channel line tracker, a TB6612FNG and two gearbox motors on a 2S 18650 pack: the cart steers to keep the tape under its middle sensor and stops at the finish bar. The whole build, an Arduino Uno and 5 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 40 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $39. On Mokxi they're free.
Step by step, with the wiring and the common mistakes: Arduino Line Follower Robot: Code and Circuit
What you'll learn
- Finding a line's position from several sensors
- Steering with proportional and derivative gains
- Driving two motors through a TB6612FNG
- Limiting speed to protect motors on a higher voltage
- Handling lost-line and finish cases
- Difficulty: Intermediate
- Time: About 40 minutes
- Board: Arduino Uno R3
- Parts: 6
How it works
A five-channel line tracker on A4 to A0 reports which sensors are over the black line. Each sensor is 15 mm from the next, so the sketch averages the positions of the ones that see the line. That gives an error in millimeters: 0 means the line is under the middle sensor.
The error steers the cart. The sketch slows the wheel on the side the line is on and speeds up the other. It uses KP for the error and KD for how fast the error changes, which stops the cart from weaving. The TB6612FNG driver runs the two motors from the battery pack.
If no sensor sees the line, the cart turns toward where it last saw it. If all five see black at once, that is the finish bar and the cart stops.
Parts list
6 parts, plus the jumper wires. They retail for about $39; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Line tracker, 5 channel
- 1 × Motor driver, TB6612FNG
- 2 × DC motorIn Robot Car Kit.
- 1 × Battery: AA, coin cell, LiPoIn Handheld Console Kit, Laser Sword Kit, Power Bank Kit.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Motor driver, TB6612FNG pin GND1 | Arduino Uno R3 pin GND |
| Motor driver, TB6612FNG pin STBY | Arduino Uno R3 pin 9 |
| Motor driver, TB6612FNG pin BIN2 | Arduino Uno R3 pin 8 |
| Motor driver, TB6612FNG pin BIN1 | Arduino Uno R3 pin 7 |
| Motor driver, TB6612FNG pin PWMB | Arduino Uno R3 pin 6 |
| Motor driver, TB6612FNG pin PWMA | Arduino Uno R3 pin 5 |
| Motor driver, TB6612FNG pin AIN2 | Arduino Uno R3 pin 4 |
| Motor driver, TB6612FNG pin AIN1 | Arduino Uno R3 pin 2 |
| Line tracker, 5 channel pin VCC | Arduino Uno R3 pin IOREF |
| Motor driver, TB6612FNG pin VCC | Arduino Uno R3 pin 5V |
| Line tracker, 5 channel pin GND | Arduino Uno R3 pin GND |
| Line tracker, 5 channel pin S5 | Arduino Uno R3 pin A0 |
| Line tracker, 5 channel pin S4 | Arduino Uno R3 pin A1 |
| Line tracker, 5 channel pin S3 | Arduino Uno R3 pin A2 |
| Line tracker, 5 channel pin S2 | Arduino Uno R3 pin A3 |
| Line tracker, 5 channel pin S1 | Arduino Uno R3 pin A4 |
| Battery: AA, coin cell, LiPo pin V+ | Motor driver, TB6612FNG pin VM |
| DC motor (1) pin 1 | Motor driver, TB6612FNG pin AO1 |
| DC motor (1) pin 2 | Motor driver, TB6612FNG pin AO2 |
| DC motor (2) pin 2 | Motor driver, TB6612FNG pin BO2 |
| DC motor (2) pin 1 | Motor driver, TB6612FNG pin BO1 |
| Battery: AA, coin cell, LiPo pin V- | Motor driver, TB6612FNG pin GND3 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Line-following cart: a five-channel line tracker across the front, two
// gearbox motors on a TB6612FNG, and two 18650 cells for the motors.
//
// A4..A0 the tracker's S1..S5, left to right (high over the black line):
// the board sits turned around under the Uno, so S1 lands on A4
// pin 5 PWMA, the left motor's speed pin 6 PWMB, the right motor's
// pin 2 AIN1 pin 4 AIN2 pin 7 BIN1 pin 8 BIN2
// pin 9 STBY
// VM the 2S pack's +, 8.4 V full; VCC the Uno's 5 V; all grounds joined
//
// # Where the line is
//
// The sensors are 15 mm apart. Each one over the tape counts its place, -2 at
// S1 to +2 at S5, and the line's position is the average of the ones that see
// it, in millimeters: 0 under S3, +7 between S3 and S4, +30 under S5.
//
// # Steering
//
// The error is that position. The cart slows the wheel on the side the line
// is on and speeds up the other, by KP x error plus KD x how fast the error
// is changing (the damping that stops it weaving). With the line to the right
// the left wheel runs faster, and the cart turns right, back over it.
//
// Lose the line and it keeps turning the way it last saw it. All five black
// at once is the finish bar across the track: it stops.
//
// # Speed and the battery
//
// Two full 18650s are 8.4 V and these are 6 V gearbox motors, so the sketch
// never asks for more than 180 of 255: about 5.9 V on average.
#include <SparkFun_TB6612.h>
// S1 (left) to S5 (right).
const int SENSORS[5] = {A4, A3, A2, A1, A0};
const int AIN1 = 2;
const int AIN2 = 4;
const int PWMA = 5;
const int BIN1 = 7;
const int BIN2 = 8;
const int PWMB = 6;
const int STBY = 9;
const int BASE = 120;
const int MAX_SPEED = 180;
const int SEARCH = 110;
// Steering gains: counts of PWM per mm of error, and per mm of change.
const int KP = 3;
const int KD = 6;
Motor leftMotor = Motor(AIN1, AIN2, PWMA, 1, STBY);
Motor rightMotor = Motor(BIN1, BIN2, PWMB, 1, STBY);
int lastError = 0;
bool finished = false;
unsigned long lastReport = 0;
int clampSpeed(int v) {
if (v > MAX_SPEED) return MAX_SPEED;
if (v < -MAX_SPEED) return -MAX_SPEED;
return v;
}
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: line-following cart");
for (int i = 0; i < 5; i++) pinMode(SENSORS[i], INPUT);
}
void report(const char *what, int error, int left, int right) {
if (millis() - lastReport < 200) return;
lastReport = millis();
Serial.print(what);
Serial.print(" ");
Serial.print(error);
Serial.print(" mm left ");
Serial.print(left);
Serial.print(" right ");
Serial.println(right);
}
void loop() {
if (finished) return;
int seen = 0;
int sum = 0;
for (int i = 0; i < 5; i++) {
if (digitalRead(SENSORS[i]) == HIGH) {
seen++;
sum += (i - 2) * 15;
}
}
if (seen == 5) {
brake(leftMotor, rightMotor);
finished = true;
Serial.println("finish bar: stopped");
return;
}
int left;
int right;
if (seen == 0) {
// Lost it: turn toward where it was last.
left = lastError >= 0 ? SEARCH : -SEARCH;
right = -left;
leftMotor.drive(left);
rightMotor.drive(right);
report("searching, last", lastError, left, right);
delay(10);
return;
}
int error = sum / seen;
int turn = KP * error + KD * (error - lastError);
lastError = error;
left = clampSpeed(BASE + turn);
right = clampSpeed(BASE - turn);
leftMotor.drive(left);
rightMotor.drive(right);
report("line", error, left, right);
delay(10);
}
Code walkthrough
The key functions of the sketch, and what each one does.
clampSpeed sketch.ino
Keeps a motor command between -MAX_SPEED and MAX_SPEED, which is 180 out of 255. The battery pack is 8.4 V when full and these are 6 V motors, so the cap keeps the average voltage near 5.9 V.
int clampSpeed(int v) {
if (v > MAX_SPEED) return MAX_SPEED;
if (v < -MAX_SPEED) return -MAX_SPEED;
return v;
}setup sketch.ino
Starts the Serial Monitor at 115200 and sets the five sensor pins as inputs. The motor pins are set up by the Motor objects.
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: line-following cart");
for (int i = 0; i < 5; i++) pinMode(SENSORS[i], INPUT);
}report sketch.ino
Prints the error and both motor speeds, but no more than once every 200 ms. This keeps the monitor readable while the loop runs about every 10 ms.
void report(const char *what, int error, int left, int right) {
if (millis() - lastReport < 200) return;
lastReport = millis();
Serial.print(what);
Serial.print(" ");
Serial.print(error);
Serial.print(" mm left ");
Serial.print(left);
Serial.print(" right ");
Serial.println(right);
}loop sketch.ino
Counts sensors that read HIGH and sums their positions. Five means finish: brake and stop. Zero means search. Otherwise it computes the error, adds KP times the error and KD times its change to get a turn value, and drives the left and right motors at BASE plus or minus that.
void loop() {
if (finished) return;
int seen = 0;
int sum = 0;
for (int i = 0; i < 5; i++) {
if (digitalRead(SENSORS[i]) == HIGH) {
seen++;
sum += (i - 2) * 15;
}
}
if (seen == 5) {
brake(leftMotor, rightMotor);
finished = true;
Serial.println("finish bar: stopped");
return;
}
int left;
int right;
if (seen == 0) {
// Lost it: turn toward where it was last.
left = lastError >= 0 ? SEARCH : -SEARCH;
right = -left;
leftMotor.drive(left);
rightMotor.drive(right);
report("searching, last", lastError, left, right);
// ... 14 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- The cart turns away from the line This project
The sensors may be reversed, or a motor's wires swapped. Check that S1 goes to A4 and S5 goes to A0. - The cart weaves back and forth This project
LowerKPor raiseKD. - Motors do nothing This project
Check STBY on pin 9 and that the battery pack's ground is joined to the Uno's ground. - The robot cannot tell the line from the floor. Line tracker
Keep the sensors about 2 mm to 10 mm above the surface. Test each channel on black and white, and adjust the trimmer. Shade the sensors from room light. - The robot follows the wrong way. Line tracker
Each output is HIGH over the black line. Check which channel is the left one and map it to the correct motor. Flip the steering in code if the car turns away from the line. - The motors do not move. Motor driver
Set STBY HIGH. Power the motors at VM (about 4.5 to 13.5 V) and logic at VCC. Join all grounds. Then set the direction pins and a PWM level on the PWM pin. - The motor stops hard instead of coasting. Motor driver
Both input pins HIGH, or PWM at 0, brakes the motor. To let it coast, set both inputs LOW with PWM high. Per channel the chip handles about 1.2 A continuous. - The motor does not spin, or the pin gets hot or the board resets when it is wired straight to a GPIO pin. DC motor
A pin gives about 20 mA and a motor needs far more. Drive it through a transistor or motor driver, with its own supply and a shared ground.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.