4. UART 寄存器编程:数据寄存器(DR)、状态寄存器(SR)、控制寄存器(CR)、波特率分频寄存器(BRR)

UART 外设的寄存器是驱动开发者与硬件交互的唯一接口。本章深入剖析 STM32 系列(以 USART2 为例)最核心的四个寄存器:数据寄存器(DR)、状态寄存器(SR)、控制寄存器(CR) 和 波特率分频寄存器(BRR)。理解每个寄存器的位域含义、读写时序和配置顺序,是编写稳定、高效 UART 驱动的基础。

寄存器编程原则: 所有寄存器操作应遵循“读-改-写”模式,避免破坏未使用的位域。对于 CR 类寄存器,建议在修改前先读取当前值,再按位或/与后写回。

4.1 寄存器概览与地址映射

下表列出了 USART2 的基地址(0x40004400)及四个关键寄存器的偏移地址。不同系列 MCU 的基地址可能不同,但寄存器偏移和位域定义高度兼容。

寄存器名称 偏移地址 全称 复位值 宽度
SR 0x00 Status Register 0xC0 32 位
DR 0x04 Data Register 0xXXXX 32 位
BRR 0x08 Baud Rate Register 0x0000 32 位
CR1 0x0C Control Register 1 0x0000 32 位
CR2 0x10 Control Register 2 0x0000 32 位
CR3 0x14 Control Register 3 0x0000 32 位
关键提示: 虽然 DR 寄存器物理宽度为 32 位,但实际有效数据位只有低 9 位(8 位数据 + 1 位校验/第9位)。写入时高 23 位被硬件忽略,读取时高 23 位返回 0。

4.2 数据寄存器(DR)—— 收发数据的核心通道

DR 寄存器是 CPU 与 UART 移位器之间的数据缓冲区。发送时,CPU 将待发送字节写入 DR;接收时,CPU 从 DR 读取已接收字节。DR 内部实际由两个物理缓冲区组成:发送数据寄存器(TDR)和接收数据寄存器(RDR),但软件层面共用同一个地址。

4.2.1 DR 位域定义

位域 名称 类型 描述
[31:9] Reserved RO 保留位,读出为 0
[8:0] DR[8:0] RW 数据值。8 位模式使用 [7:0];9 位模式使用 [8:0]

4.2.2 发送与接收操作流程

  • 发送流程: 等待 SR.TXE(发送数据寄存器空)置 1 → 写入 DR → 硬件自动将数据移入移位寄存器 → TXE 再次置 1。
  • 接收流程: 等待 SR.RXNE(接收数据寄存器非空)置 1 → 读取 DR → 硬件自动清除 RXNE 标志。
// 发送一个字节(轮询模式)
void uart_send_byte(USART_TypeDef *USARTx, uint8_t data) {
    // 等待发送缓冲区空
    while (!(USARTx->SR & USART_SR_TXE));
    // 写入数据寄存器
    USARTx->DR = (data & 0xFF);
}

// 接收一个字节(轮询模式)
uint8_t uart_receive_byte(USART_TypeDef *USARTx) {
    // 等待接收数据就绪
    while (!(USARTx->SR & USART_SR_RXNE));
    // 读取数据寄存器
    return (uint8_t)(USARTx->DR & 0xFF);
}
常见陷阱: 读取 DR 后,RXNE 标志由硬件自动清除。但如果连续读取 DR 两次,第二次读取可能得到旧数据或 0。务必在每次 RXNE 置位后只读取一次 DR。

4.3 状态寄存器(SR)—— 通信状态的实时窗口

SR 寄存器提供 UART 模块的实时状态,包括发送/接收缓冲区状态、帧错误、溢出错误、校验错误等。驱动开发中,SR 是轮询模式和中断模式的核心判断依据。

4.3.1 SR 关键位域

位 名称 描述 清除方式
7 TXE 发送数据寄存器空。置 1 表示 TDR 已空,可写入新数据 写入 DR 自动清除
6 TC 发送完成。置 1 表示当前帧已全部发送完毕(包括停止位) 读 SR 后写 DR,或写 CR1.TC 位
5 RXNE 接收数据寄存器非空。置 1 表示 RDR 中有新数据可读 读取 DR 自动清除
4 IDLE 空闲线路检测。置 1 表示检测到空闲帧 读 SR 后读 DR
3 ORE 溢出错误。RXNE 为 1 时新数据到达导致覆盖 读 SR 后读 DR
2 NF 噪声标志。接收帧检测到噪声 读 SR 后读 DR
1 FE 帧错误。停止位无效(通常为 0) 读 SR 后读 DR
0 PE 校验错误。接收校验位与计算值不匹配 读 SR 后读 DR

4.3.2 错误处理示例

// 带错误检查的接收
uint8_t uart_receive_with_error_check(USART_TypeDef *USARTx, uint8_t *data) {
    uint32_t status = USARTx->SR;

    // 检查错误标志
    if (status & (USART_SR_ORE | USART_SR_FE | USART_SR_NE)) {
        // 读取 DR 以清除错误标志
        (void)USARTx->DR;
        return 1; // 返回错误
    }

    if (status & USART_SR_RXNE) {
        *data = (uint8_t)(USARTx->DR & 0xFF);
        return 0; // 成功
    }

    return 2; // 无数据
}
最佳实践: 在中断服务函数中,建议先读取 SR 再读取 DR,确保错误标志与数据在同一帧内被捕获。读取顺序不可颠倒。

4.4 控制寄存器(CR1/CR2/CR3)—— 配置 UART 工作模式

控制寄存器组(CR1、CR2、CR3)负责配置 UART 的使能、数据位长度、停止位、校验、中断使能、DMA 使能等。其中 CR1 最为常用,CR2 主要配置停止位和同步模式,CR3 配置流控制和 DMA 方向。

4.4.1 CR1 关键位域

位 名称 描述
13 UE UART 使能。置 1 开启 UART,清 0 关闭并复位状态
12 M 字长选择。0=8 位,1=9 位
11 WAKE 唤醒方式。0=空闲线路唤醒,1=地址标记唤醒
10 PCE 校验使能。置 1 使能校验位发送/检查
9 PS 校验选择。0=偶校验,1=奇校验
8 PEIE 校验错误中断使能
7 TXEIE 发送缓冲区空中断使能
6 TCIE 发送完成中断使能
5 RXNEIE 接收缓冲区非空中断使能
4 IDLEIE 空闲线路中断使能
3 TE 发送使能。置 1 使能发送器
2 RE 接收使能。置 1 使能接收器
0 SBK 发送间隔帧。置 1 发送间隔符

4.4.2 典型配置序列

// 配置 USART2:115200-8-N-1
void uart_init(USART_TypeDef *USARTx, uint32_t baud) {
    // 1. 使能 USART 时钟(以 STM32F4 为例)
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN;

    // 2. 配置 GPIO 为复用功能(TX=PA2, RX=PA3)
    // ... GPIO 初始化代码 ...

    // 3. 配置 CR1:8 位数据、无校验、使能发送和接收
    USARTx->CR1 &= ~(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS);
    USARTx->CR1 |= (USART_CR1_TE | USART_CR1_RE);

    // 4. 配置 CR2:1 位停止位
    USARTx->CR2 &= ~USART_CR2_STOP;

    // 5. 配置波特率
    uart_set_baudrate(USARTx, baud);

    // 6. 使能 UART
    USARTx->CR1 |= USART_CR1_UE;
}
配置顺序要求: UE 位必须在所有其他配置(M、PCE、PS、TE、RE)设置完成后再置 1。如果在配置过程中 UE 为 1,某些位域的修改可能被硬件忽略或导致不可预测行为。

4.5 波特率分频寄存器(BRR)—— 精确时钟分频

BRR 寄存器决定 UART 通信速率。其值由系统时钟频率(f_CK)和目标波特率计算得出。STM32 支持两种分频模式:标准模式(OVER8=0)和过采样模式(OVER8=1)。

4.5.1 BRR 位域结构

位域 名称 描述
[15:4] DIV_Mantissa 分频系数整数部分(12 位)
[3:0] DIV_Fraction 分频系数小数部分(4 位,OVER8=0 时有效)

4.5.2 波特率计算公式

  • 标准模式(OVER8=0): BRR = f_CK / (16 × 目标波特率)
  • 过采样模式(OVER8=1): BRR = f_CK / (8 × 目标波特率)

计算出的 BRR 值需拆分为整数部分和小数部分:

  • 整数部分 = BRR 右移 4 位(DIV_Mantissa)
  • 小数部分 = BRR 低 4 位(DIV_Fraction)
// 设置波特率(标准模式,OVER8=0)
void uart_set_baudrate(USART_TypeDef *USARTx, uint32_t baud) {
    uint32_t apb_clock = 84000000; // 假设 APB1 时钟为 84 MHz
    uint32_t brr_value;

    // 计算 BRR 值
    brr_value = (apb_clock + (baud * 8)) / (baud * 16); // 四舍五入

    USARTx->BRR = brr_value;
}

// 更精确的实现(含小数舍入)
void uart_set_baudrate_precise(USART_TypeDef *USARTx, uint32_t baud) {
    uint32_t apb_clock = 84000000;
    uint32_t mantissa, fraction;
    uint32_t brr;

    // 计算 16 倍分频系数
    brr = (apb_clock * 10) / (baud * 16); // 放大 10 倍提高精度

    mantissa = brr / 10;
    fraction = ((brr % 10) * 16 + 5) / 10; // 小数部分转换为 4 位

    USARTx->BRR = (mantissa << 4) | (fraction & 0x0F);
}

4.5.3 常见波特率与 BRR 值对照表(f_CK=84MHz,OVER8=0)

目标波特率 理论 BRR DIV_Mantissa DIV_Fraction 实际波特率 误差
9600 546.875 546 14 9598.6 0.015%
19200 273.4375 273 7 19207.7 0.04%
115200 45.5729 45 9 115226 0.02%
921600 5.6966 5 11 922509 0.10%
误差容忍度: 大多数 UART 接收器能容忍 ±2% 的波特率误差。上表中所有常见波特率的误差均远低于此阈值。如果误差超过 1%,建议调整系统时钟或使用过采样模式。

4.6 寄存器编程综合流程图

下图展示了 UART 初始化过程中四个寄存器的配置顺序和依赖关系。箭头表示数据流或控制流方向。

UART 寄存器编程综合流程图 1. 使能时钟 & 配置 GPIO 2. 配置 CR1(M, PCE, PS, TE, RE) 3. 配置 CR2(STOP 位) 4. 配置 BRR(波特率分频值) 5. 置位 CR1.UE(使能 UART) UART 就绪 配置要点 • UE 最后使能 • 先配 CR 再配 BRR • 读-改-写保位域 常见错误 • 先使能 UE 再配 CR • BRR 计算未四舍五入 • 忘记使能 TE/RE

4.7 寄存器编程避坑指南

  • UE 位操作: 修改 CR1 中除 UE 外的其他位时,建议先清 UE,配置完成后再置位。部分 MCU 在 UE=1 时修改 M 位无效。
  • DR 写入时机: 必须在 TXE=1 时写入 DR。如果在 TXE=0 时写入,数据可能丢失或覆盖未发送的数据。
  • 错误标志清除: ORE、NF、FE、PE 等错误标志必须通过“读 SR 后读 DR”序列清除。仅读 SR 不会清除这些标志。
  • BRR 更新时机: 建议在 UE=0 时修改 BRR。如果在通信过程中修改 BRR,可能导致当前帧波特率错乱。
  • 保留位处理: 写寄存器时,保留位必须写 0。读寄存器时,保留位应被忽略(不依赖其值)。
实战建议: 在驱动开发初期,建议将每个寄存器的配置值打印或保存到调试变量中,与参考手册逐位对比。一个位域的误配置可能导致整个 UART 无法工作,而这类问题往往难以通过逻辑分析仪直接发现。

4.8 本章小结