3. UART通信调试:波特率不匹配问题、数据帧格式错误、DMA传输异常、中断丢失与溢出错误

UART 这个外设,看起来简单,就两根线(TX/RX),但调试起来坑是真不少。我这些年经手的项目里,UART 出问题的频率仅次于 GPIO 和时钟。说白了,串口调不通,大部分时候不是硬件坏了,而是配置没对齐。

这一章,我把 UART 调试里最常见的四类问题拆开揉碎了讲。每个问题我都会结合自己的踩坑经历,给你一套可复现的排查思路。

3.1 波特率不匹配问题

波特率不匹配,是 UART 通信里最基础也最容易犯的错。两边波特率不一样,收出来的数据全是乱码。

3.1.1 现象与根因

现象很典型:发送端发 "Hello",接收端收到一堆乱码,比如 "0x48 0x65 0x6C" 变成 "0xA4 0x32 0xDB"。用逻辑分析仪抓波形,你会发现每个 bit 的宽度跟预期对不上。

根因就两个:

  • 时钟源偏差:STM32 的 UART 波特率由 APB 时钟分频得到。如果 APB 时钟不准(比如 HSE 起振失败,自动切到 HSI),波特率就偏了。
  • 分频计算误差:某些波特率(如 115200)在特定时钟下无法整除,误差超过 ±2% 就会丢数据。

关键指标:UART 通信的波特率误差必须控制在 ±2% 以内。超过这个范围,数据采样点会偏移到 bit 边界上,导致误码。

3.1.2 排查步骤

  1. 确认时钟源:用示波器或逻辑分析仪测量 MCU 的 MCO 引脚,确认 HSE 频率正确。
  2. 计算实际波特率:根据 APB 时钟和 USART_BRR 寄存器的值,反算实际波特率。
  3. 对比误差:实际波特率与目标波特率的差值,除以目标波特率,得到误差百分比。
  4. 调整分频:如果误差超标,换一个能整除的波特率(如 115200 不行就换 57600),或者调整 APB 时钟频率。

我的习惯:调试阶段,我会在代码里加一个串口初始化后的自检流程——发一个已知字节(如 0x55,二进制 01010101),然后用逻辑分析仪抓波形,量每个 bit 的宽度。宽度 = 1/波特率,一量就知道偏没偏。

3.1.3 代码示例:波特率计算与验证

/* 假设 APB2 时钟 = 72 MHz,目标波特率 = 115200 */
uint32_t apb_clock = 72000000;
uint32_t target_baud = 115200;

/* 计算 USARTDIV */
float usartdiv = (float)apb_clock / (16 * target_baud);
uint32_t brr_value = (uint32_t)(usartdiv * 16) & 0xFFFF;

/* 反算实际波特率 */
float actual_baud = (float)apb_clock / (16 * (brr_value / 16.0));
float error = (actual_baud - target_baud) / target_baud * 100;

printf("目标波特率: %d\n", target_baud);
printf("实际波特率: %.2f\n", actual_baud);
printf("误差: %.2f%%\n", error);

if (error > 2.0 || error < -2.0) {
    printf("警告:波特率误差超标!\n");
}

3.2 数据帧格式错误

波特率对了,但数据还是不对?那八成是帧格式没对齐。UART 的帧格式包括:数据位、停止位、校验位。两边必须完全一致。

3.2.1 常见配置组合

参数 常见值 说明
数据位 8 / 9 8 位最常用,9 位用于多机通信或带校验
停止位 0.5 / 1 / 1.5 / 2 1 位停止位最常用
校验位 无 / 偶校验 / 奇校验 无校验最常用,校验位会占用数据位中的 1 位

注意:如果使能了校验位,数据位要设置为 9 位(8 位数据 + 1 位校验)。很多人在这里搞错,设成 8 位数据 + 校验,结果收发永远对不上。

3.2.2 排查方法

我曾经在一个 Modbus 项目里被帧格式坑过。设备 A 发 8N1(8 数据位、无校验、1 停止位),设备 B 配成了 8E1(8 数据位、偶校验、1 停止位)。结果 B 收到的每个字节都多了一个校验位,数据全错位。

排查方法很简单:

  • 用逻辑分析仪抓一帧数据,数一下 bit 数。
  • 起始位(低电平)1 bit + 数据位(LSB 先发)+ 校验位(如果有)+ 停止位(高电平)。
  • 数出来是 10 bit 就是 8N1,11 bit 就是 8E1 或 9N1。

3.3 DMA传输异常

UART + DMA 是高性能通信的标配,但 DMA 出问题的时候,排查起来比中断模式麻烦得多。

3.3.1 典型现象

  • 只发一次:DMA 传输完第一个 buffer 后就停了,不再触发下一次。
  • 数据错位:收到的数据顺序不对,或者中间有重复/缺失。
  • 传输未完成:DMA 传输了一半,卡住了,再也进不了完成中断。

3.3.2 根因分析

我遇到过最典型的一个问题:DMA 配置了循环模式,但 UART 的发送完成标志(TC)没有清除,导致 DMA 请求一直被阻塞。说白了,DMA 把数据搬到 UART 的 DR 寄存器后,UART 要等 TC 置位才能发下一个字节。如果 TC 没清,DMA 就卡死了。

另一个常见问题是 DMA 中断优先级配置不当。如果 DMA 中断优先级低于某个频繁触发的中断,DMA 传输会被频繁打断,导致数据溢出。

3.3.3 排查清单

检查项 正确配置 错误示例
DMA 通道 UART_TX 用 DMA 通道 4(STM32F1) 用了 SPI 的 DMA 通道
DMA 方向 发送:内存→外设;接收:外设→内存 方向设反
DMA 模式 发送用 Normal,接收用 Circular 发送用了 Circular,导致无限重发
UART 请求 使能 UART 的 DMA 发送/接收请求 只配了 DMA,没配 UART 的 DMA 使能位
中断优先级 DMA 中断优先级高于 UART 中断 DMA 优先级低于定时器中断

我的调试技巧:DMA 出问题时,先关掉 DMA,用轮询模式发几个字节。如果轮询模式正常,说明 UART 本身没问题,问题一定在 DMA 配置上。这叫「二分法」——先隔离外设,再查 DMA。

3.4 中断丢失与溢出错误

中断丢失和溢出错误,是 UART 接收端最常见的问题。尤其是在高波特率或连续大数据量传输时,稍不注意就会丢数据。

3.4.1 中断丢失

中断丢失的意思是:UART 收到了数据,产生了 RXNE(接收非空)中断,但 CPU 没来得及响应,下一个数据又来了,把当前数据覆盖了。

为什么会这样?

  • 中断优先级太低:UART 中断被其他更高优先级的中断长时间阻塞。
  • 中断服务函数太长:在 ISR 里做数据处理、打印、延时等耗时操作,导致来不及响应下一个中断。
  • 中断嵌套:多个中断同时触发,UART 中断被挤掉了。

3.4.2 溢出错误(ORE)

溢出错误是 UART 硬件检测到的错误。当 RXNE 为 1(数据还没读走),新的数据又到达时,硬件会置位 ORE 标志,并丢弃新数据。

我曾在一次 921600 波特率的调试中遇到 ORE 频繁触发。排查后发现,ISR 里用了 HAL_UART_Receive_IT() 重新使能接收,但这个函数本身有几十个时钟周期的开销。在高波特率下,这个时间窗口足够让下一个字节覆盖上来。

3.4.3 解决方案

  1. 提高 UART 中断优先级:在 NVIC 中把 UART 中断优先级设到最高(或次高)。
  2. ISR 里只做轻量操作:在中断里只读 DR 寄存器,把数据放到环形缓冲区,数据处理放到主循环或任务里。
  3. 使用 DMA 接收:DMA 不需要 CPU 逐字节干预,能从根本上解决中断丢失问题。
  4. 检查 ORE 标志:在 ISR 中检查 ORE,如果置位,先清除它,再读 DR。
/* UART 中断服务函数示例 - 使用环形缓冲区 */
void USART1_IRQHandler(void)
{
    uint8_t data;
    
    if (LL_USART_IsActiveFlag_RXNE(USART1)) {
        data = LL_USART_ReceiveData8(USART1);
        /* 检查溢出错误 */
        if (LL_USART_IsActiveFlag_ORE(USART1)) {
            LL_USART_ClearFlag_ORE(USART1);  /* 先清 ORE */
            /* 记录溢出次数,用于调试 */
            overflow_count++;
        }
        /* 将数据放入环形缓冲区 */
        ring_buffer_put(&rx_buffer, data);
    }
}

重要:读取 USART_DR 寄存器会自动清除 RXNE 标志。但 ORE 标志不会自动清除,必须手动写 0。很多人只清 RXNE,忘了清 ORE,导致 ORE 一直挂起,后续数据再也收不进来。

3.5 知识体系总览

下面这张图把 UART 调试的四类问题以及它们之间的关联关系画出来了。你可以把它当作排查时的路线图。

UART通信调试知识体系 UART通信调试知识体系 UART通信调试 波特率不匹配 帧格式错误 DMA传输异常 中断丢失/溢出 时钟源偏差 分频计算误差 数据位/停止位 校验位配置 通道/方向错误 中断优先级 TC标志未清除 ISR过长 优先级过低 ORE未清除 排查原则:先查波特率 → 再查帧格式 → 最后查中断/DMA 排查步骤:1. 量波形 → 2. 对配置 → 3. 查中断 → 4. 试DMA 工具:逻辑分析仪 + 串口助手 + 示波器

这张图把 UART 调试的四个核心问题以及它们的子原因都列出来了。你排查的时候,按「先物理层、再协议层、最后软件层」的顺序走,能省不少时间。

嗯,UART 调试说难不难,说简单也不简单。关键是养成好习惯:每次配置完,先用逻辑分析仪抓波形确认一下。别问我为什么强调这个——我当年因为少抓一次波形,硬是调了三天才发现是杜邦线接触不良。