ToF 测距传感器 VL6180 使用踩坑记(软件 I2C) 您所在的位置:网站首页 有什么好的测距软件 ToF 测距传感器 VL6180 使用踩坑记(软件 I2C)

ToF 测距传感器 VL6180 使用踩坑记(软件 I2C)

2024-06-16 09:04| 来源: 网络整理| 查看: 265

VL6180 使用坎坷过程 ...... by 矜辰所致 .. 更新测距范围说明 2024.2 前言

最近项目上用到一款测距传感器 VL6180 ,实际网上资料已经很多了,而且都有现成的 Demo ,甚至拿来直接用都可以,实际上在使用 STM32 芯片做测试的时候,参考网上的现成例程,一切看起来都是正常的,但是在移植到项目需要的 51 上的时候,真的是一波三折,问题频出。

上一篇文章写到的 单片机中的 nop 函数 也是因为在移植的过程中遇到了问题,所以特地记录分析了一下,那么本文 主要就来说明一下在移植 VL6180 驱动的过程中遇到的问题,以及如何解决的。(本文的驱动为 软件 I2C 驱动)

我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

目录 前言一、 基本介绍1.1 测距范围1.2 引脚以及原理图 二、驱动程序2.1 VL6180 传感器驱动2.2 I2C 通用驱动2.3 us 延时函数 三、移植问题说明3.1 硬件设计注意事项3.2 驱动移植3.3 VL6180 主要问题(DataNotReady 单次读取一直等不到 Ready)`while((VL6180X_ReadByte(...&0x04)) `尝试解决 3.4 解决问题 结语

一、 基本介绍

传感器描述的基本介绍这里就不多说了,也不是什么新的传感器了,ST 官网和网上资料一大堆,简单来说,就是一款可以 测距离 和 光照强度 的传感器,通讯接口为 I2C 。

参考资料可以直接从 ST 官网下载,本文主要针对他的 测距功能说明。

1.1 测距范围

可以测量的距离手册上是写的为 0 ~ 62 cm 最大(根据测量环境决定)。

个人建议如果你需要测量的是 20cm 以内的范围,传感器可以完美的适配。

如果大于 20cm ,就得斟酌一下,虽然我只需要测量 10 cm 以内的范围,但是发现超过 20 cm 的话准确度没那么高。(也许是我大距离没有认真测试,也许与我的测试环境有关。) 对于可以测量距离更远的传感器, ST 公司也有另一个型号的传感器:VL53L 系列的 ToF 测距传感器,测距可以达数米范围。

更新补充,上面说到超过 20 cm 有问题,并不是传感器本身的问题,而是传感器默认的测距范围就是 ~ 20cm ,如果需要测量到 40cm 或者 60cm ,需要修改配置,具体修改方式可参考我下面这篇文章:

ToF 测距传感器 VL6180 测量范围修改(软件 I2C)

1.2 引脚以及原理图

扯远了,我们回来看看我们的 VL6180 ,主要看看传感器引脚以及如何自己画原理图以及 PCB。

当然,大多人接触到传感器都是现成的小模块,比如某宝买的小板子,使用起来直接给 3.3V GND SCL 和 SDA 接口连接即可,如下图:

在这里插入图片描述

关于上面某宝 VL6180 小模块的说明,模块上的 SCL SDA 是已经做过电平转换的,因为 SCL 和 SDA 是需要上拉到 2.8V ,所以使用小模块我们不管接 3.3V 或者 5V 的 IO,都可以直接连接使用, 但是如果 使用的单片机 为 1.8V 的 IO ,那么小模块不可以直接连接。

我在最开始的时候测试使用的是 STM32 ,也是直接用的现成的模块,但是因为后面移植到 1.8V 的单片机上(其实可以在小板子上面去掉电平转换的 MOS管后飞线使用),所以需要了解引脚以及原理图设计,所以这里还是需要说明一下这一部分,手册中对于引脚的定义如下:

在这里插入图片描述

手册上面推荐的原理图如下:

在这里插入图片描述

上图中红色部分,7脚本来是 NC ,但是建议连接 GND(不接的话一般正常使用也是没有问题的),因为在另一份官方文档中有这样的建议,如下图:

在这里插入图片描述 不仅如此,因为传感器使用的过程中,我遇到了一些问题,在官方的社区看到了 ST的工程师有过类似的回复,如下图:

在这里插入图片描述

以上介绍的是芯片的使用原理图,大家如果自己画 PCB ,直接按照上面的来就行了。

需要说明的是,官方推荐的 上拉是到 2.8V or 1.8V,我测试的时候发现 GPIO0 接 1.8V 不行,需要接2.8V。

然后 SCL 和 SDA ,1.8V 或者 2.8V 都可以,注意如果单片机的 IO 口为 1.8V高电平,那么可以直接连接,如果是 3.3V 或者 5V,就需要做电平转换。

GPIO1 不使用的话就什么都不接, 根据以上说明,那么传感器配上驱动就能正常使用了。

二、驱动程序

不管是自己画的 PCB 还是买的小模块,准备好了硬件后,就得写驱动程序了。

一个 软件 I2C 驱动包括,通用驱动(实现 i2c 起始信号,结束信号,读写功能的驱动) 和 传感器驱动(针对使用的传感器实现不同的数据读写的驱动)。

2.1 VL6180 传感器驱动

文章开头也说了,VL6180 传感器驱动程序实际上网上有现成的,比如博主找到的就是下面博文提供的驱动:

STM32驱动VL6180X测距

驱动不用多说,大家可以直接参考,我也确实在 STM32 上面测试成功了,结果如下图:

在这里插入图片描述

那对于上面推荐文章的驱动,我也是直接拿过来用的,当然,I2C 的通用驱动程序得自己实现,相信大家自己手头也应该有一份,我这里也再次放一遍,这个驱动好像是从以前正点原子那儿看到的一直沿用下来的。

2.2 I2C 通用驱动

i2c.c:

#include "i2c.h" // ------------------------------------------------------------------ void i2c_init(void) { // the SDA and SCL pins are defined as input with pull up enabled // pins are initialized as inputs, ext. pull => SDA and SCL = high } // ------------------------------------------------------------------ // send start sequence (S) void I2C_Start(void) { MYIIC_DATA_HIGH; delay_us(5); MYIIC_CLK_HIGH; delay_us(10); MYIIC_DATA_LOW; delay_us(10); MYIIC_CLK_LOW; //使SCL置低,准备发送或者接受数据 delay_us(10); } // ------------------------------------------------------------------ // send stop sequence (P) void I2C_Stop(void) { MYIIC_DATA_LOW; delay_us(5); MYIIC_CLK_LOW; delay_us(10); MYIIC_CLK_HIGH; delay_us(5); MYIIC_DATA_HIGH; delay_us(10); } // ------------------------------------------------------------------ // returns the ACK or NACK //读1个字节,ack=1时,发送ACK,ack=0,发送nACK u8 I2C_Read_Byte(unsigned char ack) { unsigned char i,receive=0; // MYSDA_IN;//SDA设置为输入 for(i=0;i u8 ucErrTime=0; delay_us(5); MYIIC_DATA_HIGH;delay_us(5); //MCU DATA 置高,外面高就是高,外面低就是低 MYIIC_CLK_HIGH; delay_us(5); //CLK 高电平期间数据有效 while(MYIIC_DATA_STATE) //低电平为有应答,高电平无应答 { ucErrTime++; if(ucErrTime>250) { I2C_Stop(); return 1; } } delay_us(10); MYIIC_CLK_LOW; return 0; } void IIC_Ack(void) { MYIIC_CLK_LOW; //SCL为低,SDA为低,SCL为高,SDA为低,应答低电平有效,SCL为低,产生应答信号 // MYSDA_OUT; MYIIC_DATA_LOW; delay_us(10); MYIIC_CLK_HIGH; delay_us(10); MYIIC_CLK_LOW; delay_us(10); MYIIC_DATA_HIGH; } void IIC_NAck(void) { MYIIC_CLK_LOW; //SCL为低,SDA为高,SCL为高,SCL为低 // MYSDA_OUT; MYIIC_DATA_HIGH; delay_us(10); MYIIC_CLK_HIGH; delay_us(10); MYIIC_CLK_LOW; } //IIC发送一个字节 //返回从机有无应答 //1,有应答 //0,无应答 void I2C_Send_Byte(u8 txd) { u8 t; // MYSDA_OUT; MYIIC_CLK_LOW; //拉低时钟开始数据传输 ,SCL为低,SDA变高或者变低(数据位),SCL变高,SCL变低,期间SDA为1既1,为0既0 for(t=0;t uint32_t cnt = Delay * 8; // 32Mhz , Delay * 8 其他主频适当调整 uint32_t i = 0; for(i = 0; i sda_high(); _nop_(); _nop_();_nop_();_nop_(); _nop_();_nop_(); _nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); scl_high(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); sda_low(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); scl_low(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); }

然后时间回到了正常的 us 级别,可能会比我们还想要的 5us ,10 us 多一点点 20到 30us 左右吧,但是这样就行了把,测试一下 … …

开始了各种怀疑(期间过程做过的事情)

发现不行,没有数据,折腾了好久,用示波器看一看,程序卡在上面那条语句。

怀疑是不是板子坏了? 换了一块小板子,不行! 换回 STM32 ,因为 STM32 是 3.3V 的IO ,所以也不能直接换,要注意电平转换之类的。 换回 STM32 就是正常的! 会不会是电平转换的问题? 1.8V 我不和 2.8V 转换了,我直接自己画一块板子,VL6180 的SDA 和 SCL 直接与 单片机的 IO 口连接,上拉到1.8V 然后找原理图,画了小板子 期间还发现一个问题,GPIO 拉高到1.8V 不行,得拉高到2.8V 等待自己画的板子… … 到了后测试 … … 还是不行,一样的问题等不到数据OK! 然后开始用示波器分析,先把在 STM32 上正常的流程观察一遍,我甚至都做了如下记录:

在这里插入图片描述

在while((VL6180X_ReadByte(VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) & 0x04));这条语句基本上循环几次就可以正常读到状态位 然后回到 51 的板子上面,从读取 ID 到初始化,到初始化,到读取数据前面一步,一条一条对应,都是正常的…… 为了确保初始化的 I2C 通讯成功,我把驱动中的那些写寄存器都加了 while 语句,如果不成功就会卡主,如下图:

if(VL6180X_Read_ID() == VL6180X_DEFAULT_ID) { while(VL6180X_WriteByte(0x0207, 0x01)); while(VL6180X_WriteByte(0x0208, 0x01)); while(VL6180X_WriteByte(0x0096, 0x00)); while(VL6180X_WriteByte(0x0097, 0xfd)); while(VL6180X_WriteByte(0x00e3, 0x00)); ......

但是还是一样的问题,前面所有的通讯都正常,卡在等待数据准备好 当然,因为我一直觉得,I2C 时间多一点少一点问题不大,要不然前面也不能正常通讯啊 ** 为了更加接近 STM32 的周期,我把 51 时间象征性的缩小了,减少了一些 nop ,还是同样的问题(这里是关键!!!! 考虑到过时间的问题,但是还是没有认真对待) ** 折腾了好久……

为什么? 要是有问题,干脆直接通讯不了,为什么前面所有的通讯都正常,直到给你指令等待回应等不到!!!

反正这个问题断断续续折腾了我好久,然后只能网上找找看大家有没有解决办法,实际上在上面推荐的驱动文章下面也有一些小伙伴有类似的疑问,同时在 C 站也有悬赏问题,但是没有答案的。

以至于我去官网把 VL6180 有关的资料基本全部下载看了一遍 ,想去看看有没有传感器使用注意事项,特别说明什么的 = = !:

在这里插入图片描述

然后还去 ST 官方社区 查看了所有关于 VL6180 的问题,确实也有类似的提问,如下图:

在这里插入图片描述

也没有得到可以的答案,最后自己还提问了,当然,最终还是没有什么结果。

3.4 解决问题

在曾经文章中我提到过 ,软件 I2C 通讯,如果出现一些莫名其妙的问题,大概率都是时间问题 !!

比如说,有的传感器在干某件事情的时候,必须需要等待多少时间后才能去读取,甚至有些传感器必须要快点读取吗,必须在某时间内通讯读取(这种我接触到的相对较少)。

在上面尝试解决问题的时候,我确实有把 51 上的 I2C 驱动的时钟周期放短(就是在 I2C 通用驱动中把 nop 的数量减少。),让他达到 us 级别,大概 30us 左右,接近 STM32 (STM32正常看下来是 15us 到 20us 的时钟周期)但是还是有问题,而且除了等待数据那一步,前面所有的通讯都正常!!!!当时也就没想那么多,因为在以前的不管 SHT21 温湿度传感器还是 BH1750 光照传感器,使用起来都是没有问题的,所以也并不认为因为多几个 nop 才导致了这种问题!

但是最后我实在是翻阅了所有资料,都重新做了几块测试板子,都是同样的问题,根本毫无头绪,没有理由的……

最后的最后,我实在没有办法,还是回到时间上面来把,因为在 51 上几个 nop ,几个 nop 修改也确实改过几次,但是都没有成功,我就想着既然 STM32 上面正常,我可不可以改变一下时间,看看他会不会也会出这样的问题,于是我尝试了一下,如下图:

在这里插入图片描述

我把 STM32 驱动上面的 nop 多加了一些,想看看到时是不是时间太长了,就会卡在那一步……

咦…… 出问题了:

在这里插入图片描述

原来真的是时间问题,读取数据那个地方对时间有要求?

而且我还发现,把上面的函数倍数改回8,也不正常,非得断电重启,而且有时候改回 8 断电重启也不正常,我还得改回 6 断电重启,然后再改回 8 就正常…… ,这种问题我不愿意去琢磨,但是我得出结论就是, VL6180 在某些通讯的时候对时间有要求 !!!!

那在 STM32 上重现了 51 上的问题以后,我就知道,还是时间的问题,于是乎,我老老实实的把 51 上的 i2c 驱动里面每一个函数的 nop 进行了处理,因为 STM32 上面是好的,我算着大概的时间,一一对应上去,我也实在是不愿意去一点一点定位到底是哪个地方的 延时有要求,因为太费时间而且没有什么意义,反正我把所有的函数都处理了一遍……

测试…… !!!! finally !!!!! 有数据了…… (果然还是时间问题,其实我是一肚子想吐槽的,这玩意花了我那么多时间,真是离谱 !!!)

最后用示波器看了一下,此时的通讯周期如下,高电平 10us 内:

在这里插入图片描述

下面上几个最后16MHz 的 51 核上修改的通用驱动(全部放出来有点多,我只是表明,我只是在减少 nop ):

#include "i2c.h" void i2c_start(void) { sda_high(); _nop_(); _nop_();_nop_();_nop_(); _nop_();_nop_(); _nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); scl_high(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); sda_low(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); scl_low(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); // _nop_(); _nop_();_nop_(); _nop_();_nop_(); _nop_();_nop_(); } //省略。。。。。。 void I2C_Send_Byte(uint8 txd) { uint8 t; // MYSDA_OUT; scl_low(); //拉低时钟开始数据传输 ,SCL为低,SDA变高或者变低(数据位),SCL变高,SCL变低,期间SDA为1既1,为0既0 for(t=0;t sda_high(); } else { sda_low(); } txd ucErrTime++; if(ucErrTime>250) { I2C_Stop1(); return 1; } } _nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_(); // _nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_(); scl_low(); return 0; } //省略。。。。。。

不想多说了,因为想想也没有太多的意义,一个简单的传感器,一个简单的 I2C 通讯,只是告诉大家,如果大家遇到类似的问题,大概率和我一样是时间问题。

结语

对于本次传感器出现的问题,花了我不少时间,真的是很崩溃。

问题的根本也许是因为自己一直有误区,认为 I2C 通讯时间只要差不多,在规定的范围内大差不差即可,也或许是因为自己不够谨慎,从一开始就把时序时间调节成一样。

也反应一个问题,做东西遇到问题如果不严谨的对待,一个简单的小问题可能会浪费自己大量时间。给自己一个教训,也给大家一个忠告,再简单的事情,也需要认真对待,才能高效的去完成它。

好了,本文就到这里,感谢大家!



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有