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三个寄存器的协同操作流程:

I2C主模式发送字节寄存器操作流程 I2C主模式发送字节 — 寄存器操作流程 1. 配置CR.PE=1 2. 写CR.START=1 3. 轮询SR.SB=1? 4. 写从机地址到DR 5. 轮询SR.ADDR=1? 6. 写数据字节到DR 寄存器状态变化 CR.PE: 0→1 CR.START: 0→1 SR.SB: 0→1 (硬件置位) DR: 写入从机地址 SR.SB: 读后清除 SR.ADDR: 0→1 (硬件置位) DR: 写入数据字节 SR.ADDR: 读后清除 SR.TXE: 0→1 (硬件置位) 数据发送至总线

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的操作逻辑在两种模式下完全一致,这为运行时动态切换提供了便利。驱动开发时,建议将寄存器操作封装为底层函数,上层通过函数指针或回调机制实现模式切换。