一、I2C简介
1.I2C总线特点
(1)两线制
I2C只需要SDA、SCL两根线来完成数据的传输和外围器件的扩展,器件地址采用软件寻址方式。
(2)多主机总线
I2C是一个真正的多主机总线,如果2个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据被破坏。每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只能有一个主机。
(3)传输速率
数据传输速率相对SPI不高,串行的8位双向数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。
(4)传输方式
是同步半双工通信机制,总线上传输数据的最小单位是1个字节(8bit),高位先行,每传送完1个字节,接收器必须发送一个应答位。
2.I2C总线术语
- 主机:初始化发送,产生时钟信号和终止发送的器件。
- 从机:被主机寻址的器件。
- 多主机:同时有多于一个主机尝试控制总线,但不破坏信息。
- 仲裁:是在一个有多个主机同时尝试控制总线,但只允许其中一个控制总线并使信息不被破坏的过程。
- 同步:两个或多个器件同步时钟信号的过程。
- 地址:主机用于区分不同从机而分配的地址。
- SDA:I2C通信时用于数据传输的信号线。
- SCL:I2C通信时用于时钟传输的信号线。
3.I2C硬件构成
将所有I2C设备的SDA线连在一起、SCL线连在一起,两线均要配置成开漏输出模式,SDA、SCL各接一个上拉电阻,一般为4.7KΩ左右。
4.位传输
(1)数据有效性
I2C总线以串行方式传输数据,数据传输是按照时钟节拍进行的。协议标准规定,SCL为高电平时,SDA上的数据必须保持不变;SCL为低电平时,SDA的电平状态才能改变。即SCL为高电平时数据有效。
(2)起始信号和停止信号
协议规定,SCL为高电平 时,SDA产生下降沿,表示起始信号,SDA产生上升沿,表示停止信号。
(3)重复开始信号
在I2C总线上,由主机产生一个起始信号后,在产生一个停止信号之前,再产生一个起始信号,称为重复起始信号。
(4)应答信号和非应答信号
协议规定,发送器每发送一个字节数据,接收器必须产生一个应答信号或非应答信号。实现方法是:发送器发送完8位数据后,在第9个时钟信号将SDA线拉高,接收器将SDA拉低,产生一个应答信号,或者保持SDA为高,产生一个非应答信号。
5.数据传输格式
一般情况下,一个标准的I2C通信由4部分组成,起始信号、从机地址传输、数据传输和停止信号,每次通信的数据字节数是没有限制的。
(1)I2C总线寻址约定
I2C采用软件方法实现从机寻址来简化总线连接,协议规定了起始信号后的第1个字节为寻址字节。I2C总线支持7位寻址模式和10位寻址模式。常用的7位寻址模式中,寻址字节由从机的7位地址位(D7~D1)和1位读写位(D0)组成,D0=1为读,D0=0为写。
(2)数据传输模式
主机从从机读N个字节:主机产生一个起始信号,然后发送一个寻址字节,读写位置1表示读,主机拉高SDA。从机检测到与自己相同的地址,产生一个应答信号。之后从机开始发送数据,每发送一个字节数据,主机产生一个应答信号。当数据传输完毕,主机产生一个非应答信号结束数据传输,主机产生一个停止信号结束通信,或者产生一个重复起始信号进入下一次数据传输。
主机向从机写N个字节:主机产生一个起始信号,然后发送一个寻址字节,读写位置0表示写,主机拉高SDA。从机检测到与自己相同的地址,产生一个应答信号。之后主机开始发送数据,每发送一个字节数据,从机产生一个应答信号。当数据传输完毕,从机产生应答或非应答信号均可,此时主机再产生一个停止信号结束通信,或者产生一个重复起始信号进入下一次数据传输。
主机指定地址从从机读N个字节:当主机在访问类似存储器器件(如EEPROM,一种掉电后数据不丢失的存储芯片)时,主机除了发送寻址字节来选择从机外,还要发送从机的存储地址来选择读写的位置。实际上很多器件、模块都有多个寄存器,都要指定相应的寄存器才能读取所需的数据。实现方法是先写后读,具体步骤是在上述读的基础上,在读之前先写存储地址,需要用到重复起始信号(本质就是一个起始信号)。具体如图所示:
主机指定地址向从机写N个字节:类似的,需要指定存储位置再进行写操作,具体如下:
二、STM32的I2C外设
STM32的I2C外设具有4种工作模式:主发送器模式、主接收器模式、从发送器模式、从接收器模式。其重要特征如下:
- 支持不同的通信速度。标准速度高达100KHz,快速高达400KHz。
- 完善的通信功能。可由硬件自动执行时钟生成、起始和终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担。
- 完善的错误监测。主模式时的仲裁丢失,地址/数据传输后的应答错误,检测到错位的起始或停止条件,禁止拉长时钟功能时的上溢或下溢。
- 具有2个中断向量。一个用于地址/数据通信中断,一个用于通信出错中断。
- 具有单字节缓冲器的DMA。
- 兼容系统管理总线(SMBus)。
STM32的I2C内部结构如图所示:
三、STM32 I2C外设的通信方式
1.I2C主模式
STM32的I2C模块作为主机使用,启动数据传输并产生时钟信号。
(1)主模式发送
I2C主模式发送示意图如图所示:
在使用STM32的I2C外设时,需要遵循上图中的时序逻辑,等待相应事件的发生再执行下一步,各事件的意义说明如下:
- EV5: 表示已检测到起始条件,可以开始发送寻址字节。
- EV6:表示已发送从机地址,且已检测到从机的应答。
- EV8_1:表示数据寄存器为空且上一个数据字节的传输已经完成,这意味着可以发送下一个数据字节。
- EV8:表示数据寄存器为空,可以发送数据。
- EV8_2:表示从机成功接收到主机发出的数据字节并发出应答信号,表明从机已准备好接收下一个数据字节。
- EV9:暂不说明。
(2)主模式接收
I2C主模式接收示意图如图所示:
同样的,使用STM32的I2C外设时需要遵循上图中的时序逻辑,各事件的说明如下:
- EV5: 表示已检测到起始条件,可以开始发送寻址字节。
- EV6:表示已发送从机地址,且已检测到从机的应答。
- EV7:表示数据寄存器不为空,即接收到了数据。
- EV8_2:表示从机成功接收到主机发出的数据字节并发出应答信号,表明从机已准备好接收下一个数据字节。
2.I2C从模式
该模式即STM32的I2C模块作为从机,这里暂不介绍。
四、STM32 I2C外设使用流程
虽然不同器件实现的功能不同,但是只要遵循I2C协议,其通信方式都是一样的,配置流程也基本相同。对于STM32,首先要对I2C进行配置,使其能够正常工作,再结合不同器件的驱动程序,完成STM32与不同器件的数据传输。这里只介绍STM32作为主机的使用流程,从机可以是AT24XX系列EEPROM或MPU6050等器件,使用流程和参考代码如下:
- 配置GPIO。
- 配置I2C。
- 编写读取从机数据的函数。
- 编写向从机写数据的函数。
void I2C_Init(void) //初始化STM32的I2C2模块,使其能够使用
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //将PB10和PB11引脚初始化为复用
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;//开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
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; //时钟占空比,选择占空比为1/3
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_Cmd(I2C2, ENABLE); //使能I2C2,开始运行
}
void Write_Byte(uint8_t 存储地址, uint8_t Data) //向从机写一个字节数据,存储地址不一定为8位
{
I2C_GenerateSTART(I2C2, ENABLE);
//产生起始条件
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
//等待EV5,即等待检测到起始条件
I2C_Send7bitAddress(I2C2, 从机地址, I2C_Direction_Transmitter);
//发送从机地址
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
//等待EV6,即等待发送从机地址并受到从机应答信号
I2C_SendData(I2C2, 存储地址);
//发送存储地址
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
//等待EV8,即等待数据寄存器为空,可以发送数据
I2C_SendData(I2C2, Data);
//发送数据
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);
//等待EV8_2,即等待从机接收到数据并传回应答信号
I2C_GenerateSTOP(I2C2, ENABLE);
//产生停止信号
}
uint8_t Read_Byte(uint8_t 数据所在地址) //从从机读取一个字节数据,数据所在地址不一定为8位
{
uint8_t Data;
//用于暂存数据
I2C_GenerateSTART(I2C2, ENABLE);
//产生起始条件
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
//等待EV5,即等待检测到起始条件
I2C_Send7bitAddress(I2C2, 从机地址, I2C_Direction_Transmitter);
//发送从机地址
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
//等待EV6,即等待发送从机地址并受到从机应答信号
I2C_SendData(I2C2, 数据所在地址);
//发送从机的数据所在地址
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);
//等待EV8_2,即等待从机接收到数据并传回应答信号
I2C_GenerateSTART(I2C2, ENABLE);
//产生重复起始条件
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
//等待EV5,即等待检测到起始条件
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
//发送从机地址
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
//等待EV6,即等待发送从机地址并受到从机应答信号
I2C_AcknowledgeConfig(I2C2, DISABLE);
//在接收最后一个字节之前提前将应答失能,之后从机不再传输数据
I2C_GenerateSTOP(I2C2, ENABLE);
//在接收最后一个字节之前提前申请停止条件
while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
//等待EV7,即等待数据寄存器不为空,收到了数据
Data = I2C_ReceiveData(I2C2);
//将接收到的数据存到Data变量中
I2C_AcknowledgeConfig(I2C2, ENABLE);
//将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
return Data;
}
代码仅供参考,具体使用需要结合相关从机器件的开发文档。