Community project

ROVIE: ESP32 Obstacle Avoiding Rover

ESP32
Photo of ROVIE: ESP32 Obstacle Avoiding Rover

Chukwuemeka Ifeanyi

Last updated August 29, 2026

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

Wiring diagram for ROVIE: ESP32 Obstacle Avoiding Rover

Gather all the parts

QtyComponent
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

DC Motor

Front-Left TT motor

DC motor (requires motor driver like L298N)

1

DC Motor

Rear-Left TT motor

DC motor (requires motor driver like L298N)

1

DC Motor

Front-Right TT motor

DC motor (requires motor driver like L298N)

1

DC Motor

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

HC-SR04

Ultrasonic distance measurement sensor

1

18650 Holder

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

24v Buck Converter

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"

FunctionbatteryESP32
powerBAT+ → DFRobot MDV 2x2A DC Motor Controller (L298N) VSEXT
powerBAT+ → 24v Buck Converter VIN+EXT
groundBAT-GND

2. Connections between "buck5v" and "ESP32"

Functionbuck5vESP32
groundVIN-GND
groundVOUT-GND
powerVOUT+VIN

3. Connections between "l298n" and "ESP32"

Functionl298nESP32
groundGNDGND
power5VVIN
digitalIN1GPIO 13
digitalIN2GPIO 14
digitalIN3GPIO 26
digitalIN4GPIO 27
pwmENAGPIO 25
pwmENBGPIO 33
outputOUT1 → DC Motor IN1EXT
outputOUT2 → DC Motor IN2EXT
outputOUT3 → DC Motor IN1EXT
outputOUT4 → DC Motor IN2EXT

4. Connections between "motor_fl" and "ESP32"

Functionmotor_flESP32
digitalIN1 → DC Motor IN1EXT
digitalIN2 → DC Motor IN2EXT
powerVCC → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT1EXT
groundGND → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT2EXT

5. Connections between "motor_fr" and "ESP32"

Functionmotor_frESP32
digitalIN1 → DC Motor IN1EXT
digitalIN2 → DC Motor IN2EXT
powerVCC → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT3EXT
groundGND → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT4EXT

6. Connections between "ir_left" and "ESP32"

Functionir_leftESP32
powerVCCVIN
groundGNDGND
digitalDOGPIO 32

7. Connections between "ir_right" and "ESP32"

Functionir_rightESP32
powerVCCVIN
groundGNDGND
digitalDOGPIO 4

8. Connections between "sonar" and "ESP32"

FunctionsonarESP32
powerVCCVIN
groundGNDGND
digitalTRIGGPIO 16
digitalECHOGPIO 17

9. Connections between "motor_rl" and "ESP32"

Functionmotor_rlESP32
powerVCC → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT1EXT
groundGND → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT2EXT

10. Connections between "motor_rr" and "ESP32"

Functionmotor_rrESP32
powerVCC → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT3EXT
groundGND → DFRobot MDV 2x2A DC Motor Controller (L298N) OUT4EXT

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.

Open in Schematik