This post contains affiliate links, marked with *. As an Amazon Associate I earn from qualifying purchases, at no extra cost to you. It helps keep the tutorials free.
This robot car drives on its own and steers around obstacles. It builds on four earlier tutorials: Arduino programming basics, servo motor control, ultrasonic distance sensing and DC motor control with motor drivers.
📋 Quick Summary
An HC-SR04 ultrasonic sensor on an SG90 servo measures the distance ahead and to the sides. An Arduino Uno with the Arduino Motor Shield Rev3 decides where to go and drives the two DC motors. The post covers the parts list, assembly, wiring, the full sketch (the Arduino program) with a walkthrough, tuning values and troubleshooting.
How the robot works
The robot repeats a sense-think-act loop: it measures the distance (sense), compares it with fixed thresholds (think) and drives the motors accordingly (act). A threshold is a fixed distance value in the code at which the behavior changes. The robot uses two: below the far threshold it slows down, and below the close threshold it stops.
HC-SR04 ultrasonic sensor (distance measurement)
The HC-SR04 ultrasonic sensor sends out a short ultrasonic pulse and listens for the echo from the nearest object. It has two signal pins: Trig receives the start pulse from the Arduino, and Echo returns a pulse that lasts as long as the sound took to travel out and back. The Arduino measures that time and converts it into a distance. This measurement happens many times per second while the robot drives.
SG90 servo motor (sensor scan)
The sensor sits on an SG90 servo motor, a small servo of the 9 g weight class. While the robot drives forward, the servo sweeps the sensor left and right, so the robot sees the path ahead and to both sides. When an obstacle is closer than the close threshold, the servo turns the sensor to the far left and then to the far right, and the robot measures where there is more room.
Arduino Motor Shield Rev3 (motor driver)
The Arduino Motor Shield Rev3 is an add-on board (a shield) that plugs onto the pin headers of the Uno (the two rows of sockets along its edges). It drives the two DC motors. Its driver chip, an L298P, contains two H-bridges. An H-bridge is a circuit of four switches that sends current through a motor in either direction, which is how the shield sets the direction (forward or reverse) of each motor separately. The shield also sets the speed of each motor. Speed is set with PWM (pulse-width modulation): the Arduino switches the motor on and off about 490 times per second, and the share of on-time (the duty cycle) sets the speed. The Arduino function analogWrite() outputs this PWM signal. If you run the left and right motors in opposite directions, the robot turns in place.

The motors connect to screw terminals (you clamp the wire with a small screw). Each motor has its own direction, speed and brake pin on the Arduino; the brake pin stops the motor quickly.
| Function | Motor A (left) | Motor B (right) |
|---|---|---|
| Direction | 12 | 13 |
| Speed (PWM) | 3 | 11 |
| Brake | 9 | 8 |
The shield drives two DC motors or one stepper motor (a motor that moves in fixed angular steps) and handles up to 2 A continuous per channel. For more on H-bridges and motor drivers, see my tutorial on the L298N dual H-bridge and the Arduino Motor Shield.

Decision logic
The Arduino compares each distance reading with two thresholds. Far from any obstacle it drives at cruising speed and sweeps the sensor. Below the far threshold it slows down. Below the close threshold it stops, measures the distance to the left and right and turns toward the side with more room. If both sides are blocked (a dead end), it reverses first and scans again.
Components (bill of materials)
| Component | Qty | Required / Optional | Purpose | Notes |
|---|---|---|---|---|
| Arduino Uno | 1 | Required | Main microcontroller: runs the code | Any Uno-compatible board works |
| Arduino Motor Shield Rev3 | 1 | Required | Drives and controls the two DC motors | Plugs directly onto the Arduino Uno |
| DC motors (3 to 6 V) | 2 | Required | Left and right drive wheels | Standard hobby geared DC motors (a gearbox on the motor lowers the speed and raises the torque, the turning force) |
| Servo motor (SG90) | 1 | Required | Sweeps the ultrasonic sensor left and right | Any small servo of the 9 g weight class works |
| Ultrasonic sensor (HC-SR04) | 1 | Required | Measures the distance to obstacles | 4-pin module: VCC (5 V supply), Trig, Echo, GND (ground, 0 V) |
| Jumper wires | Several | Required | All electrical connections between the components | Male-to-male and male-to-female (male = pin end, female = socket end) |
| AA rechargeable cells (NiMH, 1.2 V) | 8 | Required | Power supply for the motors and the Arduino | NiMH is nickel-metal hydride. 8 × 1.2 V = 9.6 V nominal. The shield accepts 5 to 12 V and passes the voltage on to the Uno. |
| Battery holders (4 × AA) | 2 | Required | Hold the cells and connect them in series | Wired in series for 9.6 V |
| Toggle switch | 1 | Required | Main power switch | A lever switch that stays in the on or off position. It goes in the positive battery wire. |
| Robot chassis | 1 | Required | Frame for all components | Acrylic or 3D-printed plate |
| Wheels | 2 | Required | Fit onto the DC motor shafts | Match your motor shaft size |
| Ball caster (support wheel) | 1 | Required | Third point of contact with the ground | A swivel wheel also works |
| Screws, nuts and spacers | Assorted | Required | Mechanical assembly | M3 (3 mm metric thread) is the common size for Arduino and chassis |
| 3D-printed mounts | Set | Optional | Holders for the servo, the ultrasonic sensor and the Arduino | Hot glue, zip ties or brackets work instead |
Step-by-step assembly
I designed the three mounts for the Arduino, the servo and the ultrasonic sensor in Autodesk Inventor (a 3D CAD program) and printed them on my 3D printer.
Step 1: Mount the battery holders
Screw both 4 × AA battery holders to the underside or rear of the chassis. Solder the two holders in series (positive of one holder to negative of the other), so all 8 cells are in series for 9.6 V nominal. The two holders with their cells form the battery pack.
Step 2: Install the power switch
Solder the toggle switch into the positive wire of the battery pack. Mount it on the chassis where you can reach it to switch the robot off quickly.
Step 3: Attach the DC motors
Solder a wire to each of the two terminals of each motor. Mount one motor on the left and one on the right side of the chassis with screws, nuts or motor brackets.
Step 4: Prepare the Arduino mount
If you use the 3D-printed Arduino mount, it takes threaded brass inserts (brass sleeves with an internal thread). Heat each insert with a soldering iron and press it into its mounting hole. This gives you durable screw threads for the Arduino.
Step 5: Mount the Arduino and the shield
Screw the 3D-printed mount (or spacers from the parts list) onto the chassis, and the Arduino Uno onto the mount or spacers. Plug the shield onto the Arduino and check that every header pin sits in its socket before you press it down.
Step 6: Install the servo motor
Attach the 3D-printed servo mount to the front of the chassis with double-sided tape or screws, and fit the SG90 into it. Before you fit the servo horn (the plastic arm on the servo shaft), set the servo to 90°. Upload a short sketch with myservo.write(90), or turn the shaft by hand to the middle of its range. Fit the horn so that the sensor faces straight ahead. The horn must turn freely from left to right.
Step 7: Mount the ultrasonic sensor
Insert the HC-SR04 into its 3D-printed bracket (or fix it with hot glue). Screw the bracket onto the servo horn with a small screw, so the sensor turns with the servo.
Step 8: Attach the ball caster
Mount the ball caster on the front or rear of the chassis, opposite the DC motors. The two drive wheels and the caster give the robot a stable three-point contact with the ground.
Step 9: Attach the drive wheels
Press the wheels onto the motor shafts on both sides. They must sit firmly and turn without rubbing on the chassis.
Wiring the robot car

DC motor connections
| Motor | Shield terminal |
|---|---|
| Motor A (left) wire 1 | A− |
| Motor A (left) wire 2 | A+ |
| Motor B (right) wire 1 | B− |
| Motor B (right) wire 2 | B+ |
Servo motor connections
| Servo wire | Connects to |
|---|---|
| Signal (orange or yellow) | Arduino digital pin 5 (on the shield header) |
| VCC (red) | 5 V pin on the shield |
| GND (brown or black) | GND pin on the shield |
Important: connect the servo’s power wire to the 5 V pin on the shield, not to the 3.3 V pin. The SG90 needs 5 V; on 3.3 V it jitters and moves weakly.
Ultrasonic sensor (HC-SR04) connections
| HC-SR04 pin | Connects to |
|---|---|
| VCC | 5 V on the shield |
| Trig | Arduino digital pin 7 |
| Echo | Arduino digital pin 6 |
| GND | GND on the shield |
Power supply connections
| Battery or switch wire | Connects to |
|---|---|
| Positive (from the switch output) | Shield Vin power terminal (+) |
| Negative (battery ground) | Shield GND power terminal (−) |
The shield passes the battery voltage through its Vin Connect jumper (a small jumper on the back) to the Uno’s VIN pin. The regulator on the Uno converts it to 5 V, so no USB cable is needed while the battery pack is connected. In my build the jumper stays connected, so the Uno and the motors share the one battery pack.
Check all connections before inserting the cells: reversed polarity can damage the shield and the Arduino. Insert the 8 cells into the holders only after this check, and leave the switch off until the sketch is uploaded.
Arduino code
The sketch uses the Servo library (ready-made code for controlling servos), which ships with the Arduino IDE (the program in which you write sketches and upload them to the board).
Setup and pin definitions
setup() runs once after power-up: it attaches the servo, sets the pin modes, applies the brakes and centers the servo.
/**
* Author: Omar Draidrya
* Date: 2024-06-09
* This code controls a robot with ultrasonic sensors and servo motors to avoid obstacles.
*/
#include <Servo.h>
// Create a Servo object to control the servo motor
Servo myservo;
int pos = 0; // Variable to store the servo position
// Define motor pins
int directionA = 12; // Pin for direction of Motor A
int speedA = 3; // Pin for speed of Motor A
int brakeA = 9; // Pin for brake of Motor A
int directionB = 13; // Pin for direction of Motor B
int speedB = 11; // Pin for speed of Motor B
int brakeB = 8; // Pin for brake of Motor B
// Define ultrasonic sensor pins
int trigger = 7; // Trigger pin
int echo = 6; // Echo pin
long duration = 0; // Variable to store the duration of the echo
long distance = 0; // Variable to store the distance from the obstacle
int currentAngle = 90; // Start with the sensor facing forward
int scanDirection = 1; // 1 for left, -1 for right
void setup() {
// Attach the servo on pin 5 to the servo object
myservo.attach(5);
// Set the trigger pin as OUTPUT and the echo pin as INPUT
pinMode(trigger, OUTPUT);
pinMode(echo, INPUT);
// Initialize Motor A pins
pinMode(directionA, OUTPUT);
pinMode(directionB, OUTPUT);
pinMode(brakeA, OUTPUT);
pinMode(brakeB, OUTPUT);
// Apply brakes (stop the motors)
digitalWrite(brakeA, HIGH);
digitalWrite(brakeB, HIGH);
// Set the servo to the center position initially
myservo.write(90);
delay(500);
}
Main loop and helper functions
loop() runs over and over after setup() has finished.
void loop() {
// Measure the distance to the nearest obstacle
distance = measureDistance();
if (distance < 40) {
// Gradually reduce speed as the obstacle approaches
int speed = map(distance, 20, 40, 75, 128);
motorTurn(directionA, speedA, HIGH, speed);
motorTurn(directionB, speedB, HIGH, speed);
if (distance < 15) {
// Stop the motors
digitalWrite(brakeA, HIGH);
digitalWrite(brakeB, HIGH);
delay(500);
// Check surroundings
int leftDistance = checkDirection(175);
int rightDistance = checkDirection(5);
// Determine if the robot is in a dead end
if (leftDistance < 15 && rightDistance < 15) {
// Move backward
motorTurn(directionA, speedA, LOW, 128);
motorTurn(directionB, speedB, LOW, 128);
delay(500);
// Apply brakes
digitalWrite(brakeA, HIGH);
digitalWrite(brakeB, HIGH);
delay(500);
// Scan again after moving back
leftDistance = checkDirection(150);
rightDistance = checkDirection(30);
}
// Determine the best direction to turn
if (leftDistance > rightDistance) {
// Turn left
motorTurn(directionA, speedA, LOW, 128);
motorTurn(directionB, speedB, HIGH, 128);
} else {
// Turn right
motorTurn(directionA, speedA, HIGH, 128);
motorTurn(directionB, speedB, LOW, 128);
}
delay(500);
// Apply brakes
digitalWrite(brakeA, HIGH);
digitalWrite(brakeB, HIGH);
delay(500);
// Set the servo back to the center position
myservo.write(90);
delay(500);
}
} else {
// Move forward at a reduced speed
motorTurn(directionA, speedA, HIGH, 100);
motorTurn(directionB, speedB, HIGH, 100);
// Continuously scan left and right
currentAngle += scanDirection * 10;
if (currentAngle >= 150 || currentAngle <= 30) {
scanDirection = -scanDirection;
}
myservo.write(currentAngle);
delay(40); // Adjusted delay for more realistic servo movement
}
}
motorTurn(), measureDistance() and checkDirection() are the three helper functions that loop() calls.
// Function to set the direction and speed of motors
void motorTurn(int directionPin, int speedPin, int direction, int speed) {
digitalWrite(directionPin, direction);
analogWrite(speedPin, speed);
if (directionPin == directionA) {
digitalWrite(brakeA, LOW);
} else {
digitalWrite(brakeB, LOW);
}
}
// Function to measure the distance using the ultrasonic sensor
int measureDistance() {
digitalWrite(trigger, LOW);
delayMicroseconds(2);
digitalWrite(trigger, HIGH);
delayMicroseconds(10);
digitalWrite(trigger, LOW);
duration = pulseIn(echo, HIGH);
distance = (duration / 2) / 29.1;
return distance;
}
// Function to check the distance in a specific direction
int checkDirection(int angle) {
myservo.write(angle);
delay(500); // Give the servo time to move
int measuredDistance = measureDistance();
return measuredDistance;
}
What each part of the sketch does
Pin assignments
The motor pins (12, 3, 9 for Motor A and 13, 11, 8 for Motor B) are fixed by the shield. Pins 7 (Trig), 6 (Echo) and 5 (servo signal) are free pins the shield does not use; you can move them to other free pins if you change the numbers in the code.
Distance measurement
measureDistance() sends a 10 µs pulse on the Trig pin (the variable trigger in the code) and waits for the echo. It calculates the distance in centimeters with distance = (duration / 2) / 29.1. The division by 2 accounts for the round trip of the sound wave, and 29.1 is the approximate number of microseconds sound needs per centimeter in air.
Motor control
motorTurn() sets the direction and speed of one motor. HIGH and LOW are the two signal levels of a digital pin: HIGH is 5 V, LOW is 0 V. motorTurn() writes the direction pin HIGH (forward) or LOW (reverse), sets the speed with analogWrite() (0 to 255) and releases the brake. Writing a brake pin HIGH stops that motor quickly.
How the robot slows down
loop() measures the distance ahead on every pass and reacts in steps:
- More than 40 cm (the far threshold): the robot drives forward at PWM 100 and sweeps the sensor.
- 40 cm to 20 cm: the robot slows down in proportion to the distance. map() rescales a number from one range to another; here map(distance, 20, 40, 75, 128) turns the distance into a PWM value of 128 at 40 cm and 75 at 20 cm. Because 128 is above the cruising speed of 100, the robot speeds up briefly when it passes 40 cm. For a steady slow-down, change 128 in map() to 100.
- 20 cm to 15 cm: map() does not clamp its output, so the speed keeps dropping below 75 and reaches about 62 at 15 cm.
- Below 15 cm (the close threshold): the robot stops and scans.
Scan logic
When the distance drops below 15 cm, the robot brakes both motors and waits 500 ms. It calls checkDirection(175) to look left and checkDirection(5) to look right. checkDirection() turns the servo to the given angle, waits 500 ms for it to arrive, takes one distance reading and returns it. The robot turns toward the side with the larger distance: one motor forward, the other in reverse, at PWM 128 for 500 ms. After the turn it brakes, waits 500 ms and centers the servo at 90°.
Dead-end reverse behavior
If both the left and the right distance are below 15 cm, the robot is in a dead end. It reverses for 500 ms at PWM 128, brakes for 500 ms and scans again at 150° (left) and 30° (right). It compares the two new readings and turns toward the side with more room, as after the first scan.
Servo sweep while driving
With more than 40 cm of free space, the robot drives at PWM 100. The servo sweeps the sensor between 30° and 150° in 10° steps, with a 40 ms pause per step. The sweep also catches obstacles to the left and right of the path. The scan direction reverses each time the servo reaches 30° or 150°.
Upload, test and observe
- Connect the Arduino to your computer with a USB cable and open the Arduino IDE.
- Select Tools > Board > Arduino Uno and the port of your board under Tools > Port.
- Upload the sketch.
- Disconnect USB, place the robot on the floor with free space ahead and switch the power on. The robot starts driving about half a second after power-up.
The robot should drive forward, slow down as it approaches an obstacle, stop, scan left and right and turn toward the clearer side. If a wheel turns backward or the robot turns to the wrong side, the Troubleshooting table has the fix.
How to tune the robot
Adjust these values in the code to match your motors and floor. The values are written into loop() as plain numbers. Search the sketch for 40, 20, 15, 100, 128 and 500 to change them. The far threshold 40 appears twice: in if (distance < 40) and in map().
| Parameter | Default value | What it controls | Tuning tip |
|---|---|---|---|
| Far threshold | 40 cm | Distance below which the robot slows down | Increase if the robot brakes too late; decrease if it slows down too early |
| Slow-down end | 20 cm | Distance at which map() reaches its lower speed of 75 (PWM) | Appears only in map(distance, 20, 40, 75, 128) |
| Close threshold | 15 cm | Distance at which the robot stops and scans | Increase if the robot crashes before stopping; decrease for tighter navigation |
| Forward speed | 100 (PWM) | Cruising speed when the path is clear | Range 0 to 255. Higher is faster but harder to stop in time |
| Turn speed | 128 (PWM) | Speed during left and right turns and when reversing | Increase if the robot does not complete the turn; decrease on slippery floors |
| Turn duration | 500 ms | How long the robot turns before stopping | Increase for wider turns; decrease for small corrections |
| Reverse duration | 500 ms | How long the robot reverses in a dead end | Increase if the robot does not back up far enough |
| Servo scan range | 30° to 150° | Sweep range while driving forward | Wider range: better side detection but slower scans |
| Servo scan step | 10° | Angle change per loop pass | Smaller: smoother scan but slower coverage |
| Servo scan delay | 40 ms | Pause between servo steps, and so between two distance readings | Lower values scan faster but leave less time between readings. Increase if the servo jitters; use 60 ms if the readings fluctuate (see Troubleshooting) |
Troubleshooting
| Problem | Likely cause | Solution |
|---|---|---|
| Robot does not move at all | No power, loose wiring, or brakes not released | Check the power switch and the battery voltage (9.6 V nominal, about 10.5 to 11 V with freshly charged cells). Check the motor terminal connections and that the sketch uploads without errors. |
| Only one motor runs | One motor not connected, or a broken wire | Connect the working motor to the other motor’s terminals to test that channel. Check the solder joints on the motor. |
| One wheel turns backward (robot spins or curves instead of driving straight) | Motor wires reversed | Swap the two wires of that motor (A+ and A−, or B+ and B−). |
| Robot turns right when it should turn left | Motor A and Motor B swapped | Swap the Motor A and Motor B connections on the shield. |
| Ultrasonic sensor gives unstable or zero readings | Loose wiring, wrong pins, or object too close or too far | Check Trig (pin 7) and Echo (pin 6). The HC-SR04 range is about 2 cm to 400 cm. Wait at least 60 ms between two measurements so the previous echo has died out. The sweep pauses only 40 ms per step, so raise delay(40) in loop() to 60. To see the readings, add Serial.begin(9600); to setup() and Serial.println(distance); to loop(), then open the Serial Monitor (the window in the Arduino IDE that shows text the board sends over USB). |
| Servo jitters or twitches | Servo on 3.3 V, weak supply, or an unsteady 5 V supply | Connect the servo VCC to 5 V, not 3.3 V. Add a 100 µF electrolytic capacitor (a capacitor with a plus and a minus leg that smooths short voltage dips) across the servo power lines, with the striped (negative) leg on GND. |
| Robot reacts too late and crashes into obstacles | Thresholds too low, or delays in the loop too long | Increase the close threshold (for example from 15 cm to 20 cm) so the robot stops earlier, or the far threshold (for example from 40 cm to 50 cm) so it slows down earlier. Shorten the delays in the loop. |
| Battery pack drains quickly | Motors drawing high current, or cells not fully charged | Use freshly charged NiMH cells. Reduce the motor speed. Check that the wheels turn without rubbing. |
| Arduino resets when the motors start | Voltage drop from the motor current | Add an electrolytic capacitor of 470 to 1000 µF (rated 16 V or more) across the shield’s power terminals, with the striped (negative) leg on GND. If the resets continue, cut the Vin Connect jumper on the back of the shield, so the shield and the Uno get separate supplies. Power the Uno from its own battery through its DC jack (the round power socket on the Uno). |
Frequently asked questions
The Arduino Motor Shield Rev3 plugs onto the Arduino Uno headers and also fits an Arduino Mega. It does not fit a Nano. For a Nano, use an L298N module instead; see my L298N motor driver tutorial.
Yes. The L298N module uses the same driver family as the shield (about 2 A per channel) but is a separate board. You connect it with jumper wires and change the pin numbers in the code. My L298N motor driver tutorial covers both options.
This build uses 8 AA NiMH cells in series: 8 × 1.2 V = 9.6 V nominal. The shield accepts 5 to 12 V on its power terminals and passes the voltage to the Uno’s VIN pin. A 2S LiPo (two lithium-polymer cells in series, 7.4 V nominal, 8.4 V fully charged) also works. A 3S LiPo reaches 12.6 V when full, which is above the shield’s 12 V limit.
Possible extensions
- Add Bluetooth or Wi-Fi control: combine obstacle avoidance with remote control through an HC-05 Bluetooth module or an ESP8266 Wi-Fi module. My Bluetooth-controlled robot car shows how to wire and program the HC-05. That car drives its motors with the Adafruit Motor Shield V1.2; its HC-05 sits on pins 0 and 1, which the Arduino Motor Shield Rev3 leaves free.
- Add a line-following mode: infrared (IR) sensors detect a black line on the floor, and the robot switches between following the line and avoiding obstacles.
- Upgrade to a 4WD chassis (four driven wheels): the shield has only two channels, so wire the two motors of each side in parallel to one channel. Keep the current per channel below 2 A.
For a later robot car I replaced the single sensor on the servo with four fixed HC-SR04 sensors, one on each side, and an Arduino Mega. That build is my advanced autonomous and Bluetooth-controlled robot car.

من أفضل ماشاهدت في هذا المجال بمافيه من توضيحات. سهل وميسر. تحياتي لكم أستاذي العزيز. أخوكم من سلطنة عمان
شكراً جزيلاً أخي سعيد على كلماتك الطيبة. سعيد جداً أن الشرح كان واضح ومفيد لك، هذا هو هدفي. تحياتي لك من ألمانيا إلى سلطنة عمان