VL53L1X移植到STM32实战记录,使用软件IIC(附源代码)


序言

VL53L1X是一个很小又很优秀的测距传感器,它相比于上一代VL53L0X有着不小的提升,这次毕业设计打算将这个传感器用起来,就来移植了一下,遇到的坑怎么说还是有一些,故在此分享给大家。

开发环境

IDE:Keil V5 STM32CubeMX ,使用HAL库

具体操作

获取官方库并进行移植

这一步是移植的基础,首先从官方把API库下下来,并添加到IDE中:
在这里插入图片描述

适配硬件

这里由于不同的硬件平台,他们对io的操作也是各异的,因此具体平台的c文件也是需要自己去修改的。由于VL53L1支持IIC通信,而硬件IIC通信速度也就那样,我更趋向于使用软件的模拟IIC,使用软件模拟IIC可以在进行硬件设计的时候可以使用任意的IO口,而不限制于具体的硬件IIC接口,而且移植起来也很简单(毕竟模拟IIC的硬件需求只有最普通的GPIO)。因此再导入模拟IIC的库文件,可以将两个io口模拟为IIC_SCL和IIC_SDA,同时模拟IIC提供了以下读写操作
在这里插入图片描述
IIC的接口也准备好了,那么我们就可以看vl53l1_platform.c这个平台适配文件了。
这个文件如果是从官方库拿下来的话,它的正文基本上全部都是一些空名函数,所有的函数列表如下:在这里插入图片描述
这些就是VL53的硬件操作函数了,如VL53L1_RdByte()这个函数就是VL53读取一个字节的函数。VL53的API库中的所有功能最终操作具体硬件的时候用的都是这个文件下的函数,因此我们必须把这些操作函数给移植到我们的硬件平台上,这样API函数才能调用我们实际的IIC硬件,最终完成操作硬件的目的。
以VL53L1_RdByte函数的移植为例

1
2
3
4
5
6
VL53L1_Error VL53L1_RdByte(VL53L1_DEV Dev, uint16_t index, uint8_t *data) {
    if(IIC_ReadOneByte(Dev->I2cDevAddr,index,data))
    {
        return VL53L1_ERROR_NONE;
    }else return VL53L1_ERROR_CONTROL_INTERFACE;
}

其中 Dev代表的API手册中的VL53结构体,它的其他元素我们并不关心,但传感器的器件地址就保存在Dev->I2cDevAddr里,器件地址是IIC操作的必须参数,另外两个必须参数则是读取的器件内的存储地址index和读取的长度了。因为在这里是只读一个字节,因此长度为1,使用了IIC_ReadOneByte这个函数。可以看到,移植API的硬件操作函数,其实就是在这个函数里面套用实际硬件的操作函数,套个壳而已罢了。但是在这种简单的套壳下,还有很多需要注意的坑,如读取一个半字的函数的实现如下:

1
2
3
4
5
6
VL53L1_Error VL53L1_RdWord(VL53L1_DEV Dev, uint16_t index, uint16_t *data) {
    uint8_t ret[2];
    IICreadBytes(Dev->I2cDevAddr,index,2,(uint8_t*)ret);
    *data=(ret[0]<<8) | ret[1];
    return VL53L1_ERROR_NONE;
}

可以看到,读取半字的操作函数竟然不是直接读2个字节返回,而是读取之后进行了高低位的字节的调换,这是因为STM32的存储方式是小端模式,而进行IIC通信的时候数据的传输方式是大端传输,因此为了保证数据的一致性就得进行调换。前面的例子不需要调换是因为它只涉及了一个字节的传输,就不存在大小端的问题。如果对大小端不了解的话可以去搜索引擎了解一下,它在数据交互的时候是一个非常需要注意的点。

在将这个文件的函数一个对应一个套娃完成后,这个API的硬件操作接口就算被我们实现了,那么我们现在就可以使用API的操作函数进行初始化设备了!

软件初始化

关于VL53的初始化,可以多多参考VL53的API手册,来进行如距离,测量速度等参数的确定。
我的初始化代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
VL53L1_Error VL53L1Init(VL53L1_Dev_t* pDev)
{
    VL53L1_Error Status = VL53L1_ERROR_NONE;
    pDev->I2cDevAddr=0x52;//默认地址
    pDev->comms_type=1;//默认通信模式
    pDev->comms_speed_khz = 400;//通信速率(可到400hz)
    Status = VL53L1_WaitDeviceBooted(pDev);
    if(Status!=VL53L1_ERROR_NONE)
    {
        printf("Wait device Boot failed!\r\n");
        return Status;
    }
    osDelay(2);

    Status = VL53L1_DataInit(pDev);//device init
    if(Status!=VL53L1_ERROR_NONE)
    {
        printf("datainit failed!\r\n");
        return Status;
    }

    osDelay(2);
    Status = VL53L1_StaticInit(pDev);
    if(Status!=VL53L1_ERROR_NONE)
    {
        printf("static init failed!\r\n");
        return Status;
    }
    osDelay(2);
    Status = VL53L1_SetDistanceMode(pDev, VL53L1_DISTANCEMODE_LONG);    //short,medium,long
    if(Status!=VL53L1_ERROR_NONE)
    {
        printf("set discance mode failed!\r\n");
        return Status;
    }
    osDelay(2);
    return Status;
}

其中前面三句都是标准的初始化流程 是调用任何其他API前的初始化函数,而最后一个则决定了测量模式。在这些都设置好了之后就可以使用VL53L1_StartMeasurement(pDev);来开启这个测距传感器的测量了!

传感器的校准

大家可以看到我初始化的时候和其他例程有一定的出入,在刚刚的代码中我并没有进行传感器的校准,因为阅读API手册后可以发现,校准函数在进行校准时是有校准条件的,一般都要求有一定的距离,校准面的颜色,和环境光的要求。如果每次都进行一次完全的校准的话,很有可能因为传感器校准的时候环境不够严格导致最终得到的数据不准确。因此我将校准函数和初始化函数进行了分离,只在模块完全部署好之后进行一次严格的校准然后保存这次结果,到时候每次启动的时候就录入这个结果就可以了。我的校准代码如下,仅包括默认校准和偏移校准,不包括串扰校准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
VL53L1_Error VL53Cali(VL53L1_Dev_t* pDev,void * save)
{
    VL53L1_Error Status = VL53L1_ERROR_NONE;
    Status = VL53L1_StopMeasurement(pDev);
    if(Status!=VL53L1_ERROR_NONE)
        return Status;
    Status = VL53L1_PerformRefSpadManagement(pDev);//perform ref SPAD management
    if(Status!=VL53L1_ERROR_NONE)
        return Status;
   
    Status = VL53L1_PerformOffsetSimpleCalibration(pDev,140);//14cm的出厂校验值
    if(Status!=VL53L1_ERROR_NONE)
        return Status;
   
    Status = VL53L1_GetCalibrationData(pDev,save);
    if(Status!=VL53L1_ERROR_NONE)
        return Status;
    //全部完成 重新打开测量
    Status = VL53L1_StartMeasurement(pDev);
    return Status;
}

该函数执行时要求离一个14cm的白色墙面,尽量在无光环境下执行,校准完成后得到的数据会存储到save变量中。以后启动的时候可以使用VL53L1_SetCalibrationData(save)函数来读取校准数据。

读取数据的函数

传感器开启测量后,我们就可以通过一系列的操作函数来完成测距的功能了,测距的具体流程如下

1
2
3
4
5
6
7
8
9
10
11
12
    VL53L1_RangingMeasurementData_t result_data;
    int32_t distance;
    status = VL53L1_WaitMeasurementDataReady(pDev);//等待测量就绪
    if(status!=VL53L1_ERROR_NONE)
    {
        printf("Wait too long!\r\n");
        return status;
    }
    status = VL53L1_GetRangingMeasurementData(pDev, &result_data);//得到测量数据集
    distance = result_data.RangeMilliMeter;//从测量数据集中提取距离
    status = VL53L1_ClearInterruptAndStartMeasurement(pDev);//清除标志 等待下一次测量
    return status;

最终distance便是我们得到的测量距离(单位:mm)。同时result_data中有更全面的各种测量数据,如测量范围差,测量强度,可信度,错误号码等。如果发现测量得到的数据和真实数据出入较大,建议看看result_data中的错误码再进行代码的排查。

效果展示

下面是专门新建的一个简单例程,任意选择两个io口作为传感器接口,如图所示
在这里插入图片描述
使用自动生成的工程,只需要添加方框所示的代码即可得到距离值。
在这里插入图片描述
本例程也上传到了我的github