One-sentence summary
In this project we build a small autonomous robot that uses an ultrasonic sensor, two motors and a motor driver to notice an obstacle in front of it, stop, turn and drive on toward the open direction.
Why does it matter?
Until now we learned about sensors, motors and Arduino separately. This lesson brings them together into one working machine. The robot now makes its own decision without a person steering it: "Is something in front of me? If so, stop and look another way."
This is the core idea behind autonomous systems. Robot vacuum cleaners, cars with parking sensors and warehouse robots all run the same three steps: sense, decide, act. When you build this project, you see that loop with your own hands. It also makes the next step easier: turning this robot into a line-following robot.
The robot's logic: sense, decide, act
Our robot runs a simple loop over and over. First it measures the distance, then it compares that to a threshold value, then it sends a command to the motors.
Pseudocode
Before writing code, let us write the logic in plain language. This makes the plan in our head clear.
Repeat forever:
measure the distance
If the distance is less than the threshold
stop moving forward
back up a little
change direction (turn)
Otherwise
move forward
The threshold here is the boundary distance at which the robot says "too close now." We will start with 20 centimetres. If you choose it too small, the robot cannot stop before hitting the obstacle; too large, and the robot runs away from everything and never moves ahead.
Two example situations
Example 1 — Clear ahead: The sensor reads 60 cm. Since 60 is greater than the threshold (20), the robot drives straight.
Example 2 — Wall ahead: The sensor reads 12 cm. Since 12 is less than the threshold, the robot stops, backs up a little and turns right. Then it measures again. If the new direction is clear, it keeps moving.
Parts and wiring
Parts list
- Arduino Uno (or a compatible board)
- 2 DC motors + wheels and a chassis
- L298N motor driver board
- HC-SR04 ultrasonic distance sensor
- A separate battery holder of 4–6 AA cells (for the motors)
- Jumper wires
Why a separate motor driver and a separate battery?
Motors draw far more current than an Arduino pin can supply. If you connect a motor straight to the Arduino, you can damage the board. So we place a motor driver (L298N) in between: the Arduino gives the driver a weak signal such as "go forward," and the driver passes power from the separate battery holder to the motor.
One important rule: the negative terminals (GND) of the Arduino and the battery holder must be connected together — a common ground. Without a common ground, the driver cannot read the Arduino's signal correctly.
Wiring summary
| Part | Connection |
|---|---|
| HC-SR04 VCC | Arduino 5V |
| HC-SR04 GND | Arduino GND |
| HC-SR04 Trig | Arduino pin 9 |
| HC-SR04 Echo | Arduino pin 10 |
| L298N IN1 / IN2 | Arduino pin 5 / 6 (left motor) |
| L298N IN3 / IN4 | Arduino pin 7 / 8 (right motor) |
| L298N motor power | Separate battery holder (+) |
| Common GND | Arduino GND + battery (−) together |
Arduino code (split into functions)
We split the code into small parts. This way we can test and understand each part on its own.
1. Pins and setup
// Ultrasonic sensor pins
const int trig = 9;
const int echo = 10;
// Motor driver pins
const int in1 = 5, in2 = 6; // left motor
const int in3 = 7, in4 = 8; // right motor
const int threshold = 20; // obstacle threshold (cm)
void setup() {
pinMode(trig, OUTPUT);
pinMode(echo, INPUT);
pinMode(in1, OUTPUT); pinMode(in2, OUTPUT);
pinMode(in3, OUTPUT); pinMode(in4, OUTPUT);
}
2. Function that measures distance
// Returns distance from the sensor in cm
long measureDistance() {
digitalWrite(trig, LOW);
delayMicroseconds(2);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);
long duration = pulseIn(echo, HIGH);
return duration * 0.034 / 2; // convert using speed of sound
}
3. Movement functions
void forward() {
digitalWrite(in1, HIGH); digitalWrite(in2, LOW);
digitalWrite(in3, HIGH); digitalWrite(in4, LOW);
}
void stopMotors() {
digitalWrite(in1, LOW); digitalWrite(in2, LOW);
digitalWrite(in3, LOW); digitalWrite(in4, LOW);
}
void turn() { // turn right in place
digitalWrite(in1, HIGH); digitalWrite(in2, LOW);
digitalWrite(in3, LOW); digitalWrite(in4, HIGH);
}
4. Main loop
void loop() {
long distance = measureDistance();
if (distance < threshold) { // obstacle close
stopMotors();
delay(200);
turn();
delay(400); // a short turn
} else {
forward(); // path is clear
}
delay(50);
}
Notice that the logic inside loop() is exactly the same as the pseudocode we wrote at the start. The language changed, but the decision logic stayed the same.
Mini activity
- Hold the robot with its wheels in the air and power it on. Move your hand in front of the sensor: does the motor direction change? If not, test here first.
- Set up a simple test course: place two cardboard boxes on the floor as obstacles, leaving enough space between them for the robot to pass.
- Put the robot on the floor and run it at low speed. When it nears an obstacle, does it stop and turn?
- Change the threshold in the code from 20 to 30. Does the robot now react to obstacles earlier? Observe the difference.
One bug and its fix
On my first try the robot stopped when it neared the obstacle but then drove forward again and hit it. The expected behaviour was: stop, turn, then head toward the open direction.
I looked at it step by step:
Expected: at obstacle stop -> turn -> drive in new direction
Actual: at obstacle stopped -> immediately went forward again
The problem was that I gave no time for the turn after the turn() function. I had forgotten the delay(400) line, so as soon as the robot began turning, the loop restarted and gave the "forward" command again. When I added delay(400) after turn(), the robot really changed direction and headed toward the open side. One small missing line can break the whole behaviour.
Improvement ideas
- If the robot gets stuck in a corner while turning right, make it turn randomly left or right.
- Mount the sensor on a small servo motor so it can look left and right; let the robot turn toward the more open side.
- Turn on a red LED when it gets very close to an obstacle; this lets you see the decision with your eyes.
Common mistakes
Forgetting the common ground
If the GND terminals of the Arduino and the battery holder are not connected, the motors will not turn or will behave randomly. This is the most common mistake.
Connecting the motor directly to the Arduino
Arduino pins cannot drive a motor and get damaged when forced. A motor must always be powered through the driver and a separate battery.
Choosing the wrong threshold
With too small a threshold the robot keeps hitting things; with too large a threshold it runs from everything. A range of 15–25 cm is a good start.
Not testing with the wheels in the air
Trying the code straight on the floor can cause damage if the robot falls off a table. First check the signals with the wheels in the air.
Safety note
A moving robot can pinch, fall or run into things. Before you build and run it, take care of these:
- Clear a safe test area. Choose an empty, flat surface with nothing to knock over. Do the first tests on the floor, not on a table; the robot can fall off a table.
- Keep your fingers, hair and cables away from the wheels and gears. A spinning wheel or gear can pinch.
- Test at low speed. A fast robot is harder to control and hits harder.
- Use a separate, low-voltage battery; never use mains electricity (a wall socket). A battery holder is enough for the motors.
- Connect the common ground, but do not accidentally touch the plus and minus terminals together; a short circuit heats the batteries.
- Work with an adult for any step that needs motors, soldering or cutting tools.
Lesson summary
- An obstacle-avoiding robot is the most basic example of the "sense, decide, act" loop.
- The ultrasonic sensor measures distance; the robot compares it to a threshold and decides.
- The motors are powered by a separate battery and a motor driver; the Arduino only sends a weak signal.
- The common ground between the Arduino and the battery must always be connected.
- Splitting the code into functions makes it easier both to test and to find bugs.
Check questions
- What are the three basic steps the robot runs over and over?
- What is the "threshold" for, and what happens if it is chosen too small?
- Why do we connect the motor to a motor driver instead of directly to the Arduino?
- What can happen if the common ground is not connected?
- Why is it a good idea to run the first test with the robot's wheels in the air?
Answers
- Sense (measure the distance), decide (compare with the threshold) and act (command the motors).
- The threshold is the boundary distance at which the robot counts an obstacle as "too close." If it is too small, the robot cannot stop in time and hits the obstacle.
- Motors draw far more current than Arduino pins can supply; connecting directly damages the board. The driver passes power from a separate battery to the motor.
- The driver cannot read the Arduino's signal correctly; the motors will not turn or will behave randomly.
- With the wheels in the air the robot cannot fall or run away; you can safely check the signals and motor directions.
Source and verification note
For “Project: Obstacle-Avoiding Robot”, verification focuses on whether the relationship between The robot's logic: sense, decide, act and Two example situations remains consistent across examples. Robot behaviour cannot be explained by code alone; mechanical structure, power system, sensor placement and surface conditions must be evaluated together. Test results should be recorded over several runs on the same course.
Next lesson
Project: Line-Following Robot