The most reliable baseline for diy robot projects isn't a cheap ultrasonic car kit; it is a 2-wheel drive (2WD) rover powered by an ESP32-WROOM-32 DevKit V1, a TB6612FNG MOSFET motor driver, and a 2S 18650 lithium-ion pack. This combination solves the three biggest failure points in hobby robotics: insufficient PWM frequency, voltage sag from inefficient drivers, and Wi-Fi brownouts. Below is the exact hardware blueprint, pin mapping, and modern Arduino-ESP32 v3.x firmware required to get a rover moving reliably on your bench.

Motor Driver Selection: Moving Past the L298N

If you are browsing legacy tutorials, you will see the L298N BJT-based dual H-bridge recommended everywhere. For modern ESP32 builds, the L298N is a liability. It drops roughly 2V across its bipolar junction transistors, wasting battery life as heat and starving your TT gearmotors of torque. The industry standard for hobbyist DIY robot projects has shifted to MOSFET-based drivers.

Driver IC Continuous Current Peak Current Voltage Drop Logic Voltage Approx. Cost (2026)
L298N (Legacy BJT) 2.0 A 3.0 A ~2.0 V (High loss) 5V (Needs level shift for ESP32) $4.50
TB6612FNG (MOSFET) 1.2 A 3.2 A ~0.5 V (Efficient) 2.7V - 5.5V (Native 3.3V) $6.00
DRV8871 (Single H-Bridge) 3.6 A 4.5 A ~0.4 V 6.5V minimum (Fails on 3.3V logic) $5.50 (x2 needed)
DRV8833 (Dual MOSFET) 1.5 A 2.0 A ~0.6 V 2.7V - 5.5V $4.00

The Verdict: The TB6612FNG hits the sweet spot. It natively accepts the ESP32's 3.3V logic without level shifters, handles the 1.2A continuous draw of standard yellow TT gearmotors, and its low on-resistance preserves your battery capacity. For a detailed breakdown of the ESP32's native PWM capabilities that drive these MOSFETs, refer to the Espressif LEDC API documentation.

Bill of Materials and Pin Mapping

A common mistake in DIY robot projects is powering the ESP32 directly from the motor battery via the onboard AMS1117 linear regulator. When the motors stall, voltage sags trigger a reset. We use a buck converter to isolate the logic rail.

Exact Parts List

  • MCU: ESP32-WROOM-32 DevKit V1 (38-pin variant)
  • Motor Driver: TB6612FNG Dual Motor Driver Breakout
  • Motors: 2x 1:48 TT Gearmotors (3V-6V DC)
  • Power (Motors): 2S 18650 Li-ion Battery Pack (7.4V nominal, 8.4V max) with BMS
  • Power (Logic): LM2596 DC-DC Buck Converter (set to 5.0V output)
  • Chassis: Acrylic 2WD baseplate with 65mm wheels and rear castor

Bench Tip: Do not wire the TB6612FNG STBY (Standby) pin to a GPIO. The chip draws less than 1mA in active mode. Tie STBY directly to the 3.3V VCC rail to save a microcontroller pin and avoid boot-strapping conflicts.

ESP32 to TB6612FNG Pin Mapping

TB6612FNG Pin ESP32 GPIO Function
PWMAGPIO 33Motor A Speed (PWM)
AIN1GPIO 25Motor A Direction 1
AIN2GPIO 26Motor A Direction 2
PWMBGPIO 32Motor B Speed (PWM)
BIN1GPIO 27Motor B Direction 1
BIN2GPIO 14Motor B Direction 2
STBY3.3V PinAlways Active
VCC3.3V PinLogic Power
GNDGNDCommon Ground (Crucial)
VMLM2596 OUT+Motor Power (Wait, no: VM goes to 2S Battery +)

Correction for VM: Wire the TB6612FNG VM pin directly to the 2S Battery Pack positive terminal. Wire the LM2596 input to the battery, and wire the LM2596 5V output to the ESP32 5V / VIN pin. Tie all grounds together.

Step-by-Step Wiring Procedure

  1. Prep the Buck Converter: Connect a multimeter to the LM2596 output terminals. Turn the blue trimpot until the multimeter reads exactly 5.00V. Disconnect power before wiring it to the ESP32.
  2. Establish Common Ground: Solder or crimp a star-ground wire connecting the 2S Battery negative, the TB6612FNG GND, the LM2596 GND, and the ESP32 GND. Skipping this step guarantees erratic PWM behavior.
  3. Wire Logic Levels: Connect the ESP32 3.3V pin to the TB6612FNG VCC and STBY pins. Connect GPIOs 25, 26, 27, 14, 32, and 33 to their respective AIN/BIN and PWM pins.
  4. Wire Motor Power: Connect the 2S battery positive to the TB6612FNG VM pin and the LM2596 IN+ pin. Connect the motor phases (A01/A02 and B01/B02) to the TT gearmotors. Polarity doesn't matter yet; we will fix forward/reverse in software.
  5. Verify Before Powering: Set your multimeter to continuity mode. Check for shorts between VM and GND, and VCC and GND. If it beeps, find the stray wire strand before plugging in the battery.

ESP32 v3.x Firmware: PWM Motor Control

The Arduino-ESP32 core underwent a major API shift in version 3.x. The legacy ledcSetup() and ledcAttachPin() functions are deprecated. The code below uses the modern ledc_attach() and ledc_write() functions, targeting the ESP32 DevKit V1.

#include <Arduino.h>

// --- Pin Definitions ---
#define PWMA 33
#define AIN1 25
#define AIN2 26
#define PWMB 32
#define BIN1 27
#define BIN2 14

// --- PWM Configuration ---
#define PWM_FREQ 1000    // 1kHz is optimal for TT gearmotors (avoids audible whine)
#define PWM_RES  10      // 10-bit resolution (0-1023 duty cycle)
#define MAX_DUTY 1023

void setupMotors() {
  // Initialize direction pins as standard digital outputs
  pinMode(AIN1, OUTPUT);
  pinMode(AIN2, OUTPUT);
  pinMode(BIN1, OUTPUT);
  pinMode(BIN2, OUTPUT);

  // Attach PWM channels using modern ESP32 v3.x API
  if (!ledc_attach(PWMA, PWM_FREQ, PWM_RES)) {
    Serial.println("Error: Failed to attach PWMA channel");
  }
  if (!ledc_attach(PWMB, PWM_FREQ, PWM_RES)) {
    Serial.println("Error: Failed to attach PWMB channel");
  }
}

// Motor ID enum for cleaner function calls
enum Motor { LEFT = 0, RIGHT = 1 };
enum Direction { FORWARD = 1, REVERSE = 2, BRAKE = 3 };

void driveMotor(Motor motor, Direction dir, uint16_t speed) {
  // Clamp speed to 10-bit resolution limit
  if (speed > MAX_DUTY) speed = MAX_DUTY;

  uint8_t pin_in1 = (motor == LEFT) ? AIN1 : BIN1;
  uint8_t pin_in2 = (motor == LEFT) ? AIN2 : BIN2;
  uint8_t pin_pwm = (motor == LEFT) ? PWMA : PWMB;

  switch (dir) {
    case FORWARD:
      digitalWrite(pin_in1, HIGH);
      digitalWrite(pin_in2, LOW);
      ledc_write(pin_pwm, speed);
      break;
    case REVERSE:
      digitalWrite(pin_in1, LOW);
      digitalWrite(pin_in2, HIGH);
      ledc_write(pin_pwm, speed);
      break;
    case BRAKE:
      digitalWrite(pin_in1, LOW);
      digitalWrite(pin_in2, LOW);
      ledc_write(pin_pwm, 0);
      break;
  }
}

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("ESP32 2WD Rover Booting...");
  setupMotors();
}

void loop() {
  // Test Sequence: Forward, Reverse, Pivot, Brake
  driveMotor(LEFT, FORWARD, 800);
  driveMotor(RIGHT, FORWARD, 800);
  delay(2000);

  driveMotor(LEFT, REVERSE, 800);
  driveMotor(RIGHT, REVERSE, 800);
  delay(2000);

  // Pivot left (Right motor forward, Left motor backward)
  driveMotor(LEFT, REVERSE, 600);
  driveMotor(RIGHT, FORWARD, 600);
  delay(1500);

  driveMotor(LEFT, BRAKE, 0);
  driveMotor(RIGHT, BRAKE, 0);
  delay(3000);
}

Debugging: First Three Things to Check When It Fails

When your rover refuses to move or the ESP32 reboots mid-drive, do not rewrite your code immediately. Hardware and power delivery cause 90% of failures in DIY robot projects.

1. The Serial Monitor Shows: Brownout detector was triggered

The Cause: The ESP32's brownout detector tripped because the 3.3V rail dipped below ~2.4V. This happens when motors draw stall current (up to 2A each on startup) and the voltage sag bleeds into the logic rail. The Fix: Verify your LM2596 buck converter is actually outputting 5V under load. If you are powering the ESP32 via the 3.3V pin directly from the TB6612FNG VCC, stop. The TB6612FNG VCC pin is an input for logic, not a high-current output. Power the ESP32 via the 5V/VIN pin using the dedicated buck converter.

2. One Motor Spins, The Other Just Whines or Vibrates

The Cause: Insufficient PWM duty cycle to overcome static friction, or a missing common ground. The Fix: First, check the continuity between the TB6612FNG GND and the ESP32 GND. If ground is solid, increase the starting duty cycle in your code from 400 to 700. TT gearmotors often require a 60% duty cycle just to break static friction before you can throttle down to lower speeds.

3. The Rover Drives in Circles Instead of Straight

The Cause: Manufacturing tolerances in cheap TT gearmotors mean one motor is physically 5-10% faster than the other. Furthermore, if you wired the motor phases backward, one side is pushing forward while the code thinks it's pushing backward. The Fix: Run the FORWARD test sequence. If one wheel spins backward, simply swap the two wires (e.g., B01 and B02) on the TB6612FNG terminal block for that specific motor. To fix the veering, implement a software trim variable: multiply the faster motor's speed by 0.92 until it tracks straight.

Scaling the Build: Simplify or Extend

The beauty of this specific hardware stack is its modularity. Depending on your end goal, you can easily scale this baseline up or down.

How to Simplify (For Line-Following or Sumo)

If you are building a line-following robot or a mini-sumo bot, drop the Wi-Fi overhead. Swap the ESP32 for an Arduino Nano (ATmega328P). You will need to change the ledc_attach code back to the standard analogWrite() API, but you eliminate the boot-strapping pin conflicts and Wi-Fi power draw, extending your 18650 battery life by roughly 30%.

How to Extend (For ROS2 and SLAM Mapping)

If your goal is autonomous navigation, the ESP32 is strictly a low-level motor controller. You will need to add a Raspberry Pi 5 (8GB) or an Orange Pi 5 as the high-level compute node.

  1. Mount the Pi on the top chassis plate.
  2. Connect the Pi to the ESP32 via USB-C (using Micro-ROS or standard rosserial over UART).
  3. Power the Pi using a separate high-current 5V 5A buck converter (the LM2596 used for the ESP32 cannot supply the 3A+ spikes required by the Pi 5).
  4. Add a 2D LiDAR (like the Slamtec RPLIDAR A1) to the top deck for SLAM mapping.

By starting with a MOSFET driver and isolated power rails, you build a foundation that won't melt your breadboard or brick your microcontroller when you decide to add heavier sensors and compute modules later.