第三章 UART通信协议测试:从初始化到实战排障

UART 这个接口,说简单也简单,说复杂也复杂。我做了十几年嵌入式,跟 UART 打交道的次数比跟家人吃饭还多。它不像 SPI 那样快,也不像 I2C 那样有地址仲裁,但恰恰是这种“简单”,让很多人在它身上栽了跟头。

这一章,咱们就把 UART 的测试方法掰开揉碎。从初始化配置,到环回自检,再到双机通信,最后聊聊那些让人头疼的错误帧。嗯,都是实战里摸爬滚打出来的经验。

3.1 UART初始化与波特率验证

UART 初始化,说白了就是配置几个寄存器。但波特率对不对,直接决定后面能不能通。我见过有人拿着 115200 的配置,实际跑出来是 115384,差了 0.17%,短报文看不出来,一传大文件就丢字节。

3.1.1 初始化核心参数

参数 典型值 说明
波特率 9600 / 115200 / 921600 误差应 < 2%,建议 < 1%
数据位 8 常用 8 位,7 位用于 ASCII 场景
停止位 1 / 2 1 位最常用,2 位用于抗干扰
校验位 无 / 奇 / 偶 无校验最常见
流控制 无 / 硬件 RTS/CTS 高速通信建议硬件流控

3.1.2 波特率误差计算

波特率误差 = |(实际波特率 - 目标波特率) / 目标波特率| × 100%。

举个例子。STM32F103 用 72MHz 主频,USART1 挂 APB2 总线(72MHz),要配 115200 波特率。分频系数 = 72,000,000 / (16 × 115200) = 39.0625。取整 39,实际波特率 = 72,000,000 / (16 × 39) = 115,384。误差 = 0.16%,没问题。

但如果主频是 16MHz,配 115200 呢?分频系数 = 16,000,000 / (16 × 115200) = 8.68。取整 9,实际波特率 = 16,000,000 / (16 × 9) = 111,111。误差 = 3.6%。这就危险了。

注意: 波特率误差超过 2%,长帧通信几乎必丢数据。我建议你每次配完波特率,都拿示波器量一下 TX 引脚的实际脉冲宽度。别信寄存器里的值,信示波器。

3.1.3 初始化代码示例

// STM32 UART1 初始化,115200-8N1
void UART1_Init(void) {
    // 1. 使能时钟
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

    // 2. 配置 GPIO:PA9 TX 推挽复用,PA10 RX 浮空输入
    GPIOA->CRH &= ~(0xFF << 4);
    GPIOA->CRH |= (0x0B << 4);  // TX: 复用推挽输出 50MHz
    GPIOA->CRH |= (0x04 << 8);  // RX: 浮空输入

    // 3. 配置波特率 115200 (72MHz / 16 / 115200 = 39.0625)
    USART1->BRR = 39;  // 整数部分

    // 4. 配置数据格式:8位数据,1位停止位,无校验
    USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;

    // 5. 使能 USART
    USART1->CR1 |= USART_CR1_UE;
}
小技巧: 初始化完成后,发一个 0x55(二进制 01010101),用示波器看波形。每个 bit 宽度应该是 1/波特率。比如 115200,每个 bit 约 8.68μs。数 10 个 bit 的宽度,除以 10,就是实际 bit 时间。

3.2 自发自收环回测试

环回测试,就是把 TX 和 RX 短接。自己发,自己收。这是验证 UART 硬件通路最直接的方法。我每次画完板子,第一件事就是做环回测试。如果环回过不了,后面双机通信想都别想。

3.2.1 硬件环回 vs 软件环回

方式 做法 验证范围
硬件环回 外部短接 TX-RX 引脚 GPIO、电平转换、连接器、线缆
软件环回 芯片内部 loopback 模式 仅 UART 外设逻辑

我个人习惯先做软件环回,确认外设没问题。再做硬件环回,把整个物理链路都测一遍。

3.2.2 环回测试流程

  1. 发送一个已知数据序列(如 0x00~0xFF 递增)
  2. 接收并比对每个字节
  3. 记录错误字节数和错误位置
  4. 重复 1000 次以上,统计误码率
// 环回测试函数
uint32_t Loopback_Test(void) {
    uint8_t tx_buf[256];
    uint8_t rx_buf[256];
    uint32_t errors = 0;

    // 填充发送数据
    for (int i = 0; i < 256; i++) {
        tx_buf[i] = (uint8_t)i;
    }

    // 发送并接收
    for (int i = 0; i < 256; i++) {
        UART1_SendByte(tx_buf[i]);
        // 等待接收,超时 100ms
        rx_buf[i] = UART1_ReceiveByte(100);
        if (rx_buf[i] != tx_buf[i]) {
            errors++;
            // 记录错误位置和内容
            printf("ERR: idx=%d, tx=0x%02X, rx=0x%02X\n",
                   i, tx_buf[i], rx_buf[i]);
        }
    }

    return errors;
}
关键指标: 环回测试 1000 次,每次 256 字节,误码率应为 0。如果出现偶发错误,先检查电源噪声和地回路。我曾经被一个开关电源的纹波坑了三天,UART 总是隔几分钟丢一个字节,换了线性电源就好了。

3.3 双机通信数据一致性测试

环回过了,不代表双机通信没问题。两个设备之间,有电平转换、有长线缆、有地电位差。这些都会引入问题。

3.3.1 测试拓扑

我一般用三台设备做测试:A 发、B 收、C 监听。C 用逻辑分析仪或示波器挂在总线上,不参与通信,只做第三方验证。

双机通信测试拓扑 设备A(发送) 设备B(接收) 设备C(逻辑分析仪监听) C 设备只监听不发送,用于比对 A 发的内容和 B 收的内容是否一致

3.3.2 数据一致性测试用例

测试用例 数据模式 帧长度 预期结果
递增字节 0x00~0xFF 循环 256 字节 100% 一致
固定模式 0xAA, 0x55 交替 1024 字节 100% 一致
随机数据 伪随机序列 4096 字节 100% 一致
边界值 全 0x00, 全 0xFF 512 字节 100% 一致
经验之谈: 双机通信测试,至少跑 10 万帧。我习惯写个自动化脚本,让两台设备通宵跑。第二天看日志,如果零错误,基本可以放心。如果出现零星错误,别急着改代码,先检查地线。

3.4 常见错误帧分析与调试

UART 的错误帧,说白了就那几种。但每种错误的根因,可能差得很远。我整理了一份常见错误帧清单,都是我在项目里踩过的坑。

3.4.1 错误帧类型

错误类型 现象 常见原因
帧错误 (FE) 停止位不是高电平 波特率不匹配、噪声干扰
溢出错误 (ORE) 数据未及时读取被覆盖 中断响应太慢、DMA 未配置
校验错误 (PE) 校验位与数据不匹配 校验模式不一致、数据被篡改
噪声错误 (NE) 采样点电平不稳定 信号线过长、地回路噪声

3.4.2 帧错误(FE)调试

帧错误是最常见的。我遇到过最离谱的一次,是客户把 115200 配成了 15200,少了个 1。结果通信时好时坏,他们查了两天。

调试步骤:

  1. 用示波器看 TX 波形,测量 bit 宽度
  2. 对比两边的实际波特率
  3. 检查时钟源精度(晶振 vs 内部 RC)
  4. 检查线缆长度和终端匹配

3.4.3 溢出错误(ORE)调试

溢出错误,说白了就是 CPU 没来得及读数据。我建议你这么做:

  • 用 DMA 接收,别用中断逐字节读
  • 如果必须用中断,确保中断优先级够高
  • 接收缓冲区用环形队列,别用单字节变量
// 环形缓冲区接收示例
#define RX_BUF_SIZE 1024

static uint8_t rx_buf[RX_BUF_SIZE];
static volatile uint16_t rx_head = 0;
static volatile uint16_t rx_tail = 0;

void UART1_IRQHandler(void) {
    if (USART1->SR & USART_SR_RXNE) {
        uint8_t data = USART1->DR;
        uint16_t next = (rx_head + 1) % RX_BUF_SIZE;
        if (next != rx_tail) {  // 缓冲区未满
            rx_buf[rx_head] = data;
            rx_head = next;
        } else {
            // 溢出标志,记录错误
            overflow_flag = 1;
        }
    }
}
注意: 环形缓冲区一定要检查满状态。我曾经忘了检查,结果缓冲区被覆盖,数据错乱,排查了整整一个下午。后来我在每个项目里都加了一个溢出计数器,一旦非零,立刻报警。

3.4.4 噪声错误(NE)调试

噪声错误,通常出现在长线通信或工业现场。我有个项目,UART 线缆走了 5 米,波特率 9600,偶尔出现噪声错误。后来加了双绞线和终端电阻,问题解决。

抗干扰措施:

  • 降低波特率(9600 比 115200 抗干扰强很多)
  • 使用 RS-232 或 RS-485 电平转换
  • 线缆用屏蔽双绞线,单端接地
  • 软件上增加帧头帧尾校验(如 0xAA + 长度 + 数据 + CRC)
核心原则: UART 调试,永远从物理层开始。先看波形,再查配置,最后怀疑代码。我见过太多人一上来就改软件,结果发现是 TX 和 RX 接反了。嗯,我也干过这种事。