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

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 = 36MHzI2C1->CCR = 180; // 标准模式(100kHz):T_high = T_low = 10us, CCR = 180I2C1->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;  // 发送 NACKI2C1->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. 发送 NACKI2C1->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 波形,验证寄存器操作是否符合预期。

相关文章:

STM32_IIC外设工作流程

STM32 IC 外设工作流程&#xff08;基于寄存器&#xff09; 在 STM32 中&#xff0c;IC 通信主要通过一系列寄存器控制。理解这些寄存器的作用&#xff0c;能够帮助我们掌握 IC 硬件的运行机制&#xff0c;实现高效的数据传输。本文以 STM32F1&#xff08;如 STM32F103&#x…...

Python 爬取唐诗宋词三百首

你可以使用 requests 和 BeautifulSoup 来爬取《唐诗三百首》和《宋词三百首》的数据。以下是一个基本的 Python 爬虫示例&#xff0c;它从 中华诗词网 或类似的网站获取数据并保存为 JSON 文件。 import requests from bs4 import BeautifulSoup import json import time# 爬取…...

浅浅初识AI、AI大模型、AGI

前记&#xff1a;这里只是简单了解&#xff0c;后面有时间会专门来扩展和深入。 当前&#xff0c;人工智能&#xff08;AI&#xff09;及其细分领域&#xff08;如AI算法工程师、自然语言处理NLP、通用人工智能AGI&#xff09;的就业前景呈现高速增长态势&#xff0c;市场需求…...

Spring40种注解(下)!!

Spring Bean 注解 ComponentScan ComponentScan注解用于配置Spring需要扫描的被组件注解注释的类所在的包。 可以通过配置其basePackages属性或者value属性来配置需要扫描的包路径。value属性是basePackages的别名。 Component Component注解用于标注一个普通的组件类&#…...

DeepSeek 系列模型:论文精读《A Survey of DeepSeek Models》

引言&#xff1a;一篇快速了解 DeepSeek 系列的论文。我在翻译时加入了一些可以提高 “可读性” 的连词 ✅ NLP 研 2 选手的学习笔记 笔者简介&#xff1a;Wang Linyong&#xff0c;NPU&#xff0c;2023级&#xff0c;计算机技术 研究方向&#xff1a;文本生成、大语言模型 论文…...

LeetCode hot 100—环形链表 II

题目 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部…...

【AI】【Unity】关于Unity接入DeepseekAPI遇到的坑

前言 由于deepseek网页端在白天日常抽风&#xff0c;无法正常的使用&#xff0c;所以调用API就成了目前最好的选择&#xff0c;尤其是Deepseek的API价格低得可怕&#xff0c;这不是和白送的一样吗&#xff01;然后使用过很多本地部署接入API的方式&#xff0c;例如Chatbox、Pa…...

计算机视觉算法实战——医学影像分割(主页有源码)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​​​ 1. 领域简介✨✨ 医学影像分割是计算机视觉在医疗领域的重要应用&#xff0c;旨在从CT、MRI、X光等医学图像中精确分割出目标区域&…...

51单片机——存储类型

主要内容&#xff1a;区分data,bdata,idata,pdata,xdata,code 8051系列单片机存储器结构的特点&#xff1a;ROM和RAM独立编址 8051系列单片机将程序存储器&#xff08;ROM&#xff09;和数据存储器&#xff08;RAM&#xff09;分开&#xff0c;并有各自的寻址机构和寻址方式。…...

python19-if和match的美

课程&#xff1a;B站大学 记录python学习&#xff0c;直到学会基本的爬虫&#xff0c;使用python搭建接口自动化测试就算学会了&#xff0c;在进阶webui自动化&#xff0c;app自动化 分支语句那些事儿 if 条件判断if...else 判断语句if...elif...else 多重条件分支嵌套也能在 e…...

期权有哪些用处?期权和期货比优势在哪?

期权如同金融市场的“瑞士军刀”&#xff0c;既能防御风险&#xff0c;又能主动出击。相较于期货的“刚性对决”&#xff0c;期权更像“柔性博弈”——通过策略组合在不确定性中捕捉确定性收益。 期权有哪些用处&#xff1f; 期权的核心价值在于其非对称性——买方风险有限&am…...

【html期末作业网页设计】

html期末作业网页设计 作者有话说项目功能介绍 网站结构完整代码网站样图 作者有话说 目前&#xff0c;我们的项目已经搭建了各页面的基本框架&#xff0c;但内容填充还不完善&#xff0c;各页面之间的跳转逻辑也还需要进一步优化。 我们深知&#xff0c;一个好的项目需要不断…...

ComfyUI AnimeDiff动画参数总结

ComfyUI AnimeDiff动画参数总结 一、动画生成核心参数 参数名称建议值/范围作用说明备注步数&#xff08;Steps&#xff09;15-25控制AI计算迭代次数&#xff0c;越高细节越精细&#xff0c;但耗时更长推荐20步&#xff0c;显存不足可降至15步CFG值7.0-8.5提示词对画面的控制…...

基于Three.js的多视图3D Tiles同步可视化技术解析

文章目录 基于Three.js的多视图3D Tiles同步可视化技术解析一、技术背景与价值二、核心实现原理2.1 视口分割算法2.2 视角同步机制三、关键代码解析3.1 渲染管线优化3.2 3D Tiles加载四、交互系统实现4.1 多视图事件分发4.2 射线拾取优化五、性能优化方案5.1 渲染性能指标5.2 W…...

7、什么是死锁,如何避免死锁?【高频】

&#xff08;1&#xff09;什么是死锁&#xff1a; 死锁 是指在两个或多个进程的执行时&#xff0c;每个进程都持有资源&#xff0c;并都在等待其他进程 释放 它所需的资源&#xff0c;如果此时所有的进程一直占有资源而不释放&#xff0c;就导致了死锁。 死锁只有同时满足 四…...

自动化学习-使用git进行版本管理

目录 一、为什么要学习git 二、git是什么 三、git如何使用 1、git的下载安装和配置 2、git常用的命令 3、gitee远程仓库的使用 &#xff08;1&#xff09;注册 &#xff08;2&#xff09;创建仓库 &#xff08;3&#xff09;配置公钥&#xff08;建立电脑和git…...

前端大文件上传

一、切片上传技术原理 切片上传是把大文件分割成多个较小的切片&#xff0c;分别上传这些切片&#xff0c;最后在服务器端将它们合并成完整文件。这种方式能有效应对网络不稳定导致的上传失败问题&#xff0c;还可利用多线程并行上传&#xff0c;提升上传效率。 二、前端实现…...

【网络】实现电脑与笔记本电脑之间的直接网络连接

要实现电脑与笔记本电脑之间的直接网络连接&#xff0c;可以通过有线或无线两种方式。以下是详细的步骤指南&#xff1a; 一、有线直连&#xff08;通过网线&#xff09; 1. 准备工具 网线&#xff1a;使用交叉网线&#xff08;适用于旧设备&#xff09;或普通直连网线&#…...

“深入浅出”系列之音视频开发:(12)使用FFmpeg实现倍速播放:技术细节与优化思路

一、前言 在音视频处理领域&#xff0c;倍速播放是一个常见的需求&#xff0c;尤其是在视频播放器、在线教育平台等场景中&#xff0c;用户常常需要以不同的速度播放视频内容。然而&#xff0c;实现一个高质量的倍速播放功能并不容易&#xff0c;尤其是在处理音频时&#xff0…...

qt作业day2

1&#xff1a;在注册登录的练习里面&#xff0c;追加一个QListWidget 项目列表 要求&#xff1a;点击注册之后&#xff0c;将账号显示到 listWidget上面去 以及&#xff0c;在listWidget中双击某个账号的时候&#xff0c;将该账号删除 .h #ifndef WIDGET_H #define WIDGET_H …...

Qt:day1

一、作业 写1个Widget窗口&#xff0c;窗口里面放1个按钮&#xff0c;按钮随便叫什么&#xff1b; 创建2个Widget对象&#xff1a; Widget w1, w2; w1.show(); w2不管&#xff1b; 要求&#xff1a; 点击 w1.btn&#xff0c;w1隐藏&#xff0c;w2显示&#xff1b; 点击 w2.btn&…...

基于微信小程序的停车场管理系统的设计与实现

第1章 绪论 1.1 课题背景 随着移动互联形式的不断发展&#xff0c;各行各业都在摸索移动互联对本行业的改变&#xff0c;不断的尝试开发出适合于本行业或者本公司的APP。但是这样一来用户的手机上就需要安装各种软件&#xff0c;但是APP作为一个只为某个公司服务的一个软件&a…...

详细Linux基础知识(不断完善)

终端类型分类 1. 物理终端 直接连接到计算机的硬件设备2. 虚拟终端 通过快捷键切换的文本模式界面: Ctrl + Alt + F1 # 登录窗口 Ctrl + Alt + F2 # 当前图形界面 Ctrl + Alt + F3 # 虚拟命令终端 Ctrl + Alt + F4-F6 # 备用虚拟终端3. 图形终端 模拟终端:图形环境中的…...

类和对象-继承-C++

1.定义 面向对象的三大特征之一&#xff0c;为了减少重复的代码 2.语法 class 子类 &#xff1a;继承方式 父类 &#xff08;子类也叫派生类&#xff0c;父类也称为基类&#xff09; 例&#xff1a;class age&#xff1a;public person&#xff1b; #include<iostrea…...

初阶数据结构(C语言实现)——3顺序表和链表(1)

目录 【本节目标】1. 线性表2.顺序表2.1概念及结构2.2 接口实现2.2.0 动态顺序表2.2.1 顺序表初始化SLInit&#xff08;&#xff09;2.2.2 销毁和打印2.2.3 尾插SLPushBack&#xff08;&#xff09;2.2.4 尾删SLPopBack&#xff08;&#xff09;2.2.5 头插2.2.6 头删2.2.7 插入…...

nuxt常用组件库html-validator、@nuxtjs/i18n、@nuxt/image、@unocss/nuxt使用解析

html-validator 主要用于自动验证nuxt服务器呈现的HTML(SSR和SSG)&#xff0c;以检测可能导致水合错误的HTML常见问题&#xff0c;有助于减少水合错误&#xff0c;检测常见的可访问性错误。 安装 npx nuxilatest module add html-validator配置 若自动更新nuxt.config.ts配置文…...

4G工业路由器在公交充电桩中的应用与优势

随着电动公交车的普及&#xff0c;公交充电桩的稳定运行和高效管理是交通营运部门最关心的问题。4G工业路由器凭借其卓越的数据采集和通讯能力&#xff0c;成为实现充电桩智能化管理的关键。 公交充电桩运维管理需求概述&#xff1a; 1.实时性&#xff1a;实时监控充电状态、剩…...

matlab 四维数据可视化(已解决)

虽然这不是传统意义上的“4维可视化”&#xff0c;但你可以通过在三维空间中表示两个维度来间接展示4维数据。例如&#xff0c;你可以使用颜色来表示第四个维度。 clc clear close all% 假设X, Y, Z为你的三维数据&#xff0c;C为第四维数据 X rand(100, 1); Y rand(100, 1);…...

歌曲分类和流行度预测

1. 项目介绍 本项目从kaggle平台上下载了数据集&#xff0c;该数据集包含了3万多首来自Spotify API 的歌曲&#xff0c;共有23个特征。首先对数据集进行预处理&#xff0c;如重复行、缺失值、标准化处理等。再对预处理后的数据进行探索性分析&#xff0c;观察各变量的分布情况&…...

经验分享:用一张表解决并发冲突!数据库事务锁的核心实现逻辑

背景 对于一些内部使用的管理系统来说&#xff0c;可能没有引入Redis&#xff0c;又想基于现有的基础设施处理并发问题&#xff0c;而数据库是每个应用都避不开的基础设施之一&#xff0c;因此分享个我曾经维护过的一个系统中&#xff0c;使用数据库表来实现事务锁的方式。 之…...

oracle decode

1. 基本语法 DECODE(expression, search1, result1, search2, result2, ..., default_result) expression &#xff1a;需要比较的表达式或列。search1, search2, ... &#xff1a;要匹配的值。result1, result2, ... &#xff1a;当 expression 等于 search 时返回的结果。def…...

让后台界面布局更灵活:在GrapesJS中复刻Java的五区式布局

当你想要在可视化编辑器中做一个类似Java BorderLayout 的五区布局&#xff0c;却发现市面上大多只能“简单拼接”而难以自由扩展时&#xff0c;你或许就需要一个更灵活的布局管理器来帮忙。本篇文章就从这个痛点开始&#xff0c;带你一步步揭秘如何用 GrapesJS 自定义并实现一…...

【网络安全 | 漏洞挖掘】分享21个基础漏洞案例

未经许可,不得转载。 文章目录 案例1:绕过500状态码案例2:修改前端实现任意文件上传案例3:Nmap扫描端口命令案例4:绕过限制实现任意文件读取案例5:删除任意目录文件案例6:锁定任意账户案例7:重置任意用户密码案例8:接管任意账户方法一方法二案例9:功能校验机制绕过案…...

期权适合什么类型的投资者交易?

财顺小编本文主要介绍期权适合什么类型的投资者交易&#xff1f;期权适合的投资者类型需结合其风险偏好、投资目标及市场判断能力综合评估。 期权适合什么类型的投资者交易&#xff1f; 1. 风险管理型投资者&#xff08;稳健型&#xff09; 适用场景&#xff1a;持有股票、大…...

浅谈C++/C命名冲突

前言 在这里我会简要地介绍产生命名冲突的原因&#xff0c;和C中处理命名冲突的方法&#xff0c;同时和C语言的解决办法进行比较。 相信你在阅读完之后一定会有收获。对于我们来说&#xff0c;了解编译器的编译链接过程才能更好的理解编译器是如何报错的&#xff0c;更能让我们…...

【星云 Orbit • STM32F4】08. 用判断数据头来接收据的串口通用程序框架

【星云 Orbit • STM32F4】08. 用判断数据头来接收据的串口通用程序框架 1. 引言 本教程旨在帮助嵌入式开发小白从零开始&#xff0c;学习如何在STM32F407微控制器上实现一个基于串口的数据接收程序。该程序能够通过判断数据头来接收一串数据&#xff0c;并将其存储到缓冲区中…...

C# OnnxRuntime部署DAMO-YOLO香烟检测

目录 说明 效果 模型信息 项目 代码 下载 参考 说明 效果 模型信息 Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Floa…...

探索Elasticsearch:索引的CRUD

在企业环境中&#xff0c;Elasticsearch的索引CRUD&#xff08;创建Create、读取Read、更新Update、删除Delete&#xff09;操作是非常基础且频繁使用的功能。这些操作对于管理和维护数据至关重要&#xff0c;尤其是在处理大规模数据集和需要实时搜索与分析的应用场景中。 目录…...

C++:多态与虚函数

1.虚函数&#xff0c;在函数前加virtual即可。有虚函数时&#xff0c;父类指针指向父类对象时就会使用父类的成员&#xff0c;指向子类对象时就可以使用子类成员&#xff0c;进而我们引入了多态的概念。 2.多态&#xff1a;父类指针指向子类的对象&#xff0c;通过父类指针调用…...

【Python 数据结构 2.时间复杂度和空间复杂度】

Life is a journey —— 25.2.28 一、引例&#xff1a;穷举法 1.单层循环 所谓穷举法&#xff0c;就是我们通常所说的枚举&#xff0c;就是把所有情况都遍历了的意思。 例&#xff1a;给定n&#xff08;n ≤ 1000&#xff09;个元素ai&#xff0c;求其中奇数有多少个 判断一…...

【Qt QML】QML鼠标事件(MouseArea)

QML鼠标事件全面解析 一、MouseArea基础概念 在 QML 中,鼠标事件是处理用户与界面元素交互的重要部分。QML 提供了多种方式来处理鼠标事件,MouseArea 是 QML 中用于处理鼠标事件的核心元素,它可以覆盖在其他元素之上,捕获鼠标操作并触发相应的信号。 1、基本用法 import …...

医脉云枢:中医药典籍知识图谱与非遗传承多维可视化系统

核心优势&#xff1a; "医脉"直击主题&#xff0c;"云枢"体现技术前瞻性 "非遗传承"呼应二十大文化政策 "多维"涵盖3D模型、时间轴、地图等多种可视化形式 技术栈&#xff1a;Vue Flask Element UI ECharts MySQL 同时参考了…...

vue 和 react 底层采用的 diff 算法的区别

Vue 3 和 React 在底层 Diff 算法上的实现确实有一些区别&#xff0c;主要体现在设计理念、性能优化策略以及具体实现方式上。以下是对两者 Diff 算法差异的详细分析&#xff1a; 1. 总体设计理念 Vue 3 的 Diff 算法 Vue 3 的虚拟 DOM Diff 算法基于“双端比较”思想&#xff…...

养老小程序方案详解居家养老小程序系统

养老小程序&#xff0c;上门居家养老小程序&#xff0c;用户端护工端小程序&#xff0c;管理后台。php开发语言&#xff0c;可源码搭建&#xff0c;二次开发或者定制开发。 一 用户端&#xff1a;小程序 核心功能模块&#xff1a;用户完善个人健康档案&#xff0c;在线选择服…...

Tauri跨平台开发问题及解决方案深度解析(React版)

Tauri跨平台开发问题及解决方案深度解析&#xff08;React版&#xff09; 一、环境配置与项目初始化难题&#xff08;React适配&#xff09; 1.1 React项目初始化 推荐模板&#xff1a; # 使用ReactTypeScript模板 npm create tauri-applatest -- --template react-ts# 项目…...

【OMCI实践】omci.lua脚本文件(独家分享)

引言 omci.lua文件是Wireshark的OMCI协议解析插件的核心组件。它配合BinDecHex.lua&#xff0c;可以解析OMCI协议的数据包&#xff0c;提取出消息类型、受管实体标识、受管实体属性等关键信息&#xff0c;并以人类可读的形式显示在Wireshark的解码视图中&#xff0c;方便研发人…...

React高级内容探索

flushSync确保了DOM立即更新 flushSync让你强制React同步刷新提供回调中的任何更新&#xff0c;这确保了DOM立即更新 flushSync是DOM更新之后的&#xff0c;像vue中的nextTick&#xff1a; import { useState,useRef} from "react" import { flushSync} from &quo…...

前端水印实现方式

一、简介 简单来说&#xff0c;前端水印就是在网页或应用程序的前端界面上添加的一种标记&#xff0c;通常是文本、图标或图案等形式。它就像给你的数字内容贴上了一个独特的 “标签”&#xff0c;用于标识内容的归属、防止未经授权的使用和传播。比如&#xff0c;一些在线图片…...

【新手入门】SQL注入之getshell(木马)

木马介绍 木马其实就是一段程序&#xff0c;这个程序运行到目标主机上时&#xff0c;主要可以对目标进行远程控制、盗取信息等功能&#xff0c;一般不会破坏目标主机&#xff0c;当然&#xff0c;这也看黑客是否想要搞破坏。 按照功能分类:远控型、破坏型、流氓软件型、盗取信…...

Git操作指南:分支合并、回退及其他重要操作

在软件开发的协作过程中&#xff0c;Git 作为一款强大的版本控制系统&#xff0c;能帮助开发者高效管理代码的各个版本和分支。本文将详细介绍 Git 中常见的分支合并、取消本地修改、回退操作等&#xff0c;并提供通俗易懂的解释和步骤指南。 一、分支合并 分支合并是 Git 工…...