ESP32 Line Follower: a car that follows a black line on its own
Line following is the classic first robotics challenge. Two IR sensors and a roll of black tape are enough to make a car track a course — and to learn the core idea of correcting direction from sensor feedback.
1. Parts
| Part | Qty | Notes |
|---|---|---|
| Obstacle-car chassis (ESP32, L298N, motors, battery) | 1 | See the obstacle car |
| TCRT5000 IR line sensor module | 2 | The kind with a blue trimmer and a DO pin |
| Black electrical tape | 1 roll | 1.5–2 cm wide |
| White floor, poster board or foam board | The track |
2. How it works
Each TCRT5000 shines infrared at the floor. White reflects strongly, black tape barely at all, and the module's comparator turns that into a digital signal — most output HIGH over black.
Mount the two sensors at the front, straddling the line:
| Left | Right | Meaning | Action |
|---|---|---|---|
| white | white | Line is between them | Go straight |
| black | white | Car drifted right | Steer left |
| white | black | Car drifted left | Steer right |
| black | black | Crossing or overshoot | Keep turning the last way; stop if lost |
3. Wiring
Motors and L298N are wired exactly as in the obstacle car. Add the sensors:
| From | To | Notes |
|---|---|---|
| Left TCRT5000 DO | ESP32 GPIO 34 | GPIO 34/35 are input-only — ideal here |
| Right TCRT5000 DO | ESP32 GPIO 35 | |
| Both VCC | ESP32 3V3 | Powered at 3.3 V, the output needs no divider |
| Both GND | GND |
Mount the sensors about 1 cm above the floor, spaced slightly wider than the tape (about 2.5–3 cm).
4. Calibrate (the step people skip)
- Upload the code and lift the car.
- With a sensor over white, turn its blue trimmer until the module's LED just turns off.
- Over the black tape, the LED should turn on. Do both sensors.
- If it's the other way round, set
LINE_IS_HIGHtofalse.
5. Code
/*
* TinyBot Forge — ESP32 Line Follower (two IR sensors)
* https://tinybotforge.com/en/
*
* Hardware: ESP32 DevKit V1, 2x TCRT5000 IR line sensor modules, L298N, two TT motors
* Track: 1.5-2 cm black electrical tape on a white floor
* Toolchain: Arduino IDE 2.x + ESP32 board package 3.x
*
* Most TCRT5000 modules output HIGH on DO over the black line and LOW over white.
* If yours is the other way round, set LINE_IS_HIGH to false.
*/
const int PIN_SENSOR_L = 34; // GPIO34/35 are input-only: perfect for sensors
const int PIN_SENSOR_R = 35;
const bool LINE_IS_HIGH = true;
const int PIN_ENA = 25, PIN_IN1 = 26, PIN_IN2 = 27;
const int PIN_ENB = 33, PIN_IN3 = 32, PIN_IN4 = 13;
const int PWM_FREQ = 1000, PWM_BITS = 8;
const int SPEED_STRAIGHT = 150;
const int SPEED_FAST = 170; // outer wheel while correcting
const int SPEED_SLOW = -60; // inner wheel while correcting (slightly reversed for a sharper turn)
const unsigned long LOST_MS = 1500; // stop if the line is lost for this long
int lastTurn = 0; // -1 = last corrected left, 1 = right
unsigned long lostSince = 0;
void setMotor(int pinEn, int pinA, int pinB, int speed) {
speed = constrain(speed, -255, 255);
digitalWrite(pinA, speed > 0 ? HIGH : LOW);
digitalWrite(pinB, speed < 0 ? HIGH : LOW);
ledcWrite(pinEn, abs(speed));
}
void drive(int left, int right) {
setMotor(PIN_ENA, PIN_IN1, PIN_IN2, left);
setMotor(PIN_ENB, PIN_IN3, PIN_IN4, right);
}
bool onLine(int pin) {
return digitalRead(pin) == (LINE_IS_HIGH ? HIGH : LOW);
}
void setup() {
Serial.begin(115200);
pinMode(PIN_SENSOR_L, INPUT);
pinMode(PIN_SENSOR_R, INPUT);
pinMode(PIN_IN1, OUTPUT); pinMode(PIN_IN2, OUTPUT);
pinMode(PIN_IN3, OUTPUT); pinMode(PIN_IN4, OUTPUT);
ledcAttach(PIN_ENA, PWM_FREQ, PWM_BITS);
ledcAttach(PIN_ENB, PWM_FREQ, PWM_BITS);
drive(0, 0);
delay(2000);
}
void loop() {
bool l = onLine(PIN_SENSOR_L);
bool r = onLine(PIN_SENSOR_R);
if (!l && !r) {
// line is between the sensors: go straight
drive(SPEED_STRAIGHT, SPEED_STRAIGHT);
lostSince = 0;
} else if (l && !r) {
// left sensor on the line: car drifted right, steer left
drive(SPEED_SLOW, SPEED_FAST);
lastTurn = -1;
lostSince = 0;
} else if (!l && r) {
// right sensor on the line: steer right
drive(SPEED_FAST, SPEED_SLOW);
lastTurn = 1;
lostSince = 0;
} else {
// both on black: a crossing or overshoot; search in the last direction
if (lostSince == 0) lostSince = millis();
if (millis() - lostSince > LOST_MS) {
drive(0, 0);
} else if (lastTurn < 0) {
drive(SPEED_SLOW, SPEED_FAST);
} else {
drive(SPEED_FAST, SPEED_SLOW);
}
}
delay(5);
}
6. Track tips
- Start with a big oval, curves at least 20 cm in radius. Add S-bends once it runs cleanly.
- Lay tape around curves in short pieces; stretched tape lifts and reflects.
- Avoid direct sunlight — it's full of infrared and blinds the sensors.
7. Troubleshooting
It flies off in corners
Too fast to correct in time. Lower SPEED_STRAIGHT to 120–130, or make SPEED_SLOW more negative for sharper turns.
It wiggles on straights
The sensors are too close together. Space them out a little.
Once completely off the line it keeps driving straight
That's the limit of two sensors: "white, white" looks the same whether the line is centered or gone. Three or five sensors plus PID fix this — that's in the book.
Free: ESP32 Robot Wiring Cheat Sheet
One printable page: safe ESP32 pins, L298N, HC-SR04, servo wiring and the most common fixes.
Download the PDF