实战:AI + SWD + 串口调试 AT32F403A——从串口乱码到寄存器根因的完整排查
Contents
起因
一块 AT32F403A(雅特力,Cortex-M4F @ 240MHz)的工业控制板,串口通信出问题了:短命令一切正常,长命令后半段全是乱码,偶发丢字节,频率不固定。用逻辑分析仪抓波形——干净得很,一个毛刺都没有。
波形正常,MCU 收到的却是错的数据。那问题出在哪?
加 printf 排查,一轮十几分钟,改了三次代码,异常依旧。这时候我意识到:问题的答案不在代码里,在芯片的寄存器里——但寄存器是看不见的,除非你有办法让程序停下来,把芯片的内部状态翻出来看。
这篇文章讲我怎么用 AI + SWD + 串口 这套组合,几分钟定位到根因,以及总结出的嵌入式 AI 调试范式。
需求
- 在 GCC + OpenOCD 环境下调试 AT32F403A(不用 Keil/IAR)
- 用 J-Link 通过 SWD 接口连接目标板,能实时查看寄存器和内存
- 解决 OpenOCD 认不出 J-Link 的驱动问题(zadig 替换 WinUSB)
- 用 Python 脚本自动化串口数据收发和验证,异常可复现
- 用 Codex 辅助生成调试脚本和修复代码
- 最终总结一套可复用的 AI + 开发板 + 开源工具 嵌入式调试范式
环境准备
硬件连接
|
|
注意 VTref 必须接目标板电源参考电压,否则 J-Link 无法识别目标芯片。
软件工具链
| 工具 | 用途 |
|---|---|
arm-none-eabi-gcc |
编译(-mcpu=cortex-m4 -mthumb -g -O0) |
arm-none-eabi-gdb |
调试器客户端 |
| OpenOCD | GDB 服务器 + SWD 调试器驱动 |
| J-Link | SWD 调试探针 |
| Python + pyserial | 串口自动化测试脚本 |
| zadig.exe | 替换 J-Link 的 USB 驱动 |
编译固件时用 -g -O0(保留调试符号、关闭优化),否则 GDB 里变量可能被优化掉,断点可能落不在预期位置。
踩坑记录:OpenOCD 认不出 J-Link
问题现象
|
|
报错:
|
|
但 J-Link 在设备管理器里显示正常,SEGGER 官方工具也能识别。
原因
OpenOCD 使用 libusb 与 J-Link 通信,需要 WinUSB 驱动。但 SEGGER 官方驱动是私有的,和 libusb 不兼容。J-Link 出厂默认装的是 SEGGER 驱动,所以 OpenOCD 找不到设备。
解决:zadig 替换驱动
- 下载 zadig.exe(OpenOCD 官方推荐的驱动替换工具)
- 拔掉 J-Link,关闭所有 IDE 和调试软件
- 重新插上 J-Link
- 打开 zadig,菜单 Options → List All Devices
- 下拉列表选择 “J-Link”
- 目标驱动选择 WinUSB
- 点击 Replace Driver,等待完成
- 设备管理器里 J-Link 的驱动将显示为 WINUSB
验证:
|
|
注意事项
- 驱动替换后 SEGGER 官方工具将不可用(J-Link Utility、Ozone、J-Flash 等)
- 还原驱动:运行 J-Link 安装目录下的
JLinkDLLUpdater.exe(SEGGER 官方驱动修复工具),它会自动重装官方 USB 驱动 - 某些 J-Link 克隆版(V8/V9 非官方固件)可能 WinUSB 替换后仍有问题,可以尝试选
libusb-win32或libusbK驱动 - 雅特力官方提供了一个打过补丁的 OpenOCD(AT32 Eclipse 工具包里),对 AT32 系列的 flash 驱动支持更好。如果用 stock OpenOCD 遇到 “flash driver missing”,换雅特力版本
启动调试
OpenOCD 配置文件
at32_swd.cfg:
|
|
启动 OpenOCD:
|
|
GDB 连接
另开一个终端:
|
|
|
|
串口收发异常排查
异常现象
调试的串口通信协议是:PC 发送命令帧(8 字节头 + N 字节数据 + 2 字节 CRC16),AT32F403A 收到后回复响应帧。实际运行中出现:
- 短命令(<16 字节)正常,长命令(>32 字节)后半段乱码
- 偶发丢字节,频率不固定
- 用逻辑分析仪看波形正常,但 MCU 收到的数据错位
排查思路
第一步:用 Python 脚本做可复现的测试输入
不依赖上位机软件,直接用 Python 精确控制发送时序和内容:
|
|
这个脚本能精确复现异常:Test 1 通过,Test 2 必现乱码。
第二步:GDB 断点定位
在 GDB 里观察串口接收中断的执行情况:
|
|
在长命令测试里,第一次中断进来时 usart1_rx_cnt 正常,继续运行几次后,发现中断处理函数里读到的数据已经跟发送的不一致了。
第三步:看寄存器,找根因
|
|
关键发现:USART1_SR 的 ORE(Overrun Error)位被置位了。
根因:接收缓冲区太小 + 中断处理太慢。
原代码用一个 32 字节的环形缓冲区存接收数据,长命令超过 32 字节后,新数据覆盖旧数据(还没被主循环取走)。加上中断处理里有几行 debug printf(又是一个串口发送,阻塞的),导致中断执行时间过长,更容易溢出。
修复
|
|
修复后重跑 Python 测试脚本:Test 1/2/3 全部通过,连续发送 1000 帧无丢包。
Codex 在调试中的角色
这篇调试过程中,Codex 做了三件事:
- 生成 Python 测试脚本——描述协议格式和异常现象,Codex 生成
test_uart.py,人工审核后使用 - 生成 DMA 接收重构代码——描述当前中断接收的问题和目标,Codex 生成修复代码,人工 review 后合入
- 辅助查寄存器手册——把 AT32F403A 参考手册里 USART 章节的寄存器描述贴给 Codex,让它生成 GDB
p/x命令和对应的位解释
Codex 生成的是初稿,寄存器地址、DMA 通道号这些关键参数需要人工核对参考手册,但框架和思路可以省去大量查文档的时间。
范式总结:AI + 开源工具 + 开发板的嵌入式调试方法论
这次排查不只是解决了一个 bug,更重要的是验证了一套可复用的范式。
核心思想:AI + SWD + 串口
|
|
SWD 是这套范式的灵魂。它让 AI 第一次能"看到"芯片的内部状态——不只是代码跑没跑,而是寄存器的每一位、内存的每一个字节,都是可见的、可验证的。AI 生成的假设,SWD 能立刻验证;AI 生成的修复,SWD 能立刻确认。
为什么不用 printf
| 方式 | 原理 | 问题 |
|---|---|---|
| printf 调试 | 在代码里插打印,重新编译下载 | 改变时序、遮挡中断、一轮十几分钟 |
| 逻辑分析仪 | 看引脚波形 | 只能看到"发了什么",看不到"芯片收了什么、为什么" |
| SWD + GDB | CPU 停下来,直接读寄存器和内存 | 看到芯片"此刻的状态",不改代码,不改时序 |
printf 调试最大的问题是:你在改变被测系统的行为来观测它。加打印会占用串口、改变中断时序、引入额外延迟——对于时序敏感的串口通信问题,printf 本身就是干扰源。SWD 是非侵入式的:CPU 暂停,状态原样呈现,恢复运行后行为不变。
范式步骤
第一步:Python 脚本做可复现输入
不依赖上位机软件,Python + pyserial 精确控制发送的每一个字节、每一次时序。异常从"偶发"变成"必现"。
第二步:AI 生成测试和修复代码的初稿
把协议格式、异常现象、参考手册片段描述给 Codex,它生成 Python 测试脚本和修复代码框架。关键参数(寄存器地址、DMA 通道号)人工核对。
第三步:SWD 断点 + 寄存器检查,定位根因
GDB 断点停在串口中断里,直接读 USART1_SR、USART1_DR——根因写在寄存器的位里(这次就是 ORE 溢出标志),不是猜出来的。
第四步:修复 + 回归验证
AI 生成修复代码(缓冲区扩大 + DMA 接收),SWD 确认修复后寄存器状态正常,Python 脚本回归测试确认不再复现。
这套范式的边界
- 需要 SWD 调试接口(J-Link/ST-Link/DAP-Link 均可)
- 需要固件带调试符号(
-g -O0编译) - AI 生成的寄存器相关代码必须人工核对参考手册——AI 会编地址、编位定义,这是它的幻觉高发区
- 时序极敏感的问题(ns 级),SWD 断点本身会引入暂停,需要结合逻辑分析仪
这套范式里,哪些事必须人工做
| 环节 | 人工 or AI | 说明 |
|---|---|---|
| 开发板接线(SWDIO/SWCLK/GND/VTref) | 人工 | 物理连接,AI 无法代劳 |
| 串口接线(TX/RX/GND) | 人工 | 同上 |
| 替换 J-Link 驱动(zadig) | 人工 | Windows 系统级操作 |
| 给 Codex 写提示词 | 人工 | 描述协议格式、异常现象、修复目标——AI 的输出质量上限取决于提示词的精确度 |
| Python 测试脚本 | AI 生成 + 人工审核 | AI 写框架,人工核对协议细节 |
| 寄存器地址、DMA 通道号 | 人工核对参考手册 | AI 幻觉高发区 |
| GDB 断点设置、寄存器解读 | AI 辅助 + 人工判断 | AI 给命令,人看结果 |
| 修复代码合入 | 人工 review | 最终责任人 |
核心原则:AI 加速"想"和"写",人负责"接"和"验",而"想"的起点——提示词——必须由人来写。接线是物理世界的操作,验证是工程责任的底线,提示词是调试思路的起点——这三头AI替不了,中间环节AI效率最高。
总结
这次串口异常从"偶发乱码"到"根因定位",耗时不到一小时。如果用传统方式(printf + 逻辑分析仪 + 翻手册),可能要两三天。
核心不是工具多先进,而是思路的转变:
| 传统调试 | 范式调试 |
|---|---|
| 猜 → 改 → 试 | 看 → 定位 → 改 |
| printf 看输出 | SWD 看寄存器 |
| 手动发测试数据 | Python 脚本自动化 |
| 自己翻手册查寄存器 | AI 生成查询命令,人工核对 |
SWD 是嵌入式的"透视眼",AI 是这只眼睛的"大脑"。 一个是让芯片的内部状态可见,一个是让可见的信息变成可执行的调试动作。两者结合,嵌入式调试从"玄学"变成"科学"。
参考链接
Author 软件开发大郭
LastMod 2026-09-07