The ESP32 obstacle-avoiding rover is one of the most reliable easy robotics projects you can build on a workbench. Unlike basic Arduino Uno builds that choke on I2C polling delays and 5V logic limitations, the ESP32's dual-core 240MHz processor handles ultrasonic ping timing and PWM motor control simultaneously without dropping frames. This guide walks through building a 2WD rover using the ESP32-DevKitC V4 (38-pin variant) and a TB6612FNG motor driver, complete with the exact voltage-divider wiring needed to prevent frying your 3.3V logic pins.
Parts List & Spec Sheet
Most beginner tutorials default to the L298N motor driver. Don't use it. The L298N drops up to 2.5V across its Darlington transistors, starving your 6V TT motors. The TB6612FNG uses MOSFETs, dropping only ~0.5V, and runs cool without a heatsink.
| Component | Exact Variant / Model | Est. Cost (2026) | Why This Variant |
|---|---|---|---|
| Microcontroller | ESP32-DevKitC V4 (38-pin) | $6.00 | 38-pin has dedicated VIN/GND layout; 30-pin variants vary wildly. |
| Motor Driver | TB6612FNG (SparkFun ROB-14451 or clone) | $5.50 | 1.2A continuous per channel, 3.2A peak. Built-in flyback diodes. |
| Sensor | HC-SR04 Ultrasonic | $2.00 | Cheap, reliable, but requires 5V VCC and 3.3V logic translation. |
| Motors | TT Gearmotors (Yellow, 1:48 ratio) | $4.00 (pair) | Standard 3-6V DC. Stall current is ~1.2A per motor. |
| Chassis | 2WD Acrylic Baseplate + Caster Wheel | $7.00 | Pre-drilled for TT motors. Use a metal ball caster, not plastic. |
| Power | 2x 18650 Li-ion (e.g., Samsung 25R) in 2S holder | $12.00 | 7.4V nominal. Provides ample current headroom for motor stalls. |
Pin Mapping & Wiring
The HC-SR04 Echo pin outputs a 5V pulse. Feeding 5V directly into an ESP32 GPIO will permanently degrade or destroy the pin. You must build a voltage divider for the Echo pin using a 1kΩ and 2kΩ resistor.
| ESP32 GPIO (38-pin) | TB6612FNG Pin | HC-SR04 Pin | Notes & Constraints |
|---|---|---|---|
| 5V (VIN) | VCC & VM | VCC | Power from 7.4V battery pack via ESP32 VIN pin. |
| GND | GND | GND | Common ground is mandatory. |
| GPIO 5 | - | Trig | 3.3V output is sufficient to trigger the 5V HC-SR04. |
| GPIO 18 | - | Echo | Must pass through 1k/2k voltage divider. |
| GPIO 27 (PWM) | PWMA | - | Left motor speed. |
| GPIO 26 | AIN2 | - | Left motor direction 2. |
| GPIO 25 | AIN1 | - | Left motor direction 1. |
| GPIO 33 (PWM) | PWMB | - | Right motor speed. |
| GPIO 32 | BIN2 | - | Right motor direction 2. |
| GPIO 14 | BIN1 | - | Right motor direction 1. |
| GPIO 13 | STBY | - | Pull HIGH to enable the motor driver. |
Assembly & Wiring Steps
- Build the Voltage Divider: Solder the 1kΩ resistor to the HC-SR04 Echo pin. Connect the other end of the 1kΩ resistor to the 2kΩ resistor. The junction between them goes to ESP32 GPIO 18. The free end of the 2kΩ resistor goes to GND. This drops the 5V pulse down to a safe ~3.3V.
- Wire the Power Distribution: Connect the 2S (7.4V) battery holder's positive lead to the ESP32 VIN pin, and the negative lead to ESP32 GND. The ESP32's onboard AMS1117 regulator will drop this to 3.3V for the logic, while the VIN pin passes the raw 7.4V to the TB6612FNG VM (Motor Voltage) pin.
- Connect the Motors: Solder the TT motor leads to the TB6612FNG AO1/AO2 and BO1/BO2 terminals. If a motor spins the wrong way during testing, simply swap its two wires.
- Verify Standby Pin: Wire ESP32 GPIO 13 to the TB6612FNG STBY pin. If you forget this and leave STBY floating or LOW, the motors will never engage.
TT gearmotors can draw up to 1.2A each when stalled (e.g., starting from a dead stop or hitting a wall). Two motors stalling simultaneously pull 2.4A. Ensure your 18650 cells can handle at least a 3A continuous discharge (like the Samsung 25R or Sony VTC6). Using cheap, low-drain flashlight cells will cause severe voltage sag.
The Code: ESP32 Rover Control
This code targets the ESP32-DevKitC V4 (38-pin). It uses the NewPing library to handle ultrasonic timeouts gracefully, preventing the CPU from hanging if a ping misses. Install "NewPing" via the Arduino Library Manager before compiling.
#include <NewPing.h>
// --- PIN DEFINITIONS ---
#define TRIG_PIN 5
#define ECHO_PIN 18
#define MAX_DISTANCE 200 // Maximum distance to ping (in cm)
// Motor A (Left)
#define PWMA 27
#define AIN2 26
#define AIN1 25
// Motor B (Right)
#define PWMB 33
#define BIN2 32
#define BIN1 14
// Motor Driver Control
#define STBY 13
// Initialize NewPing object
NewPing sonar(TRIG_PIN, ECHO_PIN, MAX_DISTANCE);
// Motor control constants
const int MOTOR_SPEED = 200; // PWM value (0-255)
const int TURN_SPEED = 150;
const int STOP_DISTANCE = 20; // Distance in cm to trigger avoidance
void setup() {
Serial.begin(115200);
// Configure Motor Pins
pinMode(PWMA, OUTPUT); pinMode(AIN1, OUTPUT); pinMode(AIN2, OUTPUT);
pinMode(PWMB, OUTPUT); pinMode(BIN1, OUTPUT); pinMode(BIN2, OUTPUT);
pinMode(STBY, OUTPUT);
// Enable Motor Driver
digitalWrite(STBY, HIGH);
// Brief startup delay to let sensors stabilize
delay(500);
Serial.println("ESP32 Rover Initialized.");
}
void loop() {
// Get distance (NewPing handles timeouts and returns 0 if out of range)
unsigned int distance = sonar.ping_cm();
// Error handling: If ping returns 0, assume path is clear or sensor error
if (distance == 0 || distance > STOP_DISTANCE) {
moveForward(MOTOR_SPEED);
} else {
// Obstacle detected
stopMotors();
delay(100);
// Reverse to clear the obstacle
moveBackward(MOTOR_SPEED);
delay(400);
stopMotors();
delay(100);
// Turn right to find a new path
turnRight(TURN_SPEED);
delay(500);
stopMotors();
}
// Small delay to prevent sensor cross-talk and CPU thrashing
delay(50);
}
// --- MOTOR CONTROL FUNCTIONS ---
void moveForward(int speed) {
digitalWrite(AIN1, HIGH); digitalWrite(AIN2, LOW);
digitalWrite(BIN1, HIGH); digitalWrite(BIN2, LOW);
analogWrite(PWMA, speed); analogWrite(PWMB, speed);
}
void moveBackward(int speed) {
digitalWrite(AIN1, LOW); digitalWrite(AIN2, HIGH);
digitalWrite(BIN1, LOW); digitalWrite(BIN2, HIGH);
analogWrite(PWMA, speed); analogWrite(PWMB, speed);
}
void turnRight(int speed) {
digitalWrite(AIN1, HIGH); digitalWrite(AIN2, LOW); // Left forward
digitalWrite(BIN1, LOW); digitalWrite(BIN2, HIGH); // Right backward
analogWrite(PWMA, speed); analogWrite(PWMB, speed);
}
void stopMotors() {
digitalWrite(AIN1, LOW); digitalWrite(AIN2, LOW);
digitalWrite(BIN1, LOW); digitalWrite(BIN2, LOW);
analogWrite(PWMA, 0); analogWrite(PWMB, 0);
}
Debugging: First 3 Things to Check When It Fails
When working with easy robotics projects that involve high-current inductive loads (motors) sharing a power rail with sensitive logic, failures are rarely software bugs. If your rover fails, check these three items in order.
1. The Exact Error: Brownout detector was triggered
If your serial monitor spits out Brownout detector was triggered and the ESP32 reboots the moment the motors start spinning, your voltage is sagging below the ESP32's 2.4V brownout threshold.
- Cause A (Most Likely): Motor stall current is collapsing the battery voltage. Fix: Add a 470µF electrolytic capacitor across the TB6612FNG VM and GND pins to supply instantaneous current, or upgrade to higher C-rating 18650 cells.
- Cause B: You are testing via USB while motors are connected. The ESP32's USB 5V line cannot supply the current for both the logic and the motors. Fix: Disconnect USB and run entirely off the 2S battery pack.
- Cause C: Thin jumper wires (28AWG) are causing a voltage drop between the battery and the ESP32 VIN. Fix: Use at least 20AWG silicone wire for the main power distribution.
2. The Rover Spins in Circles or Vibrates Without Moving
- Cause A: PWM frequency mismatch. The ESP32 default PWM frequency is 5kHz, which can cause high-pitch whining and inefficient torque in cheap TT motors. Fix: In the Arduino IDE, ensure you are using the standard
analogWrite()which handles ESP32 LEDC setup automatically in newer core versions, or manually set LEDC frequency to 1000Hz. - Cause B: One motor is wired in reverse phase. Fix: Swap the two wires for the offending motor on the TB6612FNG terminal block.
3. Ultrasonic Sensor Always Reads 0cm or Max Distance
- Cause A: The 5V Echo pulse is being clamped by the ESP32's internal protection diodes because you skipped the voltage divider. Fix: Verify the 1k/2k resistor junction with a multimeter while triggering the sensor. It must read <3.6V.
- Cause B: 5V VCC starvation. The HC-SR04 requires a solid 5V to generate the ultrasonic burst. If powered from the ESP32's 3V3 pin, it will fail silently. Fix: Ensure HC-SR04 VCC is tied to the 5V (VIN) rail.
Extending or Simplifying the Build
To Simplify: If you don't have resistors for the voltage divider, swap the HC-SR04 for the RCWL-0516 Microwave Radar Sensor. It operates natively at 3.3V logic, detects motion through the acrylic chassis, and requires only VCC, GND, and one GPIO pin. You will lose exact centimeter distance measurements, but it acts as a highly reliable digital proximity switch.
To Extend: Add an MPU-6050 IMU via I2C (SDA=GPIO 21, SCL=GPIO 22). The TT gearmotors have terrible speed matching; one wheel always spins slightly faster, causing the rover to drift. By reading the MPU-6050's Z-axis gyroscope data, you can implement a PID control loop in the code to dynamically adjust the PWM values of the left and right motors, forcing the rover to drive in a perfectly straight line.
FAQ: Easy Robotics Projects
What are the best easy robotics projects for beginners without soldering?
If you want to avoid soldering, use a solderless breadboard and pre-crimped jumper wires. The best projects for this constraint are stationary robotic arms (using SG90 micro servos) or line-following robots using the QRE1113 reflectance sensors. Avoid 2WD rovers on breadboards; the vibration from the TT motors will quickly rattle jumper wires loose, causing intermittent Brownout errors or short circuits.
How do easy robotics projects using ESP32 compare to Raspberry Pi builds?
For basic obstacle avoidance and motor control, the ESP32 is vastly superior to the Raspberry Pi. The ESP32 boots in milliseconds, has hardware PWM, and handles real-time sensor polling without an OS interrupting it. A Raspberry Pi running Linux introduces unpredictable latency (jitter) when reading GPIO pins for ultrasonic timing, leading to erratic distance readings. Reserve the Raspberry Pi for robotics projects that require computer vision (OpenCV) or complex SLAM mapping.
Why do my easy robotics projects keep resetting when the motors start?
This is almost always a power distribution issue, specifically the Brownout detector was triggered error. DC motors are inductive loads. When they start, they draw their stall current (often 5x to 10x their running current). If your battery or voltage regulator cannot supply this instantaneous current spike, the voltage drops, and the microcontroller's internal brownout detection resets the chip to prevent memory corruption. Always separate your high-current motor power from your logic power, or use large bulk capacitors to bridge the gap.






