给小车、底盘或者云台加避障,很多人的第一反应是超声波模块,其次是指红外对管。这两样便宜,但各有各的老毛病:超声波的波束角宽,测的是一整片锥形区域里最近的东西,障碍物斜着进锥就容易误判;红外对管靠反射光强反推距离,目标颜色一深一浅,读数立刻飘。VL53L1X 走的是另一条路:芯片自己打出 940nm 的不可见激光,数一数光子飞出去再弹回来花了多长时间,直接换算成毫米。Pololu 产品页对它的形容是“一颗自成一体的微型激光雷达”,环境光、目标颜色和纹理不改变测量原理,只会影响它能打到的最远距离。

先看清这颗芯片的底细
VL53L1X 是 ST 的单芯片测距方案,正式料号是 VL53L1CXV0FY/1,社区里都习惯叫它 VL53L1X。发射端是 940nm 的 Class 1 不可见 VCSEL 激光(人眼安全等级),接收端是 16×16 的 SPAD 单光子雪崩二极管阵列加集成镜头,典型视场角 27 度。距离读数走 I2C 总线输出,16 位、单位毫米,总线时钟可以跑到 fast mode 的 400kHz。
买之前先看同家族的横向对比,Pololu 给的这组数字很说明问题:
| 型号 | 最大量程 | 最小量程 | 视场角 |
|---|---|---|---|
| VL6180X | 60cm | 约10mm | 25度 |
| VL53L0X | 200cm | 约30mm | 25度 |
| VL53L1X | 400cm | 40mm | 15到27度,可编程 |
| VL53L3CX | 500cm | 10mm | 25度,支持多目标 |
做机器人避障,VL53L1X 的 4 米量程和可编程视场角是它区别于前辈 VL53L0X 的核心卖点;如果只做近距离的小型桌面设备,60cm 的 VL6180X 反而更便宜更够用。本文以 VL53L1X 为主,用 Pololu 的载板加官方 Arduino 库把流程走通。
量程怎么选:三种距离模式与时间预算
VL53L1X 有三档距离模式,这是调优的第一抓手:
- Short 短距模式:最多约 130cm,最高 50Hz 采样,对环境光干扰最不敏感;
- Medium 中距模式:暗环境下最多约 300cm,最高 30Hz;
- Long 长距模式:暗环境下最多 400cm,最高 30Hz,但最大量程受环境光影响明显。
还有一个新手最容易忽略的约束:最小测距距离 4cm。目标贴得比 4cm 更近时传感器仍会报“有东西”,但数值本身不可信。装在小车正前方时,别把传感器怼到保险杠边上。
量程之外,真正决定读数稳不稳的是测量时间预算(timing budget)。数据手册里那张 Figure 6 曲线给得很直白:反射率 54% 的灰靶、暗环境,33ms 预算下最大只能打到 310cm,重复性标准差 5mm;把预算加到 140ms 就能摸到 400cm,标准差降到 3.5mm;200ms 进一步降到 2.5mm。Pololu 库里对应的函数是 setMeasurementTimingBudget,单位微秒,Short 模式下限 20ms,Medium 和 Long 模式下限 33ms。读数跳,先想想是不是预算给太抠了。

接线:七个脚,其实只接四根

载板一共七个引脚:
| 引脚 | 作用 |
|---|---|
| VIN | 主供电,2.8V 到 5.5V,板载稳压器降到 2.8V 给芯片 |
| GND | 地,必须与主控共地 |
| SDA / SCL | I2C 数据与时钟,板上带电平位移,高电平跟随 VIN |
| VDD | 稳压后的 2.8V 输出,能带约 100mA 外设;想绕过稳压,也可以直接给它喂 2.6V 到 3.5V(VIN 悬空) |
| XSHUT | 低电平有效的关断输入,板上默认上拉到 VDD,拉低进硬件待机 |
| GPIO1 | 可编程中断输出,开漏,板上已上拉 |
单颗跑通只要四根线:VIN、GND、SDA、SCL。库的 README 给了两组标准接法——5V 板(Uno、Leonardo、Mega、A-Star 32U4)用 5V 接 VIN;3.3V 板(Due 等)用 3V3 接 VIN,其余同名相接。
两个细节值得专门提醒。第一,XSHUT 和 GPIO1 这两个脚不走电平位移,也不耐 5V:多数 3.3V 和 5V 主控“能用”,但官方页明确写了别把 XSHUT 主动推高,正确姿势是在“输出低”和“高阻输入”之间切换,后文的例程就是这么做的;拿不准就外接一颗四通道双向电平转换。第二,功耗按 20mA 典型值估,峰值可以到 40mA,电池小车要抠电的话,ST 还提供一套超低功耗 API(STSW-IMG032),部分场景能把待机检测电流压到 100 微安以内。

载板是 0.1 英寸标准间距,尺寸 13×18×2mm,不带排针重 0.5g,配两颗 0.5 英寸间距的 M2 安装孔,面包板和洞洞板都能直接插,随机附一排直插和一排弯插排针。
装库与跑通:从一颗到三颗是同一套代码
Arduino IDE 里开 Library Manager 搜 VL53L1X,装 Pololu 的这颗(写稿时最新 1.3.1,2022 年 2 月更新,这版专门加了多传感器例程);装不上就手动下 release 包,把目录改名为 VL53L1X 丢进 sketchbook 的 libraries 里,重启 IDE。库的常用函数就这几个:init() 完成初始化(默认 io_2v8=true,即 2.8V IO 模式);setDistanceMode 设 Short、Medium、Long 三档;setMeasurementTimingBudget 设时间预算;startContinuous(周期毫秒) 启动连续测距;read() 阻塞返回毫米读数,同时刷新 ranging_data,里面的 range_status 必须等于 VL53L1X::RangeValid 这条读数才算数;偶尔测一次用 readSingle()。
下面这份代码是官方库自带的 ContinuousMultipleSensors 例程,逐字符照搬,只做了一处平台兼容替换:站点编辑器会剥掉尖括号头文件,所以 #include 写成 #include "Wire.h",两种写法对库搜索路径在 Arduino 工具链下等价,编译结果一致。先看声明部分:
/*
This example shows how to set up and read multiple VL53L1X sensors connected to
the same I2C bus. Each sensor needs to have its XSHUT pin connected to a
different Arduino pin, and you should change sensorCount and the xshutPins array
below to match your setup.
For more information, see ST's application note AN4846 ("Using multiple VL53L0X
in a single design"). The principles described there apply to the VL53L1X as
well.
*/
#include "Wire.h"
#include "VL53L1X.h"
// The number of sensors in your system.
const uint8_t sensorCount = 3;
// The Arduino pin connected to the XSHUT pin of each sensor.
const uint8_t xshutPins[sensorCount] = { 4, 5, 6 };
VL53L1X sensors[sensorCount];
sensorCount 是传感器数量,xshutPins 是每个传感器 XSHUT 脚接的 Arduino 引脚,按 4、5、6 三个脚接三颗。只接一颗的话,把 sensorCount 改成 1、数组留一个脚就行,setup 里的循环逻辑完全不用动。再看 setup:
void setup()
{
while (!Serial) {}
Serial.begin(115200);
Wire.begin();
Wire.setClock(400000); // use 400 kHz I2C
// Disable/reset all sensors by driving their XSHUT pins low.
for (uint8_t i = 0; i < sensorCount; i++)
{
pinMode(xshutPins[i], OUTPUT);
digitalWrite(xshutPins[i], LOW);
}
// Enable, initialize, and start each sensor, one by one.
for (uint8_t i = 0; i < sensorCount; i++)
{
// Stop driving this sensor's XSHUT low. This should allow the carrier
// board to pull it high. (We do NOT want to drive XSHUT high since it is
// not level shifted.) Then wait a bit for the sensor to start up.
pinMode(xshutPins[i], INPUT);
delay(10);
sensors[i].setTimeout(500);
if (!sensors[i].init())
{
Serial.print("Failed to detect and initialize sensor ");
Serial.println(i);
while (1);
}
// Each sensor must have its address changed to a unique value other than
// the default of 0x29 (except for the last one, which could be left at
// the default). To make it simple, we'll just count up from 0x2A.
sensors[i].setAddress(0x2A + i);
sensors[i].startContinuous(50);
}
}
loop 里就是挨个 read 并打印,超时用 timeoutOccurred() 兜底:
void loop()
{
for (uint8_t i = 0; i < sensorCount; i++)
{
Serial.print(sensors[i].read());
if (sensors[i].timeoutOccurred()) { Serial.print(" TIMEOUT"); }
Serial.print('\t');
}
Serial.println();
}

这里插一个官方页专门提醒的坑:载板出厂可能在传感器芯片上贴着一层保护膜,上电前必须撕掉,否则测距性能直接打折。别笑,真有人贴着膜调了一下午参数。
多颗同总线的关键:XSHUT 逐个唤醒、挨个改地址
VL53L1X 出厂的 7 位 I2C 地址是 0101001b,也就是 0x29,所有芯片都一样,直接把几颗并到一条总线上必然打架。地址可以写配置寄存器改掉,但复位或断电就丢回去,所以多颗方案的标准流程是靠 XSHUT 控制“谁醒着”:全部拉低复位,然后一次只放一颗出来,趁它独占总线时立刻 setAddress 改成新地址(例程从 0x2A 往上数,最后一颗可以留在默认 0x29),改完再放下一颗。ST 的应用笔记 AN4846(Using multiple VL53L0X in a single design)讲的正是这套思路,对 VL53L1X 同样适用。
例程里最容易被误解的一行是 pinMode(xshutPins[i], INPUT):因为 XSHUT 不做电平位移也不耐 5V,放行靠的是把主控脚切成高阻输入、让载板自己的上拉电阻把它拉高,而不是 digitalWrite(HIGH) 硬推。第一次接多颗总是莫名失败的话,先检查这一处。
另外,改地址只改了 RAM,断电即失。这意味着每次上电都要重跑一遍这套唤醒流程,例程把它放在 setup 里就是这个原因;别想着改一次存进芯片,它没有这个机制。
读数不对劲,先查这四处
- 保护膜没撕:症状是距离一近就正常、一远就乱跳,先撕膜再排查别的;
- 目标吸光或环境光太强:黑绒布、深色磨砂塑料会吞掉激光,大太阳底下 Long 模式打不满 4 米,换 Short 模式、加大时间预算,或者用 ranging_data 里的 ambient_count_rate_MCPS 看看环境光计数;
- 状态位没看:range_status 不等于 RangeValid 的读数直接丢掉,库里还给 rangeStatusToString() 能把状态翻译成人话,AVR 上别调它(字符串存在 RAM 里,要吃 200 多字节);
- 目标在 4cm 以内:会报有目标但数值不准,避障逻辑要把这一段单独处理。
进阶一点还有 ROI 可调:setROISize 最小 4×4、默认 16×16(即全部 256 个 SPAD),setROICenter 默认 199 居中。把视场收窄能挡掉旁边的干扰源,代价是灵敏度下降,做窄通道避障时很好用。要注意库没有提供加盖玻璃后的校准功能,给传感器罩壳开孔的尺寸和距离自己留余量。
写在最后
整套下来,VL53L1X 的上手成本其实很低:四根线、一个库、一套固定的多传感器流程,剩下的功夫都在模式与时间预算的取舍上。它不是超声那种“插上就能糊弄”的器件,但只要按数据手册的曲线给足预算、老老实实看状态位,4 米量程加毫米级分辨率的稳定性,是红外和超声给不了的。做轮式机器人避障、机械臂防撞、跟随小车的前向测距,这颗都值得进你的常用件清单。











时间预算这块写到位了。我最早拿它做前向避障就吃过大亏:Long 模式配 33ms 预算,白天车间窗边一晒,3 米开外读数直接掉一半,还以为是模块坏了。后来照着数据手册那条曲线把预算加到 140ms,配合 Short 模式在 1.3 米内用,稳得完全不像同一个传感器。补充一个实测细节:ambient_count_rate_MCPS 这个值能当环境光指示器用,超过 1.0 左右就该考虑降模式或者加遮光罩了,比等读数跳了再排查省事得多。
XSHUT 别推高那条是真坑,我第一块板就是这么烧的 IO,血泪换来的经验。
多颗改地址这套流程我照官方例程接过三颗,一次点亮,例程写得确实扎实。
请问这套代码直接搬到 ESP32 上能用吗?引脚是不是换成任意 GPIO 就行?