两个VL53L0X激光测距传感器的使用
序言
最近在项目中需要用到VL53L0X激光传感器进行测距,于是大概的研究了一下。开始使用的是正点原子的例程,但是项目中要使用到两个,于是乎对其进行了部分更改移植,目前来说在自己画的板子上(STM32F103RCT6)能够正常运行,但是中间也是弄了好几天,目前来看,问题都是出在很多小问题上,因此在这里做个总结。
首先将我使用到的相关参考资料在这里贴出来,有需要的伙伴可以去看一下,这里我就不重复他们资料里面的问题了,只是将我使用过程中所遇到的问题指出来说一下
参考资料
1、一个VL53L0X激光测距:正点原子的Vl53L0X激光测距传感器的例程, 可以自己移步到这里下载:正点原子激光测距传感器
2、四个VL53L0X激光测距:四路Vl53L0X激光测距
问题1:
问题描述:
由于我是结合了这两份资料来进行移植修改的,因此,程序代码多少和这两份代码都有一些差异。首先是我只使用一个VL53L0X来进行测距,发现串口总是打印接口错误
问题原因:
确实就如返回的错误状态所说,接口错误,也就是说,自己画的板子,接口和程序里面的不匹配,导致初始化总是失败,这里的接口错误可能有好几个地方:一是AT24C02的模拟IIC引脚初始化和位带操作这里没有匹配;二是VL53L0X的模拟IIC和片选引脚XSH没有配置对,这里一定要多仔细一点,只要是报接口错误,多半都是要么没有初始化,要么宏定义(位带操作)没有修改,或者是没有开启相应的时钟等
问题2:
问题描述:
在修改了所有的引脚定义以后,初始化成功。但是发现又有一个新的问题,那就是测量数据总是异常,一会儿是7、8,一会儿又跳变到65530等较大的数字;有时候确是120mm以内正常,超过以后就数值异常。
问题原因:
这个问题可能有两个原因,第一个就是没有校准,所以导致测量数据异常,重新进行校准即可;这里还有第二个原因,也是困扰我两天的问题,这里贴出来。前面说过,我是结合了两份代码进行修改的,因此很多地方就是两份代码都用到过,例如这里:
原子的测量函数:
//VL53L0X 单次距离测量函数
//dev:设备I2C参数结构体
//pdata:保存测量数据结构体
VL53L0X_Error vl53l0x_start_single_test(VL53L0X_Dev_t *dev,VL53L0X_RangingMeasurementData_t *pdata,char *buf)
{
VL53L0X_Error status = VL53L0X_ERROR_NONE;
uint8_t RangeStatus;
status = VL53L0X_PerformSingleRangingMeasurement(dev, pdata);//执行单次测距并获取测距测量数据
if(status !=VL53L0X_ERROR_NONE) return status;
RangeStatus = pdata->RangeStatus;//获取当前测量状态
memset(buf,0x00,VL53L0X_MAX_STRING_LENGTH);
VL53L0X_GetRangeStatusString(RangeStatus,buf);//根据测量状态读取状态字符串
Distance_data = pdata->RangeMilliMeter;//保存最近一次测距测量数据
return status;
}
另一份代码的测量函数:
//执行单次测量
uint16_t vl53l0x_start_single_test(VL53L0X_Dev_t *pdev,VL53L0X_RangingMeasurementData_t *pdata)
{
int i = 0, j = 0, sum = 0;
VL53L0X_Error status = VL53L0X_ERROR_NONE;
if(vl53l0x_status != VL53L0X_ERROR_NONE)
return vl53l0x_status;
status = VL53L0X_PerformSingleRangingMeasurement(pdev, pdata); //VL53L0X执行单一测量范围
if(status != VL53L0X_ERROR_NONE) {
printf("error:Call of VL53L0X_PerformSingleRangingMeasurement\n");
return status;
}
for(i = 0; i < 5; i++) //这里的代码估计是想要滤波之类的,但是好像没用,可以直接删除
sum += pdata->RangeMilliMeter;
pdata->RangeMilliMeter = sum / 5;
return pdata->RangeMilliMeter;
}
这里暂时不讨论函数的内容,只看返回值,大家可以发现原子的返回值是状态,而该代码的返回值直接就是测得的距离值。而我的问题正好出在这里,我在原子的代码基础上进行更改,但是返回值改成了测量数据,从而导致我的测量距离只有120mm,大于120mm以后,数据就变成了65530等较大的数字。原因就在返回值这里,原子哥的函数返回值类型是u8类型,而实际的测量数据则是u16的类型,从而导致返回的数据在大于某个数值以后,就将低位给省略了,因此导致错误。所以大家一定要注意函数返回值和实际返回值的数据类型是否匹配。
问题3:
问题描述:
在将上面两个问题都解决以后,将两个VL53L0X都接入板子,发现总是只有最后一个才能正常工作。
问题原因:
这个问题原子哥其实说过。正点原子的VL53L0用户手册上也写明了再次使能时设备地址会恢复为0x52,这是一个坑,一定要注意。
但是由于他的历程只有一个传感器,因此就不存在这个问题,所以大家如果使用多个VL53L0X时,在初始化的时候不能按照原子哥的初始化步骤来,一定要先将IIC和所有的VL53L0X片选引脚XSH拉低,然后逐个开启(拉高)片选引脚XSH,在使能了片选引脚以后,就不能再次将该引脚拉低,否则就只有最后一个才能工作(最后一个是正常初始化,相当于只有一个)
这里贴出主要代码,没有贴出来的部分几乎都是原子的例程代码,如校准和模式设定等函数。想要工程文件的朋友可以直接移步到文末下载
代码:
主函数:
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
Uart1_init(115200); //串口1
LED_Init(); //LED端口初始化
OLED_Init();
VL53L0X_Init(); //vl53l0x初始化
OLED_ShowString(0,0,"Dis[0]:",16);
OLED_ShowString(0,16,"Dis[1]:",16);
OLED_Refresh_Gram();
//死循环
while(1)
{
VL53L0x_GetDistance(); //获取距离值
OLED_ShowNum(80,0,Distancebuff[0],5,16);//显示ASCII字符的码值
OLED_ShowNum(80,16,Distancebuff[1],5,16);//显示ASCII字符的码值
OLED_Refresh_Gram(); //更新显示到OLED
}
}
初始化函数:
void VL53L0X_Init(void)
{
AT24CXX_Init(); //EEPROM的IIC引脚SCL、SDA初始化
while(AT24CXX_Check())
{
OLED_ShowString(0,0,"NO 24C02",16);
OLED_Refresh_Gram();
LED1 =!LED1;
delay_ms(500);
}
VL53L0X_GPIO_init(); //这里一定注意!在引脚初始化时要先将所有的片选失能,然后在初始化时再逐一使能开启!否则其地址就变成了默认值
vl53l0x_init(&vl53l0x_dev0,0); //初始化引脚,地址,设备,EEPROM
vl53l0x_init(&vl53l0x_dev1,1); //初始化引脚,地址,设备,EEPROM
}
//VL53L0X引脚初始化
void VL53L0X_GPIO_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使能GPIOC时钟
//IIC引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50Mhz速度
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_7|GPIO_Pin_8);// 输出高
//片选
//XSH1----PB4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //禁止JTAG,从而PB4可以当做普通IO口
//XSH2----PC9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure);
//失能片选信号
VL53L0X_Xshut1=0; //失能VL53L0X
VL53L0X_Xshut2=0;
delay_ms(20);
}
//初始化vl53l0x
//dev:设备I2C参数结构体
VL53L0X_Error vl53l0x_init(VL53L0X_Dev_t *dev,uint8_t VL53L0X_x_id)
{
VL53L0X_Error Status = VL53L0X_ERROR_NONE; //状态定义为0,没有错误
VL53L0X_Dev_t *pMyDevice = dev; //这里注意是指针,是地址的传递
//对传感器的寄存器内容进行初始化---------------------------------------------
pMyDevice->I2cDevAddr = VL53L0X_Addr;//I2C地址(上电默认0x52)
pMyDevice->comms_type = 1; //I2C通信模式
pMyDevice->comms_speed_khz = 400; //I2C通信速率
//根据传入的ID不同,对不同的传感器进行初始化操作
switch(VL53L0X_x_id)
{
case 0:
VL53L0X_Xshut1=1; //使能片选
delay_ms(20);
vl53l0x_Addr_set(pMyDevice,0x54); //重新设定地址
AT24CXX_Read(50,(u8*)&Vl53l0x_data_1,sizeof(_vl53l0x_adjust));//读取24c02保存的校准数据,若已校准 Vl53l0x_data_1.adjustok==0xAA
if(Vl53l0x_data_1.adjustok==0xAA)//已校准
AjustOK_1=1;
else //没校准
AjustOK_1=0;
break;
case 1:
VL53L0X_Xshut2=1;//这里之所以在初始化第二个传感器的时候没有失能第一个,是因为第一个的地址已经变化了,但是第二个的地址仍然是默认地址
delay_ms(20);
vl53l0x_Addr_set(pMyDevice,0x56);
AT24CXX_Read(100,(u8*)&Vl53l0x_data_2,sizeof(_vl53l0x_adjust));
if(Vl53l0x_data_2.adjustok==0xAA)//已校准
AjustOK_2=1;
else //没校准
AjustOK_2=0;
break;
}
//设置传感器地址、设备初始化、获取设备信息、读取EEPORM数据
//设备上电初始化
Status = VL53L0X_DataInit(pMyDevice);
if(Status!=VL53L0X_ERROR_NONE) goto error;
delay_ms(2);
//获取设备ID信息
Status = VL53L0X_GetDeviceInfo(pMyDevice,&vl53l0x_dev_info);
if(Status!=VL53L0X_ERROR_NONE) goto error;
//校准--在第一次校准以后手动屏蔽
// vl53l0x_adjust(pMyDevice,VL53L0X_x_id);
//模式设定
vl53l0x_set_mode(pMyDevice,0,VL53L0X_x_id);
error:
if(Status!=VL53L0X_ERROR_NONE)
{
print_pal_error(Status);//打印错误信息
return Status;
}
return Status;
}
获取距离值:
//VL53L0X获取距离程序
void VL53L0x_GetDistance(void)
{
Distancebuff[0] = vl53l0x_start_single_test(&vl53l0x_dev0,&vl53l0x_data);
Distancebuff[1] = vl53l0x_start_single_test(&vl53l0x_dev1,&vl53l0x_data);
}
VL53L0X_Error vl53l0x_start_single_test(VL53L0X_Dev_t *dev, VL53L0X_RangingMeasurementData_t *pdata)
{
VL53L0X_Error status = VL53L0X_ERROR_NONE;
uint8_t RangeStatus;
status = VL53L0X_PerformSingleRangingMeasurement(dev, pdata);//执行单次测距并获取测距测量数据
if(status != VL53L0X_ERROR_NONE) return status;
// RangeStatus = pdata->RangeStatus;//获取当前测量状态
// memset(buf, 0x00, VL53L0X_MAX_STRING_LENGTH);
// VL53L0X_GetRangeStatusString(RangeStatus, buf); //根据测量状态读取状态字符串
// printf("State;%i \r\n",RangeStatus);//打印测量状态
return pdata->RangeMilliMeter;
}
工程文件
最后将我的工程代码放出来,大家有积分的可以到这里去下载:
VL53L0X激光测距
没有积分的可以留言发邮箱。主要代码都在画圈这几个文件中。另外,我也是新手,水平有限,欢迎各位朋友留言讨论,大佬轻喷。
注意事项:
1、在实际使用的时候一般不需要原子哥里面的复位函数,可以将其屏蔽;
2、该工程只使用了普通测量模式,需要中断模式的大家自行添加;
3、因为我这里使用的AT24C04,因此在检查有无EEPROM的函数里面大家自己根据自己的板子改一下地址;
4、另外其中一个VL53L0X使用到了PB4引脚,因此这里需要进行引脚重映射;