The most reliable follow line robot Arduino build abandons the inefficient L298N motor driver and basic bang-bang logic in favor of a TB6612FNG MOSFET driver and proportional (P) control. This guide targets the Arduino Nano V3 (ATmega328P) to minimize chassis weight while retaining full I/O capabilities. By the end of this build, your robot will smoothly track a 20mm black electrical tape line at 0.5 m/s without the jerky oscillation typical of beginner projects.
Hardware BOM & Sensor Specifications
Before cutting wires, you need the right components. The biggest mistake makers make with line followers is choosing sensors with the wrong focal distance or using a bipolar junction transistor (BJT) based motor driver that drops 2V across its H-bridge. The TB6612FNG uses MOSFETs, dropping only ~0.5V at 1A, preserving your battery voltage for the motors.
IR Sensor Module Comparison
Not all reflectance sensors are equal. Here is how the common modules compare for line tracking on standard matte surfaces.
| Sensor Module | Output Type | Optimal Height | Current Draw (per unit) | Approx. Price (2026) |
|---|---|---|---|---|
| Generic TCRT5000 (Analog) | Analog (0-5V) / Digital | 5mm - 15mm | ~20mA | $1.50 |
| Pololu QRE1113 (Analog) | Analog (RC decay) | 2mm - 8mm | ~17mA | $3.99 |
| Pololu QTR-3A Array | Analog (0-5V) | 3mm - 10mm | ~40mA (total) | $11.95 |
| Generic 5-Channel Digital Array | Digital (Potentiometer tuned) | 10mm - 20mm | ~50mA | $4.50 |
Recommendation: Use three discrete TCRT5000 analog modules spaced 15mm apart. They are cheap, easily replaceable, and provide the raw analog data needed for proportional control. For deep theory on IR reflectance physics, refer to the Pololu QTR Reflectance Sensor documentation.
Complete Parts List
- Microcontroller: Arduino Nano V3 (ATmega328P, 5V/16MHz)
- Motor Driver: TB6612FNG Dual Motor Driver Carrier (e.g., Pololu #713)
- Sensors: 3x TCRT5000 Analog IR Sensor Modules
- Chassis: 2WD Acrylic Robot Chassis Kit with TT micro gearmotors (1:48 gear ratio)
- Power: 2S LiPo Battery (7.4V nominal, 800mAh+) or 4x AA NiMH (4.8V-6.0V)
- Wiring: 22 AWG stranded silicone wire (logic), 18 AWG stranded (power/motors)
- Hardware: M3 brass standoffs, heat shrink tubing, double-sided foam tape
Pin Mapping & Chassis Wiring Steps
Wiring a TB6612FNG requires attention to the standby (STBY) pin. If left floating, the driver will randomly shut down. Tie it directly to VCC or a dedicated GPIO pin held HIGH.
Pin Mapping Table
| Component | Module Pin | Arduino Nano Pin | Notes |
|---|---|---|---|
| Left Sensor | AO (Analog Out) | A0 | Do not use DO (Digital Out) |
| Center Sensor | AO | A1 | Primary line tracker |
| Right Sensor | AO | A2 | Do not use DO |
| TB6612FNG | PWMA | D5 (PWM) | Left motor speed |
| TB6612FNG | AIN1 / AIN2 | D4 / D7 | Left motor direction |
| TB6612FNG | PWMB | D6 (PWM) | Right motor speed |
| TB6612FNG | BIN1 / BIN2 | D8 / D12 | Right motor direction |
| TB6612FNG | STBY | 5V (or D9) | Must be HIGH to operate |
| TB6612FNG | VCC | 5V | Logic voltage |
| TB6612FNG | VM | Battery (+) | Motor power (up to 15V) |
Numbered Wiring Procedure
- Mount the Sensors: Attach the three TCRT5000 modules to the front of the chassis using M3 standoffs. The IR LEDs must be exactly 10mm to 12mm above the floor. Use a caliper to verify this; even 5mm of deviation will ruin the analog gradient.
- Wire the Motor Driver: Solder 18 AWG wires from the battery connector to the TB6612FNG VM and PGND pins. Add a 100µF electrolytic capacitor across VM and PGND to suppress motor voltage spikes.
- Establish a Common Ground: Connect the Arduino Nano GND, the TB6612FNG PGND, the sensor module GNDs, and the battery negative terminal to a single common ground bus. Skipping this is the #1 cause of erratic robot behavior.
- Route Logic Wires: Run 22 AWG stranded wires from the Nano digital/analog pins to the driver and sensors. Secure them with kapton tape to prevent them from catching in the motor gears.
- Verify Voltages: Before plugging in the Nano, use a multimeter to check the battery voltage at the TB6612FNG VM pin. Ensure it reads between 4.5V and 10.8V.
Complete Compilable Arduino Code
This code uses Proportional (P) control. Instead of simply turning left or right when a digital sensor triggers (bang-bang control), it calculates the exact position of the line relative to the center sensor and adjusts motor speeds proportionally. This results in smooth, sweeping turns.
/*
* Follow Line Robot Arduino - Proportional Control
* Target: Arduino Nano V3 (ATmega328P)
* Driver: TB6612FNG
* Sensors: 3x TCRT5000 Analog
*/
// --- Pin Definitions ---
#define SENSOR_L_PIN A0
#define SENSOR_C_PIN A1
#define SENSOR_R_PIN A2
#define MOTOR_L_PWM 5
#define MOTOR_L_DIR1 4
#define MOTOR_L_DIR2 7
#define MOTOR_R_PWM 6
#define MOTOR_R_DIR1 8
#define MOTOR_R_DIR2 12
#define STBY_PIN 9
// --- Control Constants ---
const int BASE_SPEED = 180; // Base PWM value (0-255)
const float KP = 25.0; // Proportional gain (tune this!)
const int SENSOR_THRESHOLD = 50; // Minimum analog value to consider "valid"
// Variables for sensor readings
int sensorL, sensorC, sensorR;
float position, error, motorSpeedL, motorSpeedR;
void setup() {
Serial.begin(115200);
// Initialize Motor Pins
pinMode(MOTOR_L_PWM, OUTPUT);
pinMode(MOTOR_L_DIR1, OUTPUT);
pinMode(MOTOR_L_DIR2, OUTPUT);
pinMode(MOTOR_R_PWM, OUTPUT);
pinMode(MOTOR_R_DIR1, OUTPUT);
pinMode(MOTOR_R_DIR2, OUTPUT);
pinMode(STBY_PIN, OUTPUT);
// Enable TB6612FNG
digitalWrite(STBY_PIN, HIGH);
// Set initial direction (Forward)
digitalWrite(MOTOR_L_DIR1, HIGH);
digitalWrite(MOTOR_L_DIR2, LOW);
digitalWrite(MOTOR_R_DIR1, HIGH);
digitalWrite(MOTOR_R_DIR2, LOW);
Serial.println("System Initialized. Place robot on line.");
delay(1000);
}
void loop() {
// 1. Read Sensors (invert logic: high value = white, low value = black)
// We invert it so black line = high numerical weight for easier math
sensorL = 1023 - analogRead(SENSOR_L_PIN);
sensorC = 1023 - analogRead(SENSOR_C_PIN);
sensorR = 1023 - analogRead(SENSOR_R_PIN);
// 2. Error Handling: Check for sensor saturation or disconnection
if (sensorL < SENSOR_THRESHOLD && sensorC < SENSOR_THRESHOLD && sensorR < SENSOR_THRESHOLD) {
Serial.println("ERR: SENSOR_SATURATION - All sensors read low. Check ambient light or height.");
stopMotors();
delay(500);
return;
}
// 3. Calculate Line Position (Weighted Average)
// Position ranges from -1.0 (far left) to +1.0 (far right)
int totalWeight = sensorL + sensorC + sensorR;
if (totalWeight > 0) {
position = (float)(sensorR - sensorL) / totalWeight;
} else {
position = 0; // Fallback if line is completely lost
}
// 4. Calculate Error and Motor Speeds
// Setpoint is 0 (center). Error is current position.
error = position;
motorSpeedL = BASE_SPEED + (KP * error);
motorSpeedR = BASE_SPEED - (KP * error);
// 5. Constrain Speeds to valid PWM range
motorSpeedL = constrain(motorSpeedL, 0, 255);
motorSpeedR = constrain(motorSpeedR, 0, 255);
// 6. Apply PWM to Motors
analogWrite(MOTOR_L_PWM, (int)motorSpeedL);
analogWrite(MOTOR_R_PWM, (int)motorSpeedR);
// Debug output (throttled to avoid serial buffer flooding)
static unsigned long lastPrint = 0;
if (millis() - lastPrint > 100) {
Serial.print("Pos: "); Serial.print(position, 2);
Serial.print(" | L: "); Serial.print((int)motorSpeedL);
Serial.print(" | R: "); Serial.println((int)motorSpeedR);
lastPrint = millis();
}
}
void stopMotors() {
analogWrite(MOTOR_L_PWM, 0);
analogWrite(MOTOR_R_PWM, 0);
}
Debugging: When the Robot Spins in Circles
Line followers are notorious for failing on the first power-up. Before rewriting the code, check the physical layer. Here are the first three things to check when it fails:
- Common Ground Integrity: Measure the voltage between the Arduino Nano GND pin and the TB6612FNG PGND pin while the motors are running. It should read < 0.05V. If it reads higher, your ground wire is too thin or loose, causing the analog sensor readings to float wildly.
- Motor Direction Logic: If the robot immediately spins in a circle or drives backward, your H-bridge direction pins are inverted. Swap the AIN1/AIN2 wires at the Nano, or simply swap the physical motor wires at the TB6612FNG output terminals.
- Sensor Threshold Calibration: Open the Arduino IDE Serial Plotter. Place the robot over the white surface, then over the black tape. If the white surface reads 900 and the black tape reads 850, your contrast is too low. Clean the floor, use matte black tape (not glossy vinyl), and lower the sensors by 2mm.
Exact Error Strings & Ranked Causes
If you are monitoring the Serial output and encounter specific errors, use this decision tree:
Error String: ERR: SENSOR_SATURATION - All sensors read low.
- Cause 1 (Most Likely): Direct sunlight or high ambient IR is blinding the TCRT5000 phototransistors. Fix: Move indoors or shield the sensors with a 3D-printed shroud.
- Cause 2: Sensors are mounted too high (>15mm). The IR beam disperses before hitting the ground. Fix: Lower the standoffs.
- Cause 3: The IR LED current-limiting resistor on the generic module is too large (some cheap clones use 10kΩ instead of 1kΩ). Fix: Replace the resistor on the sensor board with a 1kΩ or 470Ω resistor to boost LED brightness.
Compiler Error: error: 'A3' was not declared in this scope (or similar pin mapping errors)
- Cause: You selected the wrong board in the Arduino IDE. Fix: Ensure Tools > Board is set to "Arduino Nano", and Processor is set to "ATmega328P". If using a cheap clone Nano, you may need to select "ATmega328P (Old Bootloader)".
Scaling the Build: Simplify or Extend
Once your follow line robot Arduino is reliably tracking the tape, you can adapt the platform to match your skill level or project requirements.
How to Simplify the Build
If you are teaching a beginner class or lack a TB6612FNG, you can strip this down to a basic digital bang-bang controller. Swap the analog TCRT5000 modules for digital-only 3-pin IR sensors (which have a built-in potentiometer to set a hard threshold). Change the code to simple if/else statements: if the left sensor sees black, stop the left motor; if the right sees black, stop the right motor. You will lose the smooth P-control sweeping, but the code complexity drops by 80%.
How to Extend the Build
For advanced makers looking to compete in line-following races, P-control is just the beginning. Extend the build by adding Derivative (D) control to form a full PD controller. The derivative term measures the rate of change of the error, allowing the robot to anticipate sharp corners and brake the inside wheel before it overshoots the tape. Additionally, swap the Arduino Nano for an ESP32-S3. This allows you to implement dual-core processing: Core 0 handles the high-frequency PID motor control loop (running at 1000Hz), while Core 1 handles a high-speed analogRead array and wireless telemetry, streaming live sensor graphs to a web dashboard via WebSocket.






