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 外设

寄存器配置

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. 发送停止信号

寄存器操作

c 复制代码
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. 发送停止信号

寄存器操作

c 复制代码
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 总线锁死时,可通过软件复位:

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 起始条件已发送
代码
c 复制代码
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 | 地址发送完成 |

代码
c 复制代码
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 数据发送完成
代码
c 复制代码
I2C1->DR = data;  // 发送数据
while (!(I2C1->SR1 & I2C_SR1_TXE));  // 等待数据传输完成

2.4 发送停止信号

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

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

步骤

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

3.1 发送起始信号

与主机发送数据相同:

c 复制代码
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 | 地址发送完成 |

代码
c 复制代码
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 读取数据 数据被取走
代码
c 复制代码
while (!(I2C1->SR1 & I2C_SR1_RXNE));  // 等待数据准备好
data = I2C1->DR;  // 读取数据

3.4 发送 NACK

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

3.5 发送停止信号

与主机发送数据相同:

c 复制代码
I2C1->CR1 |= I2C_CR1_STOP;  // 发送停止信号

4. 复位 I²C 总线

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

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. 代码实现

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

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

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

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

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

  • BUSY (SR21):总线忙状态。
  • BTF (SR12):字节传输完成。
  • AF (SR110):应答失败(NACK)。
  • ARLO (SR19):仲裁丢失。
  • BERR (SR18):总线错误。

错误处理

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

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

c 复制代码
// 等待 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 位(CCR15)。
  • TRISE :设置 SCL 上升时间(例如,标准模式:TRISE = APB1_CLK(MHz) + 1)。
  • CR1PE 位:置 1 使能 I2C 外设。

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

(1) 生成起始条件(START)
  1. 操作寄存器
    • CR1START 位置 1。
  2. 硬件行为
    • I2C 硬件检测总线空闲(SR2.BUSY=0)后,生成 START 条件。
  3. 状态标志变化
    • SR1.SB 置 1:表示 START 条件已生成。
  4. 关键操作
    • 必须 读取 SR1 寄存器 (清除 SB 位),然后立即写入从机地址到 DR 寄存器。
(2) 发送从机地址
  1. 写入地址到 DR
    • DR 写入 (SlaveAddr << 1) | 0(0 表示写操作)。
  2. 硬件行为
    • 硬件自动发送地址 + R/W 位,并等待从机的 ACK。
  3. 状态标志变化
    • SR1.ADDR 置 1:地址已发送且收到 ACK。
    • SR2.TRA 置 1:表示当前处于发送模式。
  4. 关键操作
    • 必须 读取 SR1SR2 寄存器以清除 ADDR 标志。

    • 示例代码:

      c 复制代码
      volatile uint32_t dummy = I2C1->SR1; // 读取 SR1 清除 ADDR
      dummy = I2C1->SR2;                   // 读取 SR2 清除 BUSY 状态
(3) 发送数据字节
  1. 写入数据到 DR
    • DR 写入待发送的数据(例如 0x55)。
  2. 硬件行为
    • 硬件将 DR 中的数据移到移位寄存器,并逐位发送到 SDA 线。
  3. 状态标志变化
    • SR1.TXE 置 1:表示 DR 已空,可以写入下一个字节。
    • SR1.BTF 置 1:表示当前字节已完全发送(包括 ACK 周期)。
  4. 关键操作
    • TXE=1,写入新数据到 DR,硬件自动清除 TXE
    • BTF=1,表示数据已发送完成,但需结合 TXE 判断。
(4) 发送停止条件(STOP)
  1. 操作寄存器
    • CR1STOP 位置 1。
  2. 硬件行为
    • 生成 STOP 条件,释放总线。
  3. 状态标志变化
    • SR2.BUSY 置 0:总线空闲。

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

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

      c 复制代码
      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. 关键代码示例(寄存器级操作)

主机发送单字节数据
c 复制代码
// 等待总线空闲
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 波形,验证寄存器操作是否符合预期。

相关推荐
国科安芯11 小时前
航天器多路并联大功率电源系统设计与ASP4644均流特性分析
单片机·嵌入式硬件·fpga开发·安全性测试
茯苓gao13 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉16 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者17 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间18 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious18 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng1 天前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计1 天前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
kebidaixu1 天前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片详解
stm32
kebidaixu1 天前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案
stm32