# 07 Laser Sensor Vl53l1x Driver

## 一、硬件介绍

### 1. VL53L0X介绍

![VL53L0X](https://img-blog.csdnimg.cn/20190815183359413.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

VL53L0X 芯片内部集成了激光发射器和 SPAD 红外接收器,采用了第二代FightSenseTM 技术,通过接收器所接收到的光子时间来计算距离,最远测量距离可达两米,适合中短距离测量的应用.

* VL53L0X传感器有**3-4cm的盲区**,有效测量范围3cm-200cm,精度+-3%
* VL53L0X芯片IO电压为2.8V,3.3V供电需要串联120R电阻
* 响应频率20ms(最快),误差+-5%

![VL53L0X精度模式](https://img-blog.csdnimg.cn/20190813153852479.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

### 2. VL53L1X介绍

![在这里插入图片描述](https://img-blog.csdnimg.cn/2019081518332341.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

VL53L1X是VL53L0X的升级版本,感光部分面积明显增大,有更远的探测距离,参数如下:

![VL53L1X](https://img-blog.csdnimg.cn/20190815183139312.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

### 3. VL53L1X典型电路

![vl53l1x pinmap](https://img-blog.csdnimg.cn/20191105105624819.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

![vl53l1x典型电路](https://img-blog.csdnimg.cn/20191105105658764.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

* XSHUT为输入引脚,用于模式选择(休眠),需要上拉电阻放置漏电流
* GPIO1为中断输出引脚,用于输出测量dataready中断
* 为了达到极限传输率(400khz),上拉电阻需要根据电压和总线电容值选择,可以参见vl53l1x datasheet

### 4. VL53L0X与VL53L1X区别？

VL53L0X为2m测距版本,VL53L1X为4m版本.目前测试发现,这两颗芯片并不只是性能区别,**官方给出的是两套库,不能相互通用**.仔细阅读芯片手册甚至调用API的配置过程也是不同的,需要特别注意一下手中的芯片是哪个版本,然后对应的去找官网的库文件.

## 二、移植过程介绍

### 1. 官方库文件如何使用？

除了数据手册,我们还能从ST官方下载一个软件开发包,这里面包含了所有官方的API,和基于STM32的示例程序.

1. VL53L1X软件包下载地址:[X-CUBE-53L1A1](https://www.st.com/en/ecosystems/x-cube-53l1a1.html):The X-CUBE-53L1A1 software package
2. 解压后,依次进入 Drivers->BSP->Components->vl53l1x ,这里的所有文件我们都要复制到自己的工程,我们可以新建一个文件夹/vl53l1x/core存放.这里是与硬件无关的基础文件,是不需要任何改动的

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190815181832417.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

1. 以上源文件还不够,还有一部分是与硬件相关的文件和用户配置文件,我们可以打开一个示例工程,复制到自己工程的/vl53l1x/platform目录.这些文件路径比较长,可以看以下图片找到.

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190815182726226.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

![在这里插入图片描述](https://img-blog.csdnimg.cn/2019081518273854.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

### 2. 如何移植到ESP32？

拿到官方库文件后,如果想移植VL53L1X到ESP32等平台,需要实现以下底层函数 .这些函数声明在vl53l1\_platform.h,需要在vl53l1\_platform.c中编写实现

```
VL53L1_WrByte、VL53L1_WrWord、
VL53L1_WrDWord 、 VL53L1_RdByte 、 VL53L1_RdWord 、 VL53L1_RdDWord 、
VL53L1_WriteMulti、VL53L1_ReadMulti、VL53L1_UpdateByte、VL53L1_PollingDelay
```

感谢kazkojima在github上分享的移植代码,[链接](https://github.com/kazkojima/esp32-vl53l1x-test) 这个工程下载下来并不能工作,还需要下载官方库文件,做少许的修改.我进行了进一步的修改:[链接](https://github.com/qljz1993/esp32-vl53l1x-test)

同理VL53L0X需要移植以下函数

```
VL53L0X_WrByte、VL53L0X_WrWord、
VL53L0X_WrDWord 、 VL53L0X_RdByte 、 VL53L0X_RdWord 、 VL53L0X_RdDWord 、
VL53L0X_WriteMulti、VL53L0X_ReadMulti、VL53L0X_UpdateByte、VL53L0X_PollingDelay
```

### 3. vl53l1x模式选择与启动顺序

根据XSHUT引脚的输入电平,可以切换传感器HW Standby模式和SW Standby模式.如果主机不管理传感器模式,XSHUT应该被上拉为高电平.

> This option optimizes power consumption as the VL53L1X can be completely powwhen not used, and then woken up through a host GPIO (using the XSHUT pin).

* HW Standby :XSHUT拉低时,传感器电源被关闭
* SW Standby :XSHUT拉高,进入boot和SW Standby 模式

![HW Standby](https://img-blog.csdnimg.cn/20191105111953202.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

![SW Standby](https://img-blog.csdnimg.cn/20191105112016606.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

### 4. 使用官方API校准传感器

校准流程:调用顺序要完全一致.**官方流程图缺少一步:在绿色校准函数调用前需要VL53L1\_SetPresetMode ！！！**

![vl53l1x校准流程图](https://img-blog.csdnimg.cn/20190920215907272.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

### 5. 使用官方GUI程序校准传感器

调试了一周,遇到了无数的坑,大部分都是卡在初始化和校准阶段,很难处理.偶然发现官方还有用于配置和显示的GUI程序,相见恨晚啊.目前只有windows版本(需要配合官方硬件 `STM32F401RE nucleo board` 连接传感器使用).使用软件校准得到基准值后,初始化时填入的即可.如果使用API自己编写校准程序,非常容易出错,如下错误排查1.

![STSW-IMG008](https://img-blog.csdnimg.cn/20190815175015575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

[STSW-IMG008:Windows Graphical User Interface (GUI) for VL53L1X Nucleo packs. Works with P-NUCLEO-53L1A1 ](https://www.st.com/content/st_com/en/products/embedded-software/proximity-sensors-software/stsw-img008.html)

[STSW-IMG008](https://img-blog.csdnimg.cn/20190815175015575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

## 三、测试代码分析

### 1. 初始化

```
/*init  vl53l1 module*/
void vl53l1_init()
{

    Roi0.TopLeftX = 0;    //测量目标区 可选最小4*4,最大16*16
    Roi0.TopLeftY = 15;
    Roi0.BotRightX = 7;
    Roi0.BotRightY = 0;
    Roi1.TopLeftX = 8;
    Roi1.TopLeftY = 15;
    Roi1.BotRightX = 15;
    Roi1.BotRightY = 0;

    int status = VL53L1_WaitDeviceBooted(Dev); //等待设备初始化完成
    status = VL53L1_DataInit(Dev); //设备初始化,上电后立刻执行
    status = VL53L1_StaticInit(Dev); //装载参数
    status = VL53L1_SetDistanceMode(Dev, VL53L1_DISTANCEMODE_LONG);//设置测量模式
    status = VL53L1_SetMeasurementTimingBudgetMicroSeconds(Dev, 50000); //设置最长时间,根据测量模式确定
    status = VL53L1_SetInterMeasurementPeriodMilliSeconds(Dev, 100); //测量间隔

    status = VL53L1_SetUserROI(Dev, &Roi0); //设置ROI
    status = VL53L1_StartMeasurement(Dev); //启动测量
    if(status) {
        printf("VL53L1_StartMeasurement failed \n");
        while(1);
    }    

}
```

* 以上初始化步骤除VL53L1\_SetUserROI,其余不可少
* PerformRefSpadManagement等初始化函数用在VL53L1上会报错,不明原因(已解决,见上面校准流程图提示).
* roi 配置参见2.4 ranging description 2.8 Sensing array optical center

![在这里插入图片描述](https://img-blog.csdnimg.cn/20190815193352784.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

### 2. 轮寻测量

测量流程图:

![vl53l1x校准流程图](https://img-blog.csdnimg.cn/20190920220321161.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

轮训测量有两种方法,如上图所示,一种是阻塞方式(drivers polling mode),一种是非阻塞方式(Host polling mode),下图展示了阻塞方式的测量.

```
/* Autonomous ranging loop*/
static void
AutonomousLowPowerRangingTest(void)
{
    printf("Autonomous Ranging Test\n");

    static VL53L1_RangingMeasurementData_t RangingData;
    VL53L1_UserRoi_t Roi1;
    int roi = 0;
    float left = 0, right = 0;
    if (0/*isInterrupt*/) {
    } else {
        do // polling mode
            {
                int status = VL53L1_WaitMeasurementDataReady(Dev); //等待测量结果
                if(!status) {
                    status = VL53L1_GetRangingMeasurementData(Dev, &RangingData); //获取单次测量数据
                    if(status==0) {
                        if (roi & 1) {
                            left = RangingData.RangeMilliMeter;
                            printf("L %3.1f R %3.1f\n", right/10.0, left/10.0);
                        } else
                            right = RangingData.RangeMilliMeter;
                    }
                    if (++roi & 1) {
                        status = VL53L1_SetUserROI(Dev, &Roi1);
                    } else {
                        status = VL53L1_SetUserROI(Dev, &Roi0);
                    }
                    status = VL53L1_ClearInterruptAndStartMeasurement(Dev); //释放中断
                }
            }
        while (1);
    }
    //  return status;
}
```

### 3. 中断测量模式

![中断测量模式](https://img-blog.csdnimg.cn/20191105113456667.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

其实这种模式和轮训测量没什么大的区别,因为轮训测量也要进行清除中断标志的操作,只是通知的方式不一样.

### 4. 传感器校准

```
/*Calibration  vl53l1 module*/
static VL53L1_CalibrationData_t vl53l1_calibration(VL53L1_Dev_t *dev)
{
    int status;
    int32_t targetDistanceMilliMeter = 703;
    VL53L1_CalibrationData_t calibrationData;
    status = VL53L1_WaitDeviceBooted(dev);
    status = VL53L1_DataInit(dev);                                       //performs the device initialization
    status = VL53L1_StaticInit(dev);                                     // load device settings specific for a given use case.
    status = VL53L1_SetPresetMode(dev,VL53L1_PRESETMODE_AUTONOMOUS);
    status = VL53L1_PerformRefSpadManagement(dev);
    status = VL53L1_PerformOffsetCalibration(dev,targetDistanceMilliMeter);
    status = VL53L1_PerformSingleTargetXTalkCalibration(dev,targetDistanceMilliMeter);
    status = VL53L1_GetCalibrationData(dev,&calibrationData);

    if (status)
    {
        ESP_LOGE(TAG, "vl53l1_calibration failed \n");
        calibrationData.struct_version = 0;
        return calibrationData;

    }else
    {
        ESP_LOGI(TAG, "vl53l1_calibration done ! version = %u \n",calibrationData.struct_version);
        return calibrationData;
    }

}
```

github源码:<https://github.com/qljz1993/esp32-vl53l1x-test>

## 四、 错误排查

### 1. 激光测量的潜在问题

1. tof镜头检测的并不是到一个点的距离,而是一个椎体中的最短距离,这会导致一个问题,当飞行器靠近墙面飞行,返回的可能不是到地板的距离,而是到墙面的距离.
2. 飞行器定高时,测距模式为长距离模式,要求在黑暗无红外光的环境,在室外强光下,激光传感器会受到很大的干扰,导致测量精度降低,在室外依旧建议气压定高.

![在这里插入图片描述](https://img-blog.csdnimg.cn/2019080816450525.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTE1NDYx,size_16,color_FFFFFF,t_70)

### 2. 提示timeout

```
# 使用I2C
ets Jun  8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:6180
load:0x40078000,len:10180
ho 0 tail 12 room 4
load:0x40080400,len:6660
entry 0x40080764
I (31) boot: ESP-IDF v3.2.2-dirty 2nd stage bootloader
I (31) boot: compile time 11:50:48
I (31) boot: Enabling RNG early entropy source...
I (36) boot: SPI Speed      : 40MHz
I (40) boot: SPI Mode       : DIO
I (44) boot: SPI Flash Size : 4MB
I (48) boot: Partition Table:
I (52) boot: ## Label            Usage          Type ST Offset   Length
I (59) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (66) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (74) boot:  2 factory          factory app      00 00 00010000 00100000
I (81) boot: End of partition table
I (86) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x0b2d8 ( 45784) map
I (111) esp_image: segment 1: paddr=0x0001b300 vaddr=0x3ffb0000 size=0x0218c (  8588) load
I (114) esp_image: segment 2: paddr=0x0001d494 vaddr=0x40080000 size=0x00400 (  1024) load
0x40080000: _WindowOverflow4 at /home/libo/esp/esp-idf-v3.2.2/components/freertos/xtensa_vectors.S:1779

I (118) esp_image: segment 3: paddr=0x0001d89c vaddr=0x40080400 size=0x02774 ( 10100) load
I (130) esp_image: segment 4: paddr=0x00020018 vaddr=0x400d0018 size=0x1c62c (116268) map
0x400d0018: _flash_cache_start at ??:?

I (176) esp_image: segment 5: paddr=0x0003c64c vaddr=0x40082b74 size=0x07e60 ( 32352) load
0x40082b74: _gettimeofday_r at /home/libo/esp/esp-idf-v3.2.2/components/newlib/time.c:220

I (196) boot: Loaded app from partition at offset 0x10000
I (196) boot: Disabling RNG early entropy source...
I (197) cpu_start: Pro cpu up.
I (200) cpu_start: Starting app cpu, entry point is 0x400810fc
0x400810fc: call_start_cpu1 at /home/libo/esp/esp-idf-v3.2.2/components/esp32/cpu_start.c:246

I (191) cpu_start: App cpu up.
I (211) heap_init: Initializing. RAM available for dynamic allocation:
I (218) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (224) heap_init: At 3FFB3348 len 0002CCB8 (179 KiB): DRAM
I (230) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (236) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (243) heap_init: At 4008A9D4 len 0001562C (85 KiB): IRAM
I (249) cpu_start: Pro cpu start user code
I (267) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (4868) vl53l0x: refSpadCount = 1, isApertureSpads = 128

I (4868) vl53l0x: VL53L0X_PerformRefSpadManagement API status: -7 : Time out error

I (5448) vl53l0x: VL53L0X_PerformSingleRangingMeasurement API status: -7 : Time out error
```

```
typedef struct {
    uint32_t TimeStamp;        /*!< 32-bit time stamp. */
    uint32_t MeasurementTimeUsec;
        /*!< Give the Measurement time needed by the device to do the
         * measurement.*/


    uint16_t RangeMilliMeter;    /*!< range distance in millimeter. */

    uint16_t RangeDMaxMilliMeter;
        /*!< Tells what is the maximum detection distance of the device
         * in current setup and environment conditions (Filled when
         *    applicable) */

    FixPoint1616_t SignalRateRtnMegaCps;
        /*!< Return signal rate (MCPS)\n these is a 16.16 fix point
         *    value, which is effectively a measure of target
         *     reflectance.*/
    FixPoint1616_t AmbientRateRtnMegaCps;
        /*!< Return ambient rate (MCPS)\n these is a 16.16 fix point
         *    value, which is effectively a measure of the ambien
         *    t light.*/

    uint16_t EffectiveSpadRtnCount;
        /*!< Return the effective SPAD count for the return signal.
         *    To obtain Real value it should be divided by 256 */

    uint8_t ZoneId;
        /*!< Denotes which zone and range scheduler stage the range
         *    data relates to. */
    uint8_t RangeFractionalPart;
        /*!< Fractional part of range distance. Final value is a
         *    FixPoint168 value. */
    uint8_t RangeStatus;
        /*!< Range Status for the current measurement. This is device
         *    dependent. Value = 0 means value is valid.
         *    See \ref RangeStatusPage */
} VL53L0X_RangingMeasurementData_t;
```

## 五、ESP32+VL53L1x例程

### 1. 例程说明

1. 实现功能:通过VL53L1x 检测到高度变化(持续一秒),红灯亮起.高度恢复正常值(持续一秒),绿灯亮起.
2. 可配置参数:通过make menuconfig 设置I2C 号码、端口号、LED端口号
3. 例程解析见代码注释与用户手册

### 2. 注意事项

1. 该例程只适用于VL53L1x,寄送的传感器为该型号.VL53L0x为老版本硬件,不适用本例程.
2. 官方标称400cm测量距离,为黑暗环境下测得.室内正常灯光环境,可以保证10cm-260cm范围的有效测量
3. 初始化函数vl53l1\_init(VL53L1\_Dev\_t \*) 中部分参数,需要根据实际使用环境确定,还有优化的空间.
4. 传感器安装位置应确保在检测位置正上方
5. 模块上电时自动矫正基准高度,如果基准高度有变化,需要重新上电重置参数

### 3. 例程链接

[点击进入下载:esp32-vl53l1x-test](https://github.com/qljz1993/esp32-vl53l1x-test/tree/master) 或者:

```
git clone https://github.com/qljz1993/esp32-vl53l1x-test.git
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://libooogo.gitbook.io/esplane/developer-guide/08esp32+-ji-guang-chuan-gan-qi-vl53l1x-yi-zhi-yu-tiao-shi-fu-yuan-ma.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
