Obstacle Avoidance
Stopping Distance vs Trigger Distance
This logic fails because robots don't stop instantly.
The physics: At speed 100, your robot might travel 5-8cm before actually stopping. If you trigger at 20cm and need 8cm to stop, you collide at 12cm.
Trigger Stop Obstacle
distance distance
[Robot]────────────┼────────────┼────────────[Wall]
20cm 8cm
Must trigger at: obstacle_distance + stopping_distance
Additional failure modes: - Sensor timeout: Ultrasonic sensors can hang waiting for echoes that never return - Surface blindness: Soft/angled surfaces absorb sound - sensor reports "no obstacle" - Update rate: Sensor measurement takes time; robot moves during measurement
Robust avoidance requires measuring your actual stopping distance at each speed, adding margins, and handling sensor failures.
This tutorial covers detecting and avoiding obstacles using the ultrasonic sensor and optocouplers.
What You'll Learn
In this tutorial, you'll understand:
- How ultrasonic sensors measure distance and their limitations
- The concept of stopping distance and why it depends on speed
- How to combine multiple sensors for robust obstacle detection
- The importance of priority ordering in safety-critical systems
- How to quantify performance through systematic testing
After completing this tutorial, you'll be able to:
- ✅ Measure ultrasonic sensor response time and update rate
- ✅ Implement stop-on-obstacle behavior with timeout handling
- ✅ Calculate required trigger distance for different speeds
- ✅ Combine ultrasonic and optocoupler sensors for edge detection
- ✅ Measure and record avoidance success rates
Prerequisites: - [[ultrasonic|Ultrasonic Sensor Tutorial]] - [[optocoupler|Optocoupler Tutorial]] - [[Reference/05-motors/motor-basics|Motor Basics]]
Sensor Comparison
Different sensors have different strengths for obstacle detection:
| Sensor | Range | Best For | Limitations |
|---|---|---|---|
| Ultrasonic | 2-400 cm | Walls, large objects | Soft/angled surfaces |
| Optocoupler | 0-2 cm | Line/edge detection | Very short range |
| IR proximity | 2-30 cm | Close objects | Affected by ambient light |
For most obstacle avoidance, the ultrasonic sensor is the primary tool.
⚡ Task 1: Measure Ultrasonic Response Time
Before implementing avoidance, understand how fast the sensor responds.
from machine import Pin
import time
trig = Pin(0, Pin.OUT)
echo = Pin(1, Pin.IN)
def measure_distance():
trig.low()
time.sleep_us(2)
trig.high()
time.sleep_us(10)
trig.low()
while echo.value() == 0:
start = time.ticks_us()
while echo.value() == 1:
end = time.ticks_us()
duration = time.ticks_diff(end, start)
return duration * 0.0343 / 2 # cm
# Measure timing
times = []
for _ in range(100):
t1 = time.ticks_us()
dist = measure_distance()
t2 = time.ticks_us()
times.append(time.ticks_diff(t2, t1))
avg_time = sum(times) / len(times)
print(f"Average measurement time: {avg_time:.0f} µs")
print(f"Maximum update rate: {1000000 / avg_time:.0f} Hz")
Record your results: - Average measurement time: _ µs - At 10 cm distance: µs - At 50 cm distance: __ µs
⚡ Task 2: Simple Stop-on-Obstacle
The most basic avoidance: stop when something is close.
from machine import Pin
from pico_car import pico_car
import time
Motor = pico_car()
trig = Pin(0, Pin.OUT)
echo = Pin(1, Pin.IN)
STOP_DISTANCE = 15 # cm
def measure_distance():
trig.low()
time.sleep_us(2)
trig.high()
time.sleep_us(10)
trig.low()
timeout = time.ticks_us()
while echo.value() == 0:
if time.ticks_diff(time.ticks_us(), timeout) > 30000:
return 999 # Timeout
start = time.ticks_us()
while echo.value() == 1:
if time.ticks_diff(time.ticks_us(), start) > 30000:
return 999
duration = time.ticks_diff(time.ticks_us(), start)
return duration * 0.0343 / 2
while True:
dist = measure_distance()
if dist < STOP_DISTANCE:
Motor.Car_Stop()
print(f"STOP! Object at {dist:.1f} cm")
else:
Motor.Car_Run(80, 80)
time.sleep(0.05)
Test and observe: - Does it stop reliably? - What happens with soft objects (pillows, fabric)? - What's the minimum stopping distance at speed 80?
⚡ Task 3: Measure Stopping Distance
Critical question: How far does the robot travel before stopping?
from machine import Pin
from pico_car import pico_car
import time
Motor = pico_car()
trig = Pin(0, Pin.OUT)
echo = Pin(1, Pin.IN)
def measure_distance():
# ... same as above ...
pass
# Test at different speeds
for speed in [60, 80, 100, 120]:
input(f"Press Enter to test speed {speed}...")
# Drive toward wall
Motor.Car_Run(speed, speed)
while True:
dist = measure_distance()
if dist < 20: # Trigger distance
Motor.Car_Stop()
time.sleep(0.5)
final_dist = measure_distance()
print(f"Speed {speed}: Stopped at {final_dist:.1f} cm")
break
time.sleep(0.02)
time.sleep(2)
Record stopping distances:
| Speed | Trigger (cm) | Final Distance (cm) | Stopping Distance (cm) |
|---|---|---|---|
| 60 | 20 | ||
| 80 | 20 | ||
| 100 | 20 | ||
| 120 | 20 |
Analysis: What trigger distance do you need for each speed to avoid collision?
⚡ Task 4: Turn-and-Continue Avoidance
When an obstacle is detected, turn and continue:
from machine import Pin
from pico_car import pico_car
import time
Motor = pico_car()
trig = Pin(0, Pin.OUT)
echo = Pin(1, Pin.IN)
STOP_DISTANCE = 20
TURN_TIME = 0.5 # seconds
def measure_distance():
# ... same as above ...
pass
while True:
dist = measure_distance()
if dist < STOP_DISTANCE:
# Stop
Motor.Car_Stop()
time.sleep(0.2)
# Back up a bit
Motor.Car_Back(80, 80)
time.sleep(0.3)
# Turn right
Motor.Car_Right(80, 80)
time.sleep(TURN_TIME)
# Resume forward
Motor.Car_Run(80, 80)
else:
Motor.Car_Run(80, 80)
time.sleep(0.05)
Tune the parameters:
| Parameter | Effect | Your Value |
|---|---|---|
| STOP_DISTANCE | When to trigger | |
| Back-up time | How far to reverse | |
| TURN_TIME | How much to turn |
⚡ Task 5: Smart Direction Choice
Choose turn direction based on which side is clearer:
from machine import Pin
from pico_car import pico_car
import time
Motor = pico_car()
trig = Pin(0, Pin.OUT)
echo = Pin(1, Pin.IN)
def measure_distance():
# ... same as above ...
pass
def scan_left():
"""Turn left and measure"""
Motor.Car_Left(60, 60)
time.sleep(0.3)
Motor.Car_Stop()
time.sleep(0.1)
return measure_distance()
def scan_right():
"""Turn right and measure"""
Motor.Car_Right(60, 60)
time.sleep(0.3)
Motor.Car_Stop()
time.sleep(0.1)
return measure_distance()
while True:
dist = measure_distance()
if dist < 25:
Motor.Car_Stop()
# Scan both directions
Motor.Car_Left(60, 60)
time.sleep(0.3)
Motor.Car_Stop()
left_dist = measure_distance()
Motor.Car_Right(60, 60)
time.sleep(0.6) # Return to center and go right
Motor.Car_Stop()
right_dist = measure_distance()
print(f"Left: {left_dist:.0f} cm, Right: {right_dist:.0f} cm")
# Choose clearer direction
if left_dist > right_dist:
Motor.Car_Left(80, 80)
time.sleep(0.5)
else:
# Already facing right, continue
pass
Motor.Car_Run(80, 80)
time.sleep(0.05)
⚡ Task 6: Edge Detection with Optocouplers
Combine ultrasonic (front obstacles) with optocouplers (floor edges):
from machine import Pin
from pico_car import pico_car
import time
Motor = pico_car()
# Ultrasonic
trig = Pin(0, Pin.OUT)
echo = Pin(1, Pin.IN)
# Optocouplers (edge detection)
opto_left = Pin(2, Pin.IN)
opto_right = Pin(5, Pin.IN)
FRONT_DISTANCE = 20
def measure_distance():
# ... same as above ...
pass
while True:
front_dist = measure_distance()
left_edge = opto_left.value() # 1 = edge/no floor
right_edge = opto_right.value()
# Priority 1: Edge detection (most dangerous)
if left_edge == 1 or right_edge == 1:
Motor.Car_Stop()
print("EDGE DETECTED!")
Motor.Car_Back(80, 80)
time.sleep(0.5)
Motor.Car_Right(80, 80)
time.sleep(0.5)
# Priority 2: Front obstacle
elif front_dist < FRONT_DISTANCE:
Motor.Car_Stop()
print(f"Obstacle at {front_dist:.0f} cm")
Motor.Car_Back(60, 60)
time.sleep(0.3)
Motor.Car_Right(80, 80)
time.sleep(0.4)
# Normal: Move forward
else:
Motor.Car_Run(80, 80)
time.sleep(0.05)
⚡ Task 7: Measure Avoidance Success Rate
Quantify performance:
# Manual test protocol
# Run robot through obstacle course 10 times
# Record: collisions, edge falls, successful avoidances
test_results = []
for trial in range(10):
result = input(f"Trial {trial+1}: (s)uccess, (c)ollision, (e)dge fall: ")
test_results.append(result)
success = test_results.count('s')
collisions = test_results.count('c')
edges = test_results.count('e')
print(f"\nResults:")
print(f"Success rate: {success}/10 ({success*10}%)")
print(f"Collisions: {collisions}")
print(f"Edge falls: {edges}")
Record your test results:
| Trial | Result | Notes |
|---|---|---|
| 1 | ||
| 2 | ||
| ... | ||
| 10 |
Success rate: _____ %
Key Takeaways
- Sensor timing affects maximum robot speed
- Stopping distance increases with speed
- Multiple sensors provide redundancy
- Priority ordering (edge > obstacle > normal) prevents dangerous situations
- Quantitative testing reveals actual performance
Comparison: Avoidance Strategies
| Strategy | Complexity | Effectiveness | Use Case |
|---|---|---|---|
| Simple stop | Low | Limited | Static demo |
| Turn-and-continue | Medium | Good | General use |
| Smart direction | Higher | Better | Complex environments |
| Multi-sensor | Highest | Best | Safety-critical |
Self-Assessment
Quick Check – Did You Get It?
🔹 Why does the ultrasonic sensor need a timeout in the measurement code? 🔹 What is the relationship between robot speed and required trigger distance? 🔹 Why do we check edge detection (optocouplers) before front obstacles (ultrasonic)? 🔹 What factors affect the stopping distance of the robot? 🔹 How would you improve the success rate if the robot keeps colliding?
Research Tasks
-
Stopping distance formula: Plot the relationship between speed and stopping distance. Is it linear? What physical factors explain the relationship?
-
Sensor fusion: Research how autonomous vehicles combine multiple sensors (LIDAR, cameras, ultrasonic). What principles apply to your robot?
-
Reaction time analysis: Measure the total delay from obstacle detection to motor stop. Where does the time go (sensor, processing, motor response)?
Next Steps
- [[Reference/05-motors/line-following|Line Following]] - Use sensors for path following
- [[Reference/06-software/state-machine|State Machine]] - Structure complex behaviors
- [[mission-tracks/ultrasonic-sensor|Ultrasonic Mission]] - Complete challenge