通信协议——I2C总线
I2C总线是一种简洁的双向二进制同步串行通信总线,在设备间建立直接联系以实现数据传输功能;其传输介质包括SCL和SDA两条单向信号线,并可实现于设备间的通信链路中高效传递数据
其中SCL作为时钟线路,在其调控下的相应时钟信号作用下,在与之连接的数据传输介质上完成信息传递过程;每一个待传送出的数据单元即一个字节必须由8个二进制位构成,并在每个字节之后附加一个响应位来确认数据的有效性;这种信息传输的方式是以每一位二进制形式依次发送的方式进行操作的,在这一过程中最先发送的是最高有效位的数据内容。
一、I2C时序图及程序
1、起始信号和终止信号

在时序图中可以看到,在完成对SDA和SCL的上抬操作后会紧接着进行下压操作,在完成对SDA的下压操作之后通常该START信号就会停止下来了。不过为了后面发送字节以及读取字节等操作的便捷,在完成对SDA的下压操作后会适当延长一段时间后再进行对SCL的操作
终止状态信号通过以下步骤实现:首先降低SDA值;随后提升SCL值;最后再次提升SDA值以完成终止状态信号的发送。
具体程序可参考如下:
[plain] [view plain]( "view plain") copy
- /*******************************************************************************
-
- 函 数 名 : I2C_Start()
-
- 函数功能 : 起始信号:在I2C_SCL时钟信号在高电平期间I2C_SDA信号产生一个下降沿
-
- 输 入 : 无
-
- 输 出 : 无
-
- 备 注 : 起始之后I2C_SDA和I2C_SCL都为0
- *******************************************************************************/
- void I2C_Start()
- {
- I2C_SDA = 1;
- I2C_SCL = 1;
- I2C_Delay_us(5);//建立时间是I2C_SDA保持时间>4.7us
- I2C_SDA = 0;
- I2C_Delay_us(4);//保持时间是>4us
- I2C_SCL = 0;
- }
- /*******************************************************************************
-
- 函 数 名 : I2C_Stop()
-
- 函数功能 : 终止信号:在I2C_SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿
-
- 输 入 : 无
-
- 输 出 : 无
-
- 备 注 : 结束之后保持I2C_SDA和I2C_SCL都为1;表示总线空闲
- *******************************************************************************/
- void I2C_Stop()
- {
- I2C_SDA = 0;
- I2C_Delay_us(4);
- I2C_SCL = 1;
- I2C_SDA = 1;
- }
2.I2C发送字节和读字节

根据时序图可以看出,在SCL处于低电平时段里,SDA进入了数据转换阶段,该阶段的数据状态呈现不稳定且难以确定的状态;一旦SCL达到高电水平,SDA将稳定接收数据,其中所接收的数据可能为高电水平或低电水平
随后将SCL下降到指定位点,并根据要发送字节当前正在处理的某一位的高低状态进行相应的值传递至SDA;完成该过程后,则提升SCL至高位以完成数据传输操作。
在读取字节时,请注意取决于SDA线上是高电平还是低电平的情况;因此必须先拉高SCL以便正确获取数据;随后需根据SDA上的数据确定其高低,并将其赋值至对应的变量;最后必须将SCL归位至低电平状态以完成操作流程。
具体程序可参考如下:
[plain] view plain copy
- /*******************************************************************************
-
- 函 数 名 : I2C_SendByte(unsigned char byt)
-
- 函数功能 : 发送一个字节
-
- 输 入 : byt
-
- 输 出 : 无
-
- 备 注 : 无
- *******************************************************************************/
- void I2C_SendByte(unsigned char byt)
- {
- unsigned char i;
- I2C_SCL = 0;
- for(i = 0;i < 8;i++)
- {
- if(byt&0x80) //从最高位开始判断发送的字节byt是0还是1来判断I2C_SDA是0还是1
- {
- I2C_SDA = 1;
- }
- else
- {
- I2C_SDA = 0;
- }
- byt <<= 1; //判断完一位后,那么要左移一位,让下一位继续判断,一个字节8位,所以一共是要判断8次
- delay_us(2);
- I2C_SCL = 1;
- delay_us(2);
- I2C_SCL = 0;
- }
- I2C_SCL = 0;
- }
- /*******************************************************************************
-
- 函 数 名 : unsigned char IIC_Read_Byte(unsigned char ack)
-
- 函数功能 : 读一个字节
-
- 输 入 : ack(0或1)
-
- 输 出 : 读到的一个字节dat
-
- 备 注 : 无
- *******************************************************************************/
- unsigned char IIC_Read_Byte(unsigned char ack)
- {
- unsigned char i,dat = 0;
- I2C_SCL = 0;
- for(i = 0;i < 8;i++ )
- {
- IIC_SCL = 1;
- dat <<= 1;
- if(I2C_SDA) //根据I2C_SDA的高低来一位一位赋值给dat
- {
- dat |= 0x01;
- }
- delay_us(1);
- I2C_SCL = 0;
- }
- IIC_Ack(ack); //如果只接收1个字节的数据,则发送非应答信号1,
- //然后产生stop信号,告诉从机单片机停止接收数据,也就是不用再发了
- //如果要接收多个字节数据,则接收完一个字节数据后要发送应答信号0,
- //告诉从机要继续发给单片机
- return dat;
- }
3.I2C主机等待应答和产生应答

等待响应与生成响应类似于读取字节与写入字节之间存在差异。区别在于每个字节由8位二进制数据组成,在这种情况下函数内部执行了8次操作;相比之下,则只需要处理单一位的数据从而无需进行循环运算其实它们的本质是一样的在此不做进一步阐述建议参考相关程序文件以获得更详细的解释
[plain] [view plain]( "view plain") copy
- /*******************************************************************************
-
- 函 数 名 : I2C_Wait_Ack()
-
- 函数功能 : 等待应答:即等待从设备把I2C_SDA拉低
-
- 输 入 : 无
-
- 输 出 : 0或1
-
- 备 注 : 0表示应答发送失败或非应答,1表示接收到应答
- *******************************************************************************/
- unsigned char I2C_Wait_Ack(void)
- {
- unsigned char acktime;
- I2C_SCL = 1;
- delay_us(1);
- while(I2C_SDA) //等待应答,即等待从设备把I2C_SDA拉低
- {
- acktime++;
- if(acktime>200) //如果超过200us没有应答,则发送失败,或者为非应答,表示接受结束
- {
- I2C_SCL = 0;
- delay_us(1);
- return 0;
- }
- }
- delay_us(1);
- I2C_SCL = 0;
- return 1;
- }
- /*******************************************************************************
-
- 函 数 名 : IIC_Ack(unsigned char ackbit)
-
- 函数功能 : 产生应答
-
- 输 入 : 0或1
-
- 输 出 : 无
-
- 备 注 : 0表示产生应答,1表示不产生应答
- *******************************************************************************/
- void IIC_Ack(unsigned char ackbit)
- {
- I2C_SCL = 0;
- I2C_SDA = ackbit;
- delay_us(2);
- I2C_SCL = 1;
- }
二、主从机I2C通信过程:
1.主机发送过程
当主机识别到总线处于空闲状态(SDA和SCL均呈高电平)时
(2)主机随后发送了一个指令字节。该字节包括7位外围器件地址和1个R/W控制位构成(其中R/W设为0)。
(3)相对应的从机收到命令字节后向主机回馈应答信号 ACK(ACK=0)
(4)主机收到从机的应答信号后开始发送第一个字节的数据
(5)从机收到数据后返回一个应答信号 ACK
(6)主机收到应答信号后再发送下一个数据字节
当主机传输最后一个数据字节并接收到从机的ACK响应后, 通过向对方发出一个终止指令P来完成本次通信并释放总线. 当从机接收到该终止指令P后, 将退出与主机的数据传输过程

2.主机接收过程
(1)主机发送启动信号后,接着发送命令字节(其中 R/W=1)
(2)对应的从机收到地址字节后,返回一个应答信号并向主机发送数据
(3)主机收到数据后向从机反馈一个应答信号
(4)从机收到应答信号后再向主机发送下一个数据
当设备完成接收数据后的操作时会向其发出确认信息(ACK=1),该确认信息被响应后相关方将终止后续操作
(6)主机发送非应答信号后,再发送一个停止信号,释放总线结束通信

三、以51单片机为主机,AT24C02为从机的I2C主从机通信程序
/******************************************************************************* * 函 数 名 : write_eeprom(unsigned char add,unsigned char val)
* 函数功能 : 向eeprom写一个字节
* 输 入 : eeprom中某个地址和要写入的数据字节
* 输 出 : 无
* 备 注 : 无
*******************************************************************************/
void write_eeprom(unsigned char add,unsigned char val)
{
I2C_Start();
I2C_SendByte(0xa0);
I2C_Wait_Ack();
I2C_SendByte(add);
I2C_Wait_Ack();
I2C_SendByte(val);
I2C_Wait_Ack();
I2C_Stop();
}
/******************************************************************************* * 函 数 名 : unsigned char read_eeprom(unsigned char add)
* 函数功能 : 从eeprom读一个字节
* 输 入 : eeprom中的某个地址
* 输 出 : 选中地址里面存储的数据
* 备 注 : 无
*******************************************************************************/
unsigned char read_eeprom(unsigned char add)
{
unsigned char da;
I2C_Start();
I2C_SendByte(0xa0);
I2C_Wait_Ack();
I2C_SendByte(add);
I2C_Wait_Ack();
I2C_Start();
I2C_SendByte(0xa1);
I2C_Wait_Ack();
da = IIC_Read_Byte();
I2C_Stop();
return da;
}
