Tutorial · Beginner · 22 min
The Obstacle-Avoidance Algorithm: Sense, Scan, Decide, Turn
The decision loop behind an obstacle-avoiding robot: a stop threshold, a servo scan, and a state machine that turns toward open space without getting stuck.
Published
Once a robot can read a clean distance and scan for the open direction, one piece remains: the logic that decides what to do. This is the brain of an obstacle-avoiding robot—a short loop that drives forward, notices when something is too close, looks around, and turns toward space. It is simpler than it sounds, and getting it right is mostly about not getting stuck.
Reactive, not planned
This robot has no map. It reacts to what is in front of it right now: sense, decide, act, repeat. That is reactive navigation, and it is the right first approach—cheap, robust, and enough to wander a cluttered room without hitting anything. It is not the same as path planning, where a robot uses a map to choose a route ahead of time (that is A* on an occupancy grid territory). Reactive avoidance is the foundation; planning comes later.
The stop threshold
Everything hinges on one number: how close is too close? Pick a distance at which the robot stops and reconsiders. Too small and it clips obstacles before it can react; too large and it stops at everything and creeps nervously. The right value depends on speed—a faster robot must react sooner, because it covers more ground before it can stop. For a small robot at a gentle pace, 15–25 cm is a sensible start.
The state machine
The cleanest way to express the behaviour is a finite-state machine: a few named states with clear rules for moving between them. It replaces a tangle of nested ifs with something you can read and debug. (For the general pattern, see finite-state machines for robot behavior.)
enum State { DRIVE, SCAN, TURN, REVERSE };
State state = DRIVE;
const int STOP_CM = 20;
void loop() {
switch (state) {
case DRIVE:
driveForward();
if (medianCm() < STOP_CM) { stopMotors(); state = SCAN; }
break;
case SCAN: {
int a = bestAngle(); // servo sweep from the scan tutorial
long open = distanceAt(a);
if (open < STOP_CM) state = REVERSE; // nowhere to go
else { turnToward(a); state = TURN; }
break;
}
case TURN:
if (turnComplete()) state = DRIVE; // opening cleared, resume
break;
case REVERSE:
backUp(400); // ms
state = SCAN; // then look again
break;
}
}
Choosing the turn direction
The scan already did the hard part: it returns the most open bearing. Turn that way. The turnToward(angle) call maps “open to the left” to a left pivot and “open to the right” to a right pivot, using differential drive—one wheel forward, the other back, to spin roughly in place.
Avoiding oscillation and dead ends
Two failure modes trap naive robots, and both have simple fixes:
- Flip-flopping between left and right at a corner where both look similar. Add a minimum turn duration so the robot commits to a turn instead of re-deciding every loop.
- Wedging into a dead end where every direction is blocked. That is what the REVERSE state is for: back off, then re-scan. A small stuck-counter—reverse harder if you have scanned several times without progress—breaks the worst traps.
Tuning: speed versus reaction distance
Two knobs decide whether the robot feels smooth or panicky: driving speed and stop threshold. Raise the speed and you must raise the threshold to keep the stopping distance safe. The fastest way to find a good pair is to change them where a mistake costs nothing—the obstacle avoidance simulator—and only then transfer the values to hardware.
Explore the graph
Part of these builds
Projects and learning paths that include this tutorial.
Further reading