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函数的移植为例

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的硬件操作函数,其实就是在这个函数里面套用实际硬件的操作函数,套个壳而已罢了。但是在这种简单的套壳下,还有很多需要注意的坑,如读取一个半字的函数的实现如下:

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

  • 26
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 55
    评论
[UV2] ORGANIZATION="Microsoft" NAME="Microsoft", "ALIENTEK" EMAIL="277038235@qq.com" ARMSEL=1 BOOK0=UV3\RELEASE_NOTES.HTM("uVision Release Notes",GEN) [ARM] PATH="F:\\STM32MDK\\ARM\\" VERSION=3.80a PATH1="C:\Program Files\CodeSourcery\Sourcery G++ Lite\" CPUDLL0=SARM.DLL(TDRV0,TDRV5,TDRV6,TDRV8) # Drivers for ARM7/9 devices CPUDLL1=SARMCM3.DLL(TDRV1,TDRV3,TDRV4,TDRV5,TDRV7,TDRV8) # Drivers for Cortex-M devices BOOK0=HLP\RELEASE_NOTES.HTM("Release Notes",GEN) BOOK1=HLP\ARMTOOLS.chm("Complete User's Guide Selection",C) BOOK2=HLP\RL_RELEASE_NOTES.HTM("RTL-ARM Release Notes",GEN) BOOK3=HLP\RVI.chm("RV Compiler Introduction",GEN) BOOK4=C:\Program Files\CodeSourcery\Sourcery G++ Lite\share\doc\arm-2007q3-53-arm-none-eabi\pdf\gcc\gcc.pdf("GNU C Compiler",GEN) BOOK5=C:\Program Files\CodeSourcery\Sourcery G++ Lite\share\doc\arm-2007q3-53-arm-none-eabi\pdf\as.pdf("GNU Assembler",GEN) BOOK6=C:\Program Files\CodeSourcery\Sourcery G++ Lite\share\doc\arm-2007q3-53-arm-none-eabi\pdf\ld.pdf("GNU Linker",GEN) BOOK7=C:\Program Files\CodeSourcery\Sourcery G++ Lite\share\doc\arm-2007q3-53-arm-none-eabi\pdf\binutils.pdf("GNU Binary Utilities",GEN) TDRV0=BIN\UL2ARM.DLL("ULINK ARM Debugger") TDRV1=BIN\UL2CM3.DLL("ULINK Cortex Debugger") TDRV2=BIN\AGDIRDI.DLL("RDI Interface Driver") TDRV3=BIN\ABLSTCM.dll("Altera Blaster Cortex Debugger") TDRV4=BIN\lmidk-agdi.dll("Luminary Eval Board") TDRV5=Signum\SigUV3Arm.dll("Signum Systems JTAGjet") TDRV6=Segger\JLTAgdi.dll("J-LINK / J-TRACE") TDRV7=Segger\JL2CM3.dll("Cortex-M3 J-LINK") TDRV8=STLink\ST-LINKIII-KEIL.dll ("ST-Link Debugger") LIC0=EXJEV-PGITZ-RXIUD-8A562-3JTCY-2C6VU [ARMADS] PATH="F:\\STM32MDK\\ARM\\" PATH1="BIN40\"
对于VL53L1x TOF激光测距模块,可以使用STM32的I2C总线进行控制和通信。下面是基于HAL库的VL53L1x驱动代码: ```c #include "vl53l1x.h" // 初始化VL53L1x模块 VL53L1_Error VL53L1x_Init(VL53L1_Dev_t *dev) { VL53L1_Error status = VL53L1_ERROR_NONE; uint8_t sensor_state = 0; uint32_t refSpadCount; uint8_t isApertureSpads; uint8_t VhvSettings; uint8_t PhaseCal; uint32_t sequence_config_timeout_us; // 打开I2C总线 HAL_I2C_MspInit(dev->I2cHandle); // 软复位 status = VL53L1_software_reset(dev); if (status != VL53L1_ERROR_NONE) { return status; } // 等待传感器初始化完成 while (sensor_state == 0) { status = VL53L1_RdByte(dev, 0x010F, &sensor_state); if (status != VL53L1_ERROR_NONE) { return status; } } // 设备版本号 uint8_t version[3]; status = VL53L1_GetVersion(dev, version); if (status != VL53L1_ERROR_NONE) { return status; } // 设备参数配置 status = VL53L1_DataInit(dev); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_StaticInit(dev); if (status != VL53L1_ERROR_NONE) { return status; } // 设备校准 status = VL53L1_PerformRefSpadManagement(dev, &refSpadCount, &isApertureSpads); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_PerformRefCalibration(dev, &VhvSettings, &PhaseCal); if (status != VL53L1_ERROR_NONE) { return status; } // 设备配置 status = VL53L1_SetXTalkCompensationEnable(dev, 0); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_SetOffsetCalibrationDataMicroMeter(dev, 0); if (status != VL53L1_ERROR_NONE) { return status; } // 设备开始测距 status = VL53L1_SetDistanceMode(dev, VL53L1_DISTANCEMODE_LONG); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_SetMeasurementTimingBudgetMicroSeconds(dev, 50000); if (status != VL53L1_ERROR_NONE) { return status; } sequence_config_timeout_us = (uint32_t)VL53L1_calc_timeout_us(dev, 2000); status = VL53L1_SetSequenceStepTimeout(dev, VL53L1_SEQUENCESTEP_TCC, sequence_config_timeout_us); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_SetSequenceStepTimeout(dev, VL53L1_SEQUENCESTEP_DSS, sequence_config_timeout_us); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_SetSequenceStepTimeout(dev, VL53L1_SEQUENCESTEP_MSRC, sequence_config_timeout_us); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_SetSequenceStepTimeout(dev, VL53L1_SEQUENCESTEP_PRE_RANGE, sequence_config_timeout_us); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_SetSequenceStepTimeout(dev, VL53L1_SEQUENCESTEP_FINAL_RANGE, sequence_config_timeout_us); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_SetInterMeasurementPeriodMilliSeconds(dev, 100); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_SetGPIOConfig(dev, 0, VL53L1_GPIOFUNCTIONALITY_NEW_MEASURE_READY, VL53L1_INTERRUPTPOLARITY_LOW); if (status != VL53L1_ERROR_NONE) { return status; } return status; } // 触发一次测量 VL53L1_Error VL53L1x_StartMeasurement(VL53L1_Dev_t *dev) { VL53L1_Error status = VL53L1_ERROR_NONE; uint8_t measurement_mode; uint8_t system_health; uint8_t measurement_device_ready; // 获取测量模式 status = VL53L1_GetMeasurementMode(dev, &measurement_mode); if (status != VL53L1_ERROR_NONE) { return status; } // 如果设备未准备好,则等待 if (measurement_mode == VL53L1_DEVICEREADY_WAIT_FOR_MEASURE_VALID) { status = VL53L1_WaitDeviceBooted(dev); if (status != VL53L1_ERROR_NONE) { return status; } } // 触发测量 status = VL53L1_StartMeasurement(dev); if (status != VL53L1_ERROR_NONE) { return status; } // 等待测量完成 do { status = VL53L1_GetMeasurementDataReady(dev, &measurement_device_ready); if (status != VL53L1_ERROR_NONE) { return status; } status = VL53L1_GetSystemHealth(dev, &system_health); if (status != VL53L1_ERROR_NONE) { return status; } } while ((measurement_device_ready == 0) && (system_health == 0)); return VL53L1_ERROR_NONE; } // 读取测量距离 VL53L1_Error VL53L1x_ReadMeasurement(VL53L1_Dev_t *dev, uint16_t *distance) { VL53L1_Error status = VL53L1_ERROR_NONE; VL53L1_RangingMeasurementData_t ranging_data; // 读取测量数据 status = VL53L1_GetRangingMeasurementData(dev, &ranging_data); if (status != VL53L1_ERROR_NONE) { return status; } // 检查数据是否有效 if (ranging_data.RangeStatus != 0) { return VL53L1_ERROR_RANGE_ERROR; } // 保存测量距离 *distance = ranging_data.RangeMilliMeter; return VL53L1_ERROR_NONE; } ``` 使用时,可以调用VL53L1x_Init()函数初始化设备,然后使用VL53L1x_StartMeasurement()函数触发一次测量,最后使用VL53L1x_ReadMeasurement()函数读取测量距离。需要注意的是,VL53L1x模块的I2C地址为0x29,可以在VL53L1x_Init()函数中进行配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 55
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值