Skip to content

ToF Lidar Radar

Time: ~120 min

Prerequisite: Ultrasonic & Timing (familiarity with distance measurement + I2C)


Learning Objectives

By the end of this module you will be able to:

  • Explain how Time-of-Flight laser ranging works and compare it to ultrasonic sensing
  • Interface a VL53L1X ToF sensor over I2C and configure distance modes and timing budgets
  • Control a servo to sweep the sensor and build a real-time radar display
  • Use the VL53L1X multizone SPAD array for spatial depth scanning
  • Stream live sensor data over WiFi/UDP to a PC visualization

Introduction

In previous labs you measured distance with an HC-SR04 ultrasonic sensor — sound pulses bouncing off objects. It works, but the beam is wide (~30°), slow (~60 ms per reading), and limited to a single distance value.

The VL53L1X is a Time-of-Flight (ToF) laser ranging sensor made by STMicroelectronics. Instead of sound, it fires a tiny infrared laser (VCSEL — Vertical-Cavity Surface-Emitting Laser) and measures the time for photons to bounce back. This gives you:

  • 4 m range in Long mode
  • Up to 50 Hz measurement rate
  • Multizone SPAD array — a 16×16 grid of single-photon detectors that can be configured as a 4×4 zone grid for spatial awareness
  • Narrow beam (~27° FoV) with configurable Region of Interest

Mount it on a servo and you get a sweeping radar. Add the SPAD zones and you get a crude depth camera. Connect it to a PC over WiFi and you get real-time visualization.

What You'll Build

  1. A servo-swept radar that sends angle + distance over UDP to a Pygame display
  2. A multizone SPAD scanner that sends 4×4 depth frames for panoramic depth mapping
  3. A servo calibration workflow for precise angular control
Hardware You Need
  • Raspberry Pi Pico 2 W (with MicroPython)
  • VL53L1X ToF sensor breakout (generic I2C board)
  • SG90 or similar 180° hobby servo
  • External 5 V power for the servo (do not power from Pico 3V3)
  • Breadboard and jumper wires

Part 1: Hardware Setup (~15 min)

Wiring

Connect the VL53L1X and servo to the Pico:

VL53L1X        Pico 2 W
-------        --------
VIN     -->    3V3
GND     -->    GND
SDA     -->    GP14
SCL     -->    GP15

Servo          Pico 2 W
-----          --------
Signal  -->    GP18
V+      -->    External 5V
GND     -->    GND (common with Pico)
Warning

The servo draws significant current during movement. Powering it from the Pico's 3V3 pin will cause brownouts and sensor I2C errors. Always use an external 5 V supply for the servo with a common ground.

I2C Bus Scan

Before using the driver, verify the sensor is visible on the I2C bus:

from machine import Pin, I2C

i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=400_000)
devices = i2c.scan()
print("I2C devices:", [hex(d) for d in devices])
# Expected: ['0x29']
Checkpoint

You should see 0x29 in the device list. If not, check wiring and power.


Part 2: VL53L1X Driver (~20 min)

How ToF Works

The VL53L1X contains:

  1. VCSEL — a tiny laser diode emitting 940 nm infrared light (invisible, eye-safe Class 1)
  2. SPAD array — a 16×16 grid of Single-Photon Avalanche Diodes that detect returning photons
  3. Time-to-Digital Converter — measures the round-trip time with picosecond precision

Distance = (speed of light × round-trip time) / 2

Because light travels at ~300 mm/ns, even a 4 m range means only ~27 ns round-trip. The sensor uses statistical photon counting across many laser pulses within a single "timing budget" to achieve millimeter-level accuracy.

✅ Task 1 — Run Diagnostics

The diagnostic script performs a step-by-step sensor check. Run it to verify everything works:

mpremote connect /dev/ttyACM0 run src/picobot/lidar/diag.py

You should see output like:

==================================================
VL53L1X Diagnostic
==================================================

[1] I2C bus scan...
    Devices found: ['0x29']

[2] Reading sensor ID...
    Model ID: 0xEACC (expected 0xEACC)
    OK
...
[10] Single-shot measurement (full 16x16 ROI)...
    Distance: 1234 mm, raw_status: 9
    Final status: 0 (0=valid, 255=invalid)
...
Diagnostic complete
Tip

If the diagnostic hangs at step 7 (VHV calibration), check that the sensor has clear line of sight to some object. The first ranging calibrates internal parameters and needs a valid return signal.

✅ Task 2 — Single-Shot Distance Readings

Place objects at known distances (use a ruler or tape measure) and verify the sensor output from the diagnostic. Compare:

Actual Distance Sensor Reading Error
100 mm
500 mm
1000 mm
2000 mm
Distance Modes

The VL53L1X has two distance modes:

Mode Range Advantage
SHORT (1) up to ~1.3 m Better ambient light immunity
LONG (2) up to ~4 m Greater range

The timing budget controls accuracy vs speed:

Budget Speed Accuracy
15 ms ~66 Hz Noisiest
20 ms ~50 Hz Good for scanning
33 ms ~30 Hz Default, balanced
50 ms ~20 Hz Good accuracy
100 ms ~10 Hz Very accurate
200 ms ~5 Hz Best for static

Lower budgets scan faster but produce noisier readings. For the radar sweep, 20–33 ms is a good starting point.


Part 3: Servo Sweep Radar (~25 min)

This is the main attraction: mount the VL53L1X on the servo and sweep it across 180°, sending each reading to a PC for real-time radar display.

Architecture

┌─────────────────┐         UDP          ┌──────────────┐
│   Pico 2 W      │ ──────────────────── │  PC (Pygame) │
│                  │   angle,dist,sweep   │              │
│  radar_sweep.py  │                      │   radar.py   │
│  ┌───────────┐  │                      │              │
│  │ VL53L1X   │  │                      │  ┌────────┐  │
│  │ on servo  │  │                      │  │ Radar  │  │
│  └───────────┘  │                      │  │ Display│  │
└─────────────────┘                      │  └────────┘  │
                                         └──────────────┘

✅ Task 3 — Run the Radar

On the Pico:

  1. Edit src/picobot/lidar/radar_sweep.py — set WIFI_SSID, WIFI_PASS, and PC_IP
  2. Copy the driver and script to the Pico:
mpremote connect /dev/ttyACM0 cp src/picobot/lib/vl53l1x.py :lib/vl53l1x.py
mpremote connect /dev/ttyACM0 run src/picobot/lidar/radar_sweep.py

On your PC:

pip install pygame
python src/picobot/host/lidar/radar.py

You should see a green radar display with distance points appearing as the servo sweeps.

Controls:

  • Q / Esc — Quit
  • C — Clear all points
Checkpoint

You should see the radar sweeping back and forth with objects appearing as bright dots. Walk in front of the sensor and watch yourself appear on the display.

✅ Task 4 — Tune Parameters

Experiment with these settings in radar_sweep.py:

Parameter Default Try Effect
ANGLE_STEP 2 1, 4 Resolution vs speed
TIMING_BUDGET_MS 33 20, 50 Noise vs accuracy
SETTLE_MS 25 10, 50 Servo settling quality
DISTANCE_MODE 2 (LONG) 1 (SHORT) Range vs ambient immunity

Record how each change affects the display quality and sweep speed.

Testing Without Hardware

You can test radar.py without a Pico by using the simulator:

# Terminal 1
python src/picobot/host/lidar/fake_pico.py

# Terminal 2
python src/picobot/host/lidar/radar.py

The simulator generates a fake room with walls and objects.


Part 4: Multizone SPAD Scanning (~25 min)

What is the SPAD Array?

The VL53L1X has a 16×16 grid of Single-Photon Avalanche Diodes (SPADs). By default, the sensor uses all 256 SPADs for a single distance measurement. But you can configure a Region of Interest (ROI) — a smaller window on the SPAD array — to measure distance in different directions without moving the sensor.

By rapidly switching between 4×4 ROI positions, you effectively get a 4×4 depth camera — 16 simultaneous distance measurements at different angles within the sensor's field of view.

Scan Modes

The SPAD scanner (spad_scanner.py) supports three modes:

Mode Zones Speed Description
Normal (N) 16 (4×4) ~3 FPS Full 4×4 grid, maximum spatial detail
Turbo (T) 4 (2×2) ~12 FPS 2×2 grid replicated to 4×4, good balance
Fast (F) 1 ~50 FPS Single zone, fastest possible

✅ Task 5 — Run the SPAD Viewer

On the Pico:

mpremote connect /dev/ttyACM0 run src/picobot/lidar/spad_scanner.py

On your PC:

python src/picobot/host/lidar/spad_viewer.py

You'll see a color-coded 4×4 grid showing distances (red = close, blue = far) with a panoramic depth strip building up as the servo sweeps.

Controls:

Key Action
S Toggle servo sweep
T Quick sweep (one pass)
F Cycle scan mode (Normal → Turbo → Fast)
1/2/3 Preset positions (Left / Center / Right)
Arrows Manual servo control
R Reset servo to center
C Clear panorama
Q Quit

✅ Task 6 — Compare Scan Modes

With the viewer running, press F to cycle through modes and observe:

Mode FPS Detail Best For
Normal 16z
Turbo 4z
Fast 1z
Tip

Turbo mode (default) gives the best trade-off for servo sweeping. Normal mode is better for static analysis of a scene. Fast mode is useful when you only need a single distance value at maximum speed.

Testing Without Hardware

# Terminal 1
python src/picobot/host/lidar/fake_spad.py

# Terminal 2
python src/picobot/host/lidar/spad_viewer.py

Part 5: Servo Calibration (~15 min)

Why Calibrate?

Servos are not precise out of the box:

  • Pulse width range varies between manufacturers (nominally 500–2500 µs, often 600–2400 µs)
  • Direction may be inverted depending on mounting orientation
  • Mechanical limits mean 0° and 180° may not be reachable

The calibration tool lets you interactively find the correct settings for your specific servo.

✅ Task 7 — Calibrate Your Servo

On the Pico:

mpremote connect /dev/ttyACM0 run src/picobot/lidar/cal_servo.py

On your PC:

python src/picobot/host/lidar/servo_cal.py

Controls:

Key Action
Left/Right ±1 degree
Down/Up ±10 degrees
Home/End Go to 0° / 180°
R Reverse direction
1/2/3 Save preset (Left / Center / Right)
P Print config for spad_scanner.py
Space Slow sweep 0° → 180° → 0°

Calibration procedure:

  1. Command 0° — the servo should point fully left (from the sensor's perspective)
  2. If it points right, press R to reverse
  3. Command 180° — verify it reaches the other extreme
  4. If the mechanical range is limited, note the safe minimum and maximum angles
  5. Press P to print the configuration values, then update SERVO_REVERSE in your scripts

Part 6: Project Ideas (~20 min)

Now that you have a working ToF radar system, here are some directions to take it further:

Obstacle Avoidance

Mount the VL53L1X on your picobot. Use the SPAD zones to detect obstacles in different directions without sweeping — the 4×4 grid gives you left/center/right awareness from a single sensor position. Combine with the ultrasonic sensor for redundancy.

Wall Following

Use the servo to point the sensor sideways and maintain a constant distance to a wall. A simple P-controller adjusts steering based on the error between desired and measured wall distance. The fast update rate of the VL53L1X (50 Hz) makes this very responsive.

Room Mapper

Do a full 180° sweep at each position, log the data, and reconstruct a 2D floor plan. This is the basic principle behind LIDAR-based SLAM (Simultaneous Localization and Mapping) used in robot vacuums and self-driving cars.

Gesture Control

With the SPAD multizone scanning, detect hand position in the 4×4 grid. Map zone activations to robot commands: hand on the left → turn left, hand close → stop, hand far → go forward.

Sensor Fusion

Compare the VL53L1X and HC-SR04 ultrasonic sensor. Measure the same objects and compare accuracy, response time, beam width, and failure modes (glass, dark surfaces, direct sunlight). Learn when ToF beats ultrasonic and vice versa.


What You Discovered

Concept Key Insight
Time-of-Flight Laser ranging gives mm-precision at up to 4 m, much faster and narrower than ultrasonic
SPAD Array A 16×16 photon detector grid enables multizone depth sensing from a single sensor
Timing Budget Trade-off between measurement speed (15 ms → 50 Hz) and accuracy (500 ms → best precision)
Distance Modes SHORT mode resists ambient light; LONG mode reaches 4 m
WiFi/UDP Lightweight datagram protocol is ideal for streaming sensor data to a PC
Servo Calibration Real servos need pulse width and direction calibration for accurate angular positioning

Source File Reference

Pico-side (MicroPython)

File Purpose
src/picobot/lib/vl53l1x.py VL53L1X I2C driver (ST ULD v3.5.5 compatible)
src/picobot/lidar/radar_sweep.py Servo sweep radar, sends angle+distance over UDP
src/picobot/lidar/spad_scanner.py 4×4 multizone SPAD scanner with servo sweep
src/picobot/lidar/diag.py Step-by-step hardware diagnostic
src/picobot/lidar/cal_servo.py Servo calibration listener (UDP-controlled)

PC-side (Python 3 + Pygame)

File Purpose
src/picobot/host/lidar/radar.py Real-time radar display
src/picobot/host/lidar/spad_viewer.py SPAD array viewer with panoramic scanning
src/picobot/host/lidar/fake_pico.py Radar simulator (no hardware needed)
src/picobot/host/lidar/fake_spad.py SPAD simulator (no hardware needed)
src/picobot/host/lidar/servo_cal.py Interactive servo calibration GUI

Reference


← Back to Advanced Topics