When you start exploring robot diy projects, the jump from blinking an LED to driving motors and reading ultrasonic sensors is where most builders hit their first wall. The ESP32 is a powerhouse for robotics, but its 3.3V logic and aggressive power management features mean you cannot simply wire it up like a 5V Arduino Uno and expect it to survive. In this guide, we are building a 2WD obstacle-avoiding rover. We will cover the exact hardware variants, the mandatory 5V-to-3.3V logic shifting that saves your board from frying, and the complete firmware to get it navigating autonomously.

Project Spec Sheet & Difficulty Rating

ParameterSpecification
Target Board VariantESP32-DevKitC V4 (ESP32-WROOM-32E, 38-pin)
Difficulty RatingIntermediate (Requires voltage divider soldering & current management)
Estimated Build Time2.5 hours (Hardware) + 1 hour (Code & Tuning)
Approximate Cost$28 - $35 USD (excluding soldering tools & 18650 cells)
Power Source2S 18650 Li-ion Pack (7.4V nominal, 8.4V fully charged)

Hardware BOM and Pin Mapping

For robot diy projects involving motors, component selection dictates your success. Do not use standard AA alkaline batteries; their internal resistance is too high to handle the stall current of TT gearmotors, leading to constant microcontroller resets. Use high-discharge 18650 Li-ion cells.

Parts List

  • Microcontroller: ESP32-DevKitC V4 (38-pin variant with ESP32-WROOM-32E module)
  • Motor Driver: L298N Dual H-Bridge Module (with onboard 5V BEC enabled)
  • Motors: 2x TT Gearmotors (1:48 ratio, 3V-6V DC) with rubber wheels
  • Sensor: HC-SR04 Ultrasonic Distance Sensor
  • Actuator: SG90 9g Micro Servo (for sensor panning)
  • Chassis: Acrylic 2WD rover baseplate with castor wheel
  • Resistors: 1x 1kΩ, 1x 2kΩ (for the Echo pin voltage divider)

Pin Mapping Table

ComponentComponent PinESP32 GPIONotes
HC-SR04VCC5V (from L298N)Requires 5V for reliable acoustic triggering
HC-SR04TrigGPIO 53.3V output from ESP32 is sufficient to trigger
HC-SR04EchoGPIO 18Must pass through 1k/2k voltage divider
SG90 ServoSignalGPIO 13PWM capable pin
L298NIN1GPIO 14Right Motor Forward
L298NIN2GPIO 27Right Motor Reverse
L298NIN3GPIO 26Left Motor Forward
L298NIN4GPIO 25Left Motor Reverse
Callout Tip: The L298N Enable Pins (ENA / ENB)
Remove the physical jumpers on ENA and ENB if you want to use ESP32 PWM for speed control. However, for this foundational build, leave the jumpers in place to supply a constant 5V to the enable pins. This runs the motors at full speed and bypasses the complex LEDC PWM API changes in recent ESP32 Arduino cores, ensuring the code compiles cleanly across all versions.

Wiring Steps

  1. Power the Driver: Connect the 2S Li-ion pack positive to the L298N 12V terminal, and negative to the L298N GND terminal.
  2. Establish Common Ground: Run a jumper wire from the L298N GND terminal to one of the ESP32 GND pins. If you skip this, the logic signals will float and the motors will behave erratically.
  3. Power the ESP32: Connect the L298N 5V output terminal to the ESP32 5V (VIN) pin. The L298N's onboard linear regulator will step down the battery voltage to power the ESP32.
  4. Build the Voltage Divider: Solder the 1kΩ resistor to the HC-SR04 Echo pin. Connect the other end of the 1kΩ resistor to ESP32 GPIO 18. Solder the 2kΩ resistor between GPIO 18 and GND. This drops the 5V Echo pulse down to a safe ~3.3V.
  5. Wire the Logic: Connect the Trig, Servo Signal, and IN1-IN4 pins according to the mapping table above.

Complete ESP32 Rover Firmware

The following C++ code is written for the Arduino IDE. Before compiling, open the Library Manager and install ESP32Servo by Kevin Harrington. Standard Arduino servo libraries often fail to compile on ESP32 due to hardware timer conflicts.

#include <ESP32Servo.h>

// --- PIN DEFINITIONS ---
#define TRIG_PIN 5
#define ECHO_PIN 18
#define SERVO_PIN 13
#define IN1 14  // Right Forward
#define IN2 27  // Right Reverse
#define IN3 26  // Left Forward
#define IN4 25  // Left Reverse

// --- ROBOT PHYSICS & THRESHOLDS ---
const int STOP_DISTANCE = 25; // cm
const long ULTRASONIC_TIMEOUT = 30000; // microseconds (approx 5 meters)

Servo panServo;

void setup() {
  Serial.begin(115200);
  
  // Initialize Motor Pins
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  stopMotors();

  // Initialize Ultrasonic Pins
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  // Initialize Servo
  // ESP32Servo library handles the hardware timer allocation automatically
  panServo.attach(SERVO_PIN, 500, 2400); 
  panServo.write(90); // Center the sensor
  delay(1000);
  
  Serial.println("ESP32 Rover Initialized. Starting navigation loop.");
}

void loop() {
  int distanceFwd = readDistance(90);
  
  if (distanceFwd > STOP_DISTANCE) {
    moveForward();
  } else {
    stopMotors();
    
    // Scan Left and Right to find the clearest path
    int distanceLeft = readDistance(160);
    int distanceRight = readDistance(20);
    
    // Return to center before moving
    panServo.write(90); 
    delay(300);
    
    if (distanceLeft > distanceRight) {
      turnLeft();
      delay(400); // Time to complete a ~90 degree pivot
    } else {
      turnRight();
      delay(400);
    }
    stopMotors();
  }
  delay(50); // Small debounce delay for the loop
}

// --- MOTOR CONTROL FUNCTIONS ---
void moveForward() {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
}

void turnLeft() {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);  digitalWrite(IN4, HIGH);
}

void turnRight() {
  digitalWrite(IN1, LOW);  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
}

void stopMotors() {
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
}

// --- SENSOR FUNCTIONS WITH ERROR HANDLING ---
int readDistance(int angle) {
  panServo.write(angle);
  // Wait for servo to physically reach the position
  // 20ms per degree is a safe rule of thumb for SG90 under load
  int moveDelay = abs(angle - 90) * 20; 
  delay(moveDelay + 100); 

  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // pulseIn returns 0 if the timeout is reached (no echo detected)
  long duration = pulseIn(ECHO_PIN, HIGH, ULTRASONIC_TIMEOUT);
  
  if (duration == 0) {
    Serial.println("Warning: Ultrasonic timeout. Assuming clear path.");
    return 200; // Return a large distance to prevent false obstacle detection
  }
  
  // Calculate distance in cm (speed of sound is ~343 m/s, or 29.1 us per cm)
  // Divided by 2 for the round trip
  int distance = duration / 58.2; 
  
  // Sanity check: HC-SR04 is unreliable below 2cm
  if (distance < 2) return 2; 
  
  return distance;
}

Debugging: First Three Things to Check & Common Errors

In robot diy projects, hardware integration is usually where the build fails. If your rover is dead on arrival, run through this diagnostic sequence before rewriting code.

The First Three Things to Check

  1. Common Ground Verification: Use your multimeter in continuity mode. Probe the GND pin on the ESP32 and the GND terminal block on the L298N. It must read < 1 ohm. Without this, the 3.3V logic signals from the ESP32 have no reference point against the L298N's opto-isolators.
  2. Echo Pin Voltage: Power the system and trigger a measurement. Put your multimeter on DC voltage, probe ESP32 GPIO 18, and trigger a sensor reading. If you see voltages exceeding 3.6V, your voltage divider is wired incorrectly or your resistors are the wrong values. Disconnect immediately to prevent silicon damage.
  3. Voltage Drop Under Load: Measure the Li-ion pack voltage while the rover is lifted off the ground with wheels spinning, then drop it on the floor. If the voltage sags below 6.0V under physical load, your battery cells cannot handle the TT motor stall current (which can spike to 1.2A per motor).

Exact Error String: Brownout Detector Triggered

If your Serial Monitor repeatedly outputs the following exact string, the ESP32's internal protection circuitry is saving your chip from undervoltage damage:

Brownout detector was triggered

According to the Espressif ESP32 Technical Reference Manual, this occurs when the core voltage drops below the brownout threshold (typically ~2.4V internally). Here are the ranked causes for this specific build:

  1. L298N Voltage Drop: The L298N uses bipolar junction transistors (BJTs), which drop roughly 2V to 3V from the supply. If your 2S Li-ion pack drops to 6.5V, the L298N only passes ~4.0V to the motors, and its onboard 5V BEC regulator starves, causing the ESP32 VIN to collapse. Fix: Swap to a TB6612FNG MOSFET driver which drops only ~0.5V.
  2. Servo Stalling: The SG90 servo can draw 700mA+ if the pan mechanism is physically jammed or the sensor mount is too heavy. The L298N 5V BEC is only rated for ~500mA continuous. Fix: Ensure the servo moves freely and power the servo from a dedicated 5V BEC.
  3. USB Cable Resistance: If this happens only when plugged into your PC via USB (without the battery connected), your USB cable has high resistance. Fix: Use a shorter, thicker-gauge USB cable.

Extending and Simplifying the Build

Not every iteration of robot diy projects needs to be complex. Here is how to adjust this build based on your current bench inventory and skill level.

How to Simplify

If you lack the SG90 servo or the resistors for the voltage divider, strip the project down to a 'bump-and-turn' or fixed-sensor rover. Mount the HC-SR04 facing strictly forward. To bypass the 5V Echo pin issue entirely without a voltage divider, replace the HC-SR04 with an RCWL-0516 Microwave Radar Sensor. The RCWL operates natively at 3.3V logic, requires no complex acoustic timing, and detects motion through non-metallic chassis materials.

How to Extend

Once the basic navigation loop is stable, add closed-loop feedback. Wire an MPU6050 IMU to the ESP32's I2C bus (GPIO 21 for SDA, GPIO 22 for SCL). By reading the Z-axis gyroscope data, you can replace the hardcoded delay(400) turn times with precise 90-degree angular rotations, compensating for battery voltage sag and uneven floor friction. For advanced communication, leverage the ESP32's native ESP-NOW protocol to build a low-latency 2.4GHz remote control without the overhead of WiFi access point connections.

FAQ: Navigating Robot DIY Projects

What is the best microcontroller for beginner robot diy projects?

For absolute beginners focusing purely on motor logic, the Arduino Uno R3 remains the standard due to its 5V tolerance and vast tutorial library. However, for projects requiring wireless telemetry, camera integration, or high-speed PID control loops, the ESP32-WROOM-32E is vastly superior. Its dual-core 240MHz processor handles sensor polling on one core while maintaining motor control and WiFi stacks on the other, preventing the jitter common in single-core AVR boards.

Why do my TT motors stall when I upload code to the ESP32?

This is a quirk of the ESP32's boot process. GPIO 12, 13, 14, and 15 are read during boot to determine the flash voltage and boot mode. If you wire motor control pins to these specific GPIOs, they will toggle erratically or pull high/low during the upload sequence, causing the L298N to short or stall the motors. Always verify your pinout against an ESP32 pinout cheat sheet to avoid strapping pins for motor control.

Can I power the ESP32 and L298N from the same 5V BEC?

Yes, but with strict current limits. The ESP32 draws roughly 160mA during WiFi transmission spikes. If you are using a standard 5V 3A UBEC (Universal Battery Elimination Circuit) commonly found in RC hobbies, you can safely power the ESP32, the SG90 servo, and the HC-SR04 from it. However, do not route the TT motor power through this BEC; motors generate severe back-EMF noise that will corrupt the ESP32's logic and trigger watchdog resets. Keep the motor power isolated on the raw Li-ion pack through the L298N.