Building 2WD Arduino robots is a foundational rite of passage for embedded hobbyists, but roughly 90% of first-time builds fail before the wheels ever turn. The culprit is rarely bad code; it is almost always power starvation, logic-level mismatches, or unhandled sensor timeouts locking up the main loop.

This guide cuts through the generic tutorials. We will build a robust, obstacle-avoiding 2WD rover targeting the Arduino Uno R3 (ATmega328P) and the pin-compatible Uno R4 Minima (RA4M1). You will get a precise bill of materials, a data-dense spec sheet, exact pin mappings, and fully compilable C++ code with built-in error handling. Finally, we will cover the exact debugging steps to take when your robot spins in circles or continuously resets.

Spec Sheet & Parts List

Before cutting wires, verify your components against this spec sheet. The most common mistake in Arduino robots is pairing high-stall-current motors with an underpowered battery or an inefficient driver. The L298N is a bipolar transistor-based H-bridge, meaning it drops roughly 2V across its internal junctions. If your battery is too low, your motors will starve.

Component Exact Model / Variant Nominal Voltage Current Draw (Peak/Stall) Est. Cost (2026)
Microcontroller Arduino Uno R3 (or R4 Minima) 5V Logic / 7-12V Vin ~50mA (no peripherals) $22.00 - $27.50
Motor Driver L298N Dual H-Bridge Module 5V to 35V (VCC) 2A per channel (3A peak) $4.50 - $7.00
Rangefinder HC-SR04 Ultrasonic Sensor 5V VCC (Strict) 15mA (active ping) $1.50 - $3.00
Drive Motors TT Gearmotor (1:48 ratio, yellow) 3V to 6V DC 200mA no-load / 800mA stall $3.00 (pair)
Power Source 2S 18650 Li-ion Pack (w/ BMS) 7.4V nom / 8.4V max Capable of 10A+ discharge $12.00 - $18.00
Callout Tip: The L298N 5V Regulator Trap
Most L298N modules feature a 5V output pin powered by an onboard 7805 linear regulator. Beginners often use this to power the Arduino. Do not do this on a 2S Li-ion pack. When your battery voltage drops below 6.5V under load, the 7805 drops out, the 5V rail collapses, and your Arduino instantly resets. Power the Arduino via its own dedicated buck converter or the barrel jack, and share a common ground.

Pin Mapping & Wiring Rules

Keep your signal wires short and route them away from the motor power leads to prevent electromagnetic interference (EMI) from triggering false echoes on the ultrasonic sensor. Below is the definitive pin mapping for this build.

Arduino Pin Target Module Module Pin Function / Notes
D2 L298N IN1 Left Motor Direction A (Digital)
D3 L298N IN2 Left Motor Direction B (Digital)
D4 L298N IN3 Right Motor Direction A (Digital)
D5 L298N IN4 Right Motor Direction B (Digital)
D6 (PWM) L298N ENA Left Motor Speed (Remove 5V jumper!)
D9 (PWM) L298N ENB Right Motor Speed (Remove 5V jumper!)
D10 HC-SR04 TRIG Ultrasonic Trigger Pulse (Output)
D11 HC-SR04 ECHO Ultrasonic Echo Return (Input)
GND ALL GND CRITICAL: Common ground between Arduino, L298N, and Sensor.

Compilable Code with Error Handling

The following C++ code is fully compilable for the AVR-based Uno R3 and the ARM-based Uno R4 Minima. It avoids blocking delays where possible and includes explicit timeout handling for the pulseIn() function to prevent the robot from freezing if the ultrasonic sensor misses an echo.

// Target Board: Arduino Uno R3 / R4 Minima
// Project: 2WD Obstacle Avoiding Robot

// --- PIN DEFINITIONS ---
#define LEFT_IN1   2
#define LEFT_IN2   3
#define RIGHT_IN3  4
#define RIGHT_IN4  5
#define LEFT_ENA   6   // Must be PWM capable
#define RIGHT_ENB  9   // Must be PWM capable

#define TRIG_PIN   10
#define ECHO_PIN   11

// --- CONSTANTS ---
#define MOTOR_SPEED 180       // PWM value (0-255)
#define STOP_DIST_CM 20       // Distance to trigger avoidance
#define SOUND_SPEED_CM 0.0343 // Speed of sound in cm/uS
#define PING_TIMEOUT 30000    // 30ms timeout for pulseIn (prevents lockup)

void setup() {
  Serial.begin(115200);
  
  // Configure Motor Pins
  pinMode(LEFT_IN1, OUTPUT);
  pinMode(LEFT_IN2, OUTPUT);
  pinMode(RIGHT_IN3, OUTPUT);
  pinMode(RIGHT_IN4, OUTPUT);
  pinMode(LEFT_ENA, OUTPUT);
  pinMode(RIGHT_ENB, OUTPUT);
  
  // Configure Sensor Pins
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  
  // Ensure motors are stopped on boot
  stopMotors();
  Serial.println("SYS: Boot complete. Entering main loop.");
}

void loop() {
  long distance_cm = readUltrasonic();
  
  // Error Handling: Sensor Timeout or Out of Bounds
  if (distance_cm == 0 || distance_cm > 400) {
    Serial.println("ERR: SENSOR_TIMEOUT or OUT_OF_BOUNDS. Stopping for safety.");
    stopMotors();
    delay(500); // Brief pause before retrying
    return;
  }
  
  Serial.print("DBG: Distance = ");
  Serial.print(distance_cm);
  Serial.println(" cm");

  // Navigation Logic
  if (distance_cm <= STOP_DIST_CM) {
    Serial.println("ACT: Obstacle detected. Executing avoidance maneuver.");
    avoidObstacle();
  } else {
    driveForward();
  }
  
  delay(50); // 50ms loop delay for stability
}

// --- MOTOR CONTROL FUNCTIONS ---
void driveForward() {
  digitalWrite(LEFT_IN1, HIGH);
  digitalWrite(LEFT_IN2, LOW);
  digitalWrite(RIGHT_IN3, HIGH);
  digitalWrite(RIGHT_IN4, LOW);
  analogWrite(LEFT_ENA, MOTOR_SPEED);
  analogWrite(RIGHT_ENB, MOTOR_SPEED);
}

void stopMotors() {
  digitalWrite(LEFT_IN1, LOW);
  digitalWrite(LEFT_IN2, LOW);
  digitalWrite(RIGHT_IN3, LOW);
  digitalWrite(RIGHT_IN4, LOW);
  analogWrite(LEFT_ENA, 0);
  analogWrite(RIGHT_ENB, 0);
}

void avoidObstacle() {
  stopMotors();
  delay(200);
  
  // Reverse
  digitalWrite(LEFT_IN1, LOW);
  digitalWrite(LEFT_IN2, HIGH);
  digitalWrite(RIGHT_IN3, LOW);
  digitalWrite(RIGHT_IN4, HIGH);
  analogWrite(LEFT_ENA, MOTOR_SPEED);
  analogWrite(RIGHT_ENB, MOTOR_SPEED);
  delay(400);
  
  // Pivot Right
  digitalWrite(LEFT_IN1, HIGH);
  digitalWrite(LEFT_IN2, LOW);
  digitalWrite(RIGHT_IN3, LOW);
  digitalWrite(RIGHT_IN4, HIGH);
  analogWrite(LEFT_ENA, MOTOR_SPEED);
  analogWrite(RIGHT_ENB, MOTOR_SPEED);
  delay(600);
  
  stopMotors();
}

// --- SENSOR FUNCTIONS ---
long readUltrasonic() {
  // Clear trigger pin
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  
  // Send 10uS pulse
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  
  // Read echo with explicit timeout to prevent infinite blocking
  long duration = pulseIn(ECHO_PIN, HIGH, PING_TIMEOUT);
  
  if (duration == 0) {
    return 0; // Return 0 to trigger error handling in loop()
  }
  
  return (duration * SOUND_SPEED_CM) / 2;
}

Debugging: The First Three Things to Check

When your robot fails to operate correctly, do not immediately rewrite your code. Hardware and power delivery are the usual suspects. If your serial monitor outputs an error or the robot behaves erratically, check these three failure modes in order.

1. The Robot Spins in Circles or One Wheel is Dead

The Cause: Polarity mismatch or PWM jumper conflict.
The Fix: First, check the physical motor wires. TT gearmotors have no standardized polarity; if one wheel spins backward, swap the two wires connected to that specific motor's terminals on the L298N. Second, verify that you removed the physical 5V jumpers on the ENA and ENB pins of the L298N. If those jumpers are left in place while you attempt to send PWM signals from D6 and D9, the 5V logic will fight the PWM signal, resulting in a dead channel or a fried ATmega pin.

2. Serial Monitor Prints "ERR: SENSOR_TIMEOUT"

The Cause: The pulseIn() function waited the full 30ms and never saw the echo pin go HIGH.
The Fix: This is almost always a wiring or voltage issue. The HC-SR04 requires a strict 5V supply to generate a strong 40kHz acoustic pulse. If you are powering it from a 3.3V rail (common if you mistakenly use an ESP32 or a 3.3V Pro Mini), the acoustic output drops drastically, and the echo never returns. Measure the VCC pin on the sensor with a multimeter; it must read between 4.8V and 5.2V. Also, check for loose breadboard jumper wires on the ECHO pin.

3. Arduino Randomly Resets (Serial Monitor Restarts)

The Cause: Power brownout caused by motor stall current.
The Fix: When a robot starts moving, the motors draw a massive inrush current (up to 800mA per motor for the TT gearmotors). If your battery pack has high internal resistance (like a standard 9V alkaline or cheap AA holders), the voltage sags below the Arduino's brownout detection threshold (usually ~4.3V for the ATmega328P), triggering a hardware reset. Solution: Use a 2S 18650 Li-ion pack as listed in the spec sheet, and ensure your wiring from the battery to the L298N is at least 18 AWG. Thin 22 AWG breadboard wires will introduce enough resistance to cause a voltage drop under load.

Extending vs. Simplifying the Build

Once the baseline 2WD rover is navigating reliably, you have two distinct paths for your next iteration, depending on your project goals.

Simplify: Upgrade to a MOSFET Driver

If your primary goal is longer run times and simpler power management, ditch the L298N. The L298N is an older bipolar design that wastes about 2V as heat. Swap it for a TB6612FNG dual motor driver. The TB6612FNG uses MOSFETs, resulting in a voltage drop of only ~0.5V. This means your 6V motors actually get 6V, they run faster, and your battery lasts roughly 30% longer. The pinout is nearly identical, requiring only minor code adjustments to the PWM frequency.

Extend: Add Dead Reckoning with an IMU

The HC-SR04 is great for not hitting walls, but it cannot drive in a straight line. TT gearmotors have slight manufacturing variances, meaning a "forward" command will always result in a slight drift. To fix this, integrate an MPU6050 IMU (Inertial Measurement Unit) via the I2C bus (SDA to A4, SCL to A5 on the Uno R3). By reading the Z-axis gyroscope data and implementing a basic PID (Proportional-Integral-Derivative) controller in your C++ code, you can dynamically adjust the PWM values of the left and right motors to maintain a perfectly straight heading. For a deep dive on tuning motor controllers, refer to Pololu's comprehensive guide on understanding motor drivers and PID implementation in embedded robotics.

Building Arduino robots teaches you the harsh realities of embedded systems: code is only as good as the power delivery and sensor integrity supporting it. Stick to the spec sheet, respect the common ground, and handle your sensor timeouts gracefully, and your rover will outperform 90% of the builds on the bench next to it.