ToF 测距传感器 VL6180 使用踩坑记

VL6180 使用坎坷过程  ...... by  矜辰所致

前言

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

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

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

目录

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

一、 基本介绍

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

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

1.1 测距范围

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

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

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

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:

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<8;i++ )
	{
        MYIIC_CLK_LOW;    //SCL为由低变高,在SCL高的时候去读 SDA的数据
        delay_us(10);
		MYIIC_CLK_HIGH;
        receive<<=1;  //第一次这里还是0,第二次开始每次接收的数据做移动一位,从高位开始接收
        if(MYIIC_DATA_STATE)receive++;   //如果数据为1,++以后就是1,数据为0,不执行就是0; 
		  delay_us(10); 
   }					 
   if (!ack)
        IIC_NAck();//发送nACK
   else
        IIC_Ack(); //发送ACK   
   return receive;
}

// ------------------------------------------------------------------

u8 I2C_Wait_Ack(void)
{
    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<8;t++)  //一个字节8位,一位一位发送
    {              
     MYIIC_CLK_LOW;
		if((txd&0x80)>>7)   //从最高位开始发送,如果是1,发送高电平
			MYIIC_DATA_HIGH;
		else
			MYIIC_DATA_LOW;
		txd<<=1; 	      //SDA处理完毕,此时可以将SCL拉高接受数据,拉高以后延时拉低
		delay_us(10);   //对TEA5767这三个延时都是必须的
		MYIIC_CLK_HIGH;
		delay_us(10); 
		MYIIC_CLK_LOW;	
		delay_us(5);
    }	 
}

i2c.h:

c 复制代码
#ifndef _I2C_H_INCLUDED
#define _I2C_H_INCLUDED

#include "main.h"
#include "Datadef.h"
                         
// ------------------------

// ------------------------
// command's
#define I2C_WRITE             0 
#define I2C_READ              1
#define I2C_ACK               0
#define I2C_NACK              1


void I2C_Start(void);
void I2C_Stop(void);
u8 I2C_Wait_Ack(void);
void IIC_NAck(void);
void IIC_Ack(void);
void I2C_Send_Byte(u8 txd);
u8 I2C_Read_Byte(unsigned char ack);

#endif

引脚宏定义:

c 复制代码
#define MYIIC_CLK_HIGH      HAL_GPIO_WritePin(I2C1_PORT,Pin_SCL,GPIO_PIN_SET) 
#define MYIIC_CLK_LOW  	    HAL_GPIO_WritePin(I2C1_PORT,Pin_SCL,GPIO_PIN_RESET) 

#define MYIIC_DATA_HIGH   HAL_GPIO_WritePin(I2C1_PORT,Pin_SDA,GPIO_PIN_SET) 
#define MYIIC_DATA_LOW    HAL_GPIO_WritePin(I2C1_PORT,Pin_SDA,GPIO_PIN_RESET)
#define MYIIC_DATA_STATE  HAL_GPIO_ReadPin(I2C1_PORT,Pin_SDA)

2.3 us 延时函数

延时函数单独拿出来说,因为在 HAL 库中没有现成的 us 延时函数,需要我们自己实现。

c 复制代码
void delay_us(uint32_t Delay)
{
	uint32_t cnt = Delay * 8;   // 32Mhz ,  Delay * 8 其他主频适当调整
  	uint32_t i = 0;
  	for(i = 0; i < cnt; i++)__NOP();
}

实现的方式呢就是使用 NOP 函数,实际上当初在刚使用这个 us 的时候我都没有太在意这个延时的时间,因为反正能用,但是直到一直 VL6180 传感器到 51 单片机的时候,让我不得不去认真对待这个函数能够延时的时间,具体的可以参考我的上一篇博文:

单片机中的 nop() 延时以及其相关的基础扩展

但是如果大家在 stm32 上用,上述延时是可行的,根据自己的时钟频率适当调整即可,对于时钟频率比较低的单片机来说,需要好好计算,具体原因我在上面这篇博文中已经详细说明。

三、移植问题说明

好的,到目前为止,我们介绍了传感器,介绍了驱动程序,一切看起来都很正常,那我测试的时候也确实都正常。

对于本次项目需求:需要用一款 51 单片机,IO 口电平1.8V ,使用软件 I2C 。

3.1 硬件设计注意事项

硬件上也有需要注意的点,如果使用的是文章开头提到的现成的小模块,用 1.8V 的IO 是无法直接连接的,而且不仅仅是自己需要做一个电平转换,还要去掉小板子上面的电平转换,直接把 VL6180 的 SDA 和 SCL 飞线过来,用自己的电平转换,因为 I2C 这种形式的电平转换是有方向要求的。

具体的可查看我的另外一篇博文 结合实际聊聊电平转换电路(常用电平转换电路总结) 在文中有提到过 I2C 的电平转换电路:

注意好电平,其他地方倒没什么特别的了。

3.2 驱动移植

讲到驱动移植,实际上也没有难点,曾经也是在我以前的文章中,移植过另外一款 I2C 传感器,光照传感器到本次使用的 51 单片机上:BH1750 传感器实战教学 ------ 驱动移植篇

所以也没什么好说的,走正常流程。

上面的 VL6180 传感器驱动都可以原封不动的拷贝过来,只需要实现以下 单片机上的 通用驱动,其实单片机上的通用驱动以前也是实现过的,包括 us 延时,直接使用 nop 。

至于 nop 怎么用,能不能类似 STM32 程序中那样写成函数,请查看上面《2.3 us 延时函数》小结推荐的我的上一篇博文,这里直接告诉大家,不能写成函数,那样需要花费相对更多的时间。

3.3 VL6180 主要问题(DataNotReady 单次读取一直等不到 Ready)

在大大小小遇到不同的问题后,最后来了本次最恶心的问题,程序卡在下面这条语句过不去:

while((VL6180X_ReadByte(...&0x04))

这条语句是在开启一次单次测量后,等待 Data 准备好的一条语句,需要传感器对应的寄存器对应的某一位置位,然后主机就知道已经有数据了,然后就可以直接获取数据:

到底为什么呢? 这里必须水一下,吐槽一下这个传感器的这一点,反正我最后是解决了,但是还是觉得恶心。

尝试解决

最开始的时候,把 nop 数量使用了函数封装:

c 复制代码
void delay_us(uint32 Delay)
{
  uint32 cnt = Delay * 4;   // 32Mhz 8 ,其他频率其他倍数  	  16Mhz慢一点  4
  uint32 i = 0;
  for(i = 0; i < cnt; i++)_nop_();
}

导致 I2C 周期特别慢,于是有了上文 ms 级别的 I2C 通讯:

但是注意,奇怪的是这么慢的周期,和传感器的 I2C 通讯一切正常, 至少前面那么多初始化都是正常的。

我们来看一下 VL6180 手册中给出的 I2C 速度为 400kHz :

那么一般我们都是这么理解的:最大 400Khz ,小于这个周期的也是可以的!(一般都是这么理解,除非传感器又特殊说明不能慢于多少,有些时候有时间要求)

所以我们可以理解为,即便我们 us 延时函数没有写好,也能够正常通行,但是这时间也太慢了,我测试下下来,初始化工作需要消耗 接近 5 s 左右...... 有点离谱,所以我们得改,不用函数封装 nop 了,直接和以前例程一样,直接要多少个写多少个。

c 复制代码
```c
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_();	
}

然后时间回到了正常的 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 语句,如果不成功就会卡主,如下图:

c 复制代码
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 !!!!! 有数据了...... (果然还是时间问题,其实我是一肚子想吐槽的,这玩意画了我那么多时间,真是离谱 !!!)

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

c 复制代码
#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<8;t++)  //一个字节8位,一位一位发送
    {              
    	scl_low();
		if((txd&0x80)>>7)   //从最高位开始发送,如果是1,发送高电平
		{
			sda_high();
		}
		else
		{
			sda_low();
		}
		txd<<=1; 	      //SDA处理完毕,此时可以将SCL拉高接受数据,拉高以后延时拉低
			_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_();
//			_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_();_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_();_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_();_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();	
			_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_();_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_();_nop_();_nop_();
    }	 
}

//省略。。。。。。   


uint8 I2C_Wait_Ack(void)
{
    uint8 ucErrTime=0;
	_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_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
//			_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
    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_();_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_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
//			_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
    while(sda_read())             //低电平为有应答,高电平无应答    
    {
      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 通讯时间只要差不多,在规定的范围内大差不差即可,也或许是因为自己不够谨慎,从一开始就把时序时间调节成一样。

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

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

相关推荐
嵌入式成长家2 个月前
[linux 驱动]i2c总线设备驱动详解与实战
linux驱动·i2c驱动·i2c总线·i2c设备
大牛攻城狮2 个月前
详解si5338 si53xx 设计使用及STM32 iic驱动设计
stm32·i2c驱动·si5338·si53xx·clockbuilder·使用详解·iic驱动
矜辰所致9 个月前
使用 C# 设计ToF测距传感器 VL53L5CX 上位机软件
开发语言·c#·tof传感器·vl53l5cx