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.

ComponentExact Variant / ModelEst. Cost (2026)Why This Variant
MicrocontrollerESP32-DevKitC V4 (38-pin)$6.0038-pin has dedicated VIN/GND layout; 30-pin variants vary wildly.
Motor DriverTB6612FNG (SparkFun ROB-14451 or clone)$5.501.2A continuous per channel, 3.2A peak. Built-in flyback diodes.
SensorHC-SR04 Ultrasonic$2.00Cheap, reliable, but requires 5V VCC and 3.3V logic translation.
MotorsTT Gearmotors (Yellow, 1:48 ratio)$4.00 (pair)Standard 3-6V DC. Stall current is ~1.2A per motor.
Chassis2WD Acrylic Baseplate + Caster Wheel$7.00Pre-drilled for TT motors. Use a metal ball caster, not plastic.
Power2x 18650 Li-ion (e.g., Samsung 25R) in 2S holder$12.007.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 PinHC-SR04 PinNotes & Constraints
5V (VIN)VCC & VMVCCPower from 7.4V battery pack via ESP32 VIN pin.
GNDGNDGNDCommon ground is mandatory.
GPIO 5-Trig3.3V output is sufficient to trigger the 5V HC-SR04.
GPIO 18-EchoMust pass through 1k/2k voltage divider.
GPIO 27 (PWM)PWMA-Left motor speed.
GPIO 26AIN2-Left motor direction 2.
GPIO 25AIN1-Left motor direction 1.
GPIO 33 (PWM)PWMB-Right motor speed.
GPIO 32BIN2-Right motor direction 2.
GPIO 14BIN1-Right motor direction 1.
GPIO 13STBY-Pull HIGH to enable the motor driver.

Assembly & Wiring Steps

  1. 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.
  2. 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.
  3. 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.
  4. 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.
⚠️ Callout Tip: Battery C-Rating Matters
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.