4. I2C寄存器映射:控制寄存器(CR)、状态寄存器(SR)、数据寄存器(DR)、时钟分频寄存器
I2C外设的寄存器映射是驱动开发的底层操作对象。MCU通过读写一组特定地址的寄存器来完成I2C协议的所有控制、状态查询、数据收发与时钟配置。本章以STM32系列I2C外设为参考模型,详细解析四个核心寄存器的位定义与操作逻辑。
4.1 寄存器映射总览
在标准I2C外设中,寄存器基地址加上偏移量构成每个寄存器的唯一访问地址。下表列出四个核心寄存器的偏移量与功能摘要:
| 寄存器名称 | 偏移地址 | 功能描述 | 访问类型 |
|---|---|---|---|
| 控制寄存器 (CR) | 0x00 | 使能外设、配置主从模式、启动/停止信号、中断使能 | 读写 |
| 状态寄存器 (SR) | 0x04 | 指示总线忙、发送/接收完成、应答失败、仲裁丢失等状态 | 只读(部分位写清除) |
| 数据寄存器 (DR) | 0x08 | 发送数据写入、接收数据读出,双缓冲映射 | 读写 |
| 时钟分频寄存器 (CKR) | 0x0C | 设置SCL时钟频率,由外设时钟源分频得到 | 读写 |
寄存器映射要点: 不同MCU厂商的I2C寄存器命名与位布局存在差异,但CR/SR/DR/CKR四类功能寄存器是业界通用结构。驱动开发时务必以目标芯片参考手册的寄存器描述为准。
4.2 控制寄存器 (CR) 详解
控制寄存器是I2C通信的“方向盘”,驱动通过配置CR的各个位域来决定I2C的工作模式与行为。以下是CR中关键位域的说明:
- PE (Peripheral Enable) [位0]: I2C外设使能位。置1使能外设,清0复位外设并释放总线。
- START [位1]: 产生起始条件。主模式下写入1发送起始信号,硬件在起始条件完成后自动清0。
- STOP [位2]: 产生停止条件。主模式下写入1发送停止信号,硬件在停止条件完成后自动清0。
- ACK [位3]: 应答使能位。置1时,从机地址或数据接收后自动发送ACK;清0则发送NACK。
- POS [位4]: 应答位置控制。用于控制NACK发送的字节位置,通常在多字节接收时使用。
- ITE [位5]: 中断使能位。置1时允许I2C事件中断(如发送完成、接收完成)产生中断请求。
- ITE_BUF [位6]: 缓冲中断使能位。置1时允许数据寄存器空/满中断产生中断请求。
- ITE_ERR [位7]: 错误中断使能位。置1时允许仲裁丢失、应答失败等错误产生中断请求。
轮询与中断切换的关键: 在轮询模式下,CR中的ITE、ITE_BUF、ITE_ERR位全部清0,驱动通过轮询SR寄存器判断状态;在中断模式下,使能对应中断位,由中断服务函数处理状态与数据。切换时只需修改CR的中断使能位,无需重新初始化外设。
4.3 状态寄存器 (SR) 详解
状态寄存器反映I2C总线的实时状态,驱动通过读取SR来判断通信进度与错误条件。SR的位定义如下:
| 位域 | 名称 | 含义 | 清除方式 |
|---|---|---|---|
| 位0 | SB | 起始条件已发送(主模式) | 读SR后写DR |
| 位1 | ADDR | 从机地址已发送并收到ACK | 读SR后读DR |
| 位2 | BTF | 字节发送完成,数据寄存器与移位寄存器均空 | 读SR后读写DR |
| 位3 | TXE | 数据寄存器空,可写入下一个发送字节 | 写DR |
| 位4 | RXNE | 数据寄存器非空,已接收到一个字节待读取 | 读DR |
| 位5 | STOPF | 停止条件已接收(从模式) | 读SR后写CR |
| 位6 | AF | 应答失败,未收到从机ACK | 写1清除 |
| 位7 | ARLO | 仲裁丢失,总线被其他主机占用 | 写1清除 |
状态寄存器读取陷阱: 部分状态位(如ADDR、STOPF)的清除需要按特定顺序访问寄存器,否则可能导致状态位无法清除,造成后续通信逻辑卡死。务必参考芯片手册的“清除序列”说明。
4.4 数据寄存器 (DR) 与时钟分频寄存器 (CKR)
数据寄存器 (DR): 数据寄存器是I2C数据收发的直接通道。发送时,驱动将待发送字节写入DR;接收时,驱动从DR读取已接收字节。DR内部采用双缓冲结构,一个缓冲面向CPU(CPU侧),另一个缓冲面向移位寄存器(总线侧),实现数据流的高效处理。
时钟分频寄存器 (CKR): SCL时钟频率由外设时钟源(如APB时钟)经过分频得到。CKR的配置值决定了I2C通信速率。标准模式(100kHz)与快速模式(400kHz)对应的分频值计算如下:
/* 时钟分频计算公式 */
SCL频率 = 外设时钟频率 / ( (CKR值 + 1) * 2 )
/* 示例:外设时钟 = 8MHz,目标SCL = 100kHz */
CKR值 = (8,000,000 / (100,000 * 2)) - 1 = 39
/* 示例:外设时钟 = 8MHz,目标SCL = 400kHz */
CKR值 = (8,000,000 / (400,000 * 2)) - 1 = 9
4.5 寄存器操作流程SVG
下图展示了I2C主模式发送一个字节时,CR、SR、DR三个寄存器的协同操作流程:
4.6 寄存器配置代码示例
以下代码展示了在轮询模式下初始化I2C并发送一个字节的寄存器操作过程:
/* I2C寄存器基地址定义(假设) */
#define I2C_BASE 0x40005400
#define I2C_CR (*(volatile uint32_t *)(I2C_BASE + 0x00))
#define I2C_SR (*(volatile uint32_t *)(I2C_BASE + 0x04))
#define I2C_DR (*(volatile uint32_t *)(I2C_BASE + 0x08))
#define I2C_CKR (*(volatile uint32_t *)(I2C_BASE + 0x0C))
/* 位定义 */
#define CR_PE (1 << 0)
#define CR_START (1 << 1)
#define CR_STOP (1 << 2)
#define CR_ACK (1 << 3)
#define SR_SB (1 << 0)
#define SR_ADDR (1 << 1)
#define SR_TXE (1 << 3)
#define SR_RXNE (1 << 4)
#define SR_AF (1 << 6)
/* I2C初始化:轮询模式,100kHz */
void I2C_Init_Polling(void) {
/* 1. 配置时钟分频:8MHz / ( (39+1)*2 ) = 100kHz */
I2C_CKR = 39;
/* 2. 使能外设,关闭所有中断位 */
I2C_CR = CR_PE | CR_ACK; /* 使能外设,使能应答 */
}
/* I2C主模式发送一个字节(轮询) */
uint8_t I2C_Master_Transmit_Polling(uint8_t slave_addr, uint8_t data) {
/* 1. 发送起始条件 */
I2C_CR |= CR_START;
/* 2. 轮询等待起始条件完成 */
while (!(I2C_SR & SR_SB));
/* 3. 发送从机地址(写操作) */
I2C_DR = (slave_addr << 1) | 0x00;
/* 4. 轮询等待地址发送完成 */
while (!(I2C_SR & SR_ADDR));
/* 5. 检查是否收到ACK */
if (I2C_SR & SR_AF) {
I2C_SR |= SR_AF; /* 清除应答失败标志 */
I2C_CR |= CR_STOP; /* 发送停止条件 */
return 1; /* 返回错误 */
}
/* 6. 发送数据字节 */
I2C_DR = data;
/* 7. 轮询等待发送完成 */
while (!(I2C_SR & SR_TXE));
/* 8. 发送停止条件 */
I2C_CR |= CR_STOP;
return 0; /* 成功 */
}
4.7 轮询与中断模式寄存器配置对比
下表总结了轮询模式与中断模式下,四个核心寄存器的配置差异:
| 寄存器 | 轮询模式配置 | 中断模式配置 | 切换要点 |
|---|---|---|---|
| CR | PE=1, ACK=1, 中断位全0 | PE=1, ACK=1, ITE=1, ITE_BUF=1, ITE_ERR=1 | 仅修改中断使能位,PE/ACK不变 |
| SR | 轮询读取判断状态位 | 中断服务函数中读取判断 | 读取逻辑相同,触发方式不同 |
| DR | 轮询TXE/RXNE后读写 | 中断服务函数中直接读写 | 数据操作逻辑相同 |
| CKR | 初始化时配置一次 | 初始化时配置一次 | 两种模式共用相同分频值 |
切换实战要点: 轮询与中断模式的切换本质是改变CR寄存器的中断使能位,同时将状态判断逻辑从主循环转移到中断服务函数。CKR和DR的操作逻辑在两种模式下完全一致,这为运行时动态切换提供了便利。驱动开发时,建议将寄存器操作封装为底层函数,上层通过函数指针或回调机制实现模式切换。