Building a reliable line-following robotics project requires more than just copying a basic sketch and hoping the motors spin. When you introduce the ESP32 into a motorized environment, you are combining high-frequency WiFi-capable silicon with noisy, inductive DC loads. If your power delivery and GPIO routing aren't dialed in, your robot will reset mid-turn or drift off the track.
This guide walks through a complete 2WD line-following robotics project targeting the ESP32 DevKit V1 (30-pin variant with the ESP-WROOM-32 module). We will cover the exact 2026 hardware stack, a modern ESP32 Arduino Core v3.x PWM implementation, and the specific bench-level debugging steps to fix the most common motor-driver failures.
Spec Sheet & Parts List
Skip the generic "smart car kits" that ship with underpowered 1.5V AA holders. For a robotics project that actually tracks lines at a reasonable speed without browning out the microcontroller, you need stable voltage and adequate current headroom. Here is the exact bill of materials.
| Component | Exact Variant / Specification | Qty | Est. Cost (2026) |
|---|---|---|---|
| Microcontroller | ESP32 DevKit V1 (30-pin, CP2102/CH340 USB-UART, ESP-WROOM-32) | 1 | $6.50 |
| Motor Driver | L298N Dual H-Bridge (Red PCB, STMicroelectronics chip, with onboard 78M05 5V regulator) | 1 | $4.00 |
| Sensor Array | 3-Channel IR Line Tracking Module (TCRT5000 reflective optocouplers, digital output) | 1 | $3.50 |
| Motors | TT Gear Motors (1:48 gear ratio, 3-6V nominal, yellow plastic body) | 2 | $4.00 |
| Chassis | 2WD Acrylic Smart Car Chassis (includes caster wheel and motor mounts) | 1 | $7.00 |
| Power Supply | 2S 18650 Battery Holder with 2x Samsung 25R (or Molicel P26A) Li-ion cells (7.4V nominal) | 1 | $14.00 |
| Misc Hardware | Jumper wires (22 AWG silicone), M3 brass standoffs, 470µF electrolytic capacitor | 1 lot | $5.00 |
Pin Mapping & Wiring Guide
The ESP32 has strict GPIO restrictions. Many robotics project tutorials fail because they assign motor PWM to pins like GPIO 12 or GPIO 15, which are strapping pins that dictate boot modes, or they use ADC2 pins while trying to run WiFi. For this build, we use input-only pins (34, 35, 39) for the digital IR sensors, and safe output pins for the motor driver. For full GPIO constraints, refer to the Espressif GPIO Documentation.
| ESP32 GPIO | Destination | Function | Notes |
|---|---|---|---|
| GPIO 27 | L298N ENA | PWM (Left Motor Speed) | Safe for LEDC PWM |
| GPIO 26 | L298N IN1 | Digital Out (Left Dir A) | - |
| GPIO 25 | L298N IN2 | Digital Out (Left Dir B) | - |
| GPIO 33 | L298N ENB | PWM (Right Motor Speed) | Safe for LEDC PWM |
| GPIO 32 | L298N IN3 | Digital Out (Right Dir A) | - |
| GPIO 14 | L298N IN4 | Digital Out (Right Dir B) | - |
| GPIO 34 | IR Sensor Left | Digital In | Input-only pin, no pull-up needed |
| GPIO 35 | IR Sensor Mid | Digital In | Input-only pin |
| GPIO 39 | IR Sensor Right | Digital In | Input-only pin (labeled SVN on some boards) |
Numbered Wiring Steps
- Power the L298N: Connect the 2S 18650 positive (red) to the L298N 12V terminal, and battery negative (black) to the L298N GND terminal. Do not use the 5V terminal for main power.
- Establish Common Ground: Run a jumper wire from the L298N GND terminal to the ESP32 GND pin. If you skip this, the PWM signals will float and the motors will stutter or spin randomly.
- ESP32 Power: Power the ESP32 via its micro-USB port for debugging. If you want standalone operation, use the L298N's onboard 5V output (leave the 5V-EN jumper on the L298N) and wire it to the ESP32's 5V/VIN pin.
- Motor Connections: Wire Left Motor to OUT1 and OUT2. Wire Right Motor to OUT3 and OUT4. Polarity doesn't matter yet; we will fix forward/reverse logic in code.
- Sensor Placement: Mount the 3-channel IR sensor 1.5cm to 2.0cm above the floor. Use the included blue trimpots to tune the sensitivity over your specific track tape.
Complete ESP32 Control Code
This code is written for the ESP32 Arduino Core v3.x. Older tutorials use the deprecated ledcSetup() and ledcAttachPin() functions. In 2026, the modern API uses ledcAttach() and ledcWrite() directly on the GPIO pin, as detailed in the Espressif LEDC API docs. We also include a basic watchdog timer check to handle sensor read timeouts.
#include <Arduino.h>
// --- Pin Definitions ---
#define ENA 27
#define IN1 26
#define IN2 25
#define ENB 33
#define IN3 32
#define IN4 14
#define IR_LEFT 34
#define IR_MID 35
#define IR_RIGHT 39
// --- Motor Speeds (0-255) ---
const uint8_t BASE_SPEED = 180;
const uint8_t TURN_SPEED = 120;
// --- Error Handling Variables ---
unsigned long lastSensorRead = 0;
const unsigned long SENSOR_TIMEOUT = 500; // ms
void stopMotors() {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
ledcWrite(ENA, 0);
ledcWrite(ENB, 0);
}
void setup() {
Serial.begin(115200);
// Configure Motor Direction Pins
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
// Configure IR Sensor Pins (Input only pins do not need pinMode, but good practice)
pinMode(IR_LEFT, INPUT);
pinMode(IR_MID, INPUT);
pinMode(IR_RIGHT, INPUT);
// Modern ESP32 Core v3.x PWM Setup (1000Hz, 8-bit resolution)
ledcAttach(ENA, 1000, 8);
ledcAttach(ENB, 1000, 8);
stopMotors();
Serial.println("Robotics Project Initialized. Calibrating sensors...");
delay(1000);
}
void loop() {
// Read Sensors (LOW = detects black line, HIGH = sees white floor)
bool left = digitalRead(IR_LEFT);
bool mid = digitalRead(IR_MID);
bool right = digitalRead(IR_RIGHT);
// Error Handling: Sensor Timeout Watchdog
if (left || mid || right) {
lastSensorRead = millis();
} else if (millis() - lastSensorRead > SENSOR_TIMEOUT) {
Serial.println("ERROR: Sensor read timeout or completely off track. Stopping.");
stopMotors();
return; // Halt loop until reset or placed back on track
}
// Bang-Bang Control Logic
if (!mid && left && right) {
// Go Straight
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
ledcWrite(ENA, BASE_SPEED);
ledcWrite(ENB, BASE_SPEED);
}
else if (!left && mid) {
// Sharp Left
digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
ledcWrite(ENA, TURN_SPEED);
ledcWrite(ENB, BASE_SPEED);
}
else if (!right && mid) {
// Sharp Right
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
ledcWrite(ENA, BASE_SPEED);
ledcWrite(ENB, TURN_SPEED);
}
else if (!left && !mid && !right) {
// All sensors on line (thick line or intersection) - Stop or push through
stopMotors();
}
else {
// Lost line - coast to a stop
stopMotors();
}
}
Debugging: "Brownout detector was triggered"
If you upload this code and your ESP32 immediately reboots with the exact serial error string Brownout detector was triggered, your robot is experiencing severe voltage sag. The ESP32's internal brownout detection (BOD) trips when VDD33 drops below ~2.4V, which happens when the TT motors stall or draw peak startup current, dragging the shared power rail down.
The First 3 Things to Check When It Fails
- Decouple the 5V Rail: Solder or plug a 470µF to 1000µF electrolytic capacitor directly across the 5V and GND rails on your breadboard or L298N terminal block. This acts as a local energy reservoir to absorb motor inductive spikes.
- Verify the L298N 5V Jumper: If you are powering the ESP32 via USB, remove the 5V-EN jumper on the L298N. Backfeeding 5V from the L298N's linear regulator into the ESP32's USB 5V line often causes ground loops and regulator overheating.
- Measure Motor Stall Current: Put your multimeter in series with one motor and physically stall the wheel. TT motors can spike to 1.2A - 1.5A. If your 18650 cells are old or low-drain (like standard laptop pull cells), they will sag under a 3A combined load. Upgrade to high-drain cells like the Molicel P26A.
Extending or Simplifying the Build
Not every robotics project needs WiFi, and not every builder wants to deal with ESP32 boot-strapping quirks.
- To Simplify: Swap the ESP32 DevKit V1 for an Arduino Nano v3 (ATmega328P). You eliminate all GPIO strapping pin conflicts, drop the power consumption, and can use the standard
analogWrite()without LEDC configuration. You lose wireless telemetry, but the bang-bang line tracking code remains 95% identical. - To Extend: Replace the bang-bang
if/elselogic with a PID (Proportional-Integral-Derivative) controller using the Arduino PID library. Calculate the error based on the weighted sum of the three IR sensors (e.g., Left = -1, Mid = 0, Right = 1). For advanced computer vision, swap the ESP32 DevKit for an ESP32-CAM module, mount it facing downward, and use OpenCV thresholding to track the line centroid, completely eliminating the IR hardware.
FAQ: Common Robotics Project Questions
How do I calibrate the IR sensors for a robotics project on glossy floors?
Glossy floors cause specular reflection, which blinds the TCRT5000 phototransistors. To fix this, power the sensor array and place it over your white floor. Use a small flathead screwdriver to turn the blue trimpots until the output LED just turns off. Then, move the sensor over the black tape; the LED should turn on. If glossy floors still cause false triggers, shroud the sensors with heat-shrink tubing to block ambient room light, or lower the sensor height to exactly 1.0cm above the floor.
Why is my ESP32 robotics project drifting to one side even on a straight line?
TT gear motors have notoriously poor manufacturing tolerances; one motor will almost always spin 5-10% faster than the other. First, verify your mechanical assembly isn't binding on the caster wheel. If the hardware is free, you must correct this in software. In the code above, reduce the BASE_SPEED of the faster motor by 10-20 points (e.g., Left = 180, Right = 165) until the robot tracks straight. For a permanent fix, implement a closed-loop system using rotary encoders on the motor shafts.
Can I power the ESP32 and L298N from the same 3S LiPo battery?
Yes, but with caveats. A 3S LiPo outputs 11.1V nominal (12.6V fully charged). The L298N can accept up to 35V, so the motors will run hot and fast (the L298N will drop ~2V, leaving ~10V for 6V motors, which will degrade them quickly). More importantly, the L298N's onboard 78M05 linear regulator will have to drop 12.6V down to 5V for the ESP32. At just 100mA of ESP32 current, the regulator must dissipate (12.6V - 5V) * 0.1A = 0.76W of heat. It will overheat and shut down. If using 3S, bypass the L298N 5V regulator entirely and use a dedicated buck converter (like an LM2596 module) to step the 12.6V down to 5V for the ESP32.






