一、 定义
IIC(Inter-Integrated Circuit,是IICBus的简称,中文:集成电路总线),它是一种符合IIC协议的串行通信总线。一般两根线,一根是双向的数据线SDA,另一根也是双向的时钟线SCL,它们都通过一个电流源或上拉电阻连接到正的电源电压。下图为IIC的硬件电路图。
二、 特点
- 通信双方分为主机与从机
IIC总线两端分别为主机与从机,其中SCL信号一般由主机产生,作用是同步通信时间,保证信息传递的完整性与一致性,SDA信号为双向的,可由主机发送至从机,也可由从机发送至主机,但同一设备在某一时刻不可以既是发送者又是接收者,这一特性使得IIC总线实现一根数据线的串行半双工通信。 - 一组IIC总线可以挂载多个设备
总线上可以有多个主机和从机,在数据传输时可通过冲突检测和仲裁防止数据被破坏。仲裁是指主机仲裁,也就是多个主机同时发起始信号,但是从机该听谁的,由主机仲裁机制决定,从机不参与,因为这时的从机不会去控制SDA或SCL。 - 总线上的设备须有一个ID
总线上挂载的主机与从机均需要一个ID号,以便在通信时主机能找到与之通信的另一方,屏蔽其他设备。 - 通信时会有应答信号
接收方会在接收到发送方传递的信息后,返回一个应答信号,发送方接收到后才会继续发消息。
三、 协议构成
IIC协议由空闲信号、起始位、再次起始位、从机地址(ID)、读写方向位、应答信号位(ACK)、片内地址、数据帧和停止位构成,下面分别介绍读和写的协议构成。
- 主机向从机写数据
协议构成如图所示。
空闲信号:空闲期间SDA不被占用为高电平;
起始位:1bit,SCL高电平期间,SDA从高电平切换到低电平,如图;
从机地址:7bits,从机的ID一般为7位,先写入最高位,SDA由主机控制;
读写方向位:1bit,该位为0时为主机向从机写入数据,SDA由主机控制;
应答信号位:1bit,该位为0时表示消息收到(ACK),为1表示无应答(NACK),SDA由从机控制;
片内地址:8bits,用来指定所操作外设的寄存器地址,此时SDA由主机控制,之后需要接收应答位;
数据帧:N Bytes,实际发送的数据,8bits为1个Byte,每发1个Byte数据,需要收1个应答位,发数据时SDA由主机控制,收应答位时SDA由从机控制;
停止位:1bit,SCL高电平期间,SDA从低电平切换到高电平,如图。
-
主机读取从机数据
协议构成如图所示。
空闲信号:空闲期间SDA不被占用为高电平;
起始位:1bit,SCL高电平期间,SDA从高电平切换到低电平,如图;
从机地址:7bits,从机的ID一般为7位,先写入最高位,SDA由主机控制;
读写方向位:1bit,该位为0时为主机向从机写入数据,SDA由主机控制;
应答信号位:1bit,该位为0时表示消息收到(ACK),为1表示无应答(NACK),SDA由从机控制;
片内地址:8bits,用来指定所操作外设的寄存器地址,此时SDA由主机控制,之后需要接收应答位;
再次起始位:1bit,此时SDA由主机控制,逻辑与起始位一致;
再发从机地址和读写方向:8bits,主机再发一次从机ID,读写方向位改为1,即主机读取从机信息;
数据帧:N Bytes,实际接收的数据,每发1个Byte数据,需要收1个应答位,发数据时SDA由从机控制,应答位时SDA由主机控制,最后1个Byte数据接收后主机需要发送无应答信号;
停止位:1bit,SCL高电平期间,SDA从低电平切换到高电平,如图。
四、 使用流程
STM32的寄存器提供了各种标志位,当产生某一事件时特定标志位会有变化,为我们使用该种通信方式提供了便利。
- 主机发送流程分析
如下图所示为主机发送的流程,两行分别表示通信流程与产生事件,通信流程不再赘述,主要解释一下各个事件。
EV5:主机将SDA拉低后将产生主模式选择事件,检测到该事件后,可继续进行后续步骤;
EV6:发送时将产生EV6事件,表示当前是主机发送模式;
EV8:主机传输数据过程中产生EV8,此时SDA被占用;
EV8_2:主机传输数据完成产生EV8_2,该事件作为停止位的前提使用。 - 主机接收流程分析
如图所示为主机接收的流程,该流程由于不包含
EV5:主机将SDA拉低后将产生主模式选择事件,检测到该事件后,可继续进行后续步骤;
EV6:发送时将产生EV6事件,表示当前是主机发送模式;
EV8_2:主机传输数据完成产生EV8_2,该事件作为停止位的前提使用;
EV7:主机接收消息完成后产生该事件,检测到后可发送停止位。
五、 代码分析
void MYIIC_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000; //给定超时计数时间
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) //循环等待指定事件
{
Timeout --; //等待时,计数值自减
if (Timeout == 0) //自减到0后,等待超时
{
/*超时的错误处理代码,可以添加到此处*/
break; //跳出等待,不等了
}
}
}
void MYIIC_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始条件
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6
I2C_SendData(I2C2, RegAddress); //硬件I2C发送寄存器地址
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //等待EV8
I2C_SendData(I2C2, Data); //硬件I2C发送数据
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2
I2C_GenerateSTOP(I2C2, ENABLE); //硬件I2C生成终止条件
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始条件
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6
I2C_SendData(I2C2, RegAddress); //硬件I2C发送寄存器地址
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2
I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成重复起始条件
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver); //硬件I2C发送从机地址,方向为接收
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //等待EV6
I2C_AcknowledgeConfig(I2C2, DISABLE); //在接收最后一个字节之前提前将应答失能
I2C_GenerateSTOP(I2C2, ENABLE); //在接收最后一个字节之前提前申请停止条件
MYIIC_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //等待EV7
Data = I2C_ReceiveData(I2C2); //接收数据寄存器
I2C_AcknowledgeConfig(I2C2, ENABLE); //将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
return Data;
}
void MYIIC_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); //开启I2C2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为复用开漏输出
/*I2C初始化*/
I2C_InitTypeDef I2C_InitStructure; //定义结构体变量
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //模式,选择为I2C模式
I2C_InitStructure.I2C_ClockSpeed = 50000; //时钟速度,选择为50KHz
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比,选择Tlow/Thigh = 2
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //应答,选择使能
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //应答地址,选择7位,从机模式下才有效
I2C_InitStructure.I2C_OwnAddress1 = 0x00; //自身地址,从机模式下才有效
I2C_Init(I2C2, &I2C_InitStructure); //将结构体变量交给I2C_Init,配置I2C2
/*I2C使能*/
I2C_Cmd(I2C2, ENABLE); //使能I2C2,开始运行
}
六、 总结
IIC是单片机通信常用的同步串行半双工通信方式,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。本文从协议构成及使用流程方面结合代码整理了该模块的使用方法。