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 (SR2[1]):总线忙状态。
  • BTF (SR1[2]):字节传输完成。
  • AF (SR1[10]):应答失败(NACK)。
  • ARLO (SR1[9]):仲裁丢失。
  • BERR (SR1[8]):总线错误。

错误处理

  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 位(CCR[15])。
  • 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 波形,验证寄存器操作是否符合预期。

相关推荐
花落已飘2 小时前
STM32Cubemx配置E22-xxxT22D lora模块实现定点传输
stm32·单片机·嵌入式硬件
BUG_MeDe3 小时前
[51 单片机] --串口编程
单片机·嵌入式硬件
华清远见成都中心5 小时前
学单片机能从事什么工作?
单片机·嵌入式硬件
Leiditech__7 小时前
浅谈汽车系统电压优缺点分析
嵌入式硬件·汽车·硬件工程·emc·mosfet
cykaw259013 小时前
如果STM32板子上晶振不是8MHz而是其他(如12MHz)怎么办?
stm32·单片机·嵌入式·电子
电子科技圈13 小时前
XMOS推出“免开发固件方案”将数字接口音频应用的开发门槛大幅降低
经验分享·科技·嵌入式硬件·音视频·语音识别·实时音视频·视频编解码
三年呀15 小时前
19.4-STM32接收数据-状态显示在屏幕
stm32·单片机·嵌入式硬件·智能小车
程序员JerrySUN15 小时前
Yocto + 树莓派摄像头驱动完整指南
linux·运维·服务器·嵌入式硬件·物联网
晓风伴月17 小时前
单片机应用:定时闪烁的LED小灯的实现
单片机·嵌入式硬件
平凡灵感码头17 小时前
STM32 两个单片机之间的通信
stm32·单片机·mongodb