Project Overview & Difficulty Rating
Building a reliable 2WD arduino robot with obstacle avoidance requires balancing mechanical tolerances, power delivery, and non-blocking sensor code. While kits are abundant, most fail because they ignore the voltage drop across cheap motor drivers and the manufacturing variance of included gearmotors. This guide provides a bench-tested build using an Arduino Uno R3, an L298N dual H-bridge, and an HC-SR04 ultrasonic sensor, targeting a total build cost of $25–$35 and an assembly time of roughly two hours.
Time to Complete: 2 Hours
Target Board Variant: Arduino Uno R3 (ATmega328P, DIP-28 package)
Required Parts & Specifications
| Component | Exact Variant / Model | Notes & Bench Realities |
|---|---|---|
| Microcontroller | Arduino Uno R3 (Clone or Genuine) | Must have ATmega16U2 USB-to-Serial (avoid CH340 if using macOS Catalina+ without drivers). |
| Motor Driver | L298N Dual H-Bridge Module | Expect a 1.5V to 2.5V voltage drop due to BJT Darlington topology. Heat sink required for >1A draws. |
| Sensor | HC-SR04 Ultrasonic | Operates at 5V logic. Blind spot is < 2 cm. Max reliable range is ~400 cm. |
| Motors | 130-size TT Gearmotors (6V, 200 RPM) | High variance. Expect ±10% RPM mismatch between left and right units out of the box. |
| Power Supply | 2x 18650 Li-ion (3000mAh) + 2S Holder | Provides 7.4V nominal (8.4V full). Do NOT use 9V PP3 alkaline smoke alarm batteries. |
| Chassis | Generic 2WD Acrylic Baseplate | Ensure it includes the brass standoffs and a front caster wheel. |
Hardware Wiring & Pin Mapping
The most common point of failure in DIY robotics is power distribution. The L298N module has an onboard 5V regulator, but it is only reliable up to ~500mA. We will use it to power the Uno's logic via the 5V pin, bypassing the Uno's onboard regulator to prevent thermal throttling, while sharing a strict common ground.
Pin Mapping Table
| Arduino Uno Pin | Component | Function |
|---|---|---|
| D2 | HC-SR04 Echo | Receives 5V pulse width proportional to distance |
| D3 | HC-SR04 Trigger | Sends 10µs 5V pulse to initiate measurement |
| D5 (PWM) | L298N ENA | Speed control for Left Motor (PWM 0-255) |
| D6 (PWM) | L298N ENB | Speed control for Right Motor (PWM 0-255) |
| D7 | L298N IN1 | Left Motor Direction A (HIGH/LOW) |
| D8 | L298N IN2 | Left Motor Direction B (HIGH/LOW) |
| D9 | L298N IN3 | Right Motor Direction A (HIGH/LOW) |
| D10 | L298N IN4 | Right Motor Direction B (HIGH/LOW) |
| 5V | L298N 5V Out | Powers Uno logic (Jumper must be ON for this) |
| GND | L298N GND | CRITICAL: Common ground reference for logic and power |
Assembly Steps
- Prepare the L298N: Leave the 12V and 5V jumper caps ON. Connect your 2S 18650 battery pack positive to the
12Vscrew terminal and negative toGND. - Establish Common Ground: Run a wire from the L298N
GNDterminal to the Arduino UnoGNDpin. Without this, the logic signals will float, causing erratic motor spinning. - Wire the Motors: Connect the left TT motor to
OUT1andOUT2. Connect the right TT motor toOUT3andOUT4. Polarity doesn't matter yet; we will fix reverse directions in software. - Mount the Sensor: Bolt the HC-SR04 to the front acrylic plate. Ensure the transducers are clear of the chassis edge to prevent acoustic reflections from blinding the sensor.
Complete Compilable Code
This code targets the Arduino Uno R3. It uses non-blocking timing via millis() to read the ultrasonic sensor without halting the motor PWM loops, preventing the "stuttering" effect common in beginner code that relies on delay(). It includes serial error handling for sensor timeouts.
// Target Board: Arduino Uno R3 (ATmega328P)
// Ultrasonic Obstacle Avoidance Robot
// --- PIN DEFINITIONS ---
const int TRIG_PIN = 3;
const int ECHO_PIN = 2;
const int ENA_PIN = 5; // Left Motor PWM
const int IN1_PIN = 7; // Left Motor Dir A
const int IN2_PIN = 8; // Left Motor Dir B
const int ENB_PIN = 6; // Right Motor PWM
const int IN3_PIN = 9; // Right Motor Dir A
const int IN4_PIN = 10; // Right Motor Dir B
// --- CONFIGURATION ---
const int BASE_SPEED = 180; // PWM value (0-255)
const int TURN_SPEED = 150; // PWM value for pivoting
const int SAFE_DISTANCE_CM = 25; // Distance to trigger avoidance
const int RIGHT_MOTOR_TRIM = 15; // Offset to correct TT motor variance
unsigned long lastSensorRead = 0;
const unsigned long SENSOR_INTERVAL = 50; // Read every 50ms
int currentDistance = 999;
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(ENA_PIN, OUTPUT);
pinMode(IN1_PIN, OUTPUT);
pinMode(IN2_PIN, OUTPUT);
pinMode(ENB_PIN, OUTPUT);
pinMode(IN3_PIN, OUTPUT);
pinMode(IN4_PIN, OUTPUT);
// Stop motors initially
digitalWrite(IN1_PIN, LOW);
digitalWrite(IN2_PIN, LOW);
digitalWrite(IN3_PIN, LOW);
digitalWrite(IN4_PIN, LOW);
analogWrite(ENA_PIN, 0);
analogWrite(ENB_PIN, 0);
Serial.println("System Initialized. Starting navigation loop.");
}
void loop() {
unsigned long currentMillis = millis();
// Non-blocking sensor read
if (currentMillis - lastSensorRead >= SENSOR_INTERVAL) {
lastSensorRead = currentMillis;
currentDistance = readUltrasonic();
}
// Navigation Logic
if (currentDistance > SAFE_DISTANCE_CM) {
driveForward();
} else {
executeAvoidanceManeuver();
}
}
int readUltrasonic() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 30000us timeout prevents code hanging if echo pin is disconnected
long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration == 0) {
Serial.println("ERR: Ultrasonic Timeout - Check Echo Pin Wiring");
return 999; // Fail-safe: assume path is clear if sensor fails
}
int distance = duration * 0.034 / 2;
return distance;
}
void driveForward() {
digitalWrite(IN1_PIN, HIGH);
digitalWrite(IN2_PIN, LOW);
digitalWrite(IN3_PIN, HIGH);
digitalWrite(IN4_PIN, LOW);
analogWrite(ENA_PIN, BASE_SPEED);
analogWrite(ENB_PIN, BASE_SPEED + RIGHT_MOTOR_TRIM);
}
void executeAvoidanceManeuver() {
// Stop
analogWrite(ENA_PIN, 0);
analogWrite(ENB_PIN, 0);
delay(100);
// Reverse slightly
digitalWrite(IN1_PIN, LOW);
digitalWrite(IN2_PIN, HIGH);
digitalWrite(IN3_PIN, LOW);
digitalWrite(IN4_PIN, HIGH);
analogWrite(ENA_PIN, BASE_SPEED);
analogWrite(ENB_PIN, BASE_SPEED);
delay(300);
// Pivot Right
digitalWrite(IN1_PIN, HIGH);
digitalWrite(IN2_PIN, LOW);
digitalWrite(IN3_PIN, LOW);
digitalWrite(IN4_PIN, HIGH);
analogWrite(ENA_PIN, TURN_SPEED);
analogWrite(ENB_PIN, TURN_SPEED);
delay(400);
// Stop pivot
analogWrite(ENA_PIN, 0);
analogWrite(ENB_PIN, 0);
}
Debugging: First Three Things to Check When It Fails
When your arduino robot fails to move or behaves erratically, do not immediately rewrite the code. Hardware and power faults account for 90% of initial failures. Here are the first three things to check, ranked by likelihood.
If the Uno is on but motors are dead, check the L298N 5V jumper cap. The L298N requires 5V on its logic side to interpret the Uno's PWM signals. If you removed the jumper to use an external 5V BEC but forgot to wire it, the H-bridge logic is unpowered.
Fix: Ensure the 5V/GND jumper cap is bridged on the L298N terminal block, or supply external 5V to the
5V pin.
ERR: Ultrasonic Timeout - Check Echo Pin WiringIf the robot drives forward blindly and crashes, or prints this exact error string to the Serial Monitor, the
pulseIn() function hit its 30ms timeout. Ranked Causes:
1. Echo and Trigger pins are swapped on the breadboard.
2. The HC-SR04 VCC is wired to 3.3V instead of 5V (the sensor requires 5V to trigger the transducers properly).
3. A broken jumper wire on the Echo pin causing it to float.
If one motor runs and the other twitches, or the robot spins violently on its axis upon boot, the logic ground is floating. The Arduino and the L298N must share the exact same ground reference. If the battery negative goes to the L298N GND, but the Arduino GND is only connected via the USB cable to your laptop, ground loops will corrupt the PWM signals.
Fix: Run a dedicated jumper wire from the Arduino
GND pin directly to the L298N GND screw terminal.
Extending and Simplifying the Build
Once the baseline 2WD arduino robot is navigating, you will quickly hit the physical limitations of the L298N and TT motors. Here is how to modify the platform based on your goals.
How to Simplify (and Improve Efficiency)
The L298N is outdated technology. It wastes nearly 30% of your battery energy as heat due to its high voltage drop. To simplify wiring and drastically improve battery life, swap the L298N for a TB6612FNG Dual Motor Driver. The TB6612FNG uses MOSFETs instead of BJTs, resulting in a voltage drop of only ~0.5V. Your 6V motors will actually receive 6V, increasing torque and speed without requiring a higher voltage battery pack. Furthermore, the TB6612FNG is a standard 0.1" breakout board, eliminating the need for bulky screw terminals.
How to Extend (Adding PID and Telemetry)
If your robot veers off course despite the RIGHT_MOTOR_TRIM variable, you are fighting mechanical friction and battery voltage sag. To extend this build into a precision rover:
- Add an MPU6050 IMU: Wire the MPU6050 to the I2C bus (A4/A5 on the Uno). Use the yaw axis data to implement a PID controller that dynamically adjusts the PWM of the left and right motors to maintain a perfectly straight heading.
- Upgrade to Encoders: Replace the standard TT motors with Pololu micro metal gearmotors with magnetic encoders. This allows you to close the loop on wheel velocity, compensating for carpet friction or inclines.
Frequently Asked Questions
Why is my Arduino robot spinning in circles instead of going straight?
This is rarely a code error and almost always a mechanical reality. The yellow TT gearmotors included in most kits are manufactured with loose tolerances. One motor may draw 150mA and spin at 200 RPM, while the other draws 180mA and spins at 180 RPM under the exact same PWM signal. Additionally, the acrylic chassis flexes under load, altering wheel traction. To fix this, use the RIGHT_MOTOR_TRIM variable in the provided code to manually offset the PWM value of the faster motor until the robot tracks straight over a 2-meter test distance.
Can I power an Arduino robot directly from the 9V battery snap?
Technically yes, but practically no. The rectangular 9V PP3 alkaline batteries have a very high internal resistance. When your TT motors start, they draw a stall current of roughly 800mA to 1A each. This massive current draw causes the battery voltage to sag below the Arduino Uno's brownout threshold (~4.5V), causing the microcontroller to constantly reset. You will see the onboard LED flicker and the robot will twitch but never move. Always use a 2S Li-ion (18650) pack or a 6x AA NiMH holder for adequate current delivery.
How do I add Bluetooth control to my Arduino robot?
To add manual RC control, integrate an HC-05 or HC-06 Bluetooth module. Wire the module's TX to Arduino Pin 10 (RX) and RX to Arduino Pin 11 (TX) using a voltage divider on the RX line to step the Uno's 5V logic down to the module's 3.3V tolerance. Use the SoftwareSerial library to read incoming characters from a smartphone app (like Bluetooth RC Controller). You will need to pause the autonomous ultrasonic avoidance loop or implement a state machine that switches between "Auto" and "Manual" modes based on serial input.






