ToF 测距传感器 VL6180 使用踩坑记(软件 I2C) | 您所在的位置:网站首页 › 有什么好的测距软件 › ToF 测距传感器 VL6180 使用踩坑记(软件 I2C) |
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(不接的话一般正常使用也是没有问题的),因为在另一份官方文档中有这样的建议,如下图:
以上介绍的是芯片的使用原理图,大家如果自己画 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 实验室设备网 版权所有 |