3、UART调试实战:使用串口助手收发数据,常见乱码与丢包问题定位

UART 调试,可以说是嵌入式开发里最基础也最频繁的操作了。很多新手上来就打开串口助手,看到数据就以为通了,看到乱码就慌了。其实,串口调试没那么玄乎,大部分问题都是有规律可循的。

我个人习惯,拿到一块新板子,第一件事就是先调通串口。为什么?因为它是你与芯片对话的“嘴巴”。嘴巴都张不开,后面什么协议栈、传感器驱动都别想跑。

3.1 串口助手的基本配置与连接检查

先别急着点“打开串口”,把下面这几项确认清楚,能省掉后面 80% 的排查时间。

配置项 常见值 我的检查习惯
波特率 115200 / 9600 先看原理图或代码里配的是多少,两边必须一致
数据位 8 GD32V 默认 8 位,很少改
停止位 1 大部分场景用 1,特殊协议才用 2
校验位 None 无校验最省事,有校验反而容易出问题
流控制 None 调试阶段千万别开 RTS/CTS,除非你接了对应线
⚠️ 常见坑: 串口助手的“DTR”和“RTS”默认勾选状态。有些开发板会因此被复位,导致你永远收不到数据。我建议,打开串口后先手动取消这两个勾。

连接方面,记住三根线就够了:TXD → RXD、RXD → TXD、GND → GND。交叉连接,这是新手最容易搞反的地方。我曾经帮同事排查一个“收不到数据”的问题,折腾了半小时,最后发现他把 TXD 接到了 TXD 上——自己跟自己说话,当然收不到。

3.2 常见乱码问题定位与解决

乱码,说白了就是“你说的话对方听不懂”。原因就那么几种,按概率排个序:

  1. 波特率不匹配 —— 最最常见,没有之一。
  2. 时钟配置错误 —— GD32V 的串口时钟源没选对。
  3. 数据格式不一致 —— 数据位、停止位、校验位对不上。
  4. 电平不匹配 —— 3.3V 的芯片接了 5V 的串口工具。

怎么快速判断?我有个土办法:发一个已知的固定字节,比如 0x55。0x55 的二进制是 01010101,在示波器上看就是方波,在串口助手里如果收到的是乱码,看它有没有规律。如果每次收到的乱码都一样,那多半是波特率问题;如果每次都不一样,可能是硬件干扰或时钟不稳。

💡 实战技巧: 用 0x55 和 0xAA 交替发送,这两个字节的波形正好互补。如果接收端能稳定收到 0x55 和 0xAA,说明基本通信链路是通的。

3.2.1 波特率误差的计算

GD32V 的串口波特率由系统时钟分频得到。公式很简单:

波特率 = 系统时钟频率 / (16 × USARTDIV)

// 举个例子,系统时钟 108MHz,目标波特率 115200:
USARTDIV = 108000000 / (16 × 115200) ≈ 58.59
// 取整后实际波特率 = 108000000 / (16 × 58) ≈ 116379
// 误差 = (116379 - 115200) / 115200 ≈ 1.02%

误差在 ±2% 以内通常没问题,超过 3% 就很容易出现乱码。我遇到过一块板子,系统时钟配成了 72MHz 但代码里按 108MHz 算分频值,结果波特率差了将近 50%,收的全是乱码。

3.3 丢包问题的常见原因

丢包比乱码更隐蔽。数据偶尔少几个字节,或者收着收着就断了。原因通常出在下面几个地方:

原因 现象 排查方向
接收缓冲区溢出 连续发送时,中间丢一段 检查中断服务程序是否处理太慢
中断优先级被抢占 高负载时丢包 检查 NVIC 优先级配置
硬件 FIFO 未使能 单字节收发正常,连续收发丢包 检查 USART_CTL2 寄存器的 TXFIFO/RXFIFO 使能位
GND 连接不良 偶尔丢,用手摸线就恢复 检查地线是否虚焊或接触不良

嗯,这里要特别说一下缓冲区溢出。GD32V 的串口接收 FIFO 深度是 16 字节。如果你在中断里做太多事——比如打印日志、处理协议——下一个字节进来时 FIFO 满了,新数据就直接丢弃。我建议:中断里只做数据搬运,把数据扔进环形缓冲区,处理逻辑放到主循环。

✅ 推荐做法: 定义一个 256 字节的环形缓冲区,中断服务函数里只做 buf[head++] = USART_DATA(USART0);,主循环里再解析。这样基本不会丢包。

3.4 实战:用串口助手定位一个典型乱码问题

假设你遇到了这样一个场景:GD32V 每隔 1 秒发送 "Hello GD32V\n",串口助手收到的是 "H弻o GD3 V\n"。你看,第一个字符对了,后面就乱了。

我的排查步骤:

  1. 先看波特率 —— 用示波器量 TXD 引脚,测一个 bit 的宽度。115200 对应约 8.68μs,如果测出来是 17.36μs,那就是配成了 57600。
  2. 再看时钟 —— 检查 system_clock_config() 里的时钟树配置,确认 HCLK、PCLK 的值。
  3. 最后看代码 —— 检查 USART 初始化结构体里的波特率寄存器赋值。

我曾经遇到一个更隐蔽的问题:代码里用了 printf 重定向到串口,但 fputc 函数里没有等待发送完成标志位。结果就是,上一个字节还没发完,下一个字节就把数据寄存器覆盖了。现象就是“第一个字符对,后面全乱”。

// 错误写法:没有等待 TC 标志
int fputc(int ch, FILE *f) {
    USART_DATA(USART0) = (uint8_t)ch;
    return ch;
}

// 正确写法:等待发送完成
int fputc(int ch, FILE *f) {
    while((USART_STAT(USART0) & USART_STAT_TC) == 0);
    USART_DATA(USART0) = (uint8_t)ch;
    return ch;
}

3.5 UART 调试知识体系

下面这张图,把 UART 调试的核心知识点串起来了。你可以把它当成一张“排查地图”,遇到问题按图索骥。

UART 调试知识体系 UART 调试实战 配置检查 配置项清单 波特率 / 数据位 / 停止位 校验位 / 流控制 / DTR/RTS 乱码问题 排查要点 ① 波特率误差 < 2% ② 时钟源配置正确 ③ 发送完成标志等待 丢包问题 排查要点 ① 中断服务程序效率 ② FIFO 使能状态 ③ 环形缓冲区设计 调试工具链 串口助手 示波器 逻辑分析仪

3.6 调试工具的选择与使用建议

串口助手是最常用的,但不同场景下工具的选择也有讲究:

  • 日常调试 —— 用串口助手就够了。推荐 SSCOM 或 Putty,轻量、稳定。
  • 分析时序 —— 用逻辑分析仪。抓一段波形,看起始位、数据位、停止位是否对齐。
  • 测波特率精度 —— 用示波器。量一个 bit 的宽度,反算实际波特率。

我个人习惯,调试包里常备一个 USB 转 TTL 模块(CH340 或 CP2102),一个 8 通道逻辑分析仪(几十块钱那种就够用)。这两个工具能解决 95% 的串口问题。

📌 核心总结: 乱码先查波特率和时钟,丢包先查中断和缓冲区。用 0x55 做测试,用示波器做验证。记住这三条,串口调试基本不会卡住。