一、I2C协议简介
I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地 使用在系统内多个集成电路(IC)间的通讯。
关于I2C协议的更多内容,可阅读《I2C总线协议》,本博文主要分析I2C波形图,对于I2C的基础知识不在做介绍。
二、I2C协议标准代码
2.1 起始信号&停止信号
起始信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换。
停止信号:当 SCL 线是高电平时 SDA 线由低电平向高电平切换。
2.1.1 起始信号代码
1 void I2C_Start(void) 2 { 3 I2C_SDA_High(); //SDA=1 4 I2C_SCL_High(); //SCL=1 5 I2C_Delay(); 6 I2C_SDA_Low(); 7 I2C_Delay(); 8 I2C_SCL_Low(); 9 I2C_Delay(); 10 }
2.1.2 停止信号代码
1 void I2C_Stop(void) 2 { 3 I2C_SDA_Low(); 4 I2C_SCL_High(); 5 I2C_Delay(); 6 I2C_SDA_High(); 7 I2C_Delay(); 8 }
2.2 发送一个字节
CPU向I2C总线设备发送一个字节(8bit)数据
1 u8 I2C_SendByte(uint8_t Byte) 2 { 3 uint8_t i; 4 5 /* 先发送高位字节 */ 6 for(i = 0 ; i < 8 ; i++) 7 { 8 if(Byte & 0x80) 9 { 10 I2C_SDA_High(); 11 } 12 else 13 { 14 I2C_SDA_Low(); 15 } 16 I2C_Delay(); 17 I2C_SCL_High(); 18 I2C_Delay(); 19 I2C_SCL_Low(); 20 I2C_Delay(); 21 22 if(i == 7) 23 { 24 I2C_SDA_High(); /* 释放SDA总线 */ 25 } 26 Byte <<= 1; /* 左移一位 */ 27 28 I2C_Delay(); 29 } 30 }
2.3 读取一个字节
CPU从I2C总线设备上读取一个字节(8bit数据)
1 u8 I2C_ReadByte(void) 2 { 3 uint8_t i; 4 uint8_t value; 5 6 /* 先读取最高位即bit7 */ 7 value = 0; 8 for(i = 0 ; i < 8 ; i++) 9 { 10 value <<= 1; 11 I2C_SCL_High(); 12 I2C_Delay(); 13 if(I2C_SDA_READ()) 14 { 15 value++; 16 } 17 I2C_SCL_Low(); 18 I2C_Delay(); 19 } 20 21 return value; 22 }
2.4 应答
2.4.1 CPU产生一个ACK信号
1 void I2C_Ack(void) 2 { 3 I2C_SDA_Low(); 4 I2C_Delay(); 5 I2C_SCL_High(); 6 I2C_Delay(); 7 I2C_SCL_Low(); 8 I2C_Delay(); 9 10 I2C_SDA_High(); 11 }
2.4.2 CPU产生一个非ACK信号
1 void I2C_NoAck(void) 2 { 3 I2C_SDA_High(); 4 I2C_Delay(); 5 I2C_SCL_High(); 6 I2C_Delay(); 7 I2C_SCL_Low(); 8 I2C_Delay(); 9 }
2.4.3 CPU产生一个时钟,并读取器件的ACK应答信号
1 uint8_t I2C_WaitToAck(void) 2 { 3 uint8_t redata; 4 5 I2C_SDA_High(); 6 I2C_Delay(); 7 I2C_SCL_High(); 8 I2C_Delay(); 9 10 if(I2C_SDA_READ()) 11 { 12 redata = 1; 13 } 14 else 15 { 16 redata = 0; 17 } 18 I2C_SCL_Low(); 19 I2C_Delay(); 20 21 return redata; 22 }
三、I2C通信时序图解析
有了上边的I2C总线标准代码的基础,下面我们进入本博文所要讲解的内容,怎么分析I2C的时序图,以O2Micro的OZ9350为例,OZ9350是一款模拟前端(AFE)的IC器件。是一款性价比不错的电源管理芯片,由于其通讯是通过I2C来进行通讯的,所以这里用OZ9350的I2C通讯做例子进行讲解。
3.1 写数据
首先我们先来看一下写数据的时序图,如下图所示:
将上图中的写数据时序图进行分解,经分解后如下图所示:
结合I2C总线协议的知识,我们可以知道OZ9350的I2C写数据由一下10个步骤组成。
第一步,发送一个起始信号。
第二步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第四步,发送寄存器地址,8bit数据。
第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第六步,发送一个数据,8bit数据。
第七步,产生一个ACK应答信号,此应答信号为从机器件产生的应答信号。
第八步,发送一个CRC校验码,此CRC校验值为2、4、6步数据产生的校验码。
第九步,既可以发送一个应答信号,也可以发送一个无应答信号,均有从机器件产生。
第十步,发送一个停止信号。
接下来,按照以上是个步骤,可以写出OZ9350的i2c写数据的函数。代码如下:
1 u8 I2C_WriteBytes(void) 2 { 3 I2C_Start(); //1 4 5 I2C_SendByte(Slaver_Addr | 0); //2 6 I2C_WaitToAck(); //3 7 8 I2C_SendByte(Reg_Addr); //4 9 I2C_WaitToAck(); //5 10 11 I2C_SendByte(data); //6 12 I2C_WaitToAck(); //7 13 14 I2C_SendByte(crc); //8 15 I2C_WaitToAck(); //9 16 17 I2C_Stop(); //10 18 }
3.2 读数据
读数据的时序图如下图所示:
读数据的时序图经分解后如下图所示:
通过分解后的时序图,可以看到OZ9350的读数据由以下13个步骤组成。
第一步,发送一个起始信号。
第二步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第四步,发送寄存器地址。
第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第六步,再次发送一个骑士信号。
第七步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。
第八步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。
第九步,读取一个字节(8bit)的数据。
第十步,产生一个ACK应答信号,此应答信号为CPU产生。
第十一步,读取一个CRC校验码。
第十二步,产生一个NACK信号。此无应答信号由CPU产生。
第十三步,产生一个停止信号。
接下来,由以上分析步骤,可以写出OZ9350的I2C读数据代码。如下所示:
1 u8 I2C_ReadBytes(void) 2 { 3 u8 data; 4 u8 crc; 5 6 I2C_Start(); //1 7 8 I2C_SendByte(Slaver_Addr | 0); //2 9 I2C_WaitToAck(); //3 10 11 I2C_SendByte(Reg_Addr); //4 12 I2C_WaitToAck(); //5 13 14 I2C_Start(); //6 15 16 I2C_SendByte(Slaver_Addr | 1); //7 1-读 17 I2C_WaitToAck(); //8 18 19 data=I2C_ReadByte(); //9 20 21 I2C_Ack(); //10 22 23 crc=I2C_ReadByte(); //11 24 25 I2C_NoAck(); //12 26 27 I2C_Stop(); //13 28 }
四、结语
关于怎样分析I2C通信的时序图,在理解原理的基础上还需要自己多动手练习,只有这样才能熟练掌握。如果在博文中出现错误之处,还望指正。