本文档详细阐述STM32微控制器中I2C(Inter-Integrated Circuit)外设的底层硬件工作逻辑,重点分析其状态机、时序控制及寄存器级操作原理,为嵌入式工程师提供透彻的底层实现理解。
I2C总线物理层由两条开漏输出线组成:
SDA(串行数据线):双向数据线
SCL(串行时钟线):时钟信号线
关键硬件特性:
// 开漏输出意味着:
// 1. 控制器只能主动将线路拉低(输出0)
// 2. 释放线路时(输出1),依赖外部上拉电阻将电平恢复至高电平
// 3. 允许多个设备同时驱动总线而不损坏 线与逻辑:
SDA线 = 设备1输出 & 设备2输出 & ... & 设备N输出
SCL线 = 主设备1输出 & 主设备2输出 & ... & 主设备N输出 任何设备输出低电平时,整条线路即为低电平。这是多主机仲裁的基础。
STM32的I2C硬件精确控制每个位的时序:
一个数据位周期:
┌───┐ ┌───┐ ┌───┐
SCL │ │ │ │ │ │
────┘ └────┘ └────┘ └──
│ 有效 │
│ 数据 │
SDA ────────────────┐ ┌──────────
│ │
└───┘
数据变化仅允许在此时发生
(SCL低电平期间) 硬件自动确保:
SCL低电平时,SDA可变化
SCL高电平时,SDA必须稳定
满足I2C协议规定的最小建立/保持时间
当从设备需要更多时间处理数据时,可执行时钟拉伸:
主设备视角:
1. 主设备拉低SCL,开始低电平周期
2. 主设备释放SCL,准备开始高电平周期
3. 从设备若仍拉低SCL,主设备检测到SCL为低
4. 主设备等待,直到从设备释放SCL
5. SCL变高,继续通信
硬件自动处理:SCLSM位控制是否使能时钟同步 STM32 I2C有两个主要状态寄存器:
SR1(状态寄存器1)关键位:
SB:起始位已发送(主模式)
ADDR:地址已发送/接收
BTF:字节传输完成
TxE:数据寄存器空(发送)
RxNE:数据寄存器非空(接收)
STOPF:检测到停止条件(从模式)
SR2(状态寄存器2)关键位:
BUSY:总线忙状态
MSL:主/从模式标志
TRA:发送/接收方向
// 从寄存器视角看发送流程
void I2C_Master_Transmit(uint8_t slaveAddr, uint8_t* data, uint32_t size) {
// 1. 生成START条件
I2C1->CR1 |= I2C_CR1_START; // 设置START位
// 等待SB标志置位
while(!(I2C1->SR1 & I2C_SR1_SB));
// 硬件已自动生成START时序
// 2. 发送从设备地址
I2C1->DR = slaveAddr << 1; // 写入地址+写方向
// 等待ADDR标志置位
while(!(I2C1->SR1 & I2C_SR1_ADDR));
// 3. 清除ADDR标志(读SR2实现)
uint32_t temp = I2C1->SR2;
// 4. 发送数据
for(uint32_t i = 0; i < size; i++) {
// 等待数据寄存器空
while(!(I2C1->SR1 & I2C_SR1_TXE));
// 写入数据
I2C1->DR = data[i];
// 可选:等待字节传输完成
if(i == size-1) { // 最后一个字节
while(!(I2C1->SR1 & I2C_SR1_BTF));
}
}
// 5. 生成STOP条件
I2C1->CR1 |= I2C_CR1_STOP;
} 状态转移的硬件视角:
写CR1.START=1
↓
硬件自动:在SCL高时拉低SDA → SB=1
↓
写DR=地址
↓
硬件自动:发送地址+检测ACK → ADDR=1
↓
读SR2清除ADDR
↓
循环:写DR → TXE=0 → 硬件发送 → TXE=1
↓
最后字节:BTF=1表示完成
↓
写CR1.STOP=1 → 硬件生成停止条件 // 接收流程的关键硬件行为
void I2C_Master_Receive(uint8_t slaveAddr, uint8_t* buffer, uint32_t size) {
// ... 前同发送流程(地址+R/W位设为读)
// 关键:ACK控制逻辑
if(size == 1) {
// 单字节接收:收到前不应答
I2C1->CR1 &= ~I2C_CR1_ACK;
} else {
// 多字节接收:非最后一字节前应答
I2C1->CR1 |= I2C_CR1_ACK;
}
// 清除ADDR标志
uint32_t temp = I2C1->SR2;
// 接收数据循环
for(uint32_t i = 0; i < size; i++) {
if(i == size - 1) {
// 最后一字节前关闭ACK
I2C1->CR1 &= ~I2C_CR1_ACK;
// 发送STOP条件
I2C1->CR1 |= I2C_CR1_STOP;
} else if(i == size - 2) {
// 倒数第二字节后,设置POS位(某些型号)
// 用于控制最后字节的ACK
}
// 等待数据接收完成
while(!(I2C1->SR1 & I2C_SR1_RxNE));
// 读取数据
buffer[i] = I2C1->DR;
}
} CCR寄存器控制SCL频率:
标准模式(100kHz):
CCR = APB1时钟 / (2 * 100000)
快速模式(400kHz):
CCR = APB1时钟 / (2 * 400000)
使能快速模式:
CCR寄存器的F/S位 = 1 TRISE寄存器控制上升时间:
TRISE = (最大上升时间 * 时钟频率) + 1
如:1000ns上升时间 @ 8MHz → TRISE = 8 + 1 = 9 STM32F4/H7等新型号使用TIMINGR:
I2C1->TIMINGR = (SCLL << 28) | // SCL低电平周期
(SCLH << 16) | // SCL高电平周期
(SDADEL << 20) | // 数据建立时间
(SCLDEL << 16) | // 数据保持时间
(PRESC << 0); // 预分频 提供更精细的时序控制。
硬件在特定状态自动设置标志并触发中断:
事件中断(EV_IRQn):
- SB: 起始位已发送
- ADDR: 地址匹配
- BTF: 字节传输完成
- TxE: 发送寄存器空
- RxNE: 接收寄存器非空
错误中断(ER_IRQn):
- BERR: 总线错误
- ARLO: 仲裁丢失
- AF: 应答失败
- OVR: 溢出
- TIMEOUT: 超时 I2C硬件可与DMA控制器协同:
发送DMA:
// 硬件行为:
// 1. 当TxE=1且DMA请求使能时,I2C向DMA控制器发出请求
// 2. DMA自动将数据从内存写入I2C->DR
// 3. 硬件自动清除TxE标志
// 4. 数据发送完成后,BTF触发DMA传输完成中断
I2C1->CR2 |= I2C_CR2_DMAEN; // 使能DMA请求
DMA1_Channel6->CCR |= DMA_CCR_EN; // 使能DMA通道 接收DMA:
// 硬件行为:
// 1. 当RxNE=1且DMA请求使能时,I2C发出DMA请求
// 2. DMA自动从I2C->DR读取数据到内存
// 3. 最后一个字节需特殊处理(无ACK+STOP) 总线错误(BERR):
检测条件:在非预期的位置检测到START/STOP
硬件动作:自动关闭通信,BERR标志置位
仲裁丢失(ARLO):
检测条件:主设备试图发送高电平但检测到SDA为低
硬件动作:自动转为从模式,ARLO标志置位
应答失败(AF):
检测条件:发送地址或数据后,第9个时钟周期SDA为高
硬件动作:AF标志置位,停止后续数据传输
void I2C_Recover_Bus(I2C_TypeDef* I2Cx) {
// 1. 禁用I2C(清除PE位)
I2Cx->CR1 &= ~I2C_CR1_PE;
// 2. 重新配置GPIO为普通输出
GPIOB->MODER |= (1 << (7 * 2)); // PB7(SDA)输出模式
GPIOB->MODER |= (1 << (6 * 2)); // PB6(SCL)输出模式
// 3. 发送9个时钟脉冲释放可能挂起的从设备
for(int i = 0; i < 9; i++) {
// SCL低
GPIOB->BSRR = (1 << 6) << 16; // PB6复位
delay_us(5);
// SCL高
GPIOB->BSRR = (1 << 6); // PB6置位
delay_us(5);
}
// 4. 发送STOP条件
// SDA低
GPIOB->BSRR = (1 << 7) << 16; // PB7复位
delay_us(5);
// SCL低
GPIOB->BSRR = (1 << 6) << 16; // PB6复位
delay_us(5);
// SCL高
GPIOB->BSRR = (1 << 6);
delay_us(5);
// SDA高
GPIOB->BSRR = (1 << 7);
delay_us(5);
// 5. 重新配置GPIO为I2C
GPIOB->MODER |= (2 << (7 * 2)) | (2 << (6 * 2)); // 复用功能
// 6. 重新使能I2C
I2Cx->CR1 |= I2C_CR1_PE;
} 硬件自动比较接收到的地址与自身地址寄存器:
// 地址匹配过程:
// 1. 硬件检测到START条件
// 2. 接收8位地址(7位地址+R/W位)
// 3. 与自身地址寄存器比较:
// - OAR1中的主地址
// - OAR2中的双地址(如果使能)
// - 广播地址(如果使能)
// 4. 匹配成功:硬件置位ADDR标志,拉低SDA应答
// 5. 软件读取SR2清除ADDR标志 从设备控制SCL线的能力:
// 从设备需要更多处理时间时:
// 1. 在SCL低电平期间保持拉低SCL
// 2. 主设备检测到SCL为低,进入等待
// 3. 从设备完成处理后释放SCL
// 4. 主设备检测到SCL变高,继续通信
// 注意:需使能时钟同步(I2C_CR1_NOSTRETCH=0) 调整时钟配置:根据实际布线长度调整TRISE
使用快速模式+:支持1MHz通信(需确认硬件支持)
减小滤波器设置:减少数字滤波器延迟(I2C_CR1_NoiseFilter)
// 优化中断处理:仅使能必要中断
void I2C_Optimize_IRQ(I2C_TypeDef* I2Cx) {
// 发送:仅使能TxE和BTF中断
I2Cx->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN;
// 接收:使能RxNE和BTF中断
// 错误:始终使能错误中断
I2Cx->CR2 |= I2C_CR2_ITERREN;
} STM32 I2C模块的底层工作逻辑:
硬件自动协议处理:起始/停止条件、位时序、ACK生成均由硬件自动完成
状态机驱动:通过状态寄存器标志位驱动通信流程
中断/DMA集成:提供灵活的通信控制方式
错误检测与恢复:内置多种错误检测机制
通过直接操作寄存器和理解状态机转移,可超越HAL/LL库的限制,实现最高效、可靠的I2C通信。