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. 主机模式发送数据
主机模式下,发送数据的流程如下:
- 检查总线状态
- 发送起始信号
- 发送从机地址(写)
- 发送数据
- 发送停止信号
寄存器操作
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. 主机模式接收数据
- 发送起始信号
- 发送从机地址(写)
- 发送寄存器地址
- 发送重复起始信号
- 发送从机地址(读)
- 读取数据
- 发送 NACK,结束传输
- 发送停止信号
寄存器操作
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 主机发送数据(寄存器变化)
步骤
- 发送 起始信号
- 发送 从机地址 + 写(bit 0 = 0)
- 发送 数据字节
- 发送 停止信号
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
| |
| 读取 SR2
清 ADDR
| | 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 主机接收数据(寄存器变化)
步骤
- 发送 起始信号
- 发送 从机地址 + 读(bit 0 = 1)
- 读取 数据
- 发送 NACK
- 发送 停止信号
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
| |
| 读取 SR2
清 ADDR
| | 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 传输状态(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. 代码实现
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
监视数据传输状态(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. 典型寄存器操作示例(主机发送)
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. 注意事项
- 时钟配置 :
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
标志。 -
示例代码:
cvolatile uint32_t dummy = I2C1->SR1; // 读取 SR1 清除 ADDR dummy = I2C1->SR2; // 读取 SR2 清除 BUSY 状态
-
(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:
cI2C1->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. 关键代码示例(寄存器级操作)
主机发送单字节数据
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 来清除。AF
和ARLO
标志需通过写 0 清除。
8. 总结
通过直接操作寄存器,可以精确控制 I2C 的每一个总线动作,但需要严格遵循以下原则:
- 状态标志的清除顺序 :如
ADDR
必须读 SR1 + SR2。 - 时序匹配 :
CCR
和TRISE
需根据 APB1 时钟频率计算。 - 错误恢复:检测到错误标志后,必须清除并重新初始化外设。
实际开发中,建议结合 STM32 参考手册的 I2C 时序图 和 寄存器描述,通过逻辑分析仪抓取 SCL/SDA 波形,验证寄存器操作是否符合预期。