序言
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以函数移植为例
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手册中的VL我们不关心它的其他元素,但传感器的设备地址保存在Dev->I2cDevAddr在里面,设备地址是IIC操作必须参数,另外两个必须参数是读取器件中的存储地址index读取长度。因为这里只读一个字节,所以长度是1,用了IIC_ReadOneByte这个函数。移植API硬件操作函数,其实就是在这个函数中套用实际硬件的操作函数,套个壳。但是在这个简单的套壳下,还有很多坑需要注意,比如读一个半字函数的实现如下:
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手册,来进行如距离,测量速度等参数的确定。 我的初始化代码如下:
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手册后可以发现,校准函数在进行校准时是有校准条件的,一般都要求有一定的距离,校准面的颜色,和环境光的要求。如果每次都进行一次完全的校准的话,很有可能因为传感器校准的时候环境不够严格导致最终得到的数据不准确。因此我将校准函数和初始化函数进行了分离,只在模块完全部署好之后进行一次严格的校准然后保存这次结果,到时候每次启动的时候就录入这个结果就可以了。我的校准代码如下,仅包括默认校准和偏移校准,不包括串扰校准:
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)
函数来读取校准数据。
读取数据的函数
传感器开启测量后,我们就可以通过一系列的操作函数来完成测距的功能了,测距的具体流程如下
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