Build an Obstacle-Avoiding Robot: Sense, Scan, and Steer
A robot that roams on its own—sensing obstacles, scanning for openings, and turning toward clear space—built from parts you understood and tuned in simulation first.
An obstacle-avoiding robot is the natural next build after a line follower. A line follower follows—it tracks a line someone drew for it. This robot decides: with no track and no map, it senses the space around it and chooses where to go. That one difference—reacting to the world instead of following it—is the first real taste of autonomy, and it is still simple enough to understand completely.
The core is a short, repeating loop: sense–scan–decide–turn. The HC-SR04 ultrasonic sensor pings forward and times the echo to estimate distance; when something is closer than a safety threshold, the robot stops, sweeps the sensor left and right on an SG90 servo, and turns toward the most open bearing. That threshold is not arbitrary — it is a safety distance built from current speed, sense-decide-actuate latency, braking distance (which grows with the square of speed), the robot’s radius and a margin, all visible as the stopping-distance curve in the simulator.
You add components a mobile robot needs and learn what each one does: an Arduino Uno for forgiving 5 V logic, the ultrasonic sensor and its blind zone (the minimum range below which it cannot report), the servo that gives the beam a second viewpoint, the L298N H-bridge that switches motor current the logic pins cannot provide, two TT gearmotors where the gearbox trades speed for usable torque, a 2WD chassis with a caster for differential steering, and a battery pack that must feed motor and logic on separate rails. The tech tree also carries you through two learning roadmaps — motor control and reactive navigation — so the same PWM, odometry and filtering ideas you met in the line follower return here in a navigation context.
Then — before spending anything or risking a miswire — tune the avoidance behaviour in the browser simulator until it navigates a cluttered space cleanly. The workspace there is top-down and two-dimensional with instant, noise-free rays; a real ultrasonic beam spreads about 15°, is blocked by the robot’s own body, and is disturbed by soft or angled surfaces, so you will filter readings and add a median filter and a short settle delay after the servo moves before trusting a distance on hardware. Finding a safe stopping distance and a clear-direction rule in simulation removes most of the guesswork before the robot ever moves.
Only then do you wire the real robot — trigger and echo to the correct voltage, servo signal to a PWM-capable pin, motor PWM and direction to the driver, motor battery to the driver’s VM pin only, every ground tied together — and let it roam a floor with boxes or chairs. The same sensing and filtering lessons lead directly into the Micromouse walled maze if you later want mapping, or into ROS 2 navigation if you add a Raspberry Pi.
Follow the tech tree below top to bottom. Each node opens once its prerequisites are done, and your progress is saved on this device, so you can build the robot over a weekend without losing your place.
Bill of materials
| Part | Qty | Approx. cost | Notes |
|---|---|---|---|
| Arduino Uno | 1 | $5–8 | 5 V logic is genuinely helpful here — the HC-SR04’s echo is 5 V |
| HC-SR04 | 1 | $2 | A VL53L0X is a good second sensor, not a replacement |
| SG90 servo | 1 | $2 | For the scanning head. MG90S if it will run for hours |
| L298N | 1 | $2–4 | Or a TB6612FNG, which wastes far less of the battery |
| TT gearmotor | 2 | $4 | |
| 2WD chassis | 1 | $6–10 | |
| Battery pack | 1 | $6 | 6×AA NiMH or 2×18650 |
| Bump switches | 2 | $1 | Add these. See below |
Total: roughly $30–40.
The two bump switches are the best forty cents on the robot, and they are the part most builds leave out. Every ranging sensor has surfaces it cannot see — a sonar is blind to curtains and to walls met at a steep angle — and a robot with no contact sensing will push a curtain until something gives. Two switches behind a wrapped bumper turn “usually avoids things” into “never gets stuck against something it could not see”.
How it all connects
| From | To | Note |
|---|---|---|
HC-SR04 VCC, GND |
5 V, GND | ~15 mA, spiking during a ping |
HC-SR04 Trig |
D9 | Any digital pin |
HC-SR04 Echo |
D10 | 5 V output — fine on an Uno, needs a divider on a 3.3 V board |
| Servo signal | D6 | The Servo library uses Timer 1, which also drives D9/D10 PWM |
| Servo power | Separate 5 V, common ground | 650 mA stalled — not the Arduino’s 5 V pin |
Driver ENA, ENB |
D5, D3 | PWM-capable pins. Remove the L298N’s enable jumpers |
Driver IN1–IN4 |
D8, D7, D4, D2 | |
| Bump switches | D11, D12, INPUT_PULLUP |
COM to ground, NO to the pin |
Battery + |
Driver VM / +12V |
Never the Arduino |
| All grounds | One point | Arduino, driver, servo supply, battery negative |
Watch the timer conflict: the Servo library takes over Timer 1, which on an Uno drives
PWM on pins 9 and 10. Put the motor PWM on 3, 5, 6 or 11 and the servo on any pin, and the
problem never arises. Put a motor enable on pin 9 and its speed control will silently stop
working the moment you attach a servo.
The stopping distance, computed rather than guessed
The stop threshold is the single most important number in this project, and it is not arbitrary. It is the sum of four distances:
stop_distance = (v x t_latency) + (v^2 / (2 x a_brake)) + robot_radius + margin
| Term | Where it comes from | Typical |
|---|---|---|
| Sensing and decision latency | Ping (up to 25 ms), servo settle, loop period | 60–100 ms |
| Braking distance | Speed squared over twice the deceleration | Grows fast |
| Robot radius | Front of the chassis to the pivot centre | 80–100 mm |
| Margin | Because the sensor cone is 15° wide and lies about soft things | 50 mm |
Worked, for a robot at 0.3 m/s with 80 ms of latency and 1.0 m/s² of braking:
= (0.3 x 0.08) + (0.09 / 2.0) + 0.09 + 0.05
= 0.024 + 0.045 + 0.09 + 0.05 = 0.21 m
So about 21 cm. Now double the speed to 0.6 m/s:
= 0.048 + 0.18 + 0.09 + 0.05 = 0.37 m
The threshold has not doubled — it has grown by 76%, because the braking term goes with the square of speed. This is why a robot that avoids reliably at walking pace starts hitting things when you speed it up, and why the fix is a speed-dependent threshold rather than one large fixed number that makes the robot timid everywhere.
Build it in milestones
| # | Milestone | The test | A pass looks like |
|---|---|---|---|
| 1 | Motors drive | Forward, reverse, pivot both ways on blocks | All six states work; you know each wheel’s deadband |
| 2 | Distance reads | Point at a wall at 30 cm and print for a minute | Steady within a centimetre or two, no zeros |
| 3 | Timeout works | Aim at open air and at the ceiling | The loop keeps its rate — no echo must not mean a frozen robot |
| 4 | Median filter | Wave a hand repeatedly through the beam | Wild single readings vanish; the real approach still registers |
| 5 | Servo sweeps | Command −60°, 0°, +60° and print distance at each | Repeatable sweep to sweep, with a settle delay before each ping |
| 6 | Settle delay proven | Remove the delay deliberately | Readings smear across angles — now you know that failure |
| 7 | Stop threshold | Drive at a wall and let it stop | It stops with your computed margin, not by luck |
| 8 | Turn decision | Place it facing a corner with one open side | It turns toward the open side, not randomly |
| 9 | Hysteresis | Park it hovering at the threshold | It commits to a decision instead of stuttering |
| 10 | Bump recovery | Drive it at a cushion | Sonar misses it, the switch catches it, the robot backs off and turns |
| 11 | Free roam | Five minutes in a cluttered room | Survives; when it does get stuck you can name the state |
Milestone 10 is the one that separates a demo from a robot. A cushion absorbs the ultrasonic pulse entirely, so the sensor reports open space and the robot drives into it at full speed. Without a bump switch it then pushes until the motors stall.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Robot freezes at random intervals | pulseIn with no timeout |
Pass a 25000 µs timeout; handle “no echo” explicitly |
| Emergency-stops in an empty room | Treating a timeout as 0 cm | A timeout means far, not near. Return a sentinel |
| Drives into curtains and cushions | Sound absorbed — physics, not a bug | Add bump switches |
| Drives into walls at an angle | Ping reflecting away from the receiver | Same fix; also scan more angles |
| Turns confidently into a wall | Reading before the servo settled | Add 100–200 ms after commanding the angle, before pinging |
| Stutters at obstacles | One threshold, no hysteresis | Stop at 20 cm, do not resume until 30 cm |
| Servo jitters, board resets | Servo current through the Arduino | Separate 5 V supply for the servo, common ground |
| Motor speed control stopped working | Servo library took Timer 1 (pins 9, 10) |
Move motor PWM to 3, 5, 6 or 11 |
| Stuck in a corner forever | No memory — inherent to reactive navigation | Add a random component and a stuck-detector escape |
| Avoids well slowly, hits things fast | Fixed threshold, quadratic braking term | Make the threshold speed-dependent |
| Wild readings when two robots run together | They hear each other’s chirps | Stagger the ping timing |
Where to take it next
Add wall following. A robot that holds a fixed distance from a wall on one side traverses a room deterministically rather than wandering, and it is the first behaviour that starts to look like a plan. It is a small code change and a big conceptual one.
Add a second sensor type. A VL53L0X fails on almost exactly the opposite surfaces to the sonar — dark matte and mirrors, versus soft and angled. Together they cover far more of the room than either alone.
Accept the ceiling and move on. A reactive robot cannot be sent anywhere, cannot promise it covered the floor, and can loop in a horseshoe-shaped room forever. Those are properties of having no map, not bugs. When they start to matter, the micromouse adds mapping and planning, and the coverage robot tackles the “have I been everywhere?” question directly.
Project roadmap
The build path
Follow the tech tree from parts to a robot that follows a taped line. Each node unlocks when its prerequisites are done, and your progress saves on this device.
0 / 18 done
Components
- ControllerArduino UnoThe forgiving 8-bit board most people meet robotics through.
- SensorHC-SR04 Ultrasonic SensorA sonar ping in a two-dollar package—how a robot feels for obstacles.
- ActuatorSG90 Servo MotorThe nine-gram servo behind most robot arms, grippers, and steering.
- DriverL298N Motor DriverThe dual H-bridge that turns weak logic pins into motor power.
- ActuatorDC Gearmotor (TT Motor)The yellow gearbox motor that turns a bare chassis into a moving robot car.
- Chassis2WD Robot ChassisThe deck two motors, a free caster, and your electronics all bolt onto.
- PowerRobot Battery & Power PackThe difference between a robot that runs and one that keeps resetting.
Tutorials in this path
- Beginner · 22 minThe Obstacle-Avoidance Algorithm: Sense, Scan, Decide, TurnThe sense–scan–decide–turn loop that lets a robot avoid obstacles on its own.
- Beginner · 20 minRead an HC-SR04 Ultrasonic Sensor: Reliable Distance in CodeTurn ping-and-echo into a steady distance in cm—with a timeout and a median filter.
- Beginner · 18 minServo-Scan an Ultrasonic Sensor to Find Open SpaceSweep an HC-SR04 on a servo to find the clearest direction before you turn.
- Beginner · 18 min readControl DC Motors with PWM and an H-BridgeWire and control a brushed DC motor safely using PWM, an H-bridge, and realistic current limits.
- Intermediate · 20 min readDifferential-Drive Odometry from Wheel EncodersIntegrate left and right wheel motion into a mobile robot pose, then identify and calibrate drift.
Practise before you wire
Tune it in the live simulator
The build path routes through a browser lab. Find gains that follow the track cleanly here, then transfer them to the real robot.
Frequently asked questions
How does an obstacle-avoiding robot work?
It drives forward while an ultrasonic sensor watches the distance ahead. When something is closer than a set threshold, the robot stops, sweeps the sensor left and right on a servo to measure distance in each direction, and turns toward whichever way is most open—then resumes driving. The whole thing is a short sense–scan–decide–turn loop running many times a second.
Which sensor is best for an obstacle-avoiding robot?
The HC-SR04 ultrasonic sensor is the default: it's cheap, easy to wire, and reliable out to a few metres. A time-of-flight sensor gives a tighter, faster, more precise reading if you need it, but for a first obstacle-avoider the ultrasonic is the right balance of cost and simplicity.
Do I need to buy parts before I start this project?
No. The sensing and the avoidance logic run in the browser simulator, so you can understand and tune the behaviour before buying anything. Only the final build steps—wiring and assembly—need the physical Arduino, HC-SR04, servo, driver, motors, chassis, and battery.
Why isn't my obstacle-avoiding robot working?
The three usual culprits are a noisy or blocked ultrasonic reading (add a median filter and keep the sensor unobstructed), reading the sensor before the servo has finished moving (add a short settle delay), and powering the motors straight from the Arduino instead of through the driver (which browns out and resets the board).
How is this different from a line follower?
A line follower follows a track laid out for it; an obstacle-avoiding robot has no track and must decide where to go. It adds a scanning sensor and a real decision loop, which makes it the natural second robot to build after a line follower.