当前位置: 首页 > article >正文

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_CR1I2C_SR1说明
`I2C1->CR1= I2C_CR1_START;`START = 1
等待 SB=1SB=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_DRI2C_SR1说明
I2C1->DR = data;发送数据TXE=0数据正在发送
等待 TXE=1TXE=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_SR1I2C_DR说明
等待 RXNE=1RXNE=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_CR1START=1产生起始信号
发送设备地址(写)I2C_DRADDR=1发送地址
发送寄存器地址I2C_DRTXE=1发送数据
发送重复起始信号I2C_CR1START=1重新开始
发送设备地址(读)I2C_DRADDR=1发送地址
读取数据I2C_DRRXNE=1接收数据
发送 NACKI2C_CR1ACK=0结束读取
发送停止信号I2C_CR1STOP=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. 典型寄存器操作示例(主机发送)

// 等待 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 标志。
    • 示例代码:
      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.STARTSR1.SBSR1.ADDRSR1.TXESR1.BTFSR2.BUSY
初始状态000000
设置 START=1100001
START 生成完成110001
写入地址到 DR100001
地址发送完成101001
清除 ADDR(读 SR1/SR2)100001
写入数据到 DR100001
数据开始发送100001
数据发送完成100111
设置 STOP=1000110

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 波形,验证寄存器操作是否符合预期。


http://www.kler.cn/a/576164.html

相关文章:

  • nature genetics | SCENT:单细胞多模态数据揭示组织特异性增强子基因图谱,并可识别致病等位基因
  • 监听-追溯
  • 光路科技将携最新TSN交换机亮相高速展,展示智慧交通创新解决方案
  • AI 实战 - pytorch框架基于retinaface实现face检测
  • 游戏引擎学习第143天
  • Nginx多服务器转发接口数据,实现单接口多服务器处理数据,达到数据共享
  • 【微信小程序】uniapp开发微信小程序
  • 网络安全区划分
  • 使用PHP实现微服务架构:挑战与解决方案
  • SQL-labs13-16闯关记录
  • Docker Desktop常见问题记录
  • 微信小程序+SpringBoot的单词学习小程序平台(程序+论文+讲解+安装+修改+售后)
  • 【计算机网络】计算机网络的性能指标——时延、时延带宽积、往返时延、信道利用率
  • Java 面试篇-SSM 框架专题(什么是 AOP?Spring 中事务时如何实现的?事务失效的场景?Spring 中循环引用怎么解决?Springboot 的自动配置原理?Spring 常见注解?)
  • BFS(八)515. 在每个树行中找最大值 中等
  • OpenCV计算摄影学(14)实现对比度保留去色(Contrast Preserving Decolorization)的函数decolor()
  • SpringBoot知识点及其源码解析(1)
  • 发行基础:热销商品榜单
  • 图形界面控件编程(iOS)
  • 机器学习——回归树