Community project
ROVIE: ESP32 Obstacle Avoiding Rover
ROVIE is a four-wheel drive obstacle-avoiding rover that combines line-following and distance-sensing capabilities. Built around an ESP32 microcontroller, it uses an L298N motor driver to control four DC motors, dual TCRT5000 IR sensors to detect black lines, and an HC-SR04 ultrasonic sensor to measure distances and avoid collisions. This guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions to get ROVIE navigating autonomously.
The included Arduino firmware implements real-time motor control with PWM speed adjustment, line-tracking logic that keeps ROVIE centered on a black line, and obstacle detection that triggers avoidance maneuvers when objects approach within 20 cm. Builders will learn how to wire a multi-motor system, configure ESP32 GPIO pins for sensor input and PWM output, and program autonomous navigation behaviors.
Wiring diagram

Gather all the parts
| Qty | Component |
|---|---|
| 1 | DFRobot MDV 2x2A DC Motor Controller (L298N) L298N dual H-bridge motor driver carrier. 2A continuous per channel, 5-46V motor supply, ~1.8V dropout (BJT-based, hot at high currents). Drives 2 brushed DC motors or 1 bipolar stepper. Pair with PWM on EN pins for speed control. |
| 1 | Front-Left TT motor DC motor (requires motor driver like L298N) |
| 1 | Rear-Left TT motor DC motor (requires motor driver like L298N) |
| 1 | Front-Right TT motor DC motor (requires motor driver like L298N) |
| 1 | Rear-Right TT motor DC motor (requires motor driver like L298N) |
| 1 | TCRT5000 IR Line Tracking Sensor Module Left line sensor Single-channel infrared reflective sensor module built around the Vishay TCRT5000 IR emitter/phototransistor pair. An onboard LM393 comparator with a sensitivity trim-pot drives a thresholded digital output (DO) for line-detection or obstacle-avoidance, while a separate analog pin (AO) exposes the raw reflected-IR reading. Runs on 3.3V-5V, senses reflective surfaces roughly 1-25mm away, and is the standard low-cost sensor on line-following and obstacle-avoiding robot kits. Sold as a generic 4-pin (VCC/GND/DO/AO) breakout under many brand names (HiLetgo, ACEIRMC, OSOYOO, etc.) rather than by a single official manufacturer. |
| 1 | TCRT5000 IR Line Tracking Sensor Module Right line sensor Single-channel infrared reflective sensor module built around the Vishay TCRT5000 IR emitter/phototransistor pair. An onboard LM393 comparator with a sensitivity trim-pot drives a thresholded digital output (DO) for line-detection or obstacle-avoidance, while a separate analog pin (AO) exposes the raw reflected-IR reading. Runs on 3.3V-5V, senses reflective surfaces roughly 1-25mm away, and is the standard low-cost sensor on line-following and obstacle-avoiding robot kits. Sold as a generic 4-pin (VCC/GND/DO/AO) breakout under many brand names (HiLetgo, ACEIRMC, OSOYOO, etc.) rather than by a single official manufacturer. |
| 1 | Ultrasonic distance measurement sensor |
| 1 | 2S (2x 18650) Generic holder for one or two removable 18650 Li-ion cells. It is a mechanical/electrical power holder, not a charger or protection circuit; pair with a charger/BMS and regulator appropriate to the cell count. |
| 1 | set to 5V LM2596-based adjustable step-down buck converter module. Commonly used to regulate a higher battery rail, such as a 2S 18650 pack, down to 5V for Arduino logic. It is a regulator, not a charger or battery protection board. |
Assemble it in 5 steps
1. Mount the four motors and driver
Fix the four TT gear motors to the chassis (two on the left side, two on the right). Mount the L298N board in the middle. The two left motors will act as one pair, and the two right motors as the other pair.
2. Wire the motors to the driver
Connect both left motors together and run them to the L298N OUT1/OUT2 screw terminals. Connect both right motors together and run them to OUT3/OUT4. If a side spins backward later, just swap that side's two wires.
- Twist each motor pair's matching wires together before screwing them into one terminal.
3. Set up battery power
Connect the battery pack's positive wire to the L298N VS terminal and also to the buck converter input (VIN+). Connect the battery negative to the L298N GND and buck converter input ground (VIN-). The buck converter's 5V output feeds the L298N 5V logic pin, both IR sensors, and the HC-SR04.
- Set the buck converter to 5V with a multimeter BEFORE connecting the ESP32 and sensors — too high a voltage can destroy them.
4. Power the ESP32 and share ground
Feed the buck converter's 5V into the ESP32 VIN pin, and connect the buck 5V to the sensors' VCC pins. Tie ALL grounds together — battery, buck, L298N, ESP32 GND, IR sensors, and HC-SR04 must share one common ground, or the signals won't read correctly.
- A missing common ground is the most common reason motors twitch and sensors read garbage.
5. Wire the control signals
L298N IN1→GPIO13, IN2→GPIO14, IN3→GPIO26, IN4→GPIO27, ENA→GPIO25, ENB→GPIO33 (remove the ENA/ENB jumpers so speed control works). Left IR DO→GPIO32, right IR DO→GPIO4. HC-SR04 TRIG→GPIO16, ECHO→GPIO17.
- Mount the two IR sensors underneath the front, close to the floor, one on each side of the line.
- Mount the ultrasonic sensor facing forward at the front.
Review all connections
1. Connections between "battery" and "ESP32"
| Function | battery | ESP32 |
|---|---|---|
| power | BAT+ → DFRobot MDV 2x2A DC Motor Controller (L298N) VS | EXT |
| power | BAT+ → 24v Buck Converter VIN+ | EXT |
| ground | BAT- | GND |
2. Connections between "buck5v" and "ESP32"
| Function | buck5v | ESP32 |
|---|---|---|
| ground | VIN- | GND |
| ground | VOUT- | GND |
| power | VOUT+ | VIN |
3. Connections between "l298n" and "ESP32"
| Function | l298n | ESP32 |
|---|---|---|
| ground | GND | GND |
| power | 5V | VIN |
| digital | IN1 | GPIO 13 |
| digital | IN2 | GPIO 14 |
| digital | IN3 | GPIO 26 |
| digital | IN4 | GPIO 27 |
| pwm | ENA | GPIO 25 |
| pwm | ENB | GPIO 33 |
| output | OUT1 → DC Motor IN1 | EXT |
| output | OUT2 → DC Motor IN2 | EXT |
| output | OUT3 → DC Motor IN1 | EXT |
| output | OUT4 → DC Motor IN2 | EXT |
4. Connections between "motor_fl" and "ESP32"
| Function | motor_fl | ESP32 |
|---|---|---|
| digital | IN1 → DC Motor IN1 | EXT |
| digital | IN2 → DC Motor IN2 | EXT |
| power | VCC → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT1 | EXT |
| ground | GND → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT2 | EXT |
5. Connections between "motor_fr" and "ESP32"
| Function | motor_fr | ESP32 |
|---|---|---|
| digital | IN1 → DC Motor IN1 | EXT |
| digital | IN2 → DC Motor IN2 | EXT |
| power | VCC → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT3 | EXT |
| ground | GND → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT4 | EXT |
6. Connections between "ir_left" and "ESP32"
| Function | ir_left | ESP32 |
|---|---|---|
| power | VCC | VIN |
| ground | GND | GND |
| digital | DO | GPIO 32 |
7. Connections between "ir_right" and "ESP32"
| Function | ir_right | ESP32 |
|---|---|---|
| power | VCC | VIN |
| ground | GND | GND |
| digital | DO | GPIO 4 |
8. Connections between "sonar" and "ESP32"
| Function | sonar | ESP32 |
|---|---|---|
| power | VCC | VIN |
| ground | GND | GND |
| digital | TRIG | GPIO 16 |
| digital | ECHO | GPIO 17 |
9. Connections between "motor_rl" and "ESP32"
| Function | motor_rl | ESP32 |
|---|---|---|
| power | VCC → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT1 | EXT |
| ground | GND → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT2 | EXT |
10. Connections between "motor_rr" and "ESP32"
| Function | motor_rr | ESP32 |
|---|---|---|
| power | VCC → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT3 | EXT |
| ground | GND → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT4 | EXT |
Deploy the firmware
#include <Arduino.h>
// 4WD line-following + obstacle-avoiding robot
// ESP32 DevKit v1 + L298N + 4x TT motors + 2x TCRT5000 IR + HC-SR04
//
// Left side (front-left + rear-left) share L298N bridge A (OUT1/OUT2, IN1/IN2, ENA).
// Right side (front-right + rear-right) share bridge B (OUT3/OUT4, IN3/IN4, ENB).
// --- Motor driver pins ---
// Forward declarations
void leftSpeed(int s);
void rightSpeed(int s);
void leftForward();
void leftReverse();
void rightForward();
void rightReverse();
void driveForward();
void turnLeft();
void turnRight();
void stopMotors();
long readDistanceCm();
const int IN1 = 13; // Bridge A dir
const int IN2 = 14; // Bridge A dir
const int IN3 = 26; // Bridge B dir
const int IN4 = 27; // Bridge B dir
const int ENA = 25; // Bridge A speed (PWM) - LEFT
const int ENB = 33; // Bridge B speed (PWM) - RIGHT
// --- Sensors ---
const int IR_LEFT = 32; // TCRT5000 DO: LOW over black line
const int IR_RIGHT = 4;
const int TRIG = 16;
const int ECHO = 17; // input-capable GPIO
// --- LEDC PWM setup (ESP32) ---
const int CH_A = 0;
const int CH_B = 1;
const int PWM_FREQ = 1000;
const int PWM_RES = 8; // 0..255
const int SPEED_FWD = 200;
const int SPEED_TURN = 180;
const int OBSTACLE_CM = 20; // stop/avoid closer than this
void leftSpeed(int s) { ledcWrite(CH_A, s); }
void rightSpeed(int s) { ledcWrite(CH_B, s); }
void leftForward() { digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); }
void leftReverse() { digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); }
void rightForward() { digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); }
void rightReverse() { digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); }
void driveForward() { leftForward(); rightForward(); leftSpeed(SPEED_FWD); rightSpeed(SPEED_FWD); }
void turnLeft() { leftReverse(); rightForward(); leftSpeed(SPEED_TURN); rightSpeed(SPEED_TURN); }
void turnRight() { leftForward(); rightReverse(); leftSpeed(SPEED_TURN); rightSpeed(SPEED_TURN); }
void stopMotors() { leftSpeed(0); rightSpeed(0); }
long readDistanceCm() {
digitalWrite(TRIG, LOW);
delayMicroseconds(2);
digitalWrite(TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG, LOW);
// 25ms timeout ~ 4.3m max
unsigned long dur = pulseIn(ECHO, HIGH, 25000UL);
if (dur == 0) return 999; // no echo = treat as clear
return (long)(dur / 58);
}
void setup() {
Serial.begin(115200);
pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
pinMode(IR_LEFT, INPUT); pinMode(IR_RIGHT, INPUT);
pinMode(TRIG, OUTPUT); pinMode(ECHO, INPUT);
ledcSetup(CH_A, PWM_FREQ, PWM_RES);
ledcSetup(CH_B, PWM_FREQ, PWM_RES);
ledcAttachPin(ENA, CH_A);
ledcAttachPin(ENB, CH_B);
stopMotors();
}
void loop() {
long dist = readDistanceCm();
// Obstacle avoidance takes priority
if (dist <= OBSTACLE_CM) {
stopMotors();
delay(120);
// back up briefly then pivot to look for a clear path
leftReverse(); rightReverse();
leftSpeed(SPEED_TURN); rightSpeed(SPEED_TURN);
delay(300);
turnRight();
delay(400);
stopMotors();
return;
}
// Line following: DO LOW = over black line
bool leftOnLine = (digitalRead(IR_LEFT) == LOW);
bool rightOnLine = (digitalRead(IR_RIGHT) == LOW);
if (!leftOnLine && !rightOnLine) {
driveForward(); // both on white -> go straight
} else if (leftOnLine && !rightOnLine) {
turnLeft(); // line drifted left -> steer left
} else if (!leftOnLine && rightOnLine) {
turnRight(); // line drifted right -> steer right
} else {
// both on black (junction/stop mark) -> creep forward slowly
driveForward();
leftSpeed(SPEED_TURN); rightSpeed(SPEED_TURN);
}
}Download project files
Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




