VL53L0X激光测距实战:接线、装库到双传感器改地址

2026-10-24 18:22:31 4阅读

做避障小车或者机器狗的时候,超声波模块 HC-SR04 几乎是默认选择,便宜、好买、教程多。但只要做过稍微较真一点的项目,就会被它的几个毛病劝退:锥形探测范围太宽,侧前方的障碍物经常误判;对软质材料(窗帘、毛衣)反射差,读数乱跳;模块体积也不小,塞不进紧凑的结构里。这两年玩家的替代方案基本收敛到一个答案:VL53L0X 激光测距模块。十几块钱一块,尺寸比指甲盖大不了多少,I2C 两根线就能挂上去。这篇把接线、装库、跑通到进阶的双传感器改地址一次说清楚。

VL53L0X 激光测距模块与 Arduino 的接线示意图

这块小板子是什么水平

VL53L0X 是意法半导体的 ToF(飞行时间)测距芯片,内部集成 940nm 的 VCSEL 激光发射器和 SPAD 单光子接收阵列。激光对人眼不可见,属于 Class 1 安全等级。几个关键参数先列出来(以官方手册和 DFRobot 产品页的数据为准):

  • 量程:30mm 到 2000mm(2 米),近距离 30mm 以内也能出数
  • 测距精度:±3%,比超声波的厘米级抖动好一个档次
  • 单次测距时间:不超过 30ms,换算下来每秒能出 30 多次读数
  • 测距视场角:约 25°,指向性比超声波窄得多,指哪测哪
  • 接口:I2C,默认地址 0x29,工作电压 3.3V 到 5V,正常工作功耗约 20mW

市面上常见的模块形态有几种:Adafruit 的蓝色小板(14.95 美元)、DFRobot 的 Gravity 接口版、还有几块钱的紫色裸 PCB 兼容板。引脚都是六个:Vin、GND、SCL、SDA、GPIO1(中断输出)、XSHUT(关断控制)。入门只用前四个就行,XSHUT 到后面双传感器玩法才会用到。模块引脚布局看下面这张引脚图。

VL53L0X 模块引脚定义图

接线:四根线搞定

以最常见的 Arduino UNO 为例,接线只有四根:

  • 模块 Vin → Arduino 5V(板载稳压,3 到 5V 都能吃)
  • 模块 GND → Arduino GND
  • 模块 SCL → Arduino A5(UNO 的 I2C 时钟脚)
  • 模块 SDA → Arduino A4(UNO 的 I2C 数据脚)

如果用的是 Mega2560,SCL 和 SDA 在数字口 21 和 20;Leonardo 和 Micro 是数字口 3 和 2。ESP32 的话默认 I2C 是 GPIO22(SCL)和 GPIO21(SDA),供电注意用 3.3V 逻辑。接线示意如下,传感器、面包板、四根杜邦线就是全部。

VL53L0X 与 Arduino 面包板接线实物图

有一个几乎所有教程都会强调、但新手还是会忘的细节:模块激光窗口上贴着一层保护膜,用之前务必撕掉。不撕的话激光穿过塑料膜会产生散射,读数会飘,而且飘得毫无规律,排查半天往往就是这张膜的事。

装库跑通第一个读数

Arduino IDE 里打开库管理器(工具 → 管理库,或者左侧边栏的书本图标),搜索 Adafruit_VL53L0X,点安装。装完后打开 文件 → 示例 → Adafruit_VL53L0X → vl53l0x,这就是一个现成的串口测距 demo,不用改任何东西,直接上传。打开串口监视器,波特率调到 115200,把手在传感器前来回移动,就能看到毫米单位的距离读数刷出来。没有对准目标或者超出量程时,程序会输出 out of range,这是正常现象,不是坏了。

Adafruit 官方教程里还提到一个小技巧:初始化时如果直接调 lox.begin() 用的是默认地址 0x29,而 lox.begin(0x30) 这样的写法可以在初始化时就给传感器换地址,这在后面挂多个传感器时很关键。

完整项目:距离显示到 OLED 上

串口看数终归不方便,做一个脱离电脑的独立测距仪才有意思。SunFounder 的项目文档里给了一个 VL53L0X 加 SSD1306 OLED 屏的完整方案:OLED 也是 I2C 接口(地址 0x3C),和 VL53L0X 挂在同一组 A4、A5 上互不冲突——这正是 I2C 总线的好处,地址不同就能并联。OLED 的 VCC 接 5V、GND 共地即可。完整代码如下,依赖 Adafruit_VL53L0X、Adafruit_SSD1306 和 Adafruit_GFX 三个库,都要在库管理器里装好:

#include 
#include "Adafruit_VL53L0X.h"
#include
#include
#include

Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire, -1);
Adafruit_VL53L0X lox = Adafruit_VL53L0X();

void setup() {
Serial.begin(9600);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.display();
delay(1000);
Wire.begin();
if (!lox.begin()) {
Serial.println(F("Failed to boot VL53L0X"));
while (1)
;
}
display.setTextSize(3);
display.setTextColor(WHITE);
}

void loop() {
VL53L0X_RangingMeasurementData_t measure;
lox.rangingTest(&measure, false);
if (measure.RangeStatus != 4) {
display.clearDisplay();
display.setCursor(12, 22);
display.print(measure.RangeMilliMeter);
display.print("mm");
display.display();
Serial.println();
delay(50);
} else {
display.display();
display.clearDisplay();
return;
}
}

烧录之后 OLED 屏幕上会实时显示毫米距离,手在传感器前晃动数值就跟着跳。这套组合的实测体验是:30cm 以内的近距离响应非常快,1 米开外对准白墙读数稳定,但对黑色绒布这类吸光材质会明显吃力,远距离误差变大,这是 ToF 原理决定的,不是模块质量问题。

进阶:两个 VL53L0X 同时挂

做小车左右双侧避障、或者机械臂两端限位时,一个传感器不够用。问题来了:每块 VL53L0X 出厂默认地址都是 0x29,直接并到 I2C 总线上必然冲突。解决思路是用 XSHUT 引脚做"逐个唤醒改地址":把所有传感器的 XSHUT 拉到不同的数字口,上电时先全部拉低让传感器都处于关断状态,然后逐个拉高、逐个用 setAddress 改成新地址。注意这个地址是掉电丢失的,每次开机都要重新走一遍这个流程。

下面这份代码用的是 Pololu 的 VL53L0X 库(库管理器搜 VL53L0X,作者 Pololu),两块传感器分别接 XSHUT 到数字口 10 和 8,地址改成 42 和 43,并且顺手开了长距模式(降低信号率上限、加大激光脉冲周期),还把测量时间预算设为 40000 微秒换更高精度:

#include  //Download this library from https://github.com/pololu/vl53l0x-arduino
#include

VL53L0X sensor_A; //Create the sensor object
VL53L0X sensor_B; //Create the sensor object

int startTime = millis(); //used for our timing loop
int mInterval = 100; //refresh rate of 10hz

#define XSHUT_A 10
#define XSHUT_B 8

void setup() {

Serial.begin(57600);

Wire.begin(); //Setup your I2C interface
Wire.setClock(400000); // use 400 kHz I2C

//Set the pin mode to output
pinMode(XSHUT_A ,OUTPUT);
pinMode(XSHUT_B ,OUTPUT);

//Turn all TOF's off
digitalWrite(XSHUT_A, LOW);
digitalWrite(XSHUT_B, LOW);

//-----------------------------------------------------------------
//FIRST WE WILL CONFIGURE AND SETUP SENSOR_A
//-----------------------------------------------------------------
delay(50);
digitalWrite(XSHUT_A, HIGH); //Turn sensor_A on
delay(50);

sensor_A.setTimeout(500); //Set the sensors timeout

if (!sensor_A.init())//try to initilise the sensor
{
//Sensor does not respond within the timeout time
Serial.println("Sensor_A is not responding, check your wiring");
}
else
{
sensor_A.setAddress(42); //Set the sensors I2C address
//SET THE SENSOR TO LONG RANGE MODE
// lower the return signal rate limit (default is 0.25 MCPS)
sensor_A.setSignalRateLimit(0.1);
// increase laser pulse periods (defaults are 14 and 10 PCLKs)
sensor_A.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18);
sensor_A.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14);
sensor_A.setMeasurementTimingBudget(40000); //Set its timing budget in microseconds longer timing budgets will give more accurate measurements
sensor_A.startContinuous(45); //Sets the interval where a measurement can be requested in milliseconds
}

//-----------------------------------------------------------------
//NOW CONFIGURE AND SETUP SENSOR_B
//-----------------------------------------------------------------
delay(50);
digitalWrite(XSHUT_B, HIGH); //Turn sensor_A on
delay(50);

sensor_B.setTimeout(500); //Set the sensors timeout

if (!sensor_B.init())//try to initilise the sensor
{
//Sensor does not respond within the timeout time
Serial.println("Sensor_A is not responding, check your wiring");
}
else
{
sensor_B.setAddress(43); //Set the sensors I2C address
//SET THE SENSOR TO LONG RANGE MODE
// lower the return signal rate limit (default is 0.25 MCPS)
sensor_B.setSignalRateLimit(0.1);
// increase laser pulse periods (defaults are 14 and 10 PCLKs)
sensor_B.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18);
sensor_B.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14);
sensor_B.setMeasurementTimingBudget(40000); //Set its timing budget in microseconds longer timing budgets will give more accurate measurements
sensor_B.startContinuous(45); //Sets the interval where a measurement can be requested in milliseconds
}
}

void loop() {
//We have to be careful here.If we request a measurement before the measurement has been taken your
//code will be blovked until the measurement is complete.In order to stop this from happening we
//must ensure that time between measurement requests is greater than the timing budget and the argument
//given in the startContinuous() function.In our case our measurement time must be greater than 50mS.

if((millis()- startTime) > mInterval)
{
Serial.print("Sensor_A Reading: ");
Serial.println(sensor_A.readRangeContinuousMillimeters()); //Get a reading in millimeters
Serial.print("Sensor_B Reading: ");
Serial.println(sensor_B.readRangeContinuousMillimeters()); //Get a reading in millimeters
startTime = millis();
}
}

代码原作者在注释里特意提醒了一个坑:连续模式的读取间隔必须大于测量时间预算加上 startContinuous 里设的周期,否则程序会阻塞在等待测量完成上。这份代码里时间预算 40ms、连续模式周期 45ms、主循环间隔 100ms,是配平过的,自己改参数时留意这个关系。

常见坑排一遍

  • 串口全是 out of range:先检查保护膜撕没撕;再检查供电是不是共地了;最后拿 I2C 扫描程序扫一下,正常应该能看到 0x29 这个地址
  • 读数飘忽:多半是目标材质问题,黑色、镜面、透明玻璃的反射特性都差,换白墙测试排除法
  • 两个传感器只有一个出数:XSHUT 时序没配对,回到上文的唤醒流程逐步查;两块板子地址绝不能设成一样
  • 户外阳光下测不远:940nm 激光会被阳光里的红外成分淹没,这类传感器室内用是主场,室外强光场景别指望 2 米量程

最后给个直接的判断:VL53L0X 十几块钱的价格、2 米量程、±3% 精度、I2C 即插即用,在室内近距离测距这个场景下几乎没有对手。做避障、做跟随、做手势识别,它都比超声波更合适;真正需要它退场的是室外强光或者 5 米以上的场景,那已经是另一个价位的传感器该干的活了。


作者:电机传感器师
链接:https://www.aiddithome.com/p/5ac645ee26637.html
本作版权归作者所有,未经允许请勿转载。若存在违规行为,您可以点击 “举报”。


登录 后发表评论
4条评论
锡点
1楼 · 1小时前

双传感器改地址那段讲清楚了,之前看英文教程看得一头雾水,原来每次上电都要重新配一遍地址

阿强焊点总飞
2楼 · 1小时前

问一下UP主,ESP32 上用的是 3.3V 逻辑,直接接 Vin 到 5V 会不会烧?我接 3.3V 倒是正常出数了

拆开看看再说
3楼 · 1小时前

比超声波干净太多了,我做的跟随小车换了 VL53L0X 之后再也没有对着空气刹车过

触底反弹的Tony
4楼 · 1小时前

上周刚在车里加了这个模块,串口一直刷 out of range,查了一晚上线,看完这篇才发现是保护膜没撕,血压上来了