文章目录
一、概述
I2C总线(Inter-Integrated Circuit),由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。常用于微控制器与外设之间的连接。
二、硬件连接
I2C仅需两根线就可以支持一主多从或者多主连接:
- SCL(Serial Clock Line):串行时钟线,时钟都是由master提供的。
- SDA(Serial Data Line):串行数据线,发数据或者收数据(收发不能同时)。
三、通信协议
I2C是串行传输总线,传输是以8位为单元数据传输的,先传输最高位(MSB),主芯片发出start信号之后,然后发出9个时钟传输数据。
- 空闲状态:SCL和SDA同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
- 开始信号(S):SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
- 结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
- 响应信号(ACK):发送器每发送一个字节,就会在时钟脉冲的第9个脉冲期间释放数据线SDA,然后由接收器反馈一个应答信号给发送器,如果这个应答信号为低电平,就为有效应答位(ACK简称应答位),表示接收器已经成功的接受了了这个字节;如果这个应答信号为高电平,就为非应答位(NACK),一般表示接收器没有成功接受这个字节。具体来说,对于反馈回来的有效应答位(ACK)的要求是:接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在他接受的最后一个字节之后,发送一个NACK信号,来通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。
SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化。如图:
宏定义
#define EEPROM_I2C_SCL_1() GPIO_SetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SCL_PIN) /* SCL = 1 */
#define EEPROM_I2C_SCL_0() GPIO_ResetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SCL_PIN) /* SCL = 0 */
#define EEPROM_I2C_SDA_1() GPIO_SetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN) /* SDA = 1 */
#define EEPROM_I2C_SDA_0() GPIO_ResetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN) /* SDA = 0 */
#define EEPROM_I2C_SDA_READ() GPIO_ReadInputDataBit(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN) /* 读SDA口线状态 */
1、起始、结束
红框处分别为起始信号和停止信号。
(1)起始信号的产生过程
保持SCL和SDA为高电平,SDA产生一个下降沿,然后SCL拉低,一个START信号产生。
void i2c_Start(void)
{
EEPROM_I2C_SCL_1();
EEPROM_I2C_SDA_1();
i2c_Delay();
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
(3)停止信号的产生过程
保持SCL为高电平,SDA为低电平,SDA产生一个上升沿,一个STOP信号产生。
void i2c_Stop(void)
{
EEPROM_I2C_SCL_1();
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SDA_1();
}
2、发送
刚开始主芯片要发出一个start信号,然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)。回应(用来确定这个设备是否存在),然后就可以传输数据,传输数据之后,要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。每传输一个数据,接受方都会有一个回应信号,数据发送完之后,主芯片就会发送一个停止信号。
(1)发送信号的产生过程
一个CLK对应发送一个bit,先将SCL拉低,然后准备好要发送的数据,然后将SCL拉高,产生一个CLK时钟脉冲,数据就能直接发往从机;其中(ucByte & 0x80)表示从高位(MSB)开始发送直到最低位(LSB),每发送完一个位后ucByte左移一位,确保每一位的数据都能正确发送,最后将SCL重新拉低。
void i2c_SendByte(uint8_t ucByte)
{
uint8_t i;
EEPROM_I2C_SCL_0(); /* 首先拉低 */
for (i = 0; i < 8; i++)
{
i2c_Delay();
if ((ucByte & 0x80) >> 7) /* 准备数据 */
{
EEPROM_I2C_SDA_1();
}
else
{
EEPROM_I2C_SDA_0();
}
EEPROM_I2C_SCL_1(); /* 产生一个CLK时钟脉冲, 发送数据 */
i2c_Delay();
ucByte <<= 1; /* 左移一个bit */
EEPROM_I2C_SCL_0(); /* 重新拉低 */
i2c_Delay();
}
}
3、接收
刚开始主芯片要发出一个start信号,然后发出一个设备地址(用来确定是从哪一个芯片读取数据),方向(读/写,0表示写,1表示读)。回应(用来确定这个设备是否存在),然后就可以传输数据,传输数据之后,要有一个回应信号(确定数据是否接受完成),然后在传输下一个数据。每传输一个数据,接受方都会有一个回应信号,数据发送完之后,主芯片就会发送一个停止信号。
(1)接收信号的产生过程
一个CLK对应发送一个bit,先将SCL拉低,然后先进行数据为的左移,此时由从机发来的数据已经准备好发往主机了,或者说已经到主机家门口了,主机只要将SCL拉高,产生一个SCK信号即一个时钟脉冲,就可以接收到一个bit,然后用EEPROM_I2C_SDA_READ()读取这个bit,bit == 1则加1,bit ==0就不做操作。每次循环都是先左移再接收数据,这种顺序,在第一次左移时,对数据没有影响;在之后的每次循环,可以保证每个bit写入正确的位置,防止数据位错位。等循环结束后,将数据取出,就完成了一次8bit数据的读取。
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value = 0;
EEPROM_I2C_SCL_0(); /* 首先拉低 */
for (i = 0; i < 8; i++)
{
i2c_Delay();
value <<= 1;
EEPROM_I2C_SCL_1(); /* 产生一个SCK时钟脉冲, 准备接收数据 */
i2c_Delay();
if (EEPROM_I2C_SDA_READ())
{
value++;
}
EEPROM_I2C_SCL_0(); /* 重新拉低 */
i2c_Delay();
}
return value;
}
4、ACK、NACK、读ACK
(1)ACK信号的产生过程
在大于一个时钟脉冲的时间保持SDA为低电平。
void i2c_Ack(void)
{
EEPROM_I2C_SDA_0(); /* CPU驱动SDA = 0 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
}
(2)NACK信号的产生过程
在大于一个时钟脉冲的时间保持SDA为高电平。
void i2c_NAck(void)
{
EEPROM_I2C_SDA_1(); /* CPU驱动SDA = 1 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
(3)读取ACK信号的产生过程
在一个时钟脉冲下完成的。在产生时钟脉冲前,要释放SDA总线,即将SDA拉高。(因为SDA返回0表示正确应答,返回1则表示无IIC设备响应,所以要先保证它在未响应状态),然后给一个时钟脉冲,并判断SDA的值,SDA返回0表示正确应答,返回1则表示无I2C设备响应。
uint8_t i2c_WaitAck(void)
{
uint8_t re;
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
i2c_Delay();
if (EEPROM_I2C_SDA_READ()) /* CPU读取SDA口线状态 */
{
re = 1;
}
else
{
re = 0;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
return re;
}
5、检测I2C设备是否存在
要检测I2C设备是否存在,只需要向设备发送设备地址,然后读取设备是否返回应答信号即可。返回值 0 表示正确即对应地址的设备存在, 返回1表示未探测到I2C设备。
uint8_t i2c_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
i2c_Start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(_Address | EEPROM_I2C_WR);
ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
i2c_Stop(); /* 发送停止信号 */
i2c_NAck(); /*若输入的是读地址,需要产生非应答信号*/
return ucAck;
}
6、发送多个8bit的数据
总体的一个过程:
- 主机发送start信号
- 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读
- 从机返回ACK响应信号
- 主机发送要给从机写入数据的地址(有的设备不用)
- 从机返回ACK响应信号
- 主机发送数据
- 从机返回ACK响应信号
- 重复第6和7步,直到从机返回一个NACK非响应信号
- 主机发送停止信号,结束数据传输
void i2c_Send2Byte(uint8_t slaveAddr, uint8_t regAddr, uint16_t ucByte)
{
uint8_t temp = 0;
i2c_Start(); /* 1.发送start信号 */
i2c_SendByte(slaveAddr); /* 2.发送从机地址 */
i2c_WaitAck(); /* 3.等待从机返回ACK响应信号 */
i2c_SendByte(regAddr); /* 4.发送从机写入数据的地址 */
i2c_WaitAck(); /* 5.等待从机返回ACK响应信号 */
temp = (uint8_t)(data >> 8);
i2c_SendByte(temp); /* 6.发送数据 */
i2c_WaitAck(); /* 7.等待从机返回ACK响应信号 */
/* 重复第6和第7步 */
temp = (uint8_t)(data & 0x00FF);
i2c_SendByte(temp); /* 发送数据 */
i2c_WaitAck(); /* 等待从机返回ACK响应信号 */
i2c_Stop(); /* 9.最后发送停止信号, 结束数据传输 */
}
7、读取多个8bit的数据
总体的一个过程:
- 主机发送start信号
- 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读
- 从机返回ACK响应信号
- 主机接收数据
- 向从机发送ACK响应信号
- 重复第4和5步,最后向从机发送一个NACK非响应信号
- 主机发送停止信号,结束数据传输
uint16_t i2c_Read2Byte(uint8_t slaveAddr)
{
uint16_t temp = 0;
i2c_Start(); /* 1.发送start信号 */
i2c_SendByte(slaveAddr); /* 2.发送从机地址 */
i2c_WaitAck(); /* 3.等待从机返回ACK响应信号 */
temp = i2c_ReadByte(); /* 4.接收数据 */
i2c_Ack(); /* 5.向从机发送响应信号 */
/* 重复第4和第5步 */
temp <<= 8;
temp |= i2c_ReadByte(); /* 接收数据 */
i2c_NAck(); /* 向从机发送非响应信号 */
i2c_Stop(); /* 7.最后发送停止信号, 结束数据传输 */
}
四、总线速度
双向传输总线:
- 标准模式(Standard-mode):速率高达 100 Kbit/s
- 快速模式(Fast-mode):速率高达 400 Kbit/s
- 快速模式+(Fast-mode Plus):速率高达 1 Mbit/s。
- 高速模式(High-speed mode):速率高达 3.4 Mbit/s
单向传输总线:
- 超快速模式(Ultra Fast-mode):速率高达 5 Mbit/s