To write reliable Arduino robot code for a 2WD obstacle-avoiding chassis, you must use explicit PWM pin definitions, implement timeout error handling for ultrasonic sensors, and account for the L298N motor driver's inherent voltage drop. The code below targets the Arduino Uno R3 (ATmega328P) and uses raw C++ timing functions rather than blocking third-party libraries, ensuring your robot remains responsive to sensor inputs while motors are in motion.
Project Overview & Difficulty Rating
Time to Build: 2-3 hours
Target Board Variant: Arduino Uno R3 (Genuine or ATmega328P-based clone like Elegoo Uno R3)
Exact Parts List
Do not substitute the motor driver without changing the code logic. The L298N uses a specific enable-pin PWM structure that differs from I2C-based drivers like the TB6612FNG.
- Microcontroller: Arduino Uno R3 (ATmega328P)
- Motor Driver: L298N Dual H-Bridge Module (Red board variant with onboard 5V regulator)
- Sensor: HC-SR04 Ultrasonic Distance Sensor (5V tolerant)
- Motors: 2x 130-size TT Gear Motors (3-6V nominal, 200mA stall current)
- Power Supply: 2S LiPo Battery (7.4V nominal, 8.4V fully charged, 1000mAh minimum) or 2x 18650 Li-ion cells in series. Do not use 4x AA alkaline batteries; the voltage sag under motor load will brownout the ATmega328P.
- Chassis: Standard 2WD acrylic chassis kit with castor wheel
Hardware Pin Mapping & Wiring Spec Sheet
Before uploading the Arduino robot code, verify your physical wiring against this table. A common failure point is wiring motor enable pins to non-PWM digital pins, which results in the motor only turning at 100% speed or not at all.
| Component | Module Pin | Arduino Uno Pin | Notes / Constraints |
|---|---|---|---|
| L298N | ENA (Left Motor PWM) | Pin 5 (PWM ~) | Must be a PWM-capable pin |
| L298N | IN1 (Left Dir 1) | Pin 4 | Digital Output |
| L298N | IN2 (Left Dir 2) | Pin 7 | Digital Output |
| L298N | ENB (Right Motor PWM) | Pin 6 (PWM ~) | Must be a PWM-capable pin |
| L298N | IN3 (Right Dir 1) | Pin 8 | Digital Output |
| L298N | IN4 (Right Dir 2) | Pin 9 (PWM ~) | Digital Output (PWM not required here) |
| HC-SR04 | Trig | Pin 10 | Digital Output |
| HC-SR04 | Echo | Pin 11 | Digital Input (5V tolerant) |
| Power | L298N 12V / GND | Battery + / Battery - | Keep 5V enable jumper ON if battery < 12V |
| Power | L298N 5V Out | Arduino 5V Pin | Powers the Uno and HC-SR04 |
| Ground | L298N GND | Arduino GND | Critical: Must share common ground |
The Complete Arduino Robot Code
This sketch uses the native pulseIn() function with a strict timeout to prevent the robot from freezing if the ultrasonic sensor fails to receive an echo. According to the official Arduino pulseIn reference, setting a timeout is mandatory for mobile robotics to maintain loop responsiveness.
// Arduino Robot Code: 2WD Obstacle Avoidance
// Target: Arduino Uno R3 (ATmega328P)
// --- PIN DEFINITIONS ---
#define ENA 5 // Left motor PWM
#define IN1 4 // Left motor direction
#define IN2 7 // Left motor direction
#define ENB 6 // Right motor PWM
#define IN3 8 // Right motor direction
#define IN4 9 // Right motor direction
#define TRIG_PIN 10 // HC-SR04 Trigger
#define ECHO_PIN 11 // HC-SR04 Echo
// --- ROBOT CONFIGURATION ---
#define MOTOR_SPEED 180 // PWM value (0-255)
#define TURN_SPEED 150 // PWM value for turning
#define SAFE_DISTANCE_CM 25 // Distance to trigger avoidance
#define MAX_DISTANCE_CM 400 // Sensor max range
#define PULSE_TIMEOUT_US 30000 // 30ms timeout for pulseIn
long duration;
int distance;
void setup() {
Serial.begin(9600);
// Motor control 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 on boot
stopMotors();
Serial.println("System Initialized. Robot Ready.");
}
void loop() {
distance = getDistance();
// Error handling: If sensor times out, assume path is clear to prevent freezing
if (distance == 0 || distance > MAX_DISTANCE_CM) {
distance = MAX_DISTANCE_CM;
}
Serial.print("Distance: ");
Serial.print(distance);
Serial.println(" cm");
if (distance <= SAFE_DISTANCE_CM) {
executeAvoidanceManeuver();
} else {
moveForward(MOTOR_SPEED);
}
delay(50); // Small delay for sensor stability (non-blocking alternative preferred for advanced builds)
}
// --- SENSOR FUNCTION WITH ERROR HANDLING ---
int getDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Read echo with strict timeout to prevent infinite blocking
duration = pulseIn(ECHO_PIN, HIGH, PULSE_TIMEOUT_US);
if (duration == 0) {
Serial.println("Warning: Sensor timeout. No echo received.");
return 0; // Handled in loop() as max distance
}
// Calculate distance (speed of sound = 343 m/s -> 0.0343 cm/us)
return (duration * 0.0343) / 2;
}
// --- MOTOR CONTROL FUNCTIONS ---
void moveForward(int speed) {
analogWrite(ENA, speed);
analogWrite(ENB, speed);
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
void moveBackward(int speed) {
analogWrite(ENA, speed);
analogWrite(ENB, speed);
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
void turnRight(int speed) {
analogWrite(ENA, speed);
analogWrite(ENB, speed);
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW); // Right motor backward
digitalWrite(IN4, HIGH);
}
void stopMotors() {
analogWrite(ENA, 0);
analogWrite(ENB, 0);
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
void executeAvoidanceManeuver() {
stopMotors();
delay(200);
moveBackward(MOTOR_SPEED);
delay(400);
turnRight(TURN_SPEED);
delay(500);
stopMotors();
}
Debugging: First Three Things to Check When It Fails
When your robot fails to move or behaves erratically, do not immediately rewrite the code. Hardware and power physics cause 90% of embedded robotics failures. Run through these three checks first:
- Verify the Common Ground Reference: The L298N, Arduino Uno, and battery pack must share a single ground node. If the GND wire between the L298N and the Arduino is loose or missing, the logic signals from the ATmega328P will float, causing the motor driver to ignore commands or trigger randomly.
- Check PWM Pin Assignment: Look at your physical ENA and ENB wires. They must be plugged into Arduino pins marked with a tilde (~), such as 5 and 6. If you wire ENA to Pin 4 (a standard digital pin),
analogWrite()will default to a binary HIGH/LOW state, giving you only 100% speed or 0% speed with no control. - Measure Power Supply Voltage Sag: The L298N uses bipolar junction transistors (BJTs) internally, which drop approximately 2V to 3V across the H-bridge under load (see the TI L298 Datasheet). If you supply 6V from 4x AA batteries, the motors only see ~3.5V, while the Arduino's 5V regulator starves, causing a brownout reset. Always use a 7.4V (2S LiPo) or 8.4V source minimum.
Common Compiler Error: Missing Library Headers
If you copied code from a forum and hit a compilation failure, you will likely see this exact error string in the Arduino IDE console:
fatal error: AFMotor.h: No such file or directory
Ranked Causes & Fixes:
- Wrong Hardware Abstraction: The code was written for the Adafruit Motor Shield V1/V2, but you are using a raw L298N breakout board. Fix: Delete the
#include <AFMotor.h>line and replace the motor commands with the rawdigitalWriteandanalogWritelogic provided in the sketch above. - Library Not Installed: You actually have the Adafruit shield but forgot to install the dependency. Fix: Go to Sketch > Include Library > Manage Libraries, search for "Adafruit Motor Shield", and install it.
- Case Sensitivity (Linux/Mac): You typed
#include <afmotor.h>. Fix: C++ is case-sensitive; correct it toAFMotor.h.
How to Extend or Simplify the Build
Depending on your application, you may need to alter the complexity of this Arduino robot code.
Simplifying the Build (Line Following / Bump Sensors)
If ultrasonic sensing is too erratic for your environment (e.g., sound-absorbing fabrics or angled walls causing scattering), strip out the HC-SR04 code entirely. Replace it with two digital IR bump sensors (like the TCRT5000 reflective modules) wired to Pins 2 and 3. Configure these pins with INPUT_PULLUP and use hardware interrupts (attachInterrupt) to trigger an immediate stop-and-turn routine. This removes all timing delays from the main loop.
Extending the Build (Closed-Loop Control)
The code above operates in "open-loop"—it assumes the robot turns exactly 90 degrees based on a 500ms delay. In reality, battery voltage drop and carpet friction will alter the turn radius. To extend this into a closed-loop system, add an MPU6050 IMU (I2C on A4/A5) to measure yaw rotation, or add optical encoders to the TT motor shafts. You will need to replace the delay() functions in the avoidance maneuver with a while() loop that monitors the encoder tick count or IMU yaw angle, stopping the motors only when the physical target is reached.
Arduino Robot Code FAQ
How do I fix Arduino robot code that compiles but the motors only click?
A rapid clicking sound without rotation means the motor is receiving power pulses but lacks the current to overcome static friction (stall torque). This happens when the Arduino is powered via a weak USB connection and the L298N 5V jumper is removed, or when the PWM frequency is too low. First, ensure your battery pack can deliver at least 1A continuous. Second, increase the MOTOR_SPEED variable in the code to 255 temporarily to test if the motors can break static friction at maximum voltage.
Why is my Arduino robot code drifting to one side when driving straight?
TT gear motors have notoriously poor manufacturing tolerances; a 5% RPM mismatch between the left and right motors is standard. To fix this in software, introduce a steering trim variable. If the robot pulls left, reduce the PWM value of the left motor. Add #define LEFT_TRIM 15 at the top of your code, and change the left motor command to analogWrite(ENA, speed - LEFT_TRIM);. Calibrate this value on a flat, hard surface.
Can I use this Arduino robot code on an Arduino Mega 2560 instead of an Uno?
Yes, but you must verify your PWM pins. The Arduino Mega has different PWM pin assignments than the Uno. On the Mega, pins 5, 6, and 9 are still PWM-capable, so the default pin mapping in this code will work without modification. However, if you decide to move the motor enables to pins 11, 12, or 13 for wiring convenience, note that on the Mega, pins 11, 12, and 13 are not PWM-capable (they are on the Uno). Always check the silkscreen on your specific Mega board for the tilde (~) symbol next to the pin number.






