初学51单片机之I2C总线与E2PROM

news2024/11/16 10:25:11

首先先推荐B站的I2C相关的视频I2C入门第一节-I2C的基本工作原理_哔哩哔哩_bilibili

     看完视频估计就大概知道怎么操作I2C了,他的LCD1602讲的也很不错,把数据建立tsp和数据保持thd,比喻成拍照时候的摆pose和按快门两个过程,感觉还是很形象的。

        数据建立tsp和数据保持thd,这两个参数在描述上就很反直觉。“建立”是数据传输的开头代表摆pose,“保持”是数据传输的结尾代表按快门,而且LCD1602和I2C在thd上不太一样,后续笔者会描述一下原因(是笔者的个人见解)。

在描述I2C之前向分享一下,笔者在写程序的时候遇到的一些错误,其实是抄程序()。

后续会贴出函数。一个是函数声明的时候忘记了分号。如

结果报了一堆错keilkeil软件没有直接指向,漏了分号那句,核对了好久才找到问题。

第二个错误:keil软件没报错,下述函数节选有一个是错的。各位可以找找看,我也是灯下黑看花了,都找不到问题在哪,最后是找源程序,一部分一部分替代后才发现问题在哪里,最后才找到。有些人可能一眼就看出来了,我就奇怪为什么Keil没报错。

void MemToStr(unsigned char *str, unsigned char *src, unsigned char len)
{
	unsigned char tmp;

	while(len--)
	{
		tmp = *src >> 4;
		if(tmp <= 9)
			*str++ = tmp + '0';
		else
			*str++ = tmp - 10 + 'A';
		tmp = *src & 0x0F;
		if(tmp <= 9)
			*str++ = tmp + '0';
		else
			*str++ = tmp - 10 + 'A';
		*str++ = ' ';
		src++;		
	}
}
void MemToStr(unsigned char *str,unsigned char *src,unsigned char len)
{
  unsigned char tmp;
	while(len--);
	{
	  tmp = *src >> 4;    //先取高4位
		 if(tmp <= 9 )        //转换为0-9或A-F
			 *str++ = tmp + '0';
		 else
			 *str++ = tmp - 10 +'A';
		 tmp = *src & 0x0F;  //再取低4位
		 if(tmp <= 9)        //转换为0-9或A-F
			 *str++ = tmp+'0';
		 else
			 *str++ = tmp - 10 + 'A';
		 *str++ = ' ';       //转换完1个字节添加一个空格
		 src++;
		 
	}
	
}

下面的程序是由有误的,while()函数后面加了个分号,也说明大括号成对出现的话,keil软件不会报错,它的存在不需要依赖函数,单个大括号还是会报错的。

转回正题:前面的博文笔者介绍了UART异步串口通信,这篇介绍另外一种通信协议I2C。

UART通信如图:

I2C通信如图:

(注:这个示意图是笔者百度随便找的,好像是站内哪个老兄的图,笔者好像在哪篇看到过,特意声明下)

     UART属于异步通信,比如计算机发送给单片机,计算机只负责把数据通过TXD发送出来即可,接收数据是单片机自己的事情。而I2C属于同步通信,SCL时钟线负责收发双方的时钟节拍,SDA数据线负责传输数据,I2C的发送方和接收方都以SCL这个时钟节拍为基数进行数据的发送和接收。

    在硬件上,I2C总线是由时钟总线SCL和数据总线SDA两条线构成。连接到总线上的所有器件的SCL都连到一起,所有SDA都连到一起。I2C总线是开漏引脚并联的结构,因此外部要添加上拉电阻

       对于开漏电路外部加上拉电阻,就组成了线“与”的关系。总线上线“与”的关系就是说,所有接入的器件保持高电平,那这条线才是高电平,而任何一个器件输出一个低电平,那这条线就会保持低电平,因此可以做到任何一个器件都可以拉低电平,也就是说任何一个器件都可以作为主机。

      虽然说任何一个设备都可以作为主机,但绝大多数情况下都是单片机来做主机,而总线上挂的多个器件,每一个都像电话机一样有自己唯一的地址,在信息传输的过程中,通过唯一的地址就可以正常识别到属于自己的信息,笔者使用的是金沙滩工作室宋老师的板子,他的开发板上就接了两个使用I2C通信的设备,一个是24C02,另一个是PCF8591.

    UART串行通信的时候,知道通信的流程为起始位、数据位、停止位(基础方式)这三部分,同理在I2C中也有起始信号,数据传输和停止信号如下图:

从图上来看,I2C和UART时序流程有相似性,也有一定的区别。

1:UART每个字节中都有1个起始位,8个数据位,1个结束位。UART是先传输数据的低位,I2C刚好相反是先传输字节的高位。

2:I2C分为起始信号、数据传输和停止信号,其中数据传输部分可以一次通信过程传输多个字节,字节数是不受限制的,而每个字节的数据最后也跟了一位,这一位叫做应答位(低电平信号),通常用ACK表示,有点类似UART的停止位,它通常是表达1个字节的数据传递结束的信号,这个数据可以是设备地址信息,设备内存地址信息,设备内存中将要或者已经存储的数据信息等。

3:ACK在“写”与“读”功能中,它的发起方是不同,因此在程序实现上也是不一样的。“写”功能的ACK是由从机发出的,所以这个程序表现是接收这个ACK信号,它意思是告诉主机1个字节数据我已经接收完了因此发了1个ACK。作为主机的单片机在发送完1个字节数据后就要检测这个从机有没有发出ACK,未接收到这个ACK前都不能发送新的数据,否则新发送的信号,从机就无法正确接收产生错误,这和LCD1602的“忙”判断非常的类似。而且在器件地址寻址和器件内存寻址时候都是使用“”这个模式的这和LCD1602一样。因此情景下的ACK是告诉主机,你发的地址信息和我匹配,我来响应你的请求。这两个ACK的区别就是写数据的时候ACK的响应上会花费更长一点的时间,毕竟需要把RAM中数据搬运到"非易失"区。这个时间由手册可知是小于5ms。

“读”功能的ACK是由主机发出的(即我们程序编写的由主机发出从机接收),它在发送完1个字节的数据后,要发1个ACK给从机,当所有的数据都发送完毕时,就不再发送ACK了而是发送NAK(高电平信号NO ACK)。

     这个NAK是主机向从机发送1个高电平信号,即字节传输时钟线第九个高脉冲的高电平过程中,从机在数据线上检测到高电平,从机就关闭允许被“读”这个功能,当然“写”功能其实也是有NAK,未响应其实就是NAK或者来不及响应就检测SDA就可能检测到高电平,区别的是这个信号来自从机本篇主机是51单片机。由于线“与”逻辑,所以ACK的信号必然合适是低电平(因为都是高电平的时候是总线释放状态,换句话说就是你没什么事,就别拉低,拉低了就代表有情况发生)。相较于UART串口通信的停止位是“1”,而ACK的停止位是“0”,作为应答位在使用习惯上ACK是反逻辑的。因此本篇在程序上会再取反,以适应使用习惯。

如图是24C02(串行E2PROM)页写入模式时序图。

4:UART通信虽然用了TXD和RXD两根线,但是实际一次通信中,1条线就可以完成,2条线是把发送和接收分开而已,而I2C每次通信,不管发送还是接收,必须两条线都参与工作才能完成。

然后看一下I2C总线中文文档里面提供的时序图解释:

这是I2C的起始条件和截止条件的时序图,

  • 起始信号:UART通信是从一直持续的高电平出现一个低电平标志起始位,当然我们现在UAR模块化了,因此这个部分在程序上都不再体现,只在传输完1个字节数据后发送1个中断标志位,通过检测标志位来确定下一个步骤。
  • 而I2C通信的起始信号的定义是SCL为高电平期间,SDA由高电平向低电平变化产生一个下降压沿,表示起始信号,如图所示的Start部分。也就是说在程序中要表现出这个下降沿。(插1句笔者在大学期间虽然也大概理解上升沿和下降沿,但是只能理解高低电平作为信号电平,因为没真正看到过或者理解上升沿或者下降压作为触发条件的电路实体,因此对跳变电平总是持有一种忽视的情绪,刚好笔者上篇博文关于51单片机IO输出高电平的强推挽模式只发生在0向1跳变这个逻辑里,展现了跳变电平对电路的控制能力)
  • 停止信号:UART通信的停止位是1位固定的高电平信号,而I2C通信停止信号的定义是SCL为高电平期间,SDA由低电平向高电平变化产生一个上升沿,表示结束信号。如图中Stop部分展示。

I2C数据传输图:

  • 数据传输:首先UART是低位在前,高位在后;而I2C通信是高位在前,低位在后。其次。UART通信数据位是固定长度,波特率分之一,一位一位固定时间发送完毕即可以。而I2C没有固定波特率,但是有时序要求,要求SCL为低电平时,SDA允许变化。也就是说,发送方必须先保持SCL是低电平,才可以改变数据线SDA,输出要发送的当前数据的一位;而当SCL在高电平的时候,SDA绝对不可以变化。因为这个时候,接收方要来读取当前SDA的电平信号是0还是1,因此要保证SDA的稳定。如上图中的每一位数据的变化,都是在SCL的低电平位置。8位数据后边跟着的是一位应答位。

  • 图6红字标的意思
  • 1:应答位信号来自从机
  • 2:字节传输完成,可以发生从机内部中断服务(如果有)
  • 3:如果中断发生,时钟线要保持在地电平(这代表着实体电路中如果有中断发生的可能,那么程序就必须要考虑到这一点让这个中断函数在这个时间段里执行完毕。对中断标志位进行判断,确定没有中断标志位才能把SCL拉高,进行下一个字节的传输)
  • 4:应答信号来自接收者
  • 5:图中的MSB中文意思是,最高有效位。

图7:表达“写”功能时,作为数据发送方和数据接收方的电平逻辑,可以看到在接收数据的时候,数据接收方的SDA要一直保持高电平。它只在接收完一个字节数据(这个可能是地址信息也可能是数据)后发送一个ACK或者NAK,而这个时候,数据发送方的电平信号就要保持在高电平。在时序上字节数据发送完的下一个时钟线高电平信号来临前,双方的SDA的线的电平信号都要提前确立。在这个时钟线的高电平期间,主机会检测SDA的信号,来确定从机是否正确接收字节数据即有没有发送ACK。一般来说数据的发送方就是主机本身。

I2C寻址模式

在发送起始信号后,传输第一个字节数据。这第一个字节数据包括从机的地址和读写功能选择(7为寻址模式)。看一下手册上是怎么说的

本篇作为从机的是Serial E2PROM存储器24C02,然后看一下24C02设备寻址的示意图

以及本案24C02采用的接线模式:

24C02后的02代表的是存储量,02代表的是2K bit即256字节

可以看到24C02的地址的前4位是固定的1010,然后是可编程的地址位A2,A1,A0.本案是直接把它们接地了,那么这个7位地址就是1010000,然后加上最后一位读写位,寻址的时候是使用“写”模式的因此24C02的寻址字节是 1010 0000 == 0x50<<1。

24C02接线过程中还有一个引脚WP,写保护引脚。它接地的时候允许“读写”功能,它接高电平就处在只读状态。这可能是为什么有些机器破解要硬破,它存储的参数处于只读状态,无法软件修改。

再看一下SCL和SDA的上拉电阻

一般来说这个总线上拉电阻RP以及电容都是有电气要求的。如果你能够看懂的话可以去啃手册,如果不懂就使用典型值,如图示的R63,R64 4.7K电阻。

实践一下:我们用程序来寻址24C02,如果是24C02的地址,24C02会发送1个应答位ACK,再发送1个不是24C02的地址,那么我们检测总线就不会找到应答信号即NAK。然后把这个应答结果用液晶显示出来。

看程序:

main.c

# include<reg52.h>
# include<intrins.h>
# define I2CDelay()  {_nop_();_nop_();_nop_();_nop_();}

sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;

bit I2CAddressing(unsigned char addr);//I2C寻址函数,返回值为器件应答值
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x,unsigned char y, unsigned char *str);

void main()
{
  bit ack;
	unsigned char str[10];
	
	InitLcd1602();
	
	ack = I2CAddressing(0x50); //应答位赋值给ACK 0x50 =0101 0000,24C02器件地址
	str[0] = '5';             //7位地址最高位是0,这个值左移1为就是写功能寻址字节
	str[1] = '0';
	str[2] = ':';
	str[3] = (unsigned char )ack + '0';//应答位强制装换为char型并转换为相应字符的ASCII码
	str[4] = '\0';
	LcdShowStr(0,0,str); //显示位置0列0行
	
	ack = I2CAddressing(0x62);
	str[0] = '6';
	str[1] = '2';
	str[2] = ':';
	str[3] = (unsigned char)ack+'0';
	str[4] = '\0';
	LcdShowStr(8,0,str); //显示位置8列0行,其实是第9列
  while(1);
}
/*产生总线起始信号    */
void I2CStart()
{
  I2C_SDA = 1; //首先确保SDA SCL都是高电平
	I2C_SCL = 1;
	I2CDelay();  //维持时间
	I2C_SDA = 0; //先拉低SDA 
	I2CDelay();  //维持时间
	I2C_SCL = 0; //再拉低SCL,此后SDA可以发送数据

}
/*产生总线停止信号   */
void I2CStop()
{
  I2C_SCL = 0;  //首先确保SDA,SCL都是低电平维持一段时间大于等于5us
	I2C_SDA = 0;
	I2CDelay();   //延迟4个机器周期
	 //先拉高SCL并维持5us.  11.0592M晶振1个机器周期的时间大概是1us左右,赋值运算是1个机器周期
	I2C_SCL = 1;
	I2CDelay();
	//在拉高SDA并维持5us
	I2C_SDA = 1; 
	I2CDelay();

}
/* I2C总线写操作,dat为待写入字节,返回值为从机应答的值    */
bit I2CWrite(unsigned char dat)
{
  bit ack;
	unsigned char mask;
	for(mask = 0x80; mask != 0; mask >>= 1)//0x80 = 1000 0000
	{
	  if((mask&dat) == 0) 
			I2C_SDA = 0;  //该处赋值是单片机输出电平信号输出电平信号需要SCL为低电平,该动作在I2CStart已操作
		else
			I2C_SDA = 1;
		I2CDelay();
	//以下这两句是读数据的过程	
		I2C_SCL = 1; 
		I2CDelay();  
		I2C_SCL = 0; //再拉低SCL完成一个周期,拉低SCL是为了下个SDA输出数据,SDA只有在SCL是低电平的时候才能改变电平
	}
  I2C_SDA = 1;//8位数据发送完后,主机释放SDA,以检测从机应答
	I2CDelay();
	I2C_SCL = 1;//拉高SCL
	ack = I2C_SDA;//读取此时SDA的值,即为从机的应答值
	I2CDelay();  //维持4个机器周期
	I2C_SCL = 0;//再拉低SCL完成应答位,并保持住总线
	
	return ack; //返回从机应答值
}

/*I2C寻址函数,即检查地址为addr的器件是否存在,返回值为从器件应答值   */
bit I2CAddressing(unsigned char addr)
{
  bit ack;
	I2CStart();           //产生起始位,即启动一次总线操作 
	ack = I2CWrite(addr << 1); //器件地址需左移一位,因寻址命令的最低位,
	                           //为“写”功能,
	I2CStop();                 //不需要进行后序读写,而直接停止本次总线操作
	return ack;                
	                            //这里如果不习惯可以直接写首地址字节,addr只代表地址不包含读写
                            
}

1602LCD.c

#include<reg52.h>

#define LCD1602_DB P0

sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;

/*等待液晶准备好,“忙”判断   */
void LcdWaitReady()
{
   unsigned char sta;
	
	LCD1602_DB = 0xFF;
	LCD1602_RS = 0;
	LCD1602_RW = 1;
	do{
	   LCD1602_E = 1;
		 sta = LCD1602_DB; //read the status of bit 7 postion
		 LCD1602_E = 0;
	
	  } while(sta & 0x80);// bit 7 equal 1,indicating that LCD is busy.Repeat the detection until it equal 0.
}
/*向LCD1602液晶写入一字节命令,cmd为待写入命令值  */
void LcdWriteCmd(unsigned char cmd)
{
   LcdWaitReady();
	 LCD1602_RS = 0;
	 LCD1602_RW = 0;
	 LCD1602_DB = cmd;
	 //High Pulse operation ,Default state is low level
	 LCD1602_E = 1;
	 LCD1602_E = 0;

}
/*向LCD1602液晶写入一字节数据,dat为待写入数据值  */
void LcdWriteDat(unsigned char dat)
{
  LcdWaitReady();
	LCD1602_RS = 1;
	LCD1602_RW = 0;
	LCD1602_DB = dat;
	//High Pulse operation ,Default state is low level
	LCD1602_E = 1;
	LCD1602_E = 0;
}
/*设置显示RAM的起始地址,亦即光标位置,(x,y) 为对于屏幕上的字符坐标   */
void LcdSetCursor(unsigned char x, unsigned char y)
{
  unsigned char addr;
	
	 if(y == 0)           
		 addr = 0x00 + x;  //The first line adress starts from 0x00;
	 else
	   addr = 0x40 + x;  //The second line adress starts from 0x40;
	LcdWriteCmd(addr|0x80);//this operation is actually adding 0x80 to the addr.
	   	 
}
/*在液晶上显示字符串,(x,y)为对应屏幕上的起始坐标,str为字符指针,len为需要显示的字符长度 */
void LcdShowStr(unsigned char x,unsigned char y, unsigned char *str)
{
  LcdSetCursor(x,y);    //Set the starting position of the cursor
	while(*str != '\0')
	{
	  LcdWriteDat(*str++);// Continuously write len character data
	}

}
/*初始化1602液晶 */
void InitLcd1602()
{
  LcdWriteCmd(0x38);//0x38 = 0011 1000 16*2显示,5*7点阵,8位数据接口

 LcdWriteCmd(0x08);//显示关闭
 LcdWriteCmd(0x01);//清屏

 LcdWriteCmd(0x06);//0x04 = 0000 0100 文字不动,地址自动加1
 LcdWriteCmd(0x0C);//显示器开 ,光标关闭
	

}

该程序,主机发出了两个地址一个是24C02的地址0x50,一个是杜撰地址0x62。然后检测应答信号。看下结果

可以看到地址0x50它接收到了应答位ACK(0),地址0x62没有接收到应答位NAK(1)。

如下图:用逻辑分析仪解析可以看到写入的地址字节0xA0(1010 0000)有ACK ,写入的0xC4字节(1100 0100)检测到的是NAK。特意提醒一下0x50是7位的设备地址,加上最低位读写位0,就是0xA0。

可以看到程序正确运行,结果也符合要求。

就着程序以及时序图和24C02时序图的要求一一对照看是不是都满足。

如下图:

如上图:

fscl:SCL时钟频率,可以看到手册给出的是100KHZ和400KHZ,笔者的24C02是可以工作在这两种频率下,根据注释1如果你的丝印上右下脚印有“D”这个字母,就可以工作在400Kb的模式下。这个值代表着通信速度。一般来说你可以通过这个参数知道高低电平的大概持续时间。也就是说实际程序产生的时序必须小于等于100K的时序参数,(为什么是小于呢?因为超过100K,有可能器件还在读取数据的时候,你时钟线就到了下一个时序,那肯定是不允许的)。也就是说传输1位的周期要大于10us,即平均一下高低电平持续时间内都不短于5us。不过这个一般看具体I2C器件的时序参数就可以。100KHZ是属于低速模式,400KHZ是属于快速模式。由手册可知它们对电源的要求也是不一样的。

按照前文所说的I2C开始通信会产生一个开始信号,看下程序以及怎么描述的

/*产生总线起始信号    */
void I2CStart()
{
  I2C_SDA = 1; //首先确保SDA SCL都是高电平
	I2C_SCL = 1;
	I2CDelay();  //维持时间
	I2C_SDA = 0; //先拉低SDA 
	I2CDelay();  //维持时间
	I2C_SCL = 0; //再拉低SCL,此后SDA可以发送数据

}
  • 首先把SDA和SCL都拉高,维持一段时间再拉低SDA。这个过程就是时序图的tsu.sta

tsu.sta:这个时序范围是SCL的上升沿到SDA的下降沿之间的时间即SDA,SCL都是高电平的持续时间,称之为重复起始条件建立时间(注:重复起始条件和起始条件的时序要求是一样的),它的时间要求是最小值是4.7us,我看了下逻辑分析仪,其实它持续的时间很长。就以第二次寻址为例它持续了385us,

当然我们这个程序控制的延时时间是:

    I2C_SCL = 1;
	I2CDelay();  //维持时间

这两句的时间大概是5us。

  • SDA变为下降沿后,维持一段时间才允许SCL电平由高变低,这个时间就是tHD.STA

tHD.STA 重复起始条件的保持时间在这 个周期后产生第一个时钟脉冲,它最小值是4us。我们看一下程序它延时时间来自

    I2C_SDA = 0; //先拉低SDA 
	I2CDelay();  //维持时间

分析下这个语句,赋值运算是1个机器周期。I2CDelay用来4个_NOP_();是4个机器周期。

一个机器周期在11.0592M的晶振下大概是1us多一点,即总计大概5us。看一下时序图的时间

这个时间是5.46us。没有问题。

这样产生总线其实信号这个过程就结束了。

  • 在SCL被拉低这段时间,SDA是被允许改变的,被拉低后再拉高,这段低电平的时间是tlow

tlow:SCL的低电平周期最小值是4.7us看下程序是怎么实现的。

I2C_SCL = 0;//这句本案是放在起始信号最后一句,但它是属于SCL低电平周期的语句
bit I2CWrite(unsigned char dat)
{
  bit ack;
	unsigned char mask;
	for(mask = 0x80; mask != 0; mask >>= 1)//0x80 = 1000 0000
	{
	  if((mask&dat) == 0) 
			I2C_SDA = 0;  //该处赋值是单片机输出电平信号输出电平信号需要SCL为低电平,该动作在I2CStart已操作
		else
			I2C_SDA = 1;
		I2CDelay();
	//以下这两句是读数据的过程	
		I2C_SCL = 1; //之前语句消耗的时间就是SCL低电平周期的时间

可以看到在实现输出第一个要传输的电平信息后,SCL拉高了。这些语句消耗的时间都是SCL低电平周期时间,从程序结构上来看,SCL拉高前还进行了4个机器周期的延时语言。这编程过程相当的保守了。我们看一下实际中这个时间是多少21.7us。

如果你仔细看的话,它其实有两个tlow,第二个tlow其实是tHD.DAT和tsu.DAT的时间和,而第一个tlow的按照功能来说只是tsu.DAT.因此保守编程的4个机器周期是为了tsu.DAT服务的。

  • tsu.DAT:数据建立时间,它的意思数据建立后要持续一段时间才能拉高时钟线。由手册可知它只有最小时间200ns,本案编程速率是100KB。因此它编程的相当保守。我们再看一下I2C手册上该时间是多少,它的要求也只是最小值250ns,而tHD.DAT它的时间要求是0.
  • 在SCL被重新拉高后,SCL时钟线要持续一段时间,然后再拉低。这段时间就是thigh

thign:SCL高电平周期持续时间是最小是4us看下程序实现

        I2C_SCL = 1; 
		I2CDelay();  

可以看到它的延时时间也应该是5us左右,

可以看到逻辑分析仪采样出来的是5.38us,好的没有问题。

然后我们再看之前提到的一个时序参数,tHD.DAT,

tHD.DAT数据保持时间,它的意思是SCL下降沿后,SDA还要保持一段时间,才允许变化。

然后我们发现手册给出时间竟然是0,不可思议!amazing!!回想一下LCD16202也是有tHD.DAT,而它的时间是10ns(写模式下)。是什么原因造成他们的不同?

答:因为他们的通信协议不一样,如果是这种答案的话未免太笼统了。因为他们的工作逻辑是不一样的,对于I2C器件(24C02)它在高电平的时间就完成了对电平信号的读取,因此下降沿后SDA数据线不需要在保持一段时间。而LCD1602它对电平信号的读取是发生在下降沿后10ns内完成。注意这个过程都发生在“写”模式下。如果以笔者推荐的时序视频描述的话,对于I2C,它摆Pose的时间发生在SCL低电平周期里,按快门的时间发生在SCL高电平周期。而对于LCD1602它摆Pose的时间不仅是在SCL的低电平周期里,它高电平周期里也是处于摆pose的状态,它按快门的时间是发生在下降沿这里,然后这个低电平的持续时间是10ns。看一下LCD1602的时序图,这是笔者觉得这个数值0的来由。(这个结论在LCD1602的博文中笔者有简单的探索,但是对于这个结论是不是百分百正确不保证,只是个人论证,可以认为是学习过程中的阶段探索.而且笔者认为这些“读”“写”功能的流程一般都是由三部分组成,开始-维持/使能-使能结束/结束,因此这个控制时序应是高脉冲或者低脉冲实现,就好比对于 LCD1602读功能,它写功能的使能条件都是高电平,但它对THD2(读)依然是有时序要求的。因此笔者认为THD2(读)是读功能完全关闭的时间。

  • 然后开始数据传输和ACK以及NAK

可以看到SDA线按照规则输出了1010 0000,最后第9九个高电平期间检测到ACK,前文提到这个ACK来自于从机(24C02),我们看一下程序是怎么实现的。

 I2C_SDA = 1;//8位数据发送完后,主机释放SDA,以检测从机应答
	I2CDelay();
	I2C_SCL = 1;//拉高SCL
	ack = I2C_SDA;//读取此时SDA的值,即为从机的应答值
	I2CDelay();  //维持4个机器周期
	I2C_SCL = 0;//再拉低SCL完成应答位,并保持住总线
	
	return ack; //返回从机应答值
}

看下程序逻辑:

1:先拉高SDA,然后维持5发个机器周期

2:再拉高SCL时钟线,立刻读取SDA的电平信息,再维持4个机器周期。

3:最后又保持总线拉低SCL。

4:可以看到在ACK信号是在字节传输的第9个高电平期间读取的。因为ACK是由从机发送的,但是这个ACK信号会维持一段时间还是维持到SCL变成低电平为止?看下图笔者拉长了ACK高电平时间

由二副图可知这个ACK信号持续了整个高电平时间,一旦SCL拉低,总线就立刻处于释放状态,即SDA总线处于高电平。 

5:前文笔者提到对于ACK信号应该要先判断有没有收到在进行下一步操作,而这个程序并没有体现。因为写地址这个过程的ACK反馈是即刻的,目前笔者知道“多字节连续写入”的时候,ACK是需要进行类似LCD1602“忙”判断程序的。这在下篇博文笔者会体现。

6:再看下NAK的时序图,地址0x62是虚假地址因此没有ACK信号,我们看下时序图:

由图可知在第9个高电平期间SDA是维持在高电平的。注:绿点事起始信号,红点是结束信号

  • 然后看下I2C结束信号的编程
void I2CStop()
{
    I2C_SCL = 0;  //首先确保SDA,SCL都是低电平维持一段时间大于等于5us
	I2C_SDA = 0;
	I2CDelay();   //延迟4个机器周期
	 //先拉高SCL并维持5us.  11.0592M晶振1个机器周期的时间大概是1us左右,赋值运算是1个机器周期
	I2C_SCL = 1;
	I2CDelay();
	//在拉高SDA并维持5us
	I2C_SDA = 1; 
	I2CDelay();

}

看下编程过程:

1:先把SCL和SDA都拉低并延长一段时间再把SCL拉高,然后SDA保持一段时间这段时间就是Tsu.STO

  • Tsu.STO停止条件的建立时间,由手册可知它只有最小值4.7us
  • 看下程序实现语句
        I2C_SCL = 1;
    	I2CDelay();

2:SDA电平跳变后,高电平持续时间是tBUF

  • tBUF:停止和启动条件之间的总线空闲时间看一下程序实现
  •    I2C_SDA = 1; 
    	I2CDelay();

这个程序的前三句的确保这个SDA = 0是在SCL是低电平发生的

    I2C_SCL = 0;  //首先确保SDA,SCL都是低电平维持一段时间大于等于5us
	I2C_SDA = 0;
	I2CDelay();   //延迟4个机器周期

到此对于I2C的主要时序功能都做了基本介绍,

     但是还有一个问题要确认即:在字节传输过程中,如果在SCL为高电平的时候,电平信号受到到干扰,有0变1或者由1变0,它会影响最终的结果吗,由前文得知它数据是在高电平只有最小值4.0us,因此读取必然是发生在这4us中。如果我把这个高脉冲再延迟4个机器周期,而在这个4个机器周期中把它的电平信号改动一下,那么最终I2C期间会接受到的数据会是什么?

结果是出错,,而且由于跳变电平发生在高电平期间,因此被I2C器件认为是重复开始或者结束的信号。

看图

修改的程序:

再测验一次另外一种电平跳动。

可以看到它从高电平变成了重复起始条件信号。

由于笔者的24c02是可以工作在400KB下的,事实上所有的延迟语句I2CDelay()删除 其实都不影响工作。

结语:本文描述了I2C 的器件寻址,以及笔者的一些个人的不成熟结论

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2170182.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CentOs-Stream-9 设置静态IP外网访问

CentOs-Stream-9 设置静态IP&#xff0c;实现外网访问。这里面有些需要注意的地方&#xff0c;比如IP网段跟我们的宿主机不一样&#xff0c;需要查看具体的网络适配器网段&#xff0c;这样可以快速实现网络互通&#xff1b;另外它的网络配置文件也是不一样的。网络适配器对应的…

放弃 startActivityForResult,Activity Result API 优雅使用

放弃 startActivityForResult&#xff0c;Activity Result API 优雅使用 Activity Result API 是 androidx 中的一个新 api&#xff0c;旨在替代原有的 startActivityForResult 方法&#xff0c;用于在两个 Activity 或 Fragment 交换数据、获取返回结果。 过去如果 Activity…

了解独享IP的概念及其独特优势

在网络世界中&#xff0c;IP地址是用来识别和定位设备的标识符。独享IP是一种服务模式。使用代理服务器时&#xff0c;用户拥有一个不与其他用户共享的专用独立IP地址。与共享IP相比&#xff0c;独享IP为用户提供了更高的独立性和隐私保护。下面详细介绍独享IP的定义、工作原理…

OJ在线评测系统 后端 代码沙箱原生实现 初始化项目

代码沙箱Java原生实现 之前我们完成了快速的前端页面开发 重点是在后端 历史问题修复 Java原生代码沙箱实现 docker代码沙箱实现 解决历史遗留问题 代码编辑器切换语言失败 监听language属性 动态更改编辑器的语言 我们在这里实现的是一个线程形式的监听 watch(() > …

总结拓展十一:S4 HANA和ECC区别

第一节 S/4 HANA系统简介 SAP系统的产品线 R/1版本——主要财务模块R/3版本——基本实现全模块ECC6.0——2005年推出&#xff08;ECC是2004年推出&#xff09;HANA——数据库产品——属于内存数据库BW on HANA——HANA与数据分析相结合 拓展&#xff1a; 数据库类型&#x…

易盾滑块验证码

前言 这玩意我就搞定get请求和check请求&#xff0c;那个b接口的d参数还是有点问题&#xff0c;还有就是b接口的返回参数怎么用&#xff0c;是不是只是加了cookie我也不确定&#xff0c;所以有高手的话希望可以指导一下。我的虽然能够成功&#xff0c;但是只有前2次成功&#x…

ARM V8 A32常用指令集

文章目录 1. 算术指令1.1 加法命令ADD\ADDS1.2 带进位加法命令ADC\ADCS1.3减法命令SUB\SUBC1.4带借位减法命令SBC\SBCS 2.逻辑运算指令2.1逻辑与指令AND、ANDS2.2位清零指令BIC2.3逻辑或指令ORR\ORRS2.4逻辑异或指令2.5 逻辑左移LSL2.6逻辑右移LSR 3.比较指令3.1直接比较指令CM…

2024年华为杯研究生数学建模竞赛C题 波形机理建模+GBDT 完整文章代码|进阶可视化

2024年华为杯研究生数学建模竞赛C题 波形机理建模GBDT 完整文章代码|进阶可视化 全部问题已经更新完成&#xff0c;可视化图表20余张&#xff0c;代码量千余行&#xff0c;实在累到了… 由于篇幅原因&#xff0c;此处放出部分内容供参考~ 完整内容可以从底部名片的群中获取~ …

vue3监听子组件的生命周期

1.Vue3使用vue&#xff0c;vue2使用hook template:<compG vue:mounted"doSomething"></compG>script://监听子组件生命周期let doSomething (e: any) > {console.log("没有啊11", e);}; 2.打印结果

昇思MindSpore进阶教程--轻量化数据处理

大家好&#xff0c;我是刘明&#xff0c;明志科技创始人&#xff0c;华为昇思MindSpore布道师。 技术上主攻前端开发、鸿蒙开发和AI算法研究。 努力为大家带来持续的技术分享&#xff0c;如果你也喜欢我的文章&#xff0c;就点个关注吧 正文开始 在资源条件允许的情况下&#…

【趣学Python算法100例】数制转换

问题描述 给定一个M进制的数x&#xff0c;实现对x向任意一个非M进制的数的转换。 问题分析 要搞定这道题&#xff0c;关键在于学会不同数制之间的转换&#xff0c;主要是二进制、八进制、十六进制和十进制这几种。理解下面这几个概念非常重要&#xff1a; 基数&#xff1a;…

Go基础学习06-Golang标准库container/list(双向链表)深入讲解;延迟初始化技术;Element;List;Ring

基础介绍 单向链表中的每个节点包含数据和指向下一个节点的指针。其特点是每个节点只知道下一个节点的位置&#xff0c;使得数据只能单向遍历。 示意图如下&#xff1a; 双向链表中的每个节点都包含指向前一个节点和后一个节点的指针。这使得在双向链表中可以从前向后或从后…

Docker仓库搭建

目录 一、Docker Hub 二、私有Registry仓库搭建 1、下载并开启仓库镜像registry 2、Registry加密传输 3、建立一个registry仓库 4、为客户端建立证书 5、测试 6、为仓库建立登录认证 三、Harbor仓库搭建 Docker 仓库&#xff08;Docker Registry&#xff09; 是用于存…

8种数值变量的特征工程技术:利用Sklearn、Numpy和Python将数值转化为预测模型的有效特征

特征工程是机器学习流程中的关键步骤&#xff0c;在此过程中&#xff0c;原始数据被转换为更具意义的特征&#xff0c;以增强模型对数据关系的理解能力。 特征工程通常涉及对现有数据应用转换&#xff0c;以生成或修改数据&#xff0c;这些转换后的数据在机器学习和数据科学的…

书生大模型实战营学习[9] OpenCompass 评测 InternLM-1.8B 实践

准备工作 打开开发机&#xff0c;选择cuda11.7环境&#xff0c;A100选择10%&#xff0c;点击创建&#xff0c;然后进入开发机即可&#xff0c;和之前的操作一样。接下来创建环境&#xff0c;下载必要的依赖包 conda create -n opencompass python3.10 conda install pytorch2…

什么是网络安全自动化以及优势与挑战

目录 网络安全自动化的工作原理 网络安全自动化的好处 增强的安全功能 改善表现和姿势 降低安全成本 简化的安全合规性和审计 更好的端点管理 网络安全自动化的挑战 耗时且容易出错的安全流程 可见性降低&#xff0c;风险和成本增加 合规管理 有用的网络安全自动化…

2024年合肥市职业院校技能大赛(中职组)赛 网络安全 竞赛样题

2024年合肥市职业院校技能大赛(中职组)赛 网络安全 竞赛样题 (总分100分) 培训、环境、资料、考证 公众号&#xff1a;Geek极安云科 网络安全群&#xff1a;624032112 网络系统管理群&#xff1a;223627079 网络建设与运维群&#xff1a;870959784 极安云科专注于技能提升&am…

基于nodejs+vue的旅游管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

Docker启动失败,Failed to start docker,只需三个步骤,看我怎么搞定它!

在项目部署上线的时候 1打开hyper-v虚拟机 怎么打开呢&#xff1f; 命令提示符输入control,点击回车&#xff0c;打开控制面板&#xff0c;打开“程序和功能”&#xff0c;“启用和关闭windows功能”&#xff0c;选择Hyper-v&#xff0c;勾选下面的每一项内容。完成之后又点…

机器学习-模型集成

文章目录 模型集成为什么要集成&#xff1f;模型集成要解决的问题主要的集成思想 Committees多个模型的结果进行融合。BaggingBagging 特点 BoostingAdaBoost算法过程 GBDT负梯度拟合 XGBoostXGBoost 参数通用参数booster 参数学习目标参数 模型保存 模型集成 三个臭皮匠顶一个…