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. 主机模式发送数据
主机模式下,发送数据的流程如下:
- 检查总线状态
- 发送起始信号
- 发送从机地址(写)
- 发送数据
- 发送停止信号
寄存器操作
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. 主机模式接收数据
- 发送起始信号
- 发送从机地址(写)
- 发送寄存器地址
- 发送重复起始信号
- 发送从机地址(读)
- 读取数据
- 发送 NACK,结束传输
- 发送停止信号
寄存器操作
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 主机发送数据(寄存器变化)
步骤
- 发送 起始信号
- 发送 从机地址 + 写(bit 0 = 0)
- 发送 数据字节
- 发送 停止信号
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
| |
| 读取 SR2
清 ADDR
| | 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 主机接收数据(寄存器变化)
步骤
- 发送 起始信号
- 发送 从机地址 + 读(bit 0 = 1)
- 读取 数据
- 发送 NACK
- 发送 停止信号
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
| |
| 读取 SR2
清 ADDR
| | 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 传输状态(SB
、ADDR
、TXE
、RXNE
)。I2C_DR
数据寄存器:用于收发数据。I2C_CR1
控制寄存器:用于产生START
、STOP
、ACK
。
寄存器级的 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_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
监视数据传输状态(SB
、ADDR
、TXE
、RXNE
)。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) 初始化配置
- 配置 GPIO:将 SCL/SDA 引脚设为复用开漏模式(需外部上拉电阻)。
- 配置 I2C 时钟:
- CR2 的
FREQ[5:0]
位:设置 APB 时钟频率(单位 MHz)。
- CR2 的
- 配置时钟分频:
- CCR 的
CCR[11:0]
位:设置 SCL 时钟分频。 - 标准模式(100 kHz)或快速模式(400 kHz)。
- CCR 的
- 配置上升时间:
- TRISE:根据模式设置(标准模式:1000ns →
TRISE = F_APB1(MHz) + 1
)。
- TRISE:根据模式设置(标准模式:1000ns →
- 使能 I2C:
- CR1 的
PE
位置 1,使能外设。
- CR1 的
(2) 发送起始条件
- 生成 START 信号:
- CR1 的
START
位置 1。
- CR1 的
- 等待起始条件完成:
- 轮询 SR1 的
SB
位(Start Bit),当SB=1
时,起始条件生成成功。 - 必须读取 SR1 后写 DR 寄存器(硬件自动清除
SB
)。
- 轮询 SR1 的
(3) 发送从机地址
- 写入从机地址 + 方向位:
- DR 写入
7-bit地址<<1 | R/W位(0 表示写)
。
- DR 写入
- 等待地址应答:
- 轮询 SR1 的
ADDR
位(地址发送完成)。 - 必须读取 SR1 和 SR2 以清除
ADDR
标志。
- 轮询 SR1 的
(4) 发送数据
- 写入数据到 DR:
- DR 写入待发送的数据字节。
- 等待数据发送完成:
- 轮询 SR1 的
TXE
位(Transmit Data Register Empty)。 - 当
TXE=1
,表示数据已转移到移位寄存器,可写入下一字节。
- 轮询 SR1 的
- 重复步骤 4.1-4.2 发送所有数据。
(5) 发送停止条件
- 生成 STOP 信号:
- CR1 的
STOP
位置 1。
- CR1 的
- 等待停止完成:
- 根据时序要求等待(无需轮询特定标志)。
3. 主机接收模式(Master Receiver)流程
流程与发送模式类似,但需注意:
- 发送从机地址时,
R/W
位置 1。 - 配置 CR1 的
ACK
位以控制是否发送应答:- 在接收最后一个字节前,
ACK
位需清零(发送 NACK)。
- 在接收最后一个字节前,
- 从 DR 读取数据前,需检查 SR1 的
RXNE
位(Receive Data Register Not Empty)。
4. 关键状态标志与错误处理
- BUSY (SR2[1]):总线忙状态。
- BTF (SR1[2]):字节传输完成。
- AF (SR1[10]):应答失败(NACK)。
- ARLO (SR1[9]):仲裁丢失。
- BERR (SR1[8]):总线错误。
错误处理:
- 检测到错误标志后,清除错误(通过写 CR1 的
SWRST
位或重新初始化)。 - 重新发起通信。
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. 注意事项
- 时钟配置:
CCR
和TRISE
需严格匹配时序要求。 - 标志清除顺序:某些标志(如
ADDR
)需通过读 SR1 + SR2 清除。 - 中断/DMA:可通过 CR2 配置中断或 DMA 传输,减少 CPU 轮询开销。
通过直接操作寄存器,开发者可以更精细地控制 I2C 时序,但需严格遵循 STM32 参考手册的流程以避免硬件错误。
好的!我将更详细地分析 I2C 外设在数据传输过程中寄存器的具体变化,结合 STM32 的硬件行为,逐步拆解每个阶段的寄存器操作和状态标志的响应。以下是深入细节的流程说明:
1. 初始化阶段的寄存器配置
(1) GPIO 模式配置
- GPIOx_CRL/CRH:配置 SCL/SDA 引脚为 复用开漏模式(
GPIO_Mode_AF_OD
)。 - GPIOx_ODR:无需手动设置,但硬件要求外部上拉电阻。
(2) I2C 时钟与模式配置
- CR2 的
FREQ[5:0]
:设置 APB1 总线时钟频率(例如,36 MHz →FREQ=36
)。 - CCR 的
CCR[11:0]
:- 标准模式(100 kHz):
CCR = APB1_CLK / (2 * 100000)
。 - 快速模式(400 kHz):
CCR = APB1_CLK / (2 * 400000)
。 - 快速模式+(1 MHz):需使能
F/S
位(CCR[15])。
- 标准模式(100 kHz):
- TRISE:设置 SCL 上升时间(例如,标准模式:
TRISE = APB1_CLK(MHz) + 1
)。 - CR1 的
PE
位:置 1 使能 I2C 外设。
2. 主机发送模式(Master Transmitter)详细流程
(1) 生成起始条件(START)
- 操作寄存器:
- CR1 的
START
位置 1。
- CR1 的
- 硬件行为:
- I2C 硬件检测总线空闲(
SR2.BUSY=0
)后,生成 START 条件。
- I2C 硬件检测总线空闲(
- 状态标志变化:
- SR1.SB 置 1:表示 START 条件已生成。
- 关键操作:
- 必须 读取 SR1 寄存器(清除
SB
位),然后立即写入从机地址到 DR 寄存器。
- 必须 读取 SR1 寄存器(清除
(2) 发送从机地址
- 写入地址到 DR:
- DR 写入
(SlaveAddr << 1) | 0
(0 表示写操作)。
- DR 写入
- 硬件行为:
- 硬件自动发送地址 + R/W 位,并等待从机的 ACK。
- 状态标志变化:
- SR1.ADDR 置 1:地址已发送且收到 ACK。
- SR2.TRA 置 1:表示当前处于发送模式。
- 关键操作:
- 必须 读取 SR1 和 SR2 寄存器以清除
ADDR
标志。 - 示例代码:
volatile uint32_t dummy = I2C1->SR1; // 读取 SR1 清除 ADDR dummy = I2C1->SR2; // 读取 SR2 清除 BUSY 状态
- 必须 读取 SR1 和 SR2 寄存器以清除
(3) 发送数据字节
- 写入数据到 DR:
- DR 写入待发送的数据(例如
0x55
)。
- DR 写入待发送的数据(例如
- 硬件行为:
- 硬件将 DR 中的数据移到移位寄存器,并逐位发送到 SDA 线。
- 状态标志变化:
- SR1.TXE 置 1:表示 DR 已空,可以写入下一个字节。
- SR1.BTF 置 1:表示当前字节已完全发送(包括 ACK 周期)。
- 关键操作:
- 若
TXE=1
,写入新数据到 DR,硬件自动清除TXE
。 - 若
BTF=1
,表示数据已发送完成,但需结合TXE
判断。
- 若
(4) 发送停止条件(STOP)
- 操作寄存器:
- CR1 的
STOP
位置 1。
- CR1 的
- 硬件行为:
- 生成 STOP 条件,释放总线。
- 状态标志变化:
- SR2.BUSY 置 0:总线空闲。
3. 主机接收模式(Master Receiver)详细流程
(1) 生成 START 并发送读地址
- 发送 START(同发送模式)。
- 写入读地址到 DR:
- DR 写入
(SlaveAddr << 1) | 1
(1 表示读操作)。
- DR 写入
- 状态标志变化:
- SR1.ADDR 置 1:地址发送成功。
- SR2.TRA 置 0:表示当前处于接收模式。
(2) 接收数据流程
- 配置 ACK/NACK:
- CR1.ACK 置 1:使能 ACK(接收每个字节后发送 ACK)。
- 在接收最后一个字节前,需 清零 ACK 位(发送 NACK)。
- 读取数据:
- 等待 SR1.RXNE=1:表示 DR 中有新数据。
- 读取 DR 寄存器,硬件自动清除
RXNE
。
- 状态标志变化:
- 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。
- 恢复操作:
- 清除
AF
标志:写 SR1 的AF
位为 0。 - 生成 STOP 或重复 START:
I2C1->CR1 |= I2C_CR1_STOP; // 强制 STOP I2C1->SR1 &= ~I2C_SR1_AF; // 清除 AF 标志
- 清除
(2) 总线仲裁丢失(Arbitration Lost)
- 触发条件:多主机竞争时,STM32 失去总线控制权。
- 状态标志:SR1.ARLO=1。
- 恢复操作:
- 清除
ARLO
:写 SR1 的ARLO
位为 0。 - 重新初始化 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 来清除。AF
和ARLO
标志需通过写 0 清除。
8. 总结
通过直接操作寄存器,可以精确控制 I2C 的每一个总线动作,但需要严格遵循以下原则:
- 状态标志的清除顺序:如
ADDR
必须读 SR1 + SR2。 - 时序匹配:
CCR
和TRISE
需根据 APB1 时钟频率计算。 - 错误恢复:检测到错误标志后,必须清除并重新初始化外设。
实际开发中,建议结合 STM32 参考手册的 I2C 时序图 和 寄存器描述,通过逻辑分析仪抓取 SCL/SDA 波形,验证寄存器操作是否符合预期。
相关文章:
STM32_IIC外设工作流程
STM32 IC 外设工作流程(基于寄存器) 在 STM32 中,IC 通信主要通过一系列寄存器控制。理解这些寄存器的作用,能够帮助我们掌握 IC 硬件的运行机制,实现高效的数据传输。本文以 STM32F1(如 STM32F103&#x…...
Python 爬取唐诗宋词三百首
你可以使用 requests 和 BeautifulSoup 来爬取《唐诗三百首》和《宋词三百首》的数据。以下是一个基本的 Python 爬虫示例,它从 中华诗词网 或类似的网站获取数据并保存为 JSON 文件。 import requests from bs4 import BeautifulSoup import json import time# 爬取…...
浅浅初识AI、AI大模型、AGI
前记:这里只是简单了解,后面有时间会专门来扩展和深入。 当前,人工智能(AI)及其细分领域(如AI算法工程师、自然语言处理NLP、通用人工智能AGI)的就业前景呈现高速增长态势,市场需求…...
Spring40种注解(下)!!
Spring Bean 注解 ComponentScan ComponentScan注解用于配置Spring需要扫描的被组件注解注释的类所在的包。 可以通过配置其basePackages属性或者value属性来配置需要扫描的包路径。value属性是basePackages的别名。 Component Component注解用于标注一个普通的组件类&#…...
DeepSeek 系列模型:论文精读《A Survey of DeepSeek Models》
引言:一篇快速了解 DeepSeek 系列的论文。我在翻译时加入了一些可以提高 “可读性” 的连词 ✅ NLP 研 2 选手的学习笔记 笔者简介:Wang Linyong,NPU,2023级,计算机技术 研究方向:文本生成、大语言模型 论文…...
LeetCode hot 100—环形链表 II
题目 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部…...
【AI】【Unity】关于Unity接入DeepseekAPI遇到的坑
前言 由于deepseek网页端在白天日常抽风,无法正常的使用,所以调用API就成了目前最好的选择,尤其是Deepseek的API价格低得可怕,这不是和白送的一样吗!然后使用过很多本地部署接入API的方式,例如Chatbox、Pa…...
计算机视觉算法实战——医学影像分割(主页有源码)
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 1. 领域简介✨✨ 医学影像分割是计算机视觉在医疗领域的重要应用,旨在从CT、MRI、X光等医学图像中精确分割出目标区域&…...
51单片机——存储类型
主要内容:区分data,bdata,idata,pdata,xdata,code 8051系列单片机存储器结构的特点:ROM和RAM独立编址 8051系列单片机将程序存储器(ROM)和数据存储器(RAM)分开,并有各自的寻址机构和寻址方式。…...
python19-if和match的美
课程:B站大学 记录python学习,直到学会基本的爬虫,使用python搭建接口自动化测试就算学会了,在进阶webui自动化,app自动化 分支语句那些事儿 if 条件判断if...else 判断语句if...elif...else 多重条件分支嵌套也能在 e…...
期权有哪些用处?期权和期货比优势在哪?
期权如同金融市场的“瑞士军刀”,既能防御风险,又能主动出击。相较于期货的“刚性对决”,期权更像“柔性博弈”——通过策略组合在不确定性中捕捉确定性收益。 期权有哪些用处? 期权的核心价值在于其非对称性——买方风险有限&am…...
【html期末作业网页设计】
html期末作业网页设计 作者有话说项目功能介绍 网站结构完整代码网站样图 作者有话说 目前,我们的项目已经搭建了各页面的基本框架,但内容填充还不完善,各页面之间的跳转逻辑也还需要进一步优化。 我们深知,一个好的项目需要不断…...
ComfyUI AnimeDiff动画参数总结
ComfyUI AnimeDiff动画参数总结 一、动画生成核心参数 参数名称建议值/范围作用说明备注步数(Steps)15-25控制AI计算迭代次数,越高细节越精细,但耗时更长推荐20步,显存不足可降至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、什么是死锁,如何避免死锁?【高频】
(1)什么是死锁: 死锁 是指在两个或多个进程的执行时,每个进程都持有资源,并都在等待其他进程 释放 它所需的资源,如果此时所有的进程一直占有资源而不释放,就导致了死锁。 死锁只有同时满足 四…...
自动化学习-使用git进行版本管理
目录 一、为什么要学习git 二、git是什么 三、git如何使用 1、git的下载安装和配置 2、git常用的命令 3、gitee远程仓库的使用 (1)注册 (2)创建仓库 (3)配置公钥(建立电脑和git…...
前端大文件上传
一、切片上传技术原理 切片上传是把大文件分割成多个较小的切片,分别上传这些切片,最后在服务器端将它们合并成完整文件。这种方式能有效应对网络不稳定导致的上传失败问题,还可利用多线程并行上传,提升上传效率。 二、前端实现…...
【网络】实现电脑与笔记本电脑之间的直接网络连接
要实现电脑与笔记本电脑之间的直接网络连接,可以通过有线或无线两种方式。以下是详细的步骤指南: 一、有线直连(通过网线) 1. 准备工具 网线:使用交叉网线(适用于旧设备)或普通直连网线&#…...
“深入浅出”系列之音视频开发:(12)使用FFmpeg实现倍速播放:技术细节与优化思路
一、前言 在音视频处理领域,倍速播放是一个常见的需求,尤其是在视频播放器、在线教育平台等场景中,用户常常需要以不同的速度播放视频内容。然而,实现一个高质量的倍速播放功能并不容易,尤其是在处理音频时࿰…...
qt作业day2
1:在注册登录的练习里面,追加一个QListWidget 项目列表 要求:点击注册之后,将账号显示到 listWidget上面去 以及,在listWidget中双击某个账号的时候,将该账号删除 .h #ifndef WIDGET_H #define WIDGET_H …...
Qt:day1
一、作业 写1个Widget窗口,窗口里面放1个按钮,按钮随便叫什么; 创建2个Widget对象: Widget w1, w2; w1.show(); w2不管; 要求: 点击 w1.btn,w1隐藏,w2显示; 点击 w2.btn&…...
基于微信小程序的停车场管理系统的设计与实现
第1章 绪论 1.1 课题背景 随着移动互联形式的不断发展,各行各业都在摸索移动互联对本行业的改变,不断的尝试开发出适合于本行业或者本公司的APP。但是这样一来用户的手机上就需要安装各种软件,但是APP作为一个只为某个公司服务的一个软件&a…...
详细Linux基础知识(不断完善)
终端类型分类 1. 物理终端 直接连接到计算机的硬件设备2. 虚拟终端 通过快捷键切换的文本模式界面: Ctrl + Alt + F1 # 登录窗口 Ctrl + Alt + F2 # 当前图形界面 Ctrl + Alt + F3 # 虚拟命令终端 Ctrl + Alt + F4-F6 # 备用虚拟终端3. 图形终端 模拟终端:图形环境中的…...
类和对象-继承-C++
1.定义 面向对象的三大特征之一,为了减少重复的代码 2.语法 class 子类 :继承方式 父类 (子类也叫派生类,父类也称为基类) 例:class age:public person; #include<iostrea…...
初阶数据结构(C语言实现)——3顺序表和链表(1)
目录 【本节目标】1. 线性表2.顺序表2.1概念及结构2.2 接口实现2.2.0 动态顺序表2.2.1 顺序表初始化SLInit()2.2.2 销毁和打印2.2.3 尾插SLPushBack()2.2.4 尾删SLPopBack()2.2.5 头插2.2.6 头删2.2.7 插入…...
nuxt常用组件库html-validator、@nuxtjs/i18n、@nuxt/image、@unocss/nuxt使用解析
html-validator 主要用于自动验证nuxt服务器呈现的HTML(SSR和SSG),以检测可能导致水合错误的HTML常见问题,有助于减少水合错误,检测常见的可访问性错误。 安装 npx nuxilatest module add html-validator配置 若自动更新nuxt.config.ts配置文…...
4G工业路由器在公交充电桩中的应用与优势
随着电动公交车的普及,公交充电桩的稳定运行和高效管理是交通营运部门最关心的问题。4G工业路由器凭借其卓越的数据采集和通讯能力,成为实现充电桩智能化管理的关键。 公交充电桩运维管理需求概述: 1.实时性:实时监控充电状态、剩…...
matlab 四维数据可视化(已解决)
虽然这不是传统意义上的“4维可视化”,但你可以通过在三维空间中表示两个维度来间接展示4维数据。例如,你可以使用颜色来表示第四个维度。 clc clear close all% 假设X, Y, Z为你的三维数据,C为第四维数据 X rand(100, 1); Y rand(100, 1);…...
歌曲分类和流行度预测
1. 项目介绍 本项目从kaggle平台上下载了数据集,该数据集包含了3万多首来自Spotify API 的歌曲,共有23个特征。首先对数据集进行预处理,如重复行、缺失值、标准化处理等。再对预处理后的数据进行探索性分析,观察各变量的分布情况&…...
经验分享:用一张表解决并发冲突!数据库事务锁的核心实现逻辑
背景 对于一些内部使用的管理系统来说,可能没有引入Redis,又想基于现有的基础设施处理并发问题,而数据库是每个应用都避不开的基础设施之一,因此分享个我曾经维护过的一个系统中,使用数据库表来实现事务锁的方式。 之…...
oracle decode
1. 基本语法 DECODE(expression, search1, result1, search2, result2, ..., default_result) expression :需要比较的表达式或列。search1, search2, ... :要匹配的值。result1, result2, ... :当 expression 等于 search 时返回的结果。def…...
让后台界面布局更灵活:在GrapesJS中复刻Java的五区式布局
当你想要在可视化编辑器中做一个类似Java BorderLayout 的五区布局,却发现市面上大多只能“简单拼接”而难以自由扩展时,你或许就需要一个更灵活的布局管理器来帮忙。本篇文章就从这个痛点开始,带你一步步揭秘如何用 GrapesJS 自定义并实现一…...
【网络安全 | 漏洞挖掘】分享21个基础漏洞案例
未经许可,不得转载。 文章目录 案例1:绕过500状态码案例2:修改前端实现任意文件上传案例3:Nmap扫描端口命令案例4:绕过限制实现任意文件读取案例5:删除任意目录文件案例6:锁定任意账户案例7:重置任意用户密码案例8:接管任意账户方法一方法二案例9:功能校验机制绕过案…...
期权适合什么类型的投资者交易?
财顺小编本文主要介绍期权适合什么类型的投资者交易?期权适合的投资者类型需结合其风险偏好、投资目标及市场判断能力综合评估。 期权适合什么类型的投资者交易? 1. 风险管理型投资者(稳健型) 适用场景:持有股票、大…...
浅谈C++/C命名冲突
前言 在这里我会简要地介绍产生命名冲突的原因,和C中处理命名冲突的方法,同时和C语言的解决办法进行比较。 相信你在阅读完之后一定会有收获。对于我们来说,了解编译器的编译链接过程才能更好的理解编译器是如何报错的,更能让我们…...
【星云 Orbit • STM32F4】08. 用判断数据头来接收据的串口通用程序框架
【星云 Orbit • STM32F4】08. 用判断数据头来接收据的串口通用程序框架 1. 引言 本教程旨在帮助嵌入式开发小白从零开始,学习如何在STM32F407微控制器上实现一个基于串口的数据接收程序。该程序能够通过判断数据头来接收一串数据,并将其存储到缓冲区中…...
C# OnnxRuntime部署DAMO-YOLO香烟检测
目录 说明 效果 模型信息 项目 代码 下载 参考 说明 效果 模型信息 Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- name:input tensor:Floa…...
探索Elasticsearch:索引的CRUD
在企业环境中,Elasticsearch的索引CRUD(创建Create、读取Read、更新Update、删除Delete)操作是非常基础且频繁使用的功能。这些操作对于管理和维护数据至关重要,尤其是在处理大规模数据集和需要实时搜索与分析的应用场景中。 目录…...
C++:多态与虚函数
1.虚函数,在函数前加virtual即可。有虚函数时,父类指针指向父类对象时就会使用父类的成员,指向子类对象时就可以使用子类成员,进而我们引入了多态的概念。 2.多态:父类指针指向子类的对象,通过父类指针调用…...
【Python 数据结构 2.时间复杂度和空间复杂度】
Life is a journey —— 25.2.28 一、引例:穷举法 1.单层循环 所谓穷举法,就是我们通常所说的枚举,就是把所有情况都遍历了的意思。 例:给定n(n ≤ 1000)个元素ai,求其中奇数有多少个 判断一…...
【Qt QML】QML鼠标事件(MouseArea)
QML鼠标事件全面解析 一、MouseArea基础概念 在 QML 中,鼠标事件是处理用户与界面元素交互的重要部分。QML 提供了多种方式来处理鼠标事件,MouseArea 是 QML 中用于处理鼠标事件的核心元素,它可以覆盖在其他元素之上,捕获鼠标操作并触发相应的信号。 1、基本用法 import …...
医脉云枢:中医药典籍知识图谱与非遗传承多维可视化系统
核心优势: "医脉"直击主题,"云枢"体现技术前瞻性 "非遗传承"呼应二十大文化政策 "多维"涵盖3D模型、时间轴、地图等多种可视化形式 技术栈:Vue Flask Element UI ECharts MySQL 同时参考了…...
vue 和 react 底层采用的 diff 算法的区别
Vue 3 和 React 在底层 Diff 算法上的实现确实有一些区别,主要体现在设计理念、性能优化策略以及具体实现方式上。以下是对两者 Diff 算法差异的详细分析: 1. 总体设计理念 Vue 3 的 Diff 算法 Vue 3 的虚拟 DOM Diff 算法基于“双端比较”思想ÿ…...
养老小程序方案详解居家养老小程序系统
养老小程序,上门居家养老小程序,用户端护工端小程序,管理后台。php开发语言,可源码搭建,二次开发或者定制开发。 一 用户端:小程序 核心功能模块:用户完善个人健康档案,在线选择服…...
Tauri跨平台开发问题及解决方案深度解析(React版)
Tauri跨平台开发问题及解决方案深度解析(React版) 一、环境配置与项目初始化难题(React适配) 1.1 React项目初始化 推荐模板: # 使用ReactTypeScript模板 npm create tauri-applatest -- --template react-ts# 项目…...
【OMCI实践】omci.lua脚本文件(独家分享)
引言 omci.lua文件是Wireshark的OMCI协议解析插件的核心组件。它配合BinDecHex.lua,可以解析OMCI协议的数据包,提取出消息类型、受管实体标识、受管实体属性等关键信息,并以人类可读的形式显示在Wireshark的解码视图中,方便研发人…...
React高级内容探索
flushSync确保了DOM立即更新 flushSync让你强制React同步刷新提供回调中的任何更新,这确保了DOM立即更新 flushSync是DOM更新之后的,像vue中的nextTick: import { useState,useRef} from "react" import { flushSync} from &quo…...
前端水印实现方式
一、简介 简单来说,前端水印就是在网页或应用程序的前端界面上添加的一种标记,通常是文本、图标或图案等形式。它就像给你的数字内容贴上了一个独特的 “标签”,用于标识内容的归属、防止未经授权的使用和传播。比如,一些在线图片…...
【新手入门】SQL注入之getshell(木马)
木马介绍 木马其实就是一段程序,这个程序运行到目标主机上时,主要可以对目标进行远程控制、盗取信息等功能,一般不会破坏目标主机,当然,这也看黑客是否想要搞破坏。 按照功能分类:远控型、破坏型、流氓软件型、盗取信…...
Git操作指南:分支合并、回退及其他重要操作
在软件开发的协作过程中,Git 作为一款强大的版本控制系统,能帮助开发者高效管理代码的各个版本和分支。本文将详细介绍 Git 中常见的分支合并、取消本地修改、回退操作等,并提供通俗易懂的解释和步骤指南。 一、分支合并 分支合并是 Git 工…...