STM32 IIC外设工作流程详解

STM32 I²C 外设工作流程(基于寄存器)

在 STM32 中,I²C 通信主要通过一系列寄存器控制。理解这些寄存器的作用,能够帮助我们掌握 I²C 硬件的运行机制,实现高效的数据传输。本文以 STM32F1(如 STM32F103)为例,详细讲解 I²C 外设的寄存器级操作。


1. STM32 I²C 相关寄存器

STM32 的 I²C 主要涉及以下寄存器:

  • 控制寄存器 1(I2C_CR1):控制 I²C 外设的启用、应答、时钟伸展等功能。
  • 控制寄存器 2(I2C_CR2):配置 I²C 的时钟、DMA 使能、中断使能等。
  • 时钟控制寄存器(I2C_CCR):设置 I²C 通信速率(时钟分频)。
  • 滤波寄存器(I2C_TRISE):配置最大上升时间,用于同步时钟。
  • 状态寄存器 1(I2C_SR1):存储 I²C 通信的状态标志,如起始位、地址匹配、数据发送完成等。
  • 状态寄存器 2(I2C_SR2):存储总线状态、模式、从机地址等信息。
  • 数据寄存器(I2C_DR):用于收发数据。

  • 2. I²C 外设初始化

    在使用 I²C 之前,需要进行初始化,主要包括:

  • 使能 I²C 时钟
  • 配置 GPIO(SCL、SDA)
  • 配置 I²C 速率
  • 使能 I²C 外设
  • 寄存器配置

    void I2C_Init(void) {
        // 1. 使能 I²C1 时钟(I²C1 挂载在 APB1 总线上)
        RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    
        // 2. 使能 GPIOB 时钟(I2C1_SCL=PB6, I2C1_SDA=PB7)
        RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    
        // 3. 配置 GPIO 为复用开漏模式
        GPIOB->CRL &= ~((0xF << (6 * 4)) | (0xF << (7 * 4)));  // 清除原配置
        GPIOB->CRL |= (0xB << (6 * 4)) | (0xB << (7 * 4)); // 复用开漏
    
        // 4. 配置 I²C 时钟
        I2C1->CR2 = 36; // PCLK1 = 36MHz
        I2C1->CCR = 180; // 标准模式(100kHz):T_high = T_low = 10us, CCR = 180
        I2C1->TRISE = 37; // TRISE = (1000ns / (1/36MHz)) + 1
    
        // 5. 使能 I²C 外设
        I2C1->CR1 |= I2C_CR1_PE;
    }
    

    3. 主机模式发送数据

    主机模式下,发送数据的流程如下:

    1. 检查总线状态
    2. 发送起始信号
    3. 发送从机地址(写)
    4. 发送数据
    5. 发送停止信号

    寄存器操作

    void I2C_WriteByte(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
        while (I2C1->SR2 & I2C_SR2_BUSY);  // 等待总线空闲
    
        I2C1->CR1 |= I2C_CR1_START;  // 发送起始信号
        while (!(I2C1->SR1 & I2C_SR1_SB)); // 等待起始信号发送完成
    
        I2C1->DR = (devAddr << 1) | 0;  // 发送从机地址 + 写
        while (!(I2C1->SR1 & I2C_SR1_ADDR)); // 等待地址发送完成
        (void)I2C1->SR2;  // 读取 SR2 以清除 ADDR 标志
    
        I2C1->DR = regAddr;  // 发送寄存器地址
        while (!(I2C1->SR1 & I2C_SR1_TXE)); // 等待数据寄存器空
    
        I2C1->DR = data;  // 发送数据
        while (!(I2C1->SR1 & I2C_SR1_BTF)); // 等待数据传输完成
    
        I2C1->CR1 |= I2C_CR1_STOP;  // 发送停止信号
    }
    

    4. 主机模式接收数据

    1. 发送起始信号
    2. 发送从机地址(写)
    3. 发送寄存器地址
    4. 发送重复起始信号
    5. 发送从机地址(读)
    6. 读取数据
    7. 发送 NACK,结束传输
    8. 发送停止信号

    寄存器操作

    uint8_t I2C_ReadByte(uint8_t devAddr, uint8_t regAddr) {
        uint8_t data;
    
        while (I2C1->SR2 & I2C_SR2_BUSY);  // 检查总线状态
    
        I2C1->CR1 |= I2C_CR1_START;  // 发送起始信号
        while (!(I2C1->SR1 & I2C_SR1_SB));
    
        I2C1->DR = (devAddr << 1) | 0;  // 发送从机地址(写)
        while (!(I2C1->SR1 & I2C_SR1_ADDR));
        (void)I2C1->SR2;
    
        I2C1->DR = regAddr;  // 发送寄存器地址
        while (!(I2C1->SR1 & I2C_SR1_TXE));
    
        I2C1->CR1 |= I2C_CR1_START;  // 发送重复起始信号
        while (!(I2C1->SR1 & I2C_SR1_SB));
    
        I2C1->DR = (devAddr << 1) | 1;  // 发送从机地址(读)
        while (!(I2C1->SR1 & I2C_SR1_ADDR));
        (void)I2C1->SR2;
    
        I2C1->CR1 &= ~I2C_CR1_ACK;  // 发送 NACK
        I2C1->CR1 |= I2C_CR1_STOP;  // 发送停止信号
        while (!(I2C1->SR1 & I2C_SR1_RXNE));
    
        data = I2C1->DR;  // 读取数据
        return data;
    }
    

    5. 复位 I²C 总线

    当 I²C 总线锁死时,可通过软件复位:

    I2C1->CR1 &= ~I2C_CR1_PE;  // 关闭 I²C
    I2C1->CR1 |= I2C_CR1_PE;   // 重新使能 I²C
    

    或手动拉高 SCL 并发送 9 个时钟脉冲。


    6. 总结

  • I²C 初始化 需要配置 GPIO、I²C 时钟、CCR 寄存器
  • 发送数据 依赖 I2C_CR1(START, STOP)I2C_SR1(SB, TXE, BTF)I2C_DR
  • 接收数据 依赖 ACK、NACK、重复起始
  • 通过 软件复位 处理总线锁死
  • 掌握这些寄存器,可以更深入地理解 STM32 I²C 外设的运行机制,优化通信效率和稳定性。


    STM32 I²C 数据收发过程(寄存器级详细解析)

    STM32 的 I²C 外设工作过程中,多个寄存器的值会发生变化。我们将逐步拆解 主机发送数据主机接收数据 的流程,并详细说明寄存器状态的变化,帮助你深入理解 STM32 I²C 硬件的底层机制。


    1. STM32 I²C 主要寄存器

    在 I²C 传输过程中,涉及以下主要寄存器:

    1.1 控制寄存器

    寄存器 作用
    I2C_CR1 控制 I²C 外设(启动、停止、应答、软件复位等)
    I2C_CR2 配置 I²C 时钟、DMA、中断

    1.2 状态寄存器

    寄存器 作用
    I2C_SR1 反映当前 I²C 事件,如 SB(起始位)、ADDR(地址匹配)、TXE(数据寄存器空)等
    I2C_SR2 反映 I²C 总线的状态,如 BUSY(总线忙)、MSL(主机模式)等

    1.3 数据寄存器

    寄存器 作用
    I2C_DR 读写数据

    2. I²C 主机发送数据(寄存器变化)

    步骤

    1. 发送 起始信号
    2. 发送 从机地址 + 写(bit 0 = 0)
    3. 发送 数据字节
    4. 发送 停止信号

    2.1 发送起始信号

    寄存器变化
    操作 I2C_CR1 I2C_SR1 说明
    `I2C1->CR1 = I2C_CR1_START;` START = 1
    等待 SB=1 SB=1 起始条件已发送
    代码
    I2C1->CR1 |= I2C_CR1_START;  // 发送起始信号
    while (!(I2C1->SR1 & I2C_SR1_SB));  // 等待起始位(SB=1)
    

    2.2 发送从机地址(写)

    寄存器变化

    | 操作 | I2C_DR | I2C_SR1 | I2C_SR2 | 说明 |
    |———|——–|———-|———-|
    | I2C1->DR = (devAddr << 1) | 0; | 发送地址 | ADDR=1 | |
    | 读取 SR2ADDR | | ADDR=0 | 地址发送完成 |

    代码
    I2C1->DR = (devAddr << 1) | 0;  // 发送从机地址 + 写
    while (!(I2C1->SR1 & I2C_SR1_ADDR));  // 等待地址匹配
    (void)I2C1->SR2;  // 读取 SR2 清除 ADDR 标志
    

    2.3 发送数据

    寄存器变化
    操作 I2C_DR I2C_SR1 说明
    I2C1->DR = data; 发送数据 TXE=0 数据正在发送
    等待 TXE=1 TXE=1 数据发送完成
    代码
    I2C1->DR = data;  // 发送数据
    while (!(I2C1->SR1 & I2C_SR1_TXE));  // 等待数据传输完成
    

    2.4 发送停止信号

    寄存器变化
    操作 I2C_CR1 说明
    `I2C1->CR1 = I2C_CR1_STOP;` STOP=1
    代码
    I2C1->CR1 |= I2C_CR1_STOP;  // 发送停止信号
    

    3. I²C 主机接收数据(寄存器变化)

    步骤

    1. 发送 起始信号
    2. 发送 从机地址 + 读(bit 0 = 1)
    3. 读取 数据
    4. 发送 NACK
    5. 发送 停止信号

    3.1 发送起始信号

    与主机发送数据相同:

    I2C1->CR1 |= I2C_CR1_START;
    while (!(I2C1->SR1 & I2C_SR1_SB));
    

    3.2 发送从机地址(读)

    寄存器变化

    | 操作 | I2C_DR | I2C_SR1 | I2C_SR2 | 说明 |
    |———|——–|———-|———-|
    | I2C1->DR = (devAddr << 1) | 1; | 发送地址 | ADDR=1 | |
    | 读取 SR2ADDR | | ADDR=0 | 地址发送完成 |

    代码
    I2C1->DR = (devAddr << 1) | 1;
    while (!(I2C1->SR1 & I2C_SR1_ADDR));
    (void)I2C1->SR2;
    

    3.3 读取数据

    寄存器变化
    操作 I2C_SR1 I2C_DR 说明
    等待 RXNE=1 RXNE=1 数据可读
    data = I2C1->DR; RXNE=0 读取数据 数据被取走
    代码
    while (!(I2C1->SR1 & I2C_SR1_RXNE));  // 等待数据准备好
    data = I2C1->DR;  // 读取数据
    

    3.4 发送 NACK

    寄存器变化
    操作 I2C_CR1 说明
    I2C1->CR1 &= ~I2C_CR1_ACK; 关闭 ACK 发送 NACK
    代码
    I2C1->CR1 &= ~I2C_CR1_ACK;  // 发送 NACK
    

    3.5 发送停止信号

    与主机发送数据相同:

    I2C1->CR1 |= I2C_CR1_STOP;  // 发送停止信号
    

    4. 复位 I²C 总线

    若 I²C 总线锁死,可执行软件复位:

    I2C1->CR1 &= ~I2C_CR1_PE;  // 关闭 I²C
    I2C1->CR1 |= I2C_CR1_PE;   // 重新启用 I²C
    

    5. 总结

  • I2C_SR1 标志寄存器:指示 I²C 传输状态(SBADDRTXERXNE)。
  • I2C_DR 数据寄存器:用于收发数据。
  • I2C_CR1 控制寄存器:用于产生 STARTSTOPACK
  • 寄存器级的 I²C 操作能提供更高的灵活性,适用于驱动底层 I²C 设备,如摄像头、EEPROM、传感器等。


    STM32 I²C 完整收发流程(寄存器级详细解析)

    为了更清楚地理解 STM32 I²C 外设的寄存器级操作,我们使用一个 完整的示例
    假设 STM32 作为 主机,从 I²C 设备(如 EEPROM、传感器)读取一个寄存器 的值,
    然后 修改该值并写回


    1. 示例任务

    目标

  • 读取 从机(设备地址 0x50)的寄存器 0x10 的值。
  • 修改该值(加 1)。
  • 写回 该值到 0x10
  • I²C 设备信息

  • 设备地址0x50
  • 寄存器地址0x10
  • I²C 速率:100kHz(标准模式)

  • 2. I²C 数据收发完整流程

    完整步骤

    1. 发送起始信号
  • I2C_CR1 |= I2C_CR1_START
  • I2C_SR1 置位 SB=1
  • 2. 发送设备地址(写)
  • I2C_DR = 0x50 << 1 | 0
  • I2C_SR1 置位 ADDR=1
  • 读取 I2C_SR2 清除 ADDR
  • 3. 发送寄存器地址
  • I2C_DR = 0x10
  • I2C_SR1 置位 TXE=1
  • 4. 发送重复起始信号
  • I2C_CR1 |= I2C_CR1_START
  • I2C_SR1 置位 SB=1
  • 5. 发送设备地址(读)
  • I2C_DR = 0x50 << 1 | 1
  • I2C_SR1 置位 ADDR=1
  • 读取 I2C_SR2 清除 ADDR
  • 6. 读取数据
  • I2C_SR1 置位 RXNE=1
  • data = I2C_DR
  • 7. 发送 NACK
  • I2C_CR1 &= ~I2C_CR1_ACK
  • 8. 发送停止信号
  • I2C_CR1 |= I2C_CR1_STOP
  • 9. 修改数据
  • data++
  • 10. 发送起始信号
  • I2C_CR1 |= I2C_CR1_START
  • 11. 发送设备地址(写)
  • I2C_DR = 0x50 << 1 | 0
  • 12. 发送寄存器地址
  • I2C_DR = 0x10
  • 13. 发送数据
  • I2C_DR = data
  • 14. 发送停止信号
  • I2C_CR1 |= I2C_CR1_STOP

  • 3. 代码实现

    #include "stm32f10x.h"
    
    #define I2C_ADDRESS  0x50  // 从设备地址
    #define REG_ADDRESS  0x10  // 目标寄存器地址
    
    void I2C_WriteByte(uint8_t devAddr, uint8_t regAddr, uint8_t data);
    uint8_t I2C_ReadByte(uint8_t devAddr, uint8_t regAddr);
    
    int main(void) {
        uint8_t data;
    
        // 1. 读取寄存器值
        data = I2C_ReadByte(I2C_ADDRESS, REG_ADDRESS);
    
        // 2. 修改数据
        data++;
    
        // 3. 写回数据
        I2C_WriteByte(I2C_ADDRESS, REG_ADDRESS, data);
    
        while (1);
    }
    
    // 读取 I2C 设备寄存器值
    uint8_t I2C_ReadByte(uint8_t devAddr, uint8_t regAddr) {
        uint8_t data;
    
        // 1. 发送起始信号
        I2C1->CR1 |= I2C_CR1_START;
        while (!(I2C1->SR1 & I2C_SR1_SB));
    
        // 2. 发送设备地址(写)
        I2C1->DR = (devAddr << 1) | 0;
        while (!(I2C1->SR1 & I2C_SR1_ADDR));
        (void)I2C1->SR2;
    
        // 3. 发送寄存器地址
        I2C1->DR = regAddr;
        while (!(I2C1->SR1 & I2C_SR1_TXE));
    
        // 4. 发送重复起始信号
        I2C1->CR1 |= I2C_CR1_START;
        while (!(I2C1->SR1 & I2C_SR1_SB));
    
        // 5. 发送设备地址(读)
        I2C1->DR = (devAddr << 1) | 1;
        while (!(I2C1->SR1 & I2C_SR1_ADDR));
        (void)I2C1->SR2;
    
        // 6. 读取数据
        while (!(I2C1->SR1 & I2C_SR1_RXNE));
        data = I2C1->DR;
    
        // 7. 发送 NACK
        I2C1->CR1 &= ~I2C_CR1_ACK;
    
        // 8. 发送停止信号
        I2C1->CR1 |= I2C_CR1_STOP;
    
        return data;
    }
    
    // 向 I2C 设备寄存器写入数据
    void I2C_WriteByte(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
        // 1. 发送起始信号
        I2C1->CR1 |= I2C_CR1_START;
        while (!(I2C1->SR1 & I2C_SR1_SB));
    
        // 2. 发送设备地址(写)
        I2C1->DR = (devAddr << 1) | 0;
        while (!(I2C1->SR1 & I2C_SR1_ADDR));
        (void)I2C1->SR2;
    
        // 3. 发送寄存器地址
        I2C1->DR = regAddr;
        while (!(I2C1->SR1 & I2C_SR1_TXE));
    
        // 4. 发送数据
        I2C1->DR = data;
        while (!(I2C1->SR1 & I2C_SR1_TXE));
    
        // 5. 发送停止信号
        I2C1->CR1 |= I2C_CR1_STOP;
    }
    

    4. 关键寄存器变化总结

    步骤 寄存器 变化 作用
    发送起始信号 I2C_CR1 START=1 产生起始信号
    发送设备地址(写) I2C_DR ADDR=1 发送地址
    发送寄存器地址 I2C_DR TXE=1 发送数据
    发送重复起始信号 I2C_CR1 START=1 重新开始
    发送设备地址(读) I2C_DR ADDR=1 发送地址
    读取数据 I2C_DR RXNE=1 接收数据
    发送 NACK I2C_CR1 ACK=0 结束读取
    发送停止信号 I2C_CR1 STOP=1 终止传输

    5. 结论

  • I2C_CR1 控制起始、停止、ACK/NACK 发送。
  • I2C_SR1 监视数据传输状态(SBADDRTXERXNE)。
  • I2C_DR 用于收发数据。
  • 这个完整的流程展示了 I²C 数据收发寄存器级操作,适用于传感器、EEPROM、摄像头等 I²C 设备的底层驱动开发。


    STM32 的 I2C 外设(通常标记为 I2Cx,如 I2C1、I2C2)在寄存器层面的工作流程涉及多个关键寄存器的配置和状态监测。以下是基于寄存器操作的工作流程详解:


    1. I2C 外设寄存器概览

    STM32 I2C 外设的核心寄存器包括:

  • CR1 (Control Register 1):配置 I2C 使能、ACK、时钟等。
  • CR2 (Control Register 2):设置时钟频率、中断/DMA 使能。
  • OAR1/OAR2 (Own Address Register):配置自身地址。
  • DR (Data Register):发送/接收数据。
  • SR1/SR2 (Status Registers):标志位(如起始条件、地址匹配、数据收发完成等)。
  • CCR (Clock Control Register):设置时钟分频和模式(标准/快速)。
  • TRISE (TRise Register):配置 SCL 上升时间。

  • 2. 主机发送模式(Master Transmitter)流程

    (1) 初始化配置
    1. 配置 GPIO:将 SCL/SDA 引脚设为复用开漏模式(需外部上拉电阻)。
    2. 配置 I2C 时钟
    3. CR2FREQ[5:0] 位:设置 APB 时钟频率(单位 MHz)。
    4. 配置时钟分频
    5. CCRCCR[11:0] 位:设置 SCL 时钟分频。
    6. 标准模式(100 kHz)或快速模式(400 kHz)。
    7. 配置上升时间
    8. TRISE:根据模式设置(标准模式:1000ns → TRISE = F_APB1(MHz) + 1)。
    9. 使能 I2C
    10. CR1PE 位置 1,使能外设。
    (2) 发送起始条件
    1. 生成 START 信号
    2. CR1START 位置 1。
    3. 等待起始条件完成
    4. 轮询 SR1SB 位(Start Bit),当 SB=1 时,起始条件生成成功。
    5. 必须读取 SR1 后写 DR 寄存器(硬件自动清除 SB)。
    (3) 发送从机地址
    1. 写入从机地址 + 方向位
    2. DR 写入 7-bit地址<<1 | R/W位(0 表示写)
    3. 等待地址应答
    4. 轮询 SR1ADDR 位(地址发送完成)。
    5. 必须读取 SR1 和 SR2 以清除 ADDR 标志。
    (4) 发送数据
    1. 写入数据到 DR
    2. DR 写入待发送的数据字节。
    3. 等待数据发送完成
    4. 轮询 SR1TXE 位(Transmit Data Register Empty)。
    5. TXE=1,表示数据已转移到移位寄存器,可写入下一字节。
    6. 重复步骤 4.1-4.2 发送所有数据。
    (5) 发送停止条件
    1. 生成 STOP 信号
    2. CR1STOP 位置 1。
    3. 等待停止完成
    4. 根据时序要求等待(无需轮询特定标志)。

    3. 主机接收模式(Master Receiver)流程

    流程与发送模式类似,但需注意:

    1. 发送从机地址时,R/W 位置 1。
    2. 配置 CR1ACK 位以控制是否发送应答:
    3. 在接收最后一个字节前,ACK 位需清零(发送 NACK)。
    4. DR 读取数据前,需检查 SR1RXNE 位(Receive Data Register Not Empty)。

    4. 关键状态标志与错误处理

  • BUSY (SR2[1]):总线忙状态。
  • BTF (SR1[2]):字节传输完成。
  • AF (SR1[10]):应答失败(NACK)。
  • ARLO (SR1[9]):仲裁丢失。
  • BERR (SR1[8]):总线错误。
  • 错误处理

    1. 检测到错误标志后,清除错误(通过写 CR1 的 SWRST 位或重新初始化)。
    2. 重新发起通信。

    5. 典型寄存器操作示例(主机发送)

    // 等待 I2C 空闲
    while (I2C1->SR2 & I2C_SR2_BUSY);
    
    // 发送 START
    I2C1->CR1 |= I2C_CR1_START;
    
    // 等待 SB=1
    while (!(I2C1->SR1 & I2C_SR1_SB));
    
    // 发送地址(写模式)
    I2C1->DR = (SlaveAddr << 1) | 0;
    
    // 等待 ADDR=1
    while (!(I2C1->SR1 & I2C_SR1_ADDR));
    volatile uint32_t temp = I2C1->SR1 | (I2C1->SR2 << 16); // 清除 ADDR
    
    // 发送数据
    I2C1->DR = DataByte;
    while (!(I2C1->SR1 & I2C_SR1_TXE));
    
    // 发送 STOP
    I2C1->CR1 |= I2C_CR1_STOP;
    

    6. 注意事项

    1. 时钟配置CCRTRISE 需严格匹配时序要求。
    2. 标志清除顺序:某些标志(如 ADDR)需通过读 SR1 + SR2 清除。
    3. 中断/DMA:可通过 CR2 配置中断或 DMA 传输,减少 CPU 轮询开销。

    通过直接操作寄存器,开发者可以更精细地控制 I2C 时序,但需严格遵循 STM32 参考手册的流程以避免硬件错误。


    好的!我将更详细地分析 I2C 外设在数据传输过程中寄存器的具体变化,结合 STM32 的硬件行为,逐步拆解每个阶段的寄存器操作和状态标志的响应。以下是深入细节的流程说明:


    1. 初始化阶段的寄存器配置

    (1) GPIO 模式配置
  • GPIOx_CRL/CRH:配置 SCL/SDA 引脚为 复用开漏模式GPIO_Mode_AF_OD)。
  • GPIOx_ODR:无需手动设置,但硬件要求外部上拉电阻。
  • (2) I2C 时钟与模式配置
  • CR2FREQ[5:0]:设置 APB1 总线时钟频率(例如,36 MHz → FREQ=36)。
  • CCRCCR[11:0]
  • 标准模式(100 kHz):CCR = APB1_CLK / (2 * 100000)
  • 快速模式(400 kHz):CCR = APB1_CLK / (2 * 400000)
  • 快速模式+(1 MHz):需使能 F/S 位(CCR[15])。
  • TRISE:设置 SCL 上升时间(例如,标准模式:TRISE = APB1_CLK(MHz) + 1)。
  • CR1PE 位:置 1 使能 I2C 外设。

  • 2. 主机发送模式(Master Transmitter)详细流程

    (1) 生成起始条件(START)
    1. 操作寄存器
    2. CR1START 位置 1。
    3. 硬件行为
    4. I2C 硬件检测总线空闲(SR2.BUSY=0)后,生成 START 条件。
    5. 状态标志变化
    6. SR1.SB 置 1:表示 START 条件已生成。
    7. 关键操作
    8. 必须 读取 SR1 寄存器(清除 SB 位),然后立即写入从机地址到 DR 寄存器。
    (2) 发送从机地址
    1. 写入地址到 DR
    2. DR 写入 (SlaveAddr << 1) | 0(0 表示写操作)。
    3. 硬件行为
    4. 硬件自动发送地址 + R/W 位,并等待从机的 ACK。
    5. 状态标志变化
    6. SR1.ADDR 置 1:地址已发送且收到 ACK。
    7. SR2.TRA 置 1:表示当前处于发送模式。
    8. 关键操作
    9. 必须 读取 SR1SR2 寄存器以清除 ADDR 标志。
    10. 示例代码:
      volatile uint32_t dummy = I2C1->SR1; // 读取 SR1 清除 ADDR
      dummy = I2C1->SR2;                   // 读取 SR2 清除 BUSY 状态
      
    (3) 发送数据字节
    1. 写入数据到 DR
    2. DR 写入待发送的数据(例如 0x55)。
    3. 硬件行为
    4. 硬件将 DR 中的数据移到移位寄存器,并逐位发送到 SDA 线。
    5. 状态标志变化
    6. SR1.TXE 置 1:表示 DR 已空,可以写入下一个字节。
    7. SR1.BTF 置 1:表示当前字节已完全发送(包括 ACK 周期)。
    8. 关键操作
    9. TXE=1,写入新数据到 DR,硬件自动清除 TXE
    10. BTF=1,表示数据已发送完成,但需结合 TXE 判断。
    (4) 发送停止条件(STOP)
    1. 操作寄存器
    2. CR1STOP 位置 1。
    3. 硬件行为
    4. 生成 STOP 条件,释放总线。
    5. 状态标志变化
    6. SR2.BUSY 置 0:总线空闲。

    3. 主机接收模式(Master Receiver)详细流程

    (1) 生成 START 并发送读地址
    1. 发送 START(同发送模式)。
    2. 写入读地址到 DR
    3. DR 写入 (SlaveAddr << 1) | 1(1 表示读操作)。
    4. 状态标志变化
    5. SR1.ADDR 置 1:地址发送成功。
    6. SR2.TRA 置 0:表示当前处于接收模式。
    (2) 接收数据流程
    1. 配置 ACK/NACK
    2. CR1.ACK 置 1:使能 ACK(接收每个字节后发送 ACK)。
    3. 在接收最后一个字节前,需 清零 ACK 位(发送 NACK)。
    4. 读取数据
    5. 等待 SR1.RXNE=1:表示 DR 中有新数据。
    6. 读取 DR 寄存器,硬件自动清除 RXNE
    7. 状态标志变化
    8. SR1.RXNE 置 1:数据已接收完毕。
    9. SR1.BTF 置 1:字节传输完成(包括 ACK/NACK 周期)。
    (3) 生成 STOP 条件
  • 同发送模式,需在接收最后一个字节后立即生成 STOP。

  • 4. 寄存器状态变化时序图(示例)

    以主机发送模式为例,展示寄存器关键位的变化时序:

    操作 CR1.START SR1.SB SR1.ADDR SR1.TXE SR1.BTF SR2.BUSY
    初始状态 0 0 0 0 0 0
    设置 START=1 1 0 0 0 0 1
    START 生成完成 1 1 0 0 0 1
    写入地址到 DR 1 0 0 0 0 1
    地址发送完成 1 0 1 0 0 1
    清除 ADDR(读 SR1/SR2) 1 0 0 0 0 1
    写入数据到 DR 1 0 0 0 0 1
    数据开始发送 1 0 0 0 0 1
    数据发送完成 1 0 0 1 1 1
    设置 STOP=1 0 0 0 1 1 0

    5. 错误处理与寄存器恢复

    (1) 应答失败(NACK)
  • 触发条件:从机未应答地址或数据。
  • 状态标志SR1.AF=1
  • 恢复操作
    1. 清除 AF 标志:写 SR1AF 位为 0。
    2. 生成 STOP 或重复 START:
      I2C1->CR1 |= I2C_CR1_STOP;  // 强制 STOP
      I2C1->SR1 &= ~I2C_SR1_AF;   // 清除 AF 标志
      
  • (2) 总线仲裁丢失(Arbitration Lost)
  • 触发条件:多主机竞争时,STM32 失去总线控制权。
  • 状态标志SR1.ARLO=1
  • 恢复操作
    1. 清除 ARLO:写 SR1ARLO 位为 0。
    2. 重新初始化 I2C 外设(PE=0 → 重新配置 → PE=1)。

  • 6. 关键代码示例(寄存器级操作)

    主机发送单字节数据
    // 等待总线空闲
    while (I2C1->SR2 & I2C_SR2_BUSY);
    
    // 1. 发送 START
    I2C1->CR1 |= I2C_CR1_START;
    
    // 2. 等待 SB=1
    while (!(I2C1->SR1 & I2C_SR1_SB));
    
    // 3. 发送从机地址(写模式)
    I2C1->DR = (0x50 << 1); // 假设从机地址 0x50
    
    // 4. 等待 ADDR=1
    while (!(I2C1->SR1 & I2C_SR1_ADDR));
    volatile uint32_t temp = I2C1->SR1; // 清除 SR1.ADDR
    temp = I2C1->SR2;                  // 清除 SR2 状态
    
    // 5. 发送数据
    I2C1->DR = 0xAA; // 发送数据 0xAA
    while (!(I2C1->SR1 & I2C_SR1_TXE)); // 等待 TXE=1
    
    // 6. 发送 STOP
    I2C1->CR1 |= I2C_CR1_STOP;
    

    7. 深入理解寄存器交互

  • DR 寄存器的双缓冲机制
  • TXE=1 时,表示 DR 已空,可以写入下一个字节,但硬件可能仍在发送前一个字节。
  • 写入 DR 会触发硬件开始发送,同时清除 TXE 位。
  • 状态标志的清除顺序
  • ADDR 标志必须通过 先读 SR1,再读 SR2 来清除。
  • AFARLO 标志需通过写 0 清除。

  • 8. 总结

    通过直接操作寄存器,可以精确控制 I2C 的每一个总线动作,但需要严格遵循以下原则:

    1. 状态标志的清除顺序:如 ADDR 必须读 SR1 + SR2。
    2. 时序匹配CCRTRISE 需根据 APB1 时钟频率计算。
    3. 错误恢复:检测到错误标志后,必须清除并重新初始化外设。

    实际开发中,建议结合 STM32 参考手册的 I2C 时序图寄存器描述,通过逻辑分析仪抓取 SCL/SDA 波形,验证寄存器操作是否符合预期。

    作者:W1E

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 IIC外设工作流程详解

    发表回复