目录
一、简介
1.概念
IIC协议(I²C,即Inter-Integrated Circuit)是一种广泛用于集成电路之间的低速串行半双工通信协议,同一时间只允许单向通信。它由飞利浦公司在20世纪80年代发明,广泛用于微控制器、传感器、显示器等设备之间的数据传输。
2.特点
- 双线结构:I²C使用两条信号线进行通信,分别是SDA(数据线)和SCL(时钟线)。
- 多主/从结构:I²C支持多主设备和多从设备,这意味着多个主设备可以控制总线并与多个从设备通信。
- 寻址机制:每个连接在I²C总线上的设备都有唯一的地址,主设备通过地址选择要通信的从设备。
- 数据传输速率:I²C有标准模式(100 kbps)、快速模式(400 kbps)、高速模式(3.4 Mbps)和超高速模式(5 Mbps)。
- 同步通信:SCL时钟线用于同步数据传输,确保数据在线上传输时能够被正确读取。
3.优缺点
优点:
①简单的双线结构:I²C只需要两条信号线(SDA和SCL),极大地简化了电路设计和布线,减少了引脚的使用。
②多主多从架构:I²C支持多主多从设备,可以在同一总线上连接多个主设备和从设备,增加了系统的灵活性。
③寻址机制:每个设备都有唯一的地址,主设备可以通过地址选择要通信的从设备,实现设备之间的互操作性。
④同步通信:使用时钟线(SCL)同步数据传输,确保数据传输的可靠性。
⑤支持多种速度模式:I²C支持标准模式(100 kbps)、快速模式(400 kbps)、高速模式(3.4 Mbps)和超高速模式(5 Mbps),可以根据应用需求选择合适的传输速率。
缺点:
①速率较低:尽管I²C支持多种速度模式,但相对于其他高速串行通信协议(如SPI、UART),其传输速率仍然较低,限制了高数据速率应用。
②总线阻抗和电容:I²C总线长度和连接设备的数量会影响总线的阻抗和电容,进而影响通信的可靠性,尤其在高速模式下更为明显。
③复杂的冲突检测和仲裁机制:在多主设备的情况下,I²C需要处理总线冲突和仲裁,增加了实现的复杂性。
④短距离通信:I²C通常用于短距离通信,长距离通信可能会受到信号衰减和干扰的影响。
⑤有限的器件数量:由于I²C的寻址机制限制了设备地址数量,通常只能连接有限数量的设备(通常为128个7位地址或1024个10位地址)。
4.应用
①传感器:温度传感器、加速度传感器、气压传感器等。
②存储器:EEPROM、RAM等存储设备。
③显示器:OLED、LCD等显示设备。
④外围设备:如ADC、DAC、时钟芯片等。
二、工作原理
1.物理层
I2C 设备之间常用的连接方式如图1所示
图1
IIC物理层只有两根线一根是主控时序 SCL,另一根是主控数据 SDA,这两根信号线都分别有上拉电阻(此处上拉电阻作用:确保总线信号电平稳定的作用,这样可以避免信号干扰和通信错误的发生),“总线”指多个设备公用的信号线,在一个 I2C 通信总线中,可 连接多个 I2C 通信设备,支持多个通信主机及多个通信从机。
为了区分设备,每个连接到总线的设备都必须有一个唯一的地址(地址通过物理接地或者拉高,可以从 I2C 器件的数据手册得知),主从设备之间通过这个地址来确定与那个器件 进行通信,在通常的应用中,把 CPU 带 I2C 总线接口的模块作为主机设备,把挂载在总线上的其它设备都作为从设备,主机可以通过这个地址进行不同设备之间的访问。当有多个主 机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
这里不得不提一下IIC总线的仲裁了:
I2C 允许多个主设备和多个从设备。如果两个或两个以上的主设备同时向总线上发送启动信号并开始传送数据,这样就形成了冲突。要解决这种冲突,就要进行仲裁的判决,这就是 I2C 总线上的仲裁。
I2C总线上的仲裁分两部分:SCL线的同步和SDA线的仲裁。简单说,它遵循“低电平优先”的原则,即谁先发送低电平谁就会掌握对总线的控制权。
通常我们为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。
2.协议层
(1)信号的产生和介绍
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:起始信号、结束信号和应答信号。
I2C 协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号 作为传输的停止条件。起始和结束信号总是由主设备产生,所有的通信都是主设备发起的, 主可以发出询问的命令,然后等待从设备的通信。
- 起始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
- 结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
- 应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
区分起始信号和结束信号可记住:“谁先拉低谁后拉高”这句话进行区分意思是起始时,SDA 比 SCL 先拉低,停止时,SDA 比 SCL 后拉高。
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他 I2C 器件 无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于 空闲状态。
(2)数据的传输
数据传输以字节为单位。主设备在 SCL 线上产生每个时钟脉冲的过 程中将在 SDA 线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后, 紧接着从设备将拉低 SDA 线,回传给主设备一个应答位, 此时才认为一个字节真正的被传 输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主 设备发送的数据时,从设备将回传一个否定应答位。数据传输的过程如图
(3)地址的指定
在前面我们还提到过,I2C 总线上的每一个设备都对应一个唯一的地址,主从设备之间 的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前要先指定从设备 的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是 7 位 的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0 表示主设备 向从设备写数据,1 表示主设备向从设备读数据。向指定设备发送数据的格式如图所 示:(每一最小包数据由 9bit 组成,8bit 内容+1bit ACK, 如果是地址数据,则 8bit 包含 1bit 方向)。
三、代码编程
以Z20k118为例展示了如何使用i2c传输数据,完整工程在资源中。
1.硬件框图
2.主要代码
①I2C结构体初始化
I2C_Config_t masterConfig =
{
I2C_MASTER, /* 模式选择 */
I2C_SPEED_FAST, /* 速度选择 */
I2C_ADDR_BITS_7, /* 地址传输选择 */
0x0000000F, /* 从设备地址*/
ENABLE /* 作为主机时是否发送RESTART */
};
②配置接受和发送FIFO深度
const I2C_FifoConfig_t fifoConfig =
{
3, /* Receive FIFO threshold level:3 */
3 /* Transmit FIFO threshold level:3 */
};
③定义需要发送的数据
uint8_t txBuffer1[ ] = {0x18,0x11,0x78,0x35,0x41,0x55,0x62};
④定义主机发送缓冲区数据
void master_send_buffer(I2C_Id_t i2cNo, uint8_t * txBuffer, uint16_t len)
{
for(int i = 0; i < len; i++)
{
/* When TX FIFO is not full, the master sends one byte */
while(RESET == I2C_GetStatus(i2cNo, I2C_STATUS_TFNF));
I2C_MasterSendByte(i2cNo,
I2C_RESTART_AND_STOP_DISABLE,*(txBuffer+i));
}
}
⑤主机接收从机的数据
void master_receive_buffer(I2C_Id_t i2cNo, uint16_t len)
{
for(int i = 0; i < len; i++)
{
/* 当TX FIFO未满时,主控发送读取命令 */
while(RESET == I2C_GetStatus(i2cNo, I2C_STATUS_TFNF));
I2C_MasterReadCmd(i2cNo, I2C_RESTART_AND_STOP_DISABLE);
/* 当RX FIFO不为空时,主控接收一个字节 */
while(RESET == I2C_GetStatus(i2cNo, I2C_STATUS_RFNE));
I2C_ReceiveByte(i2cNo);
}
}
⑥初始化,接收和发送函数调用
/* 结构体初始化 */
I2C_Init(I2C1_ID,&masterConfig);
/* FIFO深度初始化 */
I2C_FIFOConfig(I2C1_ID, &fifoConfig);
/* 选择从机地址初始化 */
I2C_SetTargetAddr(I2C1_ID, 0x0F);
/* 使能I2C */
I2C_Enable(I2C1_ID);
/* 主机发送数据 */
master_send_buffer(I2C1_ID, txBuffer1,7);
/* 主机接收数据 */
master_receive_buffer(I2C1_ID, 22);
IIC感觉不难,要写的不多,后续想起来再继续写吧。