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.
IntermediateRuns in your browser. Free, and no account needed.
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);
}
Parts list
6 parts, plus the jumper wires. Every one is in the editor's parts bin.
How it is wired
22 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Ground: Arduino Uno R3 pin GND; Motor driver, TB6612FNG pin GND1
- Arduino Uno R3 pin 9; Motor driver, TB6612FNG pin STBY
- Arduino Uno R3 pin 8; Motor driver, TB6612FNG pin BIN2
- Arduino Uno R3 pin 7; Motor driver, TB6612FNG pin BIN1
- Arduino Uno R3 pin 6; Motor driver, TB6612FNG pin PWMB
- Arduino Uno R3 pin 5; Motor driver, TB6612FNG pin PWMA
- Arduino Uno R3 pin 4; Motor driver, TB6612FNG pin AIN2
- Arduino Uno R3 pin 2; Motor driver, TB6612FNG pin AIN1
- Arduino Uno R3 pin IOREF; Line tracker, 5 channel pin VCC
- Arduino Uno R3 pin 5V; Motor driver, TB6612FNG pin VCC
- Ground: Arduino Uno R3 pin GND; Line tracker, 5 channel pin GND
- Arduino Uno R3 pin A0; Line tracker, 5 channel pin S5
- Arduino Uno R3 pin A1; Line tracker, 5 channel pin S4
- Arduino Uno R3 pin A2; Line tracker, 5 channel pin S3
- Arduino Uno R3 pin A3; Line tracker, 5 channel pin S2
- Arduino Uno R3 pin A4; Line tracker, 5 channel pin S1
- Motor driver, TB6612FNG pin VM; Battery, 9 V, AA or 18650 pin V+
- Motor driver, TB6612FNG pin AO1; DC motor (1) pin 1
- Motor driver, TB6612FNG pin AO2; DC motor (1) pin 2
- Motor driver, TB6612FNG pin BO2; DC motor (2) pin 2
- Motor driver, TB6612FNG pin BO1; DC motor (2) pin 1
- Motor driver, TB6612FNG pin GND3; Battery, 9 V, AA or 18650 pin V-
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.