Building an Obstacle-Avoiding Robot Car with Arduino, Motor Shield Rev3, Servo Motor, and Ultrasonic Sensor

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.

The video loads from YouTube (Google), which may set cookies. Privacy policy

📋 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.

Arduino Motor Shield Rev3 top view with screw terminals for motor A, motor B and Vin, the L298P driver chip and the header pins for the Uno
Arduino Motor Shield Rev3: screw terminals for motors A and B and the Vin power input (where the battery voltage goes in) on the left, L298P driver in the middle.

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.

FunctionMotor A (left)Motor B (right)
Direction1213
Speed (PWM)311
Brake98

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.

PWM waveforms for analogWrite values 255, 0, 64 and 128 and the resulting motor speed
PWM speed control: analogWrite() sets the duty cycle. 255 is full speed, 0 stops the motor, 64 and 128 run it slower.

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)

ComponentQtyRequired / OptionalPurposeNotes
Arduino Uno1RequiredMain microcontroller: runs the codeAny Uno-compatible board works
Arduino Motor Shield Rev31RequiredDrives and controls the two DC motorsPlugs directly onto the Arduino Uno
DC motors (3 to 6 V)2RequiredLeft and right drive wheelsStandard hobby geared DC motors (a gearbox on the motor lowers the speed and raises the torque, the turning force)
Servo motor (SG90)1RequiredSweeps the ultrasonic sensor left and rightAny small servo of the 9 g weight class works
Ultrasonic sensor (HC-SR04)1RequiredMeasures the distance to obstacles4-pin module: VCC (5 V supply), Trig, Echo, GND (ground, 0 V)
Jumper wiresSeveralRequiredAll electrical connections between the componentsMale-to-male and male-to-female (male = pin end, female = socket end)
AA rechargeable cells (NiMH, 1.2 V)8RequiredPower supply for the motors and the ArduinoNiMH 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)2RequiredHold the cells and connect them in seriesWired in series for 9.6 V
Toggle switch1RequiredMain power switchA lever switch that stays in the on or off position. It goes in the positive battery wire.
Robot chassis1RequiredFrame for all componentsAcrylic or 3D-printed plate
Wheels2RequiredFit onto the DC motor shaftsMatch your motor shaft size
Ball caster (support wheel)1RequiredThird point of contact with the groundA swivel wheel also works
Screws, nuts and spacersAssortedRequiredMechanical assemblyM3 (3 mm metric thread) is the common size for Arduino and chassis
3D-printed mountsSetOptionalHolders for the servo, the ultrasonic sensor and the ArduinoHot 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

Wiring diagram: Arduino Uno with the Arduino Motor Shield Rev3, HC-SR04 ultrasonic sensor, SG90 servo motor, two DC motors and battery pack
Complete wiring diagram: Arduino Uno with the Arduino Motor Shield Rev3, HC-SR04 ultrasonic sensor, SG90 servo motor, DC motors and battery pack.

DC motor connections

MotorShield terminal
Motor A (left) wire 1A−
Motor A (left) wire 2A+
Motor B (right) wire 1B−
Motor B (right) wire 2B+

Servo motor connections

Servo wireConnects 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 pinConnects to
VCC5 V on the shield
TrigArduino digital pin 7
EchoArduino digital pin 6
GNDGND on the shield

Power supply connections

Battery or switch wireConnects 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().

ParameterDefault valueWhat it controlsTuning tip
Far threshold40 cmDistance below which the robot slows downIncrease if the robot brakes too late; decrease if it slows down too early
Slow-down end20 cmDistance at which map() reaches its lower speed of 75 (PWM)Appears only in map(distance, 20, 40, 75, 128)
Close threshold15 cmDistance at which the robot stops and scansIncrease if the robot crashes before stopping; decrease for tighter navigation
Forward speed100 (PWM)Cruising speed when the path is clearRange 0 to 255. Higher is faster but harder to stop in time
Turn speed128 (PWM)Speed during left and right turns and when reversingIncrease if the robot does not complete the turn; decrease on slippery floors
Turn duration500 msHow long the robot turns before stoppingIncrease for wider turns; decrease for small corrections
Reverse duration500 msHow long the robot reverses in a dead endIncrease if the robot does not back up far enough
Servo scan range30° to 150°Sweep range while driving forwardWider range: better side detection but slower scans
Servo scan step10°Angle change per loop passSmaller: smoother scan but slower coverage
Servo scan delay40 msPause between servo steps, and so between two distance readingsLower values scan faster but leave less time between readings. Increase if the servo jitters; use 60 ms if the readings fluctuate (see Troubleshooting)

Troubleshooting

ProblemLikely causeSolution
Robot does not move at allNo power, loose wiring, or brakes not releasedCheck 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 runsOne motor not connected, or a broken wireConnect 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 reversedSwap the two wires of that motor (A+ and A−, or B+ and B−).
Robot turns right when it should turn leftMotor A and Motor B swappedSwap the Motor A and Motor B connections on the shield.
Ultrasonic sensor gives unstable or zero readingsLoose wiring, wrong pins, or object too close or too farCheck 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 twitchesServo on 3.3 V, weak supply, or an unsteady 5 V supplyConnect 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 obstaclesThresholds too low, or delays in the loop too longIncrease 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 quicklyMotors drawing high current, or cells not fully chargedUse freshly charged NiMH cells. Reduce the motor speed. Check that the wheels turn without rubbing.
Arduino resets when the motors startVoltage drop from the motor currentAdd 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

Can I use an Arduino Nano or Mega instead of an Arduino Uno?

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.

Can I use an L298N motor driver instead of the Arduino Motor Shield Rev3?

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.

What voltage and battery pack should I use?

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.

3 thoughts on “Building an Obstacle-Avoiding Robot Car with Arduino, Motor Shield Rev3, Servo Motor, and Ultrasonic Sensor”

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

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

      Reply

Leave a Comment