STM32 I2C通信协议底层工作逻辑详解
十条戒令-
编辑于 2026年04月10日 17:26
收录于文集
共13篇
嵌入式开发

本文档详细阐述STM32微控制器中I2C(Inter-Integrated Circuit)外设的底层硬件工作逻辑,重点分析其状态机、时序控制及寄存器级操作原理,为嵌入式工程师提供透彻的底层实现理解。

1. 物理层:两线电路的工作原理

I2C总线物理层由两条开漏输出线组成:

  • SDA(串行数据线):双向数据线

  • SCL(串行时钟线):时钟信号线

关键硬件特性

代码块
PlainText
自动换行
复制代码
// 开漏输出意味着:
// 1. 控制器只能主动将线路拉低(输出0)
// 2. 释放线路时(输出1),依赖外部上拉电阻将电平恢复至高电平
// 3. 允许多个设备同时驱动总线而不损坏
复制成功

线与逻辑

代码块
PlainText
自动换行
复制代码
SDA线 = 设备1输出 & 设备2输出 & ... & 设备N输出
SCL线 = 主设备1输出 & 主设备2输出 & ... & 主设备N输出
复制成功

任何设备输出低电平时,整条线路即为低电平。这是多主机仲裁的基础。

2. 核心:硬件如何执行协议

2.1 位传输时序控制

STM32的I2C硬件精确控制每个位的时序:

代码块
PlainText
自动换行
复制代码
一个数据位周期:
         ┌───┐    ┌───┐    ┌───┐
SCL      │   │    │   │    │   │
     ────┘   └────┘   └────┘   └──
         │   有效   │
         │   数据   │
SDA ────────────────┐   ┌──────────
                    │   │
                    └───┘
     数据变化仅允许在此时发生
     (SCL低电平期间)
复制成功

硬件自动确保:

  1. SCL低电平时,SDA可变化

  2. SCL高电平时,SDA必须稳定

  3. 满足I2C协议规定的最小建立/保持时间

2.2 时钟同步与时钟拉伸

当从设备需要更多时间处理数据时,可执行时钟拉伸:

代码块
PlainText
自动换行
复制代码
主设备视角:
1. 主设备拉低SCL,开始低电平周期
2. 主设备释放SCL,准备开始高电平周期
3. 从设备若仍拉低SCL,主设备检测到SCL为低
4. 主设备等待,直到从设备释放SCL
5. SCL变高,继续通信

硬件自动处理:SCLSM位控制是否使能时钟同步
复制成功

3. 状态机:寄存器级视角

3.1 状态寄存器详解

STM32 I2C有两个主要状态寄存器:

SR1(状态寄存器1)关键位

  • SB:起始位已发送(主模式)

  • ADDR:地址已发送/接收

  • BTF:字节传输完成

  • TxE:数据寄存器空(发送)

  • RxNE:数据寄存器非空(接收)

  • STOPF:检测到停止条件(从模式)

SR2(状态寄存器2)关键位

  • BUSY:总线忙状态

  • MSL:主/从模式标志

  • TRA:发送/接收方向

3.2 主模式发送:跟随状态寄存器

代码块
PlainText
自动换行
复制代码
// 从寄存器视角看发送流程
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;
}
复制成功

状态转移的硬件视角

代码块
PlainText
自动换行
复制代码
写CR1.START=1
    ↓
硬件自动:在SCL高时拉低SDA → SB=1
    ↓
写DR=地址
    ↓
硬件自动:发送地址+检测ACK → ADDR=1
    ↓
读SR2清除ADDR
    ↓
循环:写DR → TXE=0 → 硬件发送 → TXE=1
    ↓
最后字节:BTF=1表示完成
    ↓
写CR1.STOP=1 → 硬件生成停止条件
复制成功

3.3 主模式接收:应答控制逻辑

代码块
PlainText
自动换行
复制代码
// 接收流程的关键硬件行为
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;
    }
}
复制成功

4. 时钟配置:硬件计时器工作

4.1 时钟控制寄存器

CCR寄存器控制SCL频率:

代码块
PlainText
自动换行
复制代码
标准模式(100kHz):
    CCR = APB1时钟 / (2 * 100000)
快速模式(400kHz):
    CCR = APB1时钟 / (2 * 400000)
    
使能快速模式:
    CCR寄存器的F/S位 = 1
复制成功

TRISE寄存器控制上升时间:

代码块
PlainText
自动换行
复制代码
TRISE = (最大上升时间 * 时钟频率) + 1
如:1000ns上升时间 @ 8MHz → TRISE = 8 + 1 = 9
复制成功

4.2 新型号的TIMINGR寄存器

STM32F4/H7等新型号使用TIMINGR:

代码块
PlainText
自动换行
复制代码
I2C1->TIMINGR = (SCLL << 28) |  // SCL低电平周期
                (SCLH << 16) |  // SCL高电平周期
                (SDADEL << 20) | // 数据建立时间
                (SCLDEL << 16) | // 数据保持时间
                (PRESC << 0);    // 预分频
复制成功

提供更精细的时序控制。

5. 中断与DMA:硬件事件触发

5.1 中断事件映射

硬件在特定状态自动设置标志并触发中断:

代码块
PlainText
自动换行
复制代码
事件中断(EV_IRQn):
  - SB: 起始位已发送
  - ADDR: 地址匹配
  - BTF: 字节传输完成
  - TxE: 发送寄存器空
  - RxNE: 接收寄存器非空

错误中断(ER_IRQn):
  - BERR: 总线错误
  - ARLO: 仲裁丢失
  - AF: 应答失败
  - OVR: 溢出
  - TIMEOUT: 超时
复制成功

5.2 DMA集成机制

I2C硬件可与DMA控制器协同:

发送DMA

代码块
PlainText
自动换行
复制代码
// 硬件行为:
// 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

代码块
PlainText
自动换行
复制代码
// 硬件行为:
// 1. 当RxNE=1且DMA请求使能时,I2C发出DMA请求
// 2. DMA自动从I2C->DR读取数据到内存
// 3. 最后一个字节需特殊处理(无ACK+STOP)
复制成功

6. 错误检测与恢复

6.1 硬件错误检测机制

总线错误(BERR)

  • 检测条件:在非预期的位置检测到START/STOP

  • 硬件动作:自动关闭通信,BERR标志置位

仲裁丢失(ARLO)

  • 检测条件:主设备试图发送高电平但检测到SDA为低

  • 硬件动作:自动转为从模式,ARLO标志置位

应答失败(AF)

  • 检测条件:发送地址或数据后,第9个时钟周期SDA为高

  • 硬件动作:AF标志置位,停止后续数据传输

6.2 软件恢复流程

代码块
PlainText
自动换行
复制代码
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;
}
复制成功

7. 从模式工作原理

7.1 地址识别

硬件自动比较接收到的地址与自身地址寄存器:

代码块
PlainText
自动换行
复制代码
// 地址匹配过程:
// 1. 硬件检测到START条件
// 2. 接收8位地址(7位地址+R/W位)
// 3. 与自身地址寄存器比较:
//    - OAR1中的主地址
//    - OAR2中的双地址(如果使能)
//    - 广播地址(如果使能)
// 4. 匹配成功:硬件置位ADDR标志,拉低SDA应答
// 5. 软件读取SR2清除ADDR标志
复制成功

7.2 时钟拉伸实现

从设备控制SCL线的能力:

代码块
PlainText
自动换行
复制代码
// 从设备需要更多处理时间时:
// 1. 在SCL低电平期间保持拉低SCL
// 2. 主设备检测到SCL为低,进入等待
// 3. 从设备完成处理后释放SCL
// 4. 主设备检测到SCL变高,继续通信

// 注意:需使能时钟同步(I2C_CR1_NOSTRETCH=0)
复制成功

8. 性能优化要点

8.1 时序优化策略

  1. 调整时钟配置:根据实际布线长度调整TRISE

  2. 使用快速模式+:支持1MHz通信(需确认硬件支持)

  3. 减小滤波器设置:减少数字滤波器延迟(I2C_CR1_NoiseFilter)

8.2 中断优化

代码块
PlainText
自动换行
复制代码
// 优化中断处理:仅使能必要中断
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模块的底层工作逻辑:

  1. 硬件自动协议处理:起始/停止条件、位时序、ACK生成均由硬件自动完成

  2. 状态机驱动:通过状态寄存器标志位驱动通信流程

  3. 中断/DMA集成:提供灵活的通信控制方式

  4. 错误检测与恢复:内置多种错误检测机制

通过直接操作寄存器和理解状态机转移,可超越HAL/LL库的限制,实现最高效、可靠的I2C通信。

#星禾科技#​ #我在成都玩科技#​ #星禾星穗#​ #具身智能机器人#​ #编程#​