一、I2C 硬件外设概述
1.1 软件模拟 I2C 与硬件 I2C 的区别
I2C 协议的实现有两种方式:软件模拟和硬件实现。
软件模拟 I2C
原理:直接使用 CPU 控制普通 GPIO 引脚的电平,按照 I2C 协议的时序要求,手动产生起始信号、停止信号、数据位和应答信号。
优点:不占用特定硬件外设,引脚可以任意选择,兼容性好。
缺点:全程占用 CPU 资源,传输速度慢,代码复杂度高。
生活类比:软件模拟 I2C 就像你自己手动包饺子,从擀皮到包馅都要自己一步步来,虽然灵活,但速度慢还累人。
硬件 I2C
原理:STM32 芯片内部集成了专门的 I2C 硬件逻辑电路,我们只需要通过配置寄存器告诉它要做什么,它就会自动完成所有时序操作。
优点:不占用 CPU 资源,传输速度快,代码简洁。
缺点:只能使用特定的复用引脚,部分早期 STM32 芯片存在硬件 BUG。
生活类比:硬件 I2C 就像买了一台全自动包饺子机,你只要把面粉和馅料放进去,按下开关,它就会自动包好饺子,你可以同时去做其他事情。
1.2 STM32F103 的 I2C 资源
STM32F103 系列芯片内置2 个独立的 I2C 外设(I2C1 和 I2C2),完全兼容 I2C 协议标准,主要特性包括:
支持标准模式(100 Kbit/s)和快速模式(400 Kbit/s)
支持7 位和10 位设备地址
支持 DMA 数据传输
支持数据包错误校验(PEC)
兼容 SMBus 2.0 协议(主要用于电池管理)
二、I2C 外设硬件架构详解
STM32 的 I2C 外设主要由四个核心模块 组成:
2.1 通讯引脚模块
I2C 外设的信号通过特定的 GPIO 引脚引出,STM32F103 的 I2C 引脚映射如下:
| I2C 外设 | 默认引脚 | 重映射引脚 | 备注 |
|---|---|---|---|
| I2C1 | SCL: PB6 SDA: PB7 | SCL: PB8 SDA: PB9 | 需启用 AFIO 重映射功能 调用 GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE) |
| I2C2 | SCL: PB10 SDA: PB11 | 无 | 不支持引脚重映射 |
重要说明:I2C 的 SCL 和 SDA 引脚必须配置为复用开漏输出模式,并且在硬件上必须连接上拉电阻(通常为 4.7 KΩ)。
为什么需要开漏输出?
I2C 是总线结构,多个设备连接在同一根线上。开漏输出可以实现"线与"功能:任何一个设备拉低总线,整个总线都会变成低电平;当所有设备都输出高阻态时,总线由上拉电阻拉为高电平。如果使用推挽输出,当两个设备同时输出不同电平时,会造成短路损坏芯片。
2.2 时钟控制逻辑
时钟控制逻辑负责产生 I2C 总线的 SCL 时钟信号,决定了通讯速率。核心是时钟控制寄存器 CCR(Clock Control Register)。
CCR 寄存器是一个 16 位寄存器,关键位定义如下:
位 15(F/S):快速模式选择位
0:标准模式(100 KHz)
1:快速模式(400 KHz)
位 14(DUTY):快速模式下的占空比选择位
0:Tlow/Thigh = 2
1:Tlow/Thigh = 16/9
位 11:0(CCR[11:0]):时钟分频因子,用于计算 SCL 时钟周期
2.3 数据控制逻辑
数据控制逻辑负责处理 SDA 线上的数据收发,主要包含以下寄存器:
1. 数据寄存器 DR(Data Register)
8 位数据寄存器,用于缓存要发送或刚接收的数据
发送时:CPU 将数据写入 DR,硬件自动将其加载到移位寄存器
接收时:移位寄存器接收到一个完整字节后,自动将数据存入 DR
2. 数据移位寄存器
负责将并行数据转换为串行数据发送,或将串行数据转换为并行数据接收
I2C 协议采用高位先行(MSB First) 的传输方式
3. 自身地址寄存器 OAR1/OAR2
用于配置 STM32 作为 I2C 从机时的设备地址
OAR1 支持 7 位或 10 位地址
OAR2 支持第二个 7 位地址,使 STM32 可以同时响应两个不同的从机地址
4. 比较器
当 STM32 作为从机时,自动将接收到的地址与 OAR1/OAR2 中的地址进行比较
如果匹配,则产生应答信号,准备接收或发送数据
5. PEC 寄存器
用于数据包错误校验,主要在 SMBus 协议中使用,普通 I2C 通讯很少用到
2.4 整体控制逻辑
整体控制逻辑负责协调整个 I2C 外设的工作,主要包含控制寄存器和状态寄存器。
1. 控制寄存器 CR1(Control Register 1)
关键位定义:
位 0(PE):I2C 外设使能位
0:禁用 I2C 外设
1:使能 I2C 外设
位 8(START):起始信号生成位
写 1:硬件自动产生起始信号
位 9(STOP):停止信号生成位
写 1:硬件自动产生停止信号
位 10(ACK):应答使能位
0:接收到数据后不产生应答
1:接收到数据后自动产生应答
2. 状态寄存器 SR1(Status Register 1)
关键位定义:
位 0(SB):起始位已发送标志
1:起始信号已成功发送到总线
位 1(ADDR):地址已发送标志
1:从机地址已成功发送并收到应答
位 2(BTF):字节传输完成标志
1:一个字节的数据已成功传输
位 6(TXE):发送数据寄存器为空标志
1:DR 寄存器中的数据已被加载到移位寄存器,可以写入下一个数据
位 7(RXNE):接收数据寄存器非空标志
1:移位寄存器已接收到一个完整字节,可以从 DR 中读取数据
位 10(AF):应答失败标志
1:发送数据后没有收到从机的应答信号
3. 状态寄存器 SR2(Status Register 2)
关键位定义:
位 1(BUSY):总线忙标志
1:I2C 总线正在被占用
0:I2C 总线空闲
三、I2C 时钟频率计算
I2C 外设挂载在APB1 总线上,默认时钟频率为36 MHz。SCL 时钟频率由 CCR 寄存器的值决定,不同模式下计算公式不同。
3.1 标准模式(100 KHz)
标准模式下,SCL 的高电平和低电平时间相等:
Thigh = CCR * TPCLK1 Tlow = CCR * TPCLK1 TSCL = Thigh + Tlow = 2 * CCR * TPCLK1其中:
TPCLK1 = 1 / PCLK1 = 1 / 36000000 ≈ 27.78 ns
TSCL = 1 / 目标频率 = 1 / 100000 = 10000 ns
代入计算:
10000 = 2 * CCR * 27.78 CCR = 10000 / (2 * 27.78) ≈ 180因此,标准模式下 CCR 寄存器应设置为180。
3.2 快速模式(400 KHz)
快速模式下有两种占空比可选:
占空比 2:1(DUTY=0)
Thigh = CCR * TPCLK1 Tlow = 2 * CCR * TPCLK1 TSCL = 3 * CCR * TPCLK1目标频率 400 KHz,TSCL = 1 / 400000 = 2500 ns:
2500 = 3 * CCR * 27.78 CCR = 2500 / (3 * 27.78) ≈ 30占空比 16:9(DUTY=1)
Thigh = 9 * CCR * TPCLK1 Tlow = 16 * CCR * TPCLK1 TSCL = 25 * CCR * TPCLK1代入计算:
2500 = 25 * CCR * 27.78 CCR = 2500 / (25 * 27.78) ≈ 3.6 → 取整为 4注意:标准库会根据我们设置的目标波特率自动计算 CCR 的值,不需要手动计算。
四、标准库初始化流程
4.1 初始化步骤
开启 I2C 外设和对应 GPIO 的时钟
配置 GPIO 为复用开漏输出模式
配置 I2C 的工作参数
使能 I2C 外设
4.2 初始化代码示例
// bsp_i2c.c #include "bsp_i2c.h" /** * @brief I2C 初始化函数 * @param 无 * @retval 无 */ void I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; // 1. 开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 开启 GPIOB 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 开启 I2C1 时钟 // 2. 配置 GPIO 为复用开漏输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // I2C1_SCL(PB6) 和 I2C1_SDA(PB7) GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏输出 GPIO_Init(GPIOB, &GPIO_InitStructure); // 3. 配置 I2C 参数 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; // I2C 模式 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 快速模式下占空比 2:1 I2C_InitStructure.I2C_OwnAddress1 = 0x0A; // STM32 作为从机时的地址 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 使能自动应答 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7 位地址模式 I2C_InitStructure.I2C_ClockSpeed = 400000; // 通讯速率 400 KHz I2C_Init(I2C1, &I2C_InitStructure); // 4. 使能 I2C 外设 I2C_Cmd(I2C1, ENABLE); }4.3 I2C_InitTypeDef 结构体成员详解
成员 | 描述 | 可选值 |
|---|---|---|
I2C_Mode | 工作模式 | I2C_Mode_I2C(标准 I2C 模式) |
I2C_DutyCycle | 快速模式下的占空比 | I2C_DutyCycle_2(Tlow/Thigh=2) |
I2C_OwnAddress1 | STM32 作为从机时的自身地址 | 7 位或 10 位地址 |
I2C_Ack | 应答使能 | I2C_Ack_Enable(使能自动应答) |
I2C_AcknowledgedAddress | 地址长度 | I2C_AcknowledgedAddress_7bit(7 位地址) |
I2C_ClockSpeed | 通讯速率 | 最大 400000(400 KHz) |
五、避坑指南
GPIO 模式配置错误
I2C 的 SCL 和 SDA 引脚必须配置为复用开漏输出,不能是推挽输出或普通输入。
硬件上必须连接上拉电阻,否则总线会一直处于低电平。
时钟频率配置错误
I2C 外设挂载在 APB1 总线上,最大时钟频率为 36 MHz,不要使用 APB2 的 72 MHz 进行计算。
快速模式下,确保从设备支持 400 KHz 速率,否则会出现通讯错误。
总线忙死锁问题
如果程序异常退出,可能导致 I2C 总线被锁定在忙状态。
解决方法:在初始化时先模拟产生几个时钟脉冲,释放总线。
硬件 I2C 的 BUG 问题
部分 STM32F103 芯片的硬件 I2C 在某些情况下会出现死锁或数据错误。
对于对稳定性要求极高的项目,可以考虑使用软件模拟 I2C。
初学者建议先学习硬件 I2C 的原理,了解协议的底层机制。
参考出处
《零死角玩转 STM32F103 - 指南者》第 24 章 I2C - 读写 EEPROM
STM32F10x 中文参考手册 第 24 章 I2C 接口