VL53L5CX Web Console Example
Turns an ESP32-S3 / ESP32-P4 into a WebUSB / Web Serial front-end for a VL53L5CX (or VL53L7CX) multizone Time-of-Flight sensor: the hosted VL53L5CX console web app connects over native USB and can
view every ranging frame live, as a 3D surface (each zone at its measured position in the sensor’s field of view) or a bar height field, coloured by distance / signal / ambient / sigma / reflectance / status, next to a numeric grid with per-zone details and the motion indicator,
configure the sensor — resolution (4x4 / 8x8), ranging frequency, integration time, sharpener, target order, ranging mode, power mode, the motion indicator window, the I2C address — and
control it: start / stop ranging, re-initialize (firmware upload).
(Live data from a VL53L5CX on an ESP32-P4-WIFI6 dev kit, 8x8 at 15 Hz.)
The example runs the espp::Vl53l5cx driver on the device and exposes a
small high-level protocol (vl53l5cx_protocol.hpp, dispatcher module id
10, protocol id espp.vl53l5cx) through espp::Vl53l5cxService, so the web
app needs no knowledge of the sensor’s I2C / firmware interface. Both the
vendor (WebUSB) and CDC (Web Serial) interfaces carry the same protocol; the
system console/logs go to UART0 (set in sdkconfig.defaults): on the S3 /
P4, USB-Serial-JTAG shares the native USB port’s PHY with USB-OTG, which
TinyUSB takes over.
Wiring & configuration
The sensor sits on I2C (3.3 V, GND, SDA, SCL; LPn high or left to the
breakout’s pull-up). idf.py menuconfig → Example Configuration selects the
hardware (QtPy ESP32-S3: SDA 41 / SCL 40; ESP32-P4 dev kits: SDA 7 / SCL 8; or
custom pins), the I2C clock, the part (VL53L5CX / VL53L7CX — same ids,
different firmware image), the resolution and frequency at boot, whether
ranging starts at boot, and on the ESP32-P4 which USB port TinyUSB drives:
the full-speed port for the dev kits (their USB-C is the full-speed port, the
one the USB-Serial-JTAG console normally uses; espp::UsbDevice moves the
USB-OTG 1.1 controller onto those pads, see UsbDevice::Config::fs_phy), or
the high-speed port where a board routes it to a device-capable connector.
Protocol (module 10, espp.vl53l5cx v1)
Framed with stream_frame and routed by espp::Dispatcher. Requests use
types 0x1X; replies / events use 0x9X (reply flag set). Multi-byte fields
are little-endian.
Type |
Dir |
Meaning |
|---|---|---|
|
H→D |
request one STATUS snapshot |
|
H→D |
|
|
H→D |
|
|
H→D |
|
|
H→D |
|
|
H→D |
|
|
H→D |
|
|
H→D |
|
|
H→D |
|
|
H→D |
start ranging with the current settings |
|
H→D |
stop ranging |
|
H→D |
reboot the sensor + re-upload the firmware (~2 s); resets every setting |
|
H→D |
|
|
H→D |
request the latest FRAME |
|
H→D |
|
|
D→H |
|
|
D→H |
|
|
D→H |
|
|
D→H |
|
Settings are refused by the sensor while it is ranging (the device answers ERROR); STATUS reports the settings read back from the sensor.
Build & flash
idf.py set-target esp32s3 # or esp32p4
idf.py build flash monitor # console is on UART0
Then open the VL53L5CX console web app and Connect (WebUSB or Web Serial). The sensor ranges at boot by default: tick Live frames (on by default) to see the 3D view; Stop to change the settings, then Start ranging.
Standard USB services
Like every espp USB example, this one serves the standard service set on its framed USB link(s) next to its own protocol, so the hosted consoles and the Device Hub (which finds each service through discovery, by protocol id) work against it:
Service |
Module (default) |
Protocol id |
Console |
|---|---|---|---|
|
7 |
|
|
|
8 |
|
system console |
|
0 |
|
|
|
4 |
|
partitions.csv therefore carries the OTA layout (otadata, ota_0, ota_1)
plus a coredump partition, and sdkconfig.defaults enables core dumps to
flash, OTA rollback and the FreeRTOS run-time statistics the task monitor
reads. Every device->host write on a transport goes through one mutex, so the
services (and the frame stream) never interleave frames.