这段时间很忙,一直没时间看邮件,忽略了一些信息,没有及时给有需要的网友发工程代码。其实你们可以找在底下留言的网友,他们留了邮箱,加他们qq或者发邮件给他们,让他们发给你们,这样可以节省你们得时间。 ———2019.7.3
最近学习stm32单片机,用VL53L0X这个传感器进行开发,花了不少时间和精力,写这个博客一个是为了记录自己的学习过程另外一个是感谢网上各位网友的帮助。我一直秉持分享的精神同时取之大众馈之大众。谨用这篇博客感谢各位的帮助。
我以前没学过stm32这次是第一次着手做stm32。关于里面的很多东西我是用Linux的思想来理解的,所以可能会有很多解释不正确,还望大家指出共同进步
VL53L0X最远测距的距离是2m,精度可以是2mm。原理很简单,发射激光到反射物,激光折射回来给接收元件,然后VL53L0X内部计算出时间和距离最后通过stm32单片机打印出来。VL53L0X的通信是基于I2C的,关于I2C协议大家可以去搜索其他的,以后有时间我会写一篇文章,这里就不赘述了。
首先我们知道VL53L0X的设备地址是0x52(初始状态的,这个地址是可以修改的)。然后我们看一下VL53L0X需要接到stm32的引脚。分别是GND,VDD,SDA,SCL,XSHUT(这里不使用GPIO1)。下面是VL53L0X与stm32引脚对应关系:
SDA-->PA2,SCL-->PA3,XSHUT-->PA5(如果需要的话可以做修改,要同时修改vl53l0x_i2c.c,vl53l0x_i2c.h和vl53l0x.c里面对应的引脚)
原理性的东西我暂时不说太多以后有时间会补充,这里注意一下,0x52是写命令的,0x53是读命令的,这个跟I2C的读写命令是有关的。I2C传输中,最后一个bit是0代表四write,最后一个bit是1代表是read。在这里再啰嗦一句,有部分网友的设备地址可能是0x29,为什么呢?如果仔细看他们的I2C读写操作函数就会发现,他们的I2C读写操作是对地址进行了左移一位的操作,0x29左移一位就是0x52大家可以算一下。那读操作呢,在左移一位之后或上1就会变成0x53,那就是读地址了。我用的是0x52,因为I2C操作函数没有左移,但是读地址还是要或上1的,这个大家看代码要注意,下面代码分析会讲。接下来直接看代码了。我只分析关键代码,最后的工程会分享给大家!
if(vl53l0x_init(&vl53l0x_dev)) //vl53l0x初始化
{
printf("VL53L0X_Init Error!!!\r\n");
delay_ms(200);
}
else
{
printf("VL53L0X_Init OK\r\n");
VL53L0X_RdByte(&vl53l0x_dev,0xc0,&data);
printf("register1:0x%x\r\n",data);
printf("vl53l0x_dev.I2cDevAddr = 0x%x\r\n",vl53l0x_dev.I2cDevAddr);
}
if(vl53l0x_set_mode(&vl53l0x_dev,mode)) //配置测量模式
{
printf("Mode Set Error!!!\r\n");
}
else
printf("Mode Set OK!!!\r\n");
while(1)
{
//执行一次测量
Status = vl53l0x_start_single_test(&vl53l0x_dev,&vl53l0x_data,buf);
if(Status==VL53L0X_ERROR_NONE)
printf("d: %4imm\r\n",Distance_data);//打印测量距离
else
printf("Measurement is Error!!!\r\n");
}
上面是main函数的部分代码。首先看vl53l0x_init(&vl53l0x_dev);
VL53L0X_Error vl53l0x_init(VL53L0X_Dev_t *dev)
{
GPIO_InitTypeDef GPIO_InitStructure;
VL53L0X_Error Status = VL53L0X_ERROR_NONE;
VL53L0X_Dev_t *pMyDevice = dev;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // 配置XSHUT引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
pMyDevice->I2cDevAddr = VL53L0X_Addr;//I2C地址,默认0x52,定义成宏了
pMyDevice->comms_type = 1; //I2C通信,0代表spi
pMyDevice->comms_speed_khz = 400; //I2C速率,这个手册上有
VL53L0X_i2c_init();//这个是I2C的初始化,配置SDA和SCL引脚的,自己去看吧
//使能XSHUT引脚
VL53L0X_Xshut=0;
delay_ms(30);
VL53L0X_Xshut=1;
delay_ms(30);
vl53l0x_Addr_set(pMyDevice,0x52);//这里是设置VL53L0X的地址的,但是好像这里没成功。可以自行研究
if(Status!=VL53L0X_ERROR_NONE) goto error;
Status = VL53L0X_DataInit(pMyDevice);//这个是VL53L0X自己的函数,初始化设备的
if(Status!=VL53L0X_ERROR_NONE) goto error;
delay_ms(2);
Status = VL53L0X_GetDeviceInfo(pMyDevice,&vl53l0x_dev_info);//这个是获取设备信息,也是他自己的函数
if(Status!=VL53L0X_ERROR_NONE) goto error;
error:
if(Status!=VL53L0X_ERROR_NONE)
{
print_pal_error(Status);//打印错误信息
return Status;
}
return Status;
}
这个函数就是初始化VL53L0X这个传感器的。首先是引脚,然后设置通信模式和速率,初始化设备和获取设备信息。这里面的修改设备地址其实是没有成功的,可以自己debug进去看,至于为什么没有成功是他里面的一个判断条件有问题,有兴趣可以看看。然后我们看一下vl53l0x_set_mode(&vl53l0x_dev,mode)这个函数
VL53L0X_Error vl53l0x_set_mode(VL53L0X_Dev_t *dev,u8 mode)
{
VL53L0X_Error status = VL53L0X_ERROR_NONE;
uint8_t VhvSettings;
uint8_t PhaseCal;
uint32_t refSpadCount;
uint8_t isApertureSpads;
//vl53l0x_reset(dev);
status = VL53L0X_StaticInit(dev); //设备初始化
if(status!=VL53L0X_ERROR_NONE) goto error;
delay_ms(2);
status = VL53L0X_PerformRefCalibration(dev, &VhvSettings, &PhaseCal);
if(status!=VL53L0X_ERROR_NONE) goto error;
delay_ms(2);
status = VL53L0X_PerformRefSpadManagement(dev, &refSpadCount, &isApertureSpads);
if(status!=VL53L0X_ERROR_NONE) goto error;
delay_ms(2);
}
这个函数有很多内容的,我暂时就讲两个,后面的那些其实都是一下设定,虽然我暂时也没很弄明白,但是我对比了很多代码基本都是这个流程,所以就不具体研究。这里的mode是测量模式分四种,默认,高精度,长距离,高速率。我只测量长距离和默认两种。默认的最远距离是1.25m左右,精度是3mm-1cm,长距离最远可以测距2.1m左右,精度在4cm左右。
上面的两个函数分别介绍一下。这两函数都是官方提供的API
VL53L0X_PerformRefCalibration这个函数呢是Ref校准,有什么作用呢?按照官方的解释
是参考校准,但是参考校准是什么他没说,我也不知道。还说会清除中断。说得我也不懂,但是这个很重要的,不执行参考没办法测量这个函数在测量之前执行的。
VL53L0X_PerformRefSpadManagement,这个函数也是要在测量之前执行的,否则会出错的。
我个人的理解是,这两个函数其实就是启动这个传感器来发射脉冲的,但是我不知道这样理解是否正确
然后基本上就结束了。说说我耗时的地方
u8 VL_IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
VL_SDA_IN();
for(i=0;i<8;i++ )
{
VL_IIC_SCL=0;
delay_us(4);
VL_IIC_SCL=1;
receive<<=1;
if(VL_READ_SDA)receive++;
delay_us(4); //1
}
if (!ack)
VL_IIC_NAck();
else
VL_IIC_Ack();
return receive;
}
就是上面这个函数,这个函数是不正确的。这个是I2C的read函数,后来参考一位网友的代码修改成如下:
u8 VL_IIC_Read_Byte(void)
{
unsigned char i,receive=0;
VL_SDA_IN();//
VL_IIC_SDA = 1;
delay_us(4);
for(i=0;i<8;i++ )
{
receive<<=1;
VL_IIC_SCL=0;
delay_us(4);
VL_IIC_SCL=1;
delay_us(4);
if(VL_READ_SDA)
receive |= 0x01;
delay_us(4); //1
}
VL_IIC_SCL = 0;
return receive;
}
至于这两个差别我在这里就不分析了,大家自行理解。我就是卡在这里花了两个星期才搞定这个传感器。这里面说明,I2C最重要的还是协议实现是否正确,以后写I2C的项目,一定要写好read和write个人推荐第二个read的写法。因为很多单片机都是这样写的。然后第一种写法是一个网友分享的代码里面的,该网友说他是可以执行的,但是在我的环境里没办法执行。所以拿到别人的源码不一定就是可以用的,要根据自己的情况修改,当然不是说别人的代码有错,毕竟别人验证成功了,只能说情况不一样,结果就可能不一样。
最后要做个说明总结:我使用的是stm32f103vet6的CPU,不知道你们修改CPU型号和引脚之后是否能正常执行。如果不行我们再交流。然后我会给出这两份代码的下载链接,都是原作者的(我很注重别人的版权)。另外我会给出我自己结合两位作者的代码。因为只能上传到CSDN的下载,肯定需要C币的,我会把他设置到最低,如果你们有积分就去下,因为有时候我也需要积分去下东西,毕竟不是每个人都很愿意无偿分享。但是如果你像我一样没有积分,在下面给我留言,看到邮件之后我会在24小时之内给你发送我自己的代码。
最后,这篇文章应该还有补充的,里面还有还有很多知识的,而且最近我开始做VL53L1X这个传感器可以达到4m。以后再更新。
参考链接:
https://download.csdn.net/download/loop222/10489957这个我就是基于这个链接的源码做修改的,但是这个源码在我的环境情况下不能直接使用,就是上面那个read函数的问题
https://download.csdn.net/download/lifuran156/9804979我参照这位网友的read函数做了修改,然后就可以执行了。
至于我自己上传的代码今晚上传还没通过审核,所以需要等一下,通过审核之后我会把链接放上来,另外上面两个链接的代码我也有的,如果原作者同意的话我也可以通过邮件发给有需要的网友。
2019.4.24晚于广州
补充:我自己修改的代码,下载链接如下:https://download.csdn.net/download/tiramisu_l/11142368
补充:4m的VL53L1X的激光测距已经可以了,链接如下:https://blog.csdn.net/tiramisu_L/article/details/90729964