简介
I2C(Inter-Integrated Circuit,集成电路间)是一种同步串行通信总线,由飞利浦公司开发,广泛应用于单片机与外设之间的短距离通信。I2C总线仅需两根线(SCL时钟线和SDA数据线)即可实现多设备之间的通信,具有接口简单、占用引脚少、通信可靠等优点。STM32F407 系列芯片提供了多达 3 个 I2C 接口,支持标准模式(100kHz)、快速模式(400kHz)和高速模式(1MHz),可连接各种 I2C 设备,如传感器、EEPROM、LCD 等。本文从 I2C 的基本原理出发,详细讲解 STM32F407 I2C 的配置方法、代码实现、通信协议以及实际应用案例,帮助你快速掌握 I2C 通信技术。
一、I2C核心概念与分类
1.1 基本概念
I2C 是一种两线式串行总线,用于连接微控制器及其外围设备,其主要特点包括:
- 两根通信线:SCL(Serial Clock,串行时钟线)和SDA(Serial Data,串行数据线)
- 多主从架构:支持多个主设备和多个从设备
- 从设备地址:每个从设备都有唯一的7位或10位地址
- 同步通信:主设备提供时钟信号,所有设备在时钟的同步下进行通信
- 双向通信:SDA线是双向的,可实现主设备与从设备之间的数据传输
关键参数:
- 通信速率:标准模式(100kHz)、快速模式(400kHz)、高速模式(1MHz)
- 从设备地址:7位地址范围为0-127,10位地址范围为0-1023
- 数据传输格式:每个字节为8位,后跟1位应答位
- 总线仲裁:当多个主设备同时发送数据时,通过仲裁机制决定总线控制权
1.2 STM32F407 的 I2C 资源
STM32F407 系列芯片提供了 3 个 I2C 接口(I2C1、I2C2、I2C3):
| I2C | 引脚 | 最大通信速率 | 适用场景 |
|---|---|---|---|
| I2C1 | PB6(SCL), PB7(SDA) 或 PF1(SCL), PF0(SDA) | 1MHz | 主要I2C接口,支持高速模式 |
| I2C2 | PB10(SCL), PB11(SDA) 或 PH4(SCL), PH5(SDA) | 1MHz | 辅助I2C接口 |
| I2C3 | PA8(SCL), PC9(SDA) 或 PH7(SCL), PH8(SDA) | 1MHz | 辅助I2C接口 |
关键特性:
- 支持 7 位和 10 位从设备地址
- 支持标准模式、快速模式和高速模式
- 支持 DMA 传输
- 支持多主模式和从模式
- 内置噪声滤波器和错误检测功能
二、I2C通信协议
2.1 基本通信流程
I2C 通信的基本流程包括:
- 起始条件:SCL 高电平时,SDA 从高电平跳变到低电平
- 发送从设备地址:主设备发送7位从设备地址和1位读写方向位(0为写,1为读)
- 等待应答:从设备收到地址后,发送应答位
- 数据传输:主设备和从设备之间传输数据,每次传输8位,后跟1位应答位
- 停止条件:SCL 高电平时,SDA 从低电平跳变到高电平
2.2 信号时序
起始条件:
- SCL 保持高电平
- SDA 从高电平跳变到低电平
停止条件:
- SCL 保持高电平
- SDA 从低电平跳变到高电平
数据传输:
- SCL 为低电平时,SDA 可以改变状态
- SCL 为高电平时,SDA 必须保持稳定,此时从设备采样数据
应答位:
- 发送方发送8位数据后,在第9个时钟周期释放SDA线
- 接收方在第9个时钟周期将SDA线拉低,表示应答
- 接收方在第9个时钟周期保持SDA线高电平,表示非应答
2.3 通信模式
I2C 支持两种主要的通信模式:
主发送模式:主设备向从设备发送数据
- 主设备发送起始条件
- 主设备发送从设备地址(写方向)
- 从设备发送应答位
- 主设备发送数据字节
- 从设备发送应答位
- 重复步骤4-5,发送多个数据字节
- 主设备发送停止条件
主接收模式:主设备从从设备接收数据
- 主设备发送起始条件
- 主设备发送从设备地址(读方向)
- 从设备发送应答位
- 从设备发送数据字节
- 主设备发送应答位(继续接收)或非应答位(停止接收)
- 重复步骤4-5,接收多个数据字节
- 主设备发送停止条件
复合模式:主设备先向从设备发送数据(如寄存器地址),然后从从设备接收数据
- 主设备发送起始条件
- 主设备发送从设备地址(写方向)
- 从设备发送应答位
- 主设备发送寄存器地址
- 从设备发送应答位
- 主设备发送重复起始条件
- 主设备发送从设备地址(读方向)
- 从设备发送应答位
- 从设备发送数据字节
- 主设备发送非应答位
- 主设备发送停止条件
三、I2C配置与代码实现
3.1 标准库配置步骤
以 I2C1 为例,使用标准库配置 I2C 的基本步骤:
- 使能 I2C 时钟和 GPIO 时钟
- 配置 GPIO 为复用功能
- 配置 I2C 基本参数(时钟频率、地址模式等)
- 使能 I2C
- 配置中断(可选)
- 配置 DMA(可选)
3.2 代码实现(I2C1,400kHz)
#include"stm32f4xx.h"#defineI2C_SPEED400000// I2C通信速率:400kHz/** * @brief 初始化I2C1 * @param 无 * @retval 无 */voidI2C1_Init(void){GPIO_InitTypeDef GPIO_InitStructure;I2C_InitTypeDef I2C_InitStructure;// 1. 使能时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);// 2. 配置GPIOGPIO_InitStructure.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;GPIO_InitStructure.GPIO_OType=GPIO_OType_OD;GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);// 3. 将GPIO引脚连接到I2C1GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_I2C1);// SCLGPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);// SDA// 4. 配置I2C1I2C_InitStructure.I2C_ClockSpeed=I2C_SPEED;I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;I2C_InitStructure.I2C_OwnAddress1=0x00;// 主模式下不需要设置自己的地址I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;I2C_Init(I2C1,&I2C_InitStructure);// 5. 使能I2C1I2C_Cmd(I2C1,ENABLE);}/** * @brief I2C发送起始条件 * @param I2Cx: I2C接口,如I2C1、I2C2等 * @retval 成功返回0,失败返回1 */uint8_tI2C_Start(I2C_TypeDef*I2Cx){uint32_ttimeout=10000;// 发送起始条件I2C_GenerateSTART(I2Cx,ENABLE);// 等待起始条件生成while(!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_MODE_SELECT)){if(--timeout==0)return1;}return0;}/** * @brief I2C发送停止条件 * @param I2Cx: I2C接口,如I2C1、I2C2等 * @retval 无 */voidI2C_Stop(I2C_TypeDef*I2Cx){// 发送停止条件I2C_GenerateSTOP(I2Cx,ENABLE);}/** * @brief I2C发送设备地址 * @param I2Cx: I2C接口,如I2C1、I2C2等 * @param address: 设备地址 * @param direction: 方向,0为写,1为读 * @retval 成功返回0,失败返回1 */uint8_tI2C_SendAddress(I2C_TypeDef*I2Cx,uint8_taddress,uint8_tdirection){uint32_ttimeout=10000;// 发送设备地址和方向I2C_Send7bitAddress(I2Cx,address<<1,direction?I2C_Direction_Receiver:I2C_Direction_Transmitter);// 等待地址发送完成if(direction){// 读方向while(!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){if(--timeout==0)return1;}}else{// 写方向while(!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if(--timeout==0)return1;}}return0;}/** * @brief I2C发送数据 * @param I2Cx: I2C接口,如I2C1、I2C2等 * @param data: 要发送的数据 * @retval 成功返回0,失败返回1 */uint8_tI2C_SendData(I2C_TypeDef*I2Cx,uint8_tdata){uint32_ttimeout=10000;// 发送数据I2C_SendData(I2Cx,data);// 等待数据发送完成while(!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if(--timeout==0)return1;}return0;}/** * @brief I2C接收数据 * @param I2Cx: I2C接口,如I2C1、I2C2等 * @param ack: 是否发送应答,0为非应答,1为应答 * @retval 接收到的数据 */uint8_tI2C_ReceiveData(I2C_TypeDef*I2Cx,uint8_tack){// 配置应答if(ack)I2C_AcknowledgeConfig(I2Cx,ENABLE);elseI2C_AcknowledgeConfig(I2Cx,DISABLE);// 等待数据接收while(!I2C_CheckEvent(I2Cx,I2C_EVENT_MASTER_BYTE_RECEIVED));// 返回接收到的数据returnI2C_ReceiveData(I2Cx);}/** * @brief I2C写单个字节 * @param I2Cx: I2C接口,如I2C1、I2C2等 * @param dev_addr: 设备地址 * @param reg_addr: 寄存器地址 * @param data: 要写入的数据 * @retval 成功返回0,失败返回1 */uint8_tI2C_WriteByte(I2C_TypeDef*I2Cx,uint8_tdev_addr,uint8_treg_addr,uint8_tdata){// 发送起始条件if(I2C_Start(I2Cx))return1;// 发送设备地址(写方向)if(I2C_SendAddress(I2Cx,dev_addr,0)){I2C_Stop(I2Cx);return1;}// 发送寄存器地址if(I2C_SendData(I2Cx,reg_addr)){I2C_Stop(I2Cx);return1;}// 发送数据if(I2C_SendData(I2Cx,data)){I2C_Stop(I2Cx);return1;}// 发送停止条件I2C_Stop(I2Cx);return