IIC(Inter-Integrated Circuit)通信协议,也被称为I2C,是一种由飞利浦(Philips)公司在20世纪80年代开发的串行通信协议。它主要用于近距离、低速的芯片(如传感器、存储器、显示屏等)之间的通信,通过简化PCB板上的连接和通信需求,提高了系统设计的灵活性和便捷性。
一、IIC通信协议的特点
1.1. 简单性
IIC通信协议仅使用两根信号线:数据线(SDA)和时钟线(SCL),这使得它在硬件电路实现上非常简单,数据线(SDA)和时钟线(SCL)外部接一个4.7K的上拉电阻即可。
1.2. 双向传输
数据线(SDA)用于主设备和从设备之间的双向数据传输。这意味着主设备可以向从设备发送控制指令,并接收从设备返回的数据。
1.3. 多从设备支持
IIC支持多个从设备连接到同一条总线上,每个从设备都有独立的地址。主设备通过发送地址字节来确定要与哪个从设备通信,从而实现了多设备之间的数据交换和控制操作。
1.4. 时钟同步
通过时钟线(SCL)的协调,主设备和从设备之间的数据传输是同步的。这种同步机制提高了通信的可靠性,并确保了数据的正确传输。
1.5. 强应答机制
每传输完一个字节的数据,接收端都会发送一个应答信号(ACK或NACK)。ACK表示接收器已经成功地接收了该字节,而NACK则表示接收器接收该字节没有成功。这种应答机制确保了通信过程中能够及时发现并处理错误。
1.6. 通信过程
IIC通信过程通常包括起始信号、设备地址发送、数据传输和停止信号等步骤。在起始信号产生后,主设备发送从设备的地址和读写标志位(最低位为0表示写操作,为1表示读操作)。从设备在收到地址后发送应答信号以确认通信的建立。然后,主设备和从设备之间开始进行数据传输,每传输完一个字节后都会有一个应答信号。最后,当所有数据传输完成后,主设备发送停止信号以结束通信。
1.7. 数据有效性
IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定;只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。这种机制确保了数据的正确性和稳定性。
1.8. 寻址方式
IIC设备通常具有一个唯一的7位或10位地址,用于在通信过程中确定目标设备。主设备通过发送包含设备地址和读写标志的字节来寻址从设备。
二、IIC通信协议
如图为IIC通信协议的时序图。可知,数据线SDA具有两种状态,DATA IN 与 DATA OUT两种,分别对应接收数据与发送数据。该时序图可以拆解为以下6个部分。
2.1起始状态与终止状态
起始状态:意为通信的开始。时钟线SCL高电平时读取SDA的状态电平。当SCL为高电平时,SDA从高电平切换为低电平,此时表明,通信已经开始了。
/**
* 函 数:I2C起始信号
* 参 数:无
* 返 回 值:无
*/
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
终止状态:意为通信的结束,在时钟线SCL为高电平期间,检测到SDA从低电平翻转为高电平。此时表示通信终止。
/**
* 函 数:I2C终止信号
* 参 数:无
* 返 回 值:无
*/
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
2.2发送数据
IIC通信协议中,主机可以对从机发送数据,此时DATA处于发送状态。当时钟线为低电平的时候,SDA数据线按照高位先行的顺序,依次将需要发送的数据放到数据线上,当SCL为高电平时,将SDA的数据发给从机。且SCL为高电平的时候,数据线SDA不允许改变数据。按照此步骤操作八次即为发送一个字节。
/**
* 函 数:I2C发送一个字节
* 参 数:字节
* 返 回 值:无
*/
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
2.3接收数据
对于IIC通信的接收模式下,为主机读取从机发送的数据。接收一个字节:当时钟线SCL为低电平时,从机通过IIC通信协议将数据传输至数据线SDA上,高位先行。将一个数据上传到SDA数据线上后,释放SCL时钟线(即时钟线SCL置高电平),与发送状态一样,SDA在SCL高电平器件,SDA数据不允许变化.只有当SCL为低电平时,SDA才允许改变数据。先后操作八次,即可接收到一个字节的数据。
/**
* 函 数:I2C接收字节
* 参 数:无
* 返 回 值:无
*/
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i,Byte = 0x00;
MyI2C_W_SDA(1);//释放SDA
for( i = 0; i < 8 ; i++)
{
MyI2C_W_SCL(1);//释放SCL
if (MyI2C_R_SDA() == 1)
{
Byte |= (0x80 >> i);
}
MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA
}
return Byte;
}
2.4发送应答与接收应答
由于IIC通信强制应答的的特性,在主机与从机通信的过程中,主机对接收从机发送数据后,主机会对从机进行应答操作。如果SDA数据线在此时为低电平,表明主机对从机应答。如果此时SDA数据线为高电平,则说明未应答。
/**
* 函 数:I2C发送应答
* 参 数:返回状态
* 返 回 值:无
*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
与发送应答类似,IIC通信的接收应答也是这样。当主机通过SDA线发送了一个字节的数据之后,在下一个时钟接收一位数据,判断从机是否应答。接收到数据0表示应答,接收到数据1表示未应答。
/**
* 函 数:I2C接收应答
* 参 数:无
* 返 回 值:应答值
*/
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
三、AT24C02
3.1器件概述
AT24C02的外围电路比较简单,IIC通信部分用两个上拉电阻与VCC相连即可。A0~A2为地址选择位。电路直接将A0~A2接地,即器件地址为1010 0000 (0xA0);
3.2器件读操作
/**
* @brief AT24C02读取一个字节
* @param WordAddress 要读出字节的地址
* @retval 读出的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
MyI2C_Start();
MyI2C_SendByte(AT24C02_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(WordAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(AT24C02_ADDRESS|0x01);
MyI2C_ReceiveAck();
Data=MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();
return Data;
}
/**
* @brief AT24C02指定位置读取指定长度的数据
* @param *ReadBuffer 读取缓存的数组
* @param WordAddress 指定读取数据的位置
* @param size 指定读取数据的大小
* @retval 返回值1为应答成功,返回值0为应答失败
*/
uint8_t AT24C02_ReadOrderByte(uint8_t *ReadBuffer,uint8_t WordAddress, uint8_t size)
{
uint8_t i;
MyI2C_Start();
MyI2C_SendByte(AT24C02_ADDRESS|I2C_WR);
MyI2C_ReceiveAck();
MyI2C_SendByte(WordAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(AT24C02_ADDRESS|I2C_RD);
MyI2C_ReceiveAck();
for(i=0;i<size;i++)
{
ReadBuffer[i] = MyI2C_ReceiveByte();
MyI2C_SendAck(0);
}
MyI2C_Stop();
if(MyI2C_ReceiveAck()==0)
return 1;
else
return 0;
}
3.3器件写操作
/**
* @brief AT24C02写入一个字节
* @param WordAddress 要写入字节的地址
* @param Data 要写入的数据
* @retval 无
*/
void AT24C02_WriteByte(uint8_t WordAddress,uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(AT24C02_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(WordAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}