I2C通讯协议 (Inter-Integrated Circuit,读作I平方C、I方C) 是由Phiilps公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要USART、CAN等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路间的通讯。
(感谢野火的PPT,一部分内容参考了野火)如下图所示即为I2C的物理层:
下面来简要介绍物理层需要了解的知识点:
如果不是用高阻态表示高电平而是用接地表示,那么当一个设备通讯时,这个设备接电源,整个总线通过上拉电阻也接了电源,其他未通讯设备接地,就把其它设备短路掉了。
这个部分的内容相当重要,它是我们后面写代码的基础。
一个数据包的组成如下:
一个数据包的组成如下:
这个就是以上读和写的复合格式,关键在于主机的读写位,主机想要读就用读的格式,主机想要写就用写的格式。
通讯起始和停止
数据有效性
SDA数据线在SCL的每个时钟周期传输一位数据。
响应
传输时主机产生时钟,在第9个时钟时,数据发送端会释放SDA的控制权,由数据接收端控制SDA,若SDA为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
首先声明一下,STM32的硬件I2C是有缺陷的,因此我们基本都是用软件模拟I2C! 但是下面这些内容还是要了解一下,初学不必深入。
本部分可参考STM32中文参考手册第24章I2C接口。
下面将框图分为四个部分做简要介绍:
STM32芯片有两个I2C外设,它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。
SCL、SDA引脚和编号对应关系如下(可参考电路原理图):
引脚 | I2C1 | I2C2 |
---|---|---|
SCL | PB5(默认)/PB8(重映射) | PB10 |
SDA | PB6(默认)/PB9(重映射) | PB11 |
需要注意的是:在 SMBus 模式下, SMBALERT 是可选信号。如果禁止了 SMBus ,则不能使用该信号。
SMBus (System Management Bus,系统管理总线) 是1995年由Intel提出的,应用于移动PC和桌面PC系统中的低速率通讯。希望通过一条廉价并且功能强大的总线(由两条线组成),来控制主板上的设备并收集相应的信息。SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系统,设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数。
本部分可参考STM32中文参考手册24.6.8时钟控制寄存器(I2C_CCR)。
I2C时钟是由时钟控制寄存器控制的。寄存器位15可设置标准模式或快速模式,位14可设置快速模式下的占空比(THIGH/TLOW)。位11-0设置SCL时钟的配置因子CCR。CCR的计算过程了解即可(PCLK1 = APB1,默认36MHz):
SDA信号主要连接到数据移位寄存器上,数据移位寄存器的数据
来源及目标是数据寄存器(DR)、地址寄存器(OAR)、PEC寄存器以及SDA数据线。(感觉与USART工作原理比较类似,也是一位一位的发送、接收。)
本部分可参考STM32中文参考手册24.6.1 控制寄存器 1(I2C_CR1)和24.6.2 控制寄存器 2(I2C_CR2)以及24.6.6 状态寄存器 1(I2C_SR1)和24.6.7 状态寄存器 2 (I2C_SR2)。
整体控制逻辑负责协调整个I2C外设,控制逻辑的工作模式根据我们配置的控制寄存器(CR1/CR2)的参数而改变。在外设工作时,控制逻辑会根据外设的工作状态修改状态寄存器(SR1
和SR2),只要读取这些寄存器相关的寄存器位,就可以了解I2C的工作状态。需要注意的寄存器几个位:
CR1寄存器:位10(ACK)、位9(STOP)、位8(START)。
SR1寄存器:位7(TxE:数据寄存器为空(发送时) (Data register empty (transmitters)) )、位6(RxNE:数据寄存器非空(接收时) (Data register not empty (receivers)) )、位1(ADDR:地址已被发送(主模式)/地址匹配(从模式) (Address sent (master mode)/matched
(slave mode)))、位0(SB:起始位(主模式) (Start bit (Master mode)) )。
SR2寄存器:位2(TRA:发送/接收 (Transmitter/receiver) )、位1(BUSY:总线忙 (Bus busy))。
本部分可参考STM32中文参考手册24.3.3 I2C主模式,同时文字内容借鉴了野火PPT。本部分比较重要,也是我们写I2C代码的基础。
如图所示,上面一行是控制寄存器要发送接收的内容,下面一行是状态寄存器标志的内容,库函数就是通过检测这些状态寄存器来判断这些事件是否已完成。
如图所示,上面一行是控制寄存器要发送接收的内容,下面一行是状态寄存器标志的内容,库函数就是通过检测这些状态寄存器来判断这些事件是否已完成。
位于头文件stm32f10x_i2c.h,结构体定义如下:
typedef struct
{
uint32_t I2C_ClockSpeed; /*!< 设置SCL时钟频率*/
uint16_t I2C_Mode; /*!< 设置工作模式*/
uint16_t I2C_DutyCycle; /*!< 设置时钟占空比*/
uint16_t I2C_OwnAddress1; /*!< 指定自身I2C设备地址 */
uint16_t I2C_Ack; /*!< 响应使能或关闭响应使能*/
uint16_t I2C_AcknowledgedAddress; /*!< 指定地址长度(7或10位)*/
}I2C_InitTypeDef;
这里着重说明一下I2C_OwnAddress1
,这个是配置I2C设备自己的地址,对于STM32主机设备,可以不用关心这个地址位,但是如果是两个MCU进行通讯的话,是必须要进行配置的。这个地址是7位还是10位,取决于I2C_AcknowledgedAddress
,只有它设置为10位模式,I2C_OwnAddress1
才能使用10位地址。
部分常用库函数如下:
//初始化
void I2C_DeInit(I2C_TypeDef* I2Cx);
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
//产生起始条件、终止条件、使能应答、设置设备的第二个地址
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_OwnAddress2Config(I2C_TypeDef* I2Cx, uint8_t Address);
//发送数据、接收数据、发送地址、读取I2C的寄存器
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
uint16_t I2C_ReadRegister(I2C_TypeDef* I2Cx, uint8_t I2C_Register);
//清除标志位、获得标志位(重要)、标志位中断
void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
配置流程如下:
#ifndef __I2C_H
#define __I2C_H
#include "stm32f10x.h"
/****** 选择使用:I2C1 ******/
/****** 宏定义区 ******/
#define I2C_CLK_SPEED 400000
/* STM32设备地址可随意定义,只要与EEPROM地址不重合即可 */
#define STM32_ADDR 0x5F
#define EEPROM_ADDR 0xA0
#define SCL_PORT_CLK RCC_APB2Periph_GPIOB
#define SDA_PORT_CLK RCC_APB2Periph_GPIOB
#define I2Cx_CLK RCC_APB1Periph_I2C1
#define I2Cx I2C1
#define SCL_GPIO_PORT GPIOB
#define SDA_GPIO_PORT GPIOB
#define SCL_GPIO_PIN GPIO_Pin_6
#define SDA_GPIO_PIN GPIO_Pin_7
/****** 函数声明区 ******/
void I2C_Config(void);
#endif /* __I2C_H */
/**
* @brief I2C初始化配置
* @param 无
* @retval 无
*/
void I2C_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
/* 开启SCL和SDA时钟 */
RCC_APB1PeriphClockCmd(SCL_PORT_CLK | SDA_PORT_CLK, ENABLE);
RCC_APB1PeriphClockCmd(I2Cx_CLK, ENABLE);
/* 配置SCL对应的GPIO */
GPIO_InitStructure.GPIO_Pin = SCL_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SCL_GPIO_PORT, &GPIO_InitStructure);
/* 配置SDA对应的GPIO */
GPIO_InitStructure.GPIO_Pin = SDA_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SDA_GPIO_PORT, &GPIO_InitStructure);
/* 配置I2C */
I2C_InitStructure.I2C_ClockSpeed = I2C_CLK_SPEED;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = STM32_ADDR;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2Cx, &I2C_InitStructure);
I2C_Cmd(I2Cx, ENABLE);
}
受篇幅限制,I2C相关内容未完待续···