The most reliable starting point for a robot arduino project is a 2WD differential drive chassis powered by a 2S 18650 Li-ion pack, driven by an L298N dual H-bridge, and controlled by an Arduino Uno R3. This combination offers the best balance of torque, sensor compatibility, and debuggability for indoor navigation. Below is the exact decision framework, wiring schematic, and fault-tolerant code you need to get it moving—and the specific steps to fix it when it inevitably spins in circles.

The Decision Matrix: Which Robot Arduino Chassis to Pick?

Before buying parts, you need to match your chassis to your environment. Here is the decision path to select the right hardware. Read down the 'If your priority is...' column and stop at the first match.

If your priority is...Chassis TypeMotor TypeVerdict & Concrete Pick
Low budget, indoor debugging, learning PID2WD AcrylicTT Gearmotors (1:48)PICK THIS. Cheapest, easiest to wire, massive community support.
Heavy payload (>2kg), outdoor grass/carpet4WD Acrylic or MetalTT Gearmotors or 12V DCChoose 4WD. Requires two L298N drivers or one high-current BTS7960.
Stairs, extreme uneven terrainTank TreadsHigh-torque 12V DCChoose Treads. High current draw; requires robust TB6612FNG or VNH5019 drivers.
Omnidirectional movement, holonomic drivesMecanum Wheel Rover4x Independent DCAvoid for beginners. Requires complex inverse kinematics and 4 separate motor channels.

Default Recommendation: For 90% of hobbyists and students, the 2WD Acrylic Chassis with TT motors is the correct choice. It keeps the current draw under 3A total, allowing you to use cheap, widely available motor drivers without melting your wiring harness.

Exact Parts List and Spec Sheet

Do not substitute the battery pack. Using a 9V alkaline 'smoke alarm' battery is the number one reason robot arduino builds fail; they cannot supply the 1.5A+ transient current required when both motors start simultaneously.

ComponentExact Variant / ModelEst. Price (2026)Critical Specs & Notes
MicrocontrollerArduino Uno R3 (ATmega328P)$24.005V logic. Do not use the R4 Minima for this specific code without adjusting PWM frequencies.
Motor DriverL298N Dual H-Bridge Module$6.50Bipolar junction transistor (BJT) based. Drops ~2V at 1A. Max continuous current 2A per channel.
MotorsTT Gearmotors (1:48 ratio)$4.00 (pair)3V-6V nominal. Stall current is ~2A. No-load current is ~200mA.
SensorHC-SR04 Ultrasonic$3.005V VCC required. 40kHz transducers. Blind zone is <2cm.
Power Source2S 18650 Li-ion Pack (7.4V)$18.008.4V fully charged, 6.0V empty. Must include a 2S BMS to prevent over-discharge fire risk.
Chassis2WD Acrylic Baseplate$8.00Includes caster wheel. Ensure caster is metal or hard plastic, not rubber (too much drag).
Power Math: Your 2S Li-ion pack outputs 8.4V when full. The L298N drops about 2V across its internal transistors. This leaves ~6.4V for your TT motors, which is perfectly within their 3-6V optimal range. As the battery drains to 7.0V, the motors will see ~5.0V. This is why a 2S Li-ion is the gold standard for this exact chassis.

Pin Mapping and Wiring the L298N Driver

The most common wiring mistake is failing to bond the grounds. The Arduino, the L298N logic circuit, and the battery negative terminal must share a common ground. Without this, the 5V logic signals from the Arduino will float relative to the L298N, resulting in erratic motor behavior or a dead short.

Arduino Uno R3 PinDestination ModuleModule PinWire Color (Suggested)
5VL298N+5V (Logic)Red
GNDL298NGNDBlack
D5 (PWM)L298NENA (Jumper removed)Orange
D4L298NIN1Yellow
D7L298NIN2Yellow
D6 (PWM)L298NENB (Jumper removed)Orange
D8L298NIN3Green
D12L298NIN4Green
D9HC-SR04TrigBlue
D10HC-SR04EchoPurple

Battery Wiring: Connect the 2S Li-ion positive lead to the L298N 12V terminal (which accepts up to 35V, despite the label). Connect the battery negative lead to the L298N GND terminal. Ensure the 5V jumper on the L298N is removed when using a battery voltage above 12V, but for an 8.4V Li-ion, you can leave it in to power the Arduino via the L298N's onboard 5V regulator, though powering the Arduino via its own USB or barrel jack is safer for thermal management.

Complete Control Code for the Arduino Uno R3

This code targets the Arduino Uno R3 (ATmega328P). It includes strict timeout handling for the ultrasonic sensor to prevent the pulseIn() function from blocking the main loop if the sensor fails to receive an echo. It uses a non-blocking state machine approach for obstacle avoidance.


// ==========================================
// 2WD Robot Arduino - Obstacle Avoidance
// Target Board: Arduino Uno R3 (ATmega328P)
// ==========================================

// --- Pin Definitions ---
#define TRIG_PIN 9
#define ECHO_PIN 10
#define ENA 5
#define IN1 4
#define IN2 7
#define ENB 6
#define IN3 8
#define IN4 12

// --- Configuration ---
const int MOTOR_SPEED = 200;     // PWM value (0-255)
const int TURN_SPEED = 150;      // PWM value for pivoting
const int SAFE_DISTANCE = 25;    // Distance in cm to trigger avoidance
const long SENSOR_TIMEOUT = 30000; // 30ms timeout for pulseIn (approx 5 meters)

void setup() {
  Serial.begin(115200);
  
  // Motor Pins
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(ENB, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  
  // Sensor Pins
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  
  // Ensure motors are stopped at boot
  stopMotors();
  Serial.println("Robot Arduino Initialized.");
}

void loop() {
  long distance = readDistance();
  
  // Error Handling: Sensor timeout or invalid reading
  if (distance == -1) {
    Serial.println("Error: Sensor timeout or out of bounds. Stopping for safety.");
    stopMotors();
    delay(500);
    return;
  }
  
  Serial.print("Distance: ");
  Serial.print(distance);
  Serial.println(" cm");
  
  if (distance > SAFE_DISTANCE || distance == 0) {
    // Path is clear (or object is too close to measure, assume clear to reverse)
    moveForward(MOTOR_SPEED);
  } else {
    // Obstacle detected
    stopMotors();
    delay(100);
    moveBackward(MOTOR_SPEED);
    delay(400);
    stopMotors();
    pivotRight(TURN_SPEED);
    delay(500);
  }
  
  delay(50); // Small debounce delay
}

// --- Motor Control Functions ---
void moveForward(int speed) {
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, speed);
  analogWrite(ENB, speed);
}

void moveBackward(int speed) {
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
  analogWrite(ENA, speed);
  analogWrite(ENB, speed);
}

void pivotRight(int speed) {
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
  analogWrite(ENA, speed);
  analogWrite(ENB, speed);
}

void stopMotors() {
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 0);
  analogWrite(ENB, 0);
}

// --- Sensor Function with Error Handling ---
long readDistance() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  
  // pulseIn with timeout prevents infinite blocking if echo is never received
  long duration = pulseIn(ECHO_PIN, HIGH, SENSOR_TIMEOUT);
  
  if (duration == 0) {
    return -1; // Indicates timeout/error
  }
  
  // Calculate distance (speed of sound = 343 m/s -> 0.0343 cm/us)
  long distance = (duration * 0.0343) / 2;
  
  // Filter out physical impossibilities (HC-SR04 max range is ~400cm)
  if (distance > 400) {
    return -1;
  }
  
  return distance;
}

Debugging: When Your Robot Arduino Spins in Circles

When you upload the code and the robot misbehaves, do not start rewriting the logic. Hardware faults mimic software bugs. Here are the first three things to check before touching the IDE:

  1. Verify the Common Ground: Use your multimeter in continuity mode. Put one probe on the Arduino GND pin and the other on the L298N GND screw terminal. It must read < 1 ohm. If it's open, your logic signals are floating.
  2. Check Voltage Sag Under Load: Measure the DC voltage at the L298N '12V' input terminal while the robot is lifted off the ground and the motors are commanded to run. If it drops below 6.5V, your battery is undersized, depleted, or the BMS is tripping.
  3. Confirm Motor Polarity: If the robot drives backward when told to go forward, or spins in a tight circle instead of driving straight, simply swap the two wires for the offending motor on the L298N OUT1/OUT2 or OUT3/OUT4 terminals.

Specific Error Strings and Ranked Causes

Symptom 1: Serial monitor continuously prints Error: Sensor timeout or out of bounds. Stopping for safety. or Distance: 0 cm

This means the readDistance() function is failing to catch a valid echo pulse.

  • Cause 1 (Most Likely): The HC-SR04 is being powered by 3.3V instead of 5V. The Arduino Uno's 3.3V pin cannot supply the 15mA peak current the transducers need. Move the VCC wire to the 5V pin.
  • Cause 2: The Echo pin is wired to a non-digital pin or a pin conflicting with the SPI bus. Verify it is on D10.
  • Cause 3: Acoustic interference. If the robot is facing a soft surface (like a couch), the 40kHz sound wave is absorbed. Test against a flat wooden door.

Symptom 2: Motors hum loudly but the chassis does not move, or moves only when pushed.

  • Cause 1 (Most Likely): Insufficient current. The L298N drops ~2V. If your battery is at 6.5V, the motors only see 4.5V, but the PWM signal might be set too low to overcome static friction. Increase MOTOR_SPEED to 255 temporarily to test.
  • Cause 2: The L298N is in thermal shutdown. The TI L298N datasheet specifies internal thermal shutdown at 150°C junction temperature. If you stalled the motors for more than 10 seconds during testing, let the chip cool for 2 minutes.
  • Cause 3: The ENA/ENB jumpers on the L298N board were not removed. If the jumpers are left on, the board ignores your Arduino PWM pins and runs the motors at 100% battery voltage constantly, which can stall and overheat the TT motors if the logic pins are misconfigured.

Extending or Simplifying the Build

Once you have the base 2WD robot arduino navigating your living room, you will inevitably want to change the hardware profile. Here is how to scale the build in either direction.

How to Simplify (For strict budgets or younger students)

Drop the HC-SR04 ultrasonic sensor and the pulseIn() timing logic entirely. Replace it with two mechanical microswitches (limit switches) mounted on the front bumper. Wire the switches to digital inputs with internal pull-up resistors enabled (INPUT_PULLUP). When a switch reads LOW, the robot has physically bumped a wall. This eliminates all acoustic blind spots and timing errors, reducing the code to basic 'if-then' state changes. Total cost savings: ~$3, but more importantly, it removes the most complex debugging variable from the project.

How to Extend (For advanced navigation and efficiency)

The L298N is a legacy BJT-based driver. It wastes nearly 20% of your battery power as heat. To extend your runtime and precision, swap the L298N for a TB6612FNG MOSFET-based motor driver. As noted in Pololu's TB6612FNG documentation, this chip has a voltage drop of only ~0.5V at 1A, compared to the L298N's ~2V drop. This gives your TT motors an extra 1.5V of headroom, dramatically increasing torque and top speed without changing the battery.

For navigation, add an MPU6050 IMU (Inertial Measurement Unit) via the I2C bus (A4/A5 on the Uno). By fusing the accelerometer and gyroscope data, you can implement dead reckoning to track exactly how many degrees the robot has turned, replacing the blind 'delay-based' pivoting in the code above with precise, closed-loop PID control.

Your immediate next step: Order the 2S 18650 battery pack with an integrated BMS. Do not attempt to run this chassis on AA battery holders; the voltage sag will ruin your debugging session before it begins.