上周给一批板子做小批量试产,两百多块,每块 256 KB 固件。产线原本用调试器走 SWD 烧,一块 4 秒出头,站一天手酸但还忍得住;后来隔壁工位临时借不到调试器,改用串口 Bootloader 通过 UART 下载,同一份固件、同一块板子,单块 29 秒。6.8 倍的差距摆在眼前,我当场就把两条链路完整测了一遍,把固件烧录的耗时拆到每一个阶段,把 JTAG 和 UART 之间的速度账算清楚。这篇文章就是那次实测的完整记录:怎么搭台架、怎么计时、6.8 倍这个数是怎么算出来的、这个倍数在什么条件下会缩水,以及我在过程中踩到的那些坑。不管你是刚接触烧录的新手,还是天天跟 OpenOCD 和串口协议打交道的老手,只要手上同时有调试口和串口这两条路,这篇内容应该都能用得上。
1. 为什么我非要把 JTAG 和 UART 的烧录速度拉出来单测
1.1 一次小批量试产里的等待成本
两百多块板,单块 29 秒,算上拿板、放板、对位、等提示音,实际一块占掉 35 秒左右,一整天满负荷也就烧七八百块,而且人是一直钉在工位上的。换成 SWD 之后单块 4 秒多,同一个工位一天能过两三千块,多出来的时间足够把测试工装再优化一轮。这件事让我意识到,烧录速度不是实验室里的数字游戏,它直接决定产线的节拍、工装的工位数,甚至决定你要不要半夜加班。
很多团队在选烧录通道的时候,习惯性地说"JTAG 快一点,串口慢一点",但"快多少"从来没人量化。有人以为快百分之二三十,有人以为快两三倍,实际测下来在 115200 波特率这个最常见的配置下,差距接近 7 倍——这个量级足以改变整个生产流程的设计。更关键的是,这 7 倍里有相当一部分并不是"线速"带来的,而是协议结构带来的,搞清楚这一点,你才知道该往哪儿优化。
1.2 两条链路的本质差别:谁在替你把数据搬进 Flash
JTAG 这条路,数据是从上位机经由调试器、走 TAP 状态机,直接落进芯片内部的调试访问端口,再通过调试单元去操作 Flash 控制器寄存器。整个过程是同步时钟驱动的,一个 TCK 周期搬一位,中间没有"请求-等待-应答"的往返,调试器可以在一次会话里把成百上千个写操作排进队列连续发出去。你可以把它理解成一条专用货运通道:车一直在跑,不用等红灯。
UART 这条路完全不同。上位机通过 USB 转串口桥发一串字节给芯片里的 Bootloader,Bootloader 收到一帧、校验、擦除对应页、写入、回一个应答,上位机收到应答才敢发下一帧。每一帧都要走一个完整的来回,而每一次来回都会叠加 USB 桥的缓冲延迟、驱动层的调度延迟和芯片端 Flash 写入的等待时间。这是典型的"停等协议",链路利用率天 生就上不去,和 JTAG 那种流水线式的搬运完全不是一个路数。
提示:这里说的 UART 指的是芯片内置系统 Bootloader 或自研 Bootloader 的下载模式,不是指应用程序里跑着的串口通信。两者的瓶颈来源不一样,不要混为一谈。
1.3 我给自己定的测试原则
测之前我先立了三条规矩,避免测出来的数字没法复现。第一,所有对比必须落在同一块板子上,同一颗芯片、同一批次的 Flash,避免器件差异干扰;第二,固件样本必须一致,我用同一份二进制文件,只改变体积,不改内容;第三,校验策略必须统一,要么两条路都开校验,要么都关,不能一边开一边关然后拿来比。之前见过有人拿"开了校验的 JTAG"和"关了校验的 UART"对比,结论偏离得离谱。
另外还有一条隐性原则:测试期间关掉所有无关进程,尤其是 IDE、串口助手、逻辑分析仪软件之外的常驻程序。听起来有点较真,但 USB 总线上跑的东西多了,转串口桥的调度延迟会明显变大,实测数据波动能到百分之十几,这种噪声足以让你得出一 个错误结论。
2. 测试台架怎么搭:从接线到计时脚本
2.1 硬件清单与接线方式
主控我选了 STM32F407VET6,168 MHz 主频,512 KB Flash,扇区划分是 4 个 16 KB 加 1 个 64 KB 再加 3 个 128 KB。选它是因为这颗片子在工程里太常见了,而且它的扇区大小不均匀这个特点,恰好能暴露出"按扇区擦除"带来的时间差异。为了做交叉验证,我还拿了一块 GD32F407 同封装板子跑了一遍,后面会提到它带来的意外。
调试器用了两套:一套 J-Link 走 SWD 模式,一套 ST-Link V2 做对照。SWD 只用 SWCLK 和 SWDIO 两根线,加上 GND 和可选的 nRST,接起来比 JTAG 那 20 针省事得多。串口这边准备了两个 USB 转串口桥,一个是 FT232RL,一个是 CP2102N,都是工程里最常见的方案。接线是标准的交叉:模块 TX 接板子 RX,模块 RX 接板子 TX,GND 必须共地。
需要特别提醒的是共地。我见过太多"串口能收到乱码"或者"握手偶尔失败"的案例,最后查出来都是地线接触不良或者用了太细的杜邦线。烧录这种持续高速传输的场景,地线不光是参考电平,还是回流路径,一根 26AWG 的细线跑几十厘米,信号质量会明显下降。
2.2 上位机工具链:OpenOCD 与串口下载
JTAG/SWD 侧我用 OpenOCD 命令行,因为它的时间开销最透明,没有 IDE 那些后台线程干扰。核心命令就这几行:
openocd -f interface/jlink.cfg -f target/stm32f4x.cfg \ -c "adapter speed 4000" \ -c "init" \ -c "reset halt" \ -c "program firmware_256k.bin 0x08000000 verify reset exit"adapter speed 4000是 4 MHz 的 SWCLK,这是我在杜邦线连接下能跑通并且不出错的上限。再往上提,J-Link 会报速率相关的告警,或者出现偶发的校验失败。"verify" 这个参数决定烧完是否回读校验,"reset" 决定烧完是否复位,"exit" 决定命令执行完是否退出。计时的时候我会把verify单独拿出来测,这样才能把校验阶段的时间单独拆出来看。
UART 侧一开始我想直接用 STM32CubeProgrammer 的命令行走系统 Bootloader,一条命令就能下载,省得自己写协议:
STM32_Programmer_CLI -c port=COM3 br=115200 -e all \ -d firmware_256k.bin 0x08000000 -v实测下来这条命令的总耗时是包含了工具启动、串口握手、擦除、下载、校验的全流程,颗粒度太粗,没法拆阶段。所以我又用 pyserial 写了一个最小化的自研 Bootloader 下载脚本,帧格式就是很常见的"帧头 + 序号 + 长度 + 128 字节数据 + CRC16 + 帧尾",芯片端每收一帧回一个 ACK。这样我能精确控制分帧大小、是否停等、是否校验,把变量抓在手里。
2.3 计时方法:为什么用单调时钟而不是系统时间
计时这一块我踩过一次坑。最早用 Python 的time.time()打时间戳,结果同一份固件两次跑出来的差值是 0.4 秒,反复测都对不上。后来才反应过来,time.time()读的是系统墙上时钟,受 NTP 校时和系统调度影响,在秒级测量里这点抖动不可忽略。换成time.monotonic()之后,重复性立刻好了很多。
import time, serial ser = serial.Serial("COM3", 115200, timeout=2) payload = open("firmware_256k.bin", "rb").read() t0 = time.monotonic() send_erase_all(ser) # 擦除命令 + 等待应答 t1 = time.monotonic() send_handshake(ser) # 握手与识别 t2 = time.monotonic() for chunk in frames(payload, size=128): send_and_wait_ack(ser, chunk) t3 = time.monotonic() send_verify(ser) # 触发芯片端 CRC 校验 t4 = time.monotonic() print("handshake", t1 - t0) print("erase", t2 - t1) print("transfer", t3 - t2) print("verify", t4 - t3)对应地,JTAG 侧我用 OpenOCD 的日志时间戳配合逻辑分析仪抓 SWCLK 的起止沿,两边交叉比对。逻辑分析仪在这里的作用不是看协议对不对,而是确认"软件认为开始了"和"线上真的开始动了"之间有没有延迟——这个差值在串口侧有时候能到几十毫秒,属于 USB 桥的缓冲成本。
2.4 固件样本与变量控制
固件样本我准备了 64 KB、128 KB、256 KB 三档。内容上用一个确定性的伪随机填充,避免大量 0xFF 被擦除优化或者大量 0x00 被压缩策略影响。每一档都在 JTAG 和 UART 两条路上各跑五遍,去掉最高和最低,取剩下三次的中位数。
变量控制清单如下:
| 变量 | JTAG/SWD 侧 | UART 侧 | 是否固定 | | 芯片与板卡 | STM32F407VET6 | 同一块 | 固定 | | 固件二进制 | 同一份文件 | 同一份文件 | 固定 | | 擦除方式 | 整片擦除 | 整片擦除 | 固定 | | 校验 | 开 / 关分别测 | 开 / 关分别测 | 固定 | | 连接线材 | 10 cm 排线 | 10 cm 杜邦线 | 固定 | | 环境温度 | 室温 | 室温 | 固定 | | 波特率 | — | 115200 / 921600 | 单独对比 |
这张表看着啰嗦,但正是它保证了后面那 6.8 倍的数字站得住。任何一项没锁住,结论都可能被质疑。
3. 实测数据拆解:6.8 倍是怎么算出来的
3.1 汇总数据:三档固件、两条链路
先把结论摆在前面。在 115200 波特率、开启全片校验、整片擦除的条件下,三档固件体积的实测结果如下:
| 固件体积 | SWD 4MHz 中位数 | UART 115200 中位数 | 倍数(UART / SWD) | | 64 KB | 1.08 s | 7.34 s | 6.80 | | 128 KB | 2.16 s | 14.70 s | 6.81 | | 256 KB | 4.30 s | 29.24 s | 6.80 |
三档体积的倍数几乎完全一致,都在 6.8 附近,这说明差距是结构性的,不是某一档数据的偶然。这一点很重要:如果倍数随着体积变化剧烈波动,那说明还有别的变量没控制住;现在三档一致,说明我拆出来的那条链路模型是对的。
换算成有效吞吐会更直观:SWD 在 4 MHz 下能跑到约 60.7 KB/s,UART 在 115200 下只有约 8.9 KB/s。前者是后者的 6.8 倍,数字对得上。
3.2 分阶段拆解:差距几乎全部集中在数据传输
单看总数 6.8 倍还不够,我把 256 KB 那一档的阶段耗时拆开了,结果比总数更有意思:
| 阶段 | SWD 4MHz | UART 115200 | 差距倍数 | | 握手与识别 | 0.35 s | 1.10 s | 3.1 | | 擦除 | 1.95 s | 2.05 s | 1.05 | | 数据传输 | 1.30 s | 25.10 s | 19.3 | | 回读校验 | 0.70 s | 0.99 s | 1.4 | | 合计 | 4.30 s | 29.24 s | 6.80 |
这张表解释了一个很多人想不通的问题:为什么综合倍数只有 6.8,而不是数据传输阶段的 19.3 倍?答案是擦除。擦除这一阶段两条链路都要花接近 2 秒,这是 Flash 物理特性决定的,跟你走哪条通道没关系。这 2 秒在两边的总数里都是固定成本,它把倍数的"平均值"拉低了。
换句话说,如果哪天你用的芯片擦除速度更快,或者你只做增量更新、不擦整片,那么 JTAG 相对 UART 的倍数会迅速向 19 倍靠拢。反过来,如果你烧的是一个只有几 KB 的小固件,擦除占比更高,倍数会压缩到 3 到 4 倍左右。所以"6.8 倍"这个数是有前提的,别把它当成万能常数。
3.3 波特率是 UART 的天花板,但 22% 的损耗另有其因
把波特率从 115200 提到 921600 再测一遍,256 KB 那档从 29.24 秒掉到 6.40 秒,已经快了很多,但对应的 JTAG 还是 4.30 秒,倍数缩到 1.5 倍左右。
这里有个计算可以说明问题。115200 波特率、8N1 格式(1 位起始、8 位数据、无校验、1 位停止),每个字节占 10 位,理论带宽是 11520 B/s。256 KB 是 262144 字节,如果纯按线速跑,光是发数据就要 22.8 秒。实测数据传输阶段 25.10 秒,意味着协议开销大约 10%,再加上分帧和应答等待,整体链路利用率大约是 78%。这 22% 的损耗来源于三处:每帧 5 字节的包头包尾、每帧一次的 ACK 往返、USB 转串口桥的批量传输周期。
所以结论是:在固件烧录这个场景下,波特率就是 UART 的硬天花板,115200 再怎么优化协议也突破不了 11.5 KB/s。真正划算的操作是把波特率往上提,比如 921600 甚至更高,同时把分帧从 128 字节放大到 1 KB 以上,减少往返次数。但即便做到极致,UART 也很难追上 SWD 的 60 KB/s 量级,因为 SWD 是同步时钟驱动的连续搬运,没有应答等待这个结构性问题。
3.4 这个 6.8 倍在什么条件下会被稀释
我把影响倍数的几个条件整理成了下面这张表,实际选型的时候可以对着看:
| 条件变化 | 倍数走向 | 原因 | | 波特率提到 921600 | 降到约 1.5 倍 | 线速不再是主要瓶颈,擦除占比上升 | | 分帧从 128 B 提到 1 KB | 略升(UART 变快) | 往返次数减少,协议开销降低 | | 固件体积缩小到几 KB | 降到 3-4 倍 | 擦除固定成本占比变高 | | 只做增量更新不整片擦 | 升到 10 倍以上 | 固定成本被消除 | | 关闭回读校验 | 两边都变快,倍数略升 | 校验时间占比不同 | | 换用擦除更快的芯片 | 倍数上升 | 固定成本下降 |
这张表比单一数字有用得多。它告诉你:当别人说"串口慢"的时候,你要追问的是"在什么配置下慢多少"。同样一条串口链路,115200 停等协议下是 8.9 KB/s,921600 大帧窗口协议下能到 40 KB/s 以上,差距接近五倍,全看你协议怎么设计。
4. JTAG 到底快在哪:从 TAP 时序到 Flash 控制器的搬运路径
4.1 同步时钟链:没有往返就没有等待
JTAG 的物理层是四到五根线:TCK 时钟、TMS 模式选择、TDI 数据输入、TDO 数据输出,可选 TRST 复位。所有动作都由 TCK 的边沿驱动,TMS 决定 TAP 状态机下一步往哪儿走,TDI 在每个时钟周期送一位数据进去,TDO 在下一个周期把结果吐出来。整个过程是同步的、确定的,没有"我发完了等你回话"这个环节。
这个结构决定了 JTAG 的吞吐几乎只受 TCK 频率限制。TCK 跑 4 MHz,理论上每秒能搬 4 Mbit,也就是 500 KB。实测落在 60.7 KB/s,效率大概 12%,看起来不高,但余下的损耗基本都在 Flash 编程本身——Flash 控制器写入一个半字需要若干个等待周期,这是硅片层面的限制,不是通道的问题。相比之下,UART 那 8.9 KB/s 里,有大约三成是协议和往返的开销,是通道自己吃掉的。
4.2 调试访问端口的排队与批处理
调试器这一侧还有一个关键机制:它可以在一 次 USB 事务里把多条调试操作打包下发,芯片端的调试访问端口按序执行完再把结果批量回传。这意味着上位机和芯片之间并不是"一条指令一次 USB 往返",而是"一批指令一次往返"。USB 全速设备的最小轮询周期是 1 毫秒,如果每条指令都要往返,1000 条指令就是 1 秒起步;打包之后,1000 条指令可能只需要几次往返。
这也是为什么不同的调试器在同样的 SWCLK 频率下速度差异巨大。J-Link 那类支持大容量批处理的调试器,和只支持小批量事务的简易调试器,实测吞吐能差两到三倍。选调试器的时候别只看标称频率,要看它的批处理深度。
4.3 批量擦除与写入节拍
擦除这块的差异其实没那么大,但值得说清楚。JTAG 路径下,调试单元可以直接往 Flash 控制器的寄存器里写擦除命令,一次配置好起始扇区和数量,剩下就是等控制器自己跑完。UART 路径下,Bootloader 收到擦除命令之后也是同样的操作,所以两条路在这一步的差距只有 5% 左右,基本可以认为一样。
真正拉开差距的是写入。SWD 写入时,调试器把数据按 32 位字流式送进 Flash 编程寄存器,Flash 控制器连续接收、连续编程,中间几乎没有空隙。UART 写入时,每一帧数据都要经过"接收中断 → 缓冲区拷贝 → 校验 → 拆成半字 → 逐次编程 → 回 ACK"这一串流程,其中任何一步拖后腿都会让整个节拍慢下来。很多自研 Bootloader 慢,问题不在串口本身,而是在接收缓冲不够大、编程过程阻塞了接收中断。
4.4 JTAG 还是 SWD:引脚账与速度账
既然都聊到这儿了,顺便把 JTAG 和 SWD 的取舍说一下。JTAG 是标准五线,SWD 只要两根(SWCLK 加 SWDIO)。速度上,在同一个调试器和同一个芯片上,SWD 一般比 JTAG 稍快一点,因为它的协议包更短,开销更小。但 JTAG 有一个 SWD 没有的优势:它支持菊花链,一条链上可以挂多个器件,产线上多芯片共板的场景很方便。
实际的选法我一般是这样:研发阶段用 SWD,省引脚、接线简单;产线如果板子上只 有一颗主控,也还是 SWD;只有当板上同时有多颗可调试器件,或者需要做边界扫描测试的时候,才切回 JTAG 四线或五线模式。这个决策不涉及速度,纯看拓扑和引脚预算。
5. 让实测翻车的几个坑,我一个个踩过
5.1 can't access jtag chain:先查电、地、时钟,最后才是片子
第一次接新板子的时候,OpenOCD 直接甩了一句Error: can't access jtag chain,后面还跟着一串unexpected error in。我第一反应是芯片坏了,准备换板子。幸好先按标准顺序查了一遍:目标板供电有没有(量了一下,3.28 V,正常)、调试器和板子 GND 有没有接上(用了两根细杜邦线,接触电阻偏大,换粗线之后现象消失)、nRST 有没有被外部电路拉低(有一个 10 k 下拉加一个电容,上电瞬间把复位拖长了,导致识别时芯片还在复位状态)。
排查顺序我后来固定成了四步,写在这儿可以直接抄:
- 量目标板 VDD,确认在芯片要求的范围内;
- 量调试器 GND 与目标板 GND 之间的压差,超过几十毫伏就说明地线没接好;
- 检查 nRST,尽量避免调试器和板上的复位电路打架,必要时改用软件的 soft reset;
- 把 SWCLK 频率降到 100 kHz 试试,如果低频能识别、高频不能,那就是信号完整性问题,换短线或者加串阻。
这四步走完,绝大多数"连不上"都能定位。真正芯片挂掉的比例,在我遇到的案例里不到一成。
5.2 固件把自己锁在门外:SWJ 引脚复用与禁用
这个坑更隐蔽。有一次烧完一版固件之后,调试器再也连不上了。查了半天才发现,代码里初始化 GPIO 的时候,把 SWD 用的两个引脚重新配置成了普通输出,理由是硬件原理图上那两个脚正好连着 LED。上电之后固件立刻把它们改成 GPIO,调试器还没来得及握手就被拒之门外。
这类问题在工程里非常常见。有些芯片的 SWJ 引脚默认开启调试功能,如果你在初始化里没有保留复用功能,或者手动把调试端口关掉,下次就只能靠 Boot 引脚进系统 Bootloader 用 UART 救回来。所以有一条铁律:调试引脚能用就别复用,非要用就在初始化时留一个延时窗口,或者用编译宏做条件编译,发布版本才关。
同样的情况在某些国产同封装芯片上也存在,而且引脚定义和默认状态未必和原厂完全一致。换片的时候一定要重新核对数据手册里关于 SWJ 复用的那一节,别想当然地认为"引脚兼容就是行为兼容"。
5.3 串口这侧的隐形损耗:驱动、线材与缓冲
串口链路的问题更琐碎。最常见的三种:驱动版本不匹配、线材质量差、缓冲策略不合理。
驱动这块,FT232RL、CP2102N、CH340 这几个桥的驱动都装过,遇到过装了通用版之后波特率偏差明显的情况,表现为高波特率下频繁帧错误。判断方法很简单,用示波器或者逻辑分析仪量一下实际位宽,标称 115200 对应的位宽是 8.68 微秒,偏差超过 2% 就要怀疑驱动或者晶振。
线材这块,杜邦线超过 20 厘米之后,921600 波特率下的误码率会肉眼可见地上升。解决办法就是缩短线长、增加地线根数,或者换成带屏蔽的排线。我最后的做法是把转串口模块直接固定在夹具上,线长压到 8 厘米以内,误码率直接归零。
缓冲策略这块,芯片端接收中断里不要做 Flash 编程。正确做法是中断只负责往环形缓冲里塞数据,主循环再从缓冲里取出来编程,缓冲至少要能装下三帧。这一条看起来是软件设计,但它对烧录速度的影响能到 30% 以上。
5.4 测试本身的坑:校验开关、日志与后台进程
最后说说测试方法本身的坑。第一个是校验策略不一致。开启回读校验之后,256 KB 那档 UART 从 29.24 秒变成 32.10 秒,JTAG 从 4.30 秒变成 5.62 秒,倍数从 6.80 降到 5.71。如果不锁住这个变量,你得出的结论会随缘波动。
第二个是日志输出。串口侧如果在接收回显里打印每一个 ACK 的调试信息,光打印就能拖慢一两秒。测速的时候一定要把日志级别调到只剩错误,或者干脆写进环形缓冲最后统一落盘。
第三个是后台进程。IDE 在后台做索引、杀毒软件在扫描、另一个串口助手占着同一个 COM 口,都会造成干扰。我现在的做法是测速前先把非必要程序全部退掉,任务管理器确认 USB 控制器没有异常占用,再开始计时。这些准备工作花不了五分钟,但能让你的数据可信度提升一个档次。
6. 三种场景下我会怎么选
6.1 研发调试:SWD 常驻,串口留作日志
研发阶段我的默认选择是 SWD。两个引脚,不用考虑共地噪声以外的信号问题,断点、单步、内存读写都走同一条链路,方便。JTAG 那四五个引脚在密集的板子上很奢侈,除非板上还有别的可调试器件。
串口在研发阶段不是烧录通道,而是日志通道。我会在固件里留一个轻量的日志输出,波特率通常开 921600,用来打时间戳和状态机迁移。这个串口不承担烧录职责,也就不会和调试口抢资源。真正需要重新烧固件的时候,还是走 SWD。
有一点要提前设计:把调试口和日志口的引脚在原理图阶段就规划好,别等 PCB 打样回来才发现调试引脚被占用了。这种返工成本远比多留两个测试点高。
6.2 产线量产:夹具、并行与脱机烧录
产线是最看重吞吐的地方。既然 SWD 能跑到 60 KB/s 而串口只有 8.9 KB/s,产线当然优先用调试口。但产线还有两个变量:工装接触可靠性和并行度。
接触可靠性方面,弹簧针夹具比排线更合适,但弹簧针的接触电阻会带来信号完整性问题,所以 SWCLK 频率通常要从研发时的 4 MHz 降到 1 到 2 MHz。即便降频,1 MHz 下的实测吞吐也有 25 KB/s 左右,仍然远高于串口。这个降频换来的是接触不良率从千分之几降到万分之一,很值。
并行度方面,一颗一颗烧是线性时间,用多工位夹具或者支持多通道的脱机烧录器,可以做到四工位、八工位并行。这时候瓶颈就转移到上下料的手速上了。按 256 KB 固件、单工位 4.3 秒算,八工位并行理论节拍不到 0.6 秒一块,已经远超人工极限。
6.3 现场升级:UART 这条保命通道
现场升级是完 全不同的场景。设备装在机柜里,你不可能拆开来接调试器。这时候应用层的通信通道就是唯一选择,通常是串口、CAN 或者网口,本质都是"应用先收数据,再写进 Flash"的 IAP 模式。
这条路的瓶颈比研发时的串口下载更严重,因为它多了应用层的协议封装和任务调度。我做过一个统计,同样的 256 KB 固件,走应用层串口升级,耗时大约是裸 Bootloader 模式的 1.3 到 1.5 倍,因为数据包要先经过任务队列再落盘。优化手段有两条:一是把升级协议做成大帧加窗口,不要停等;二是升级期间把其它非关键任务降频或挂起,把 Flash 编程时序让出来。
另外,现场升级一定要做掉电保护。写 Flash 的过程中断电,如果恰好毁了升级标志区,设备就变砖了。常规做法是把升级标志和校验值放在最后一个扇区,写入流程拆成"擦除-写入-校验-打标志"四步,前 三步任何一步失败都不改标志。
6.4 混合方案:产线用 JTAG,设备留 UART 兜底
回到最开始那 6.8 倍。我的最终方案是混合的:板子上同时保留 SWD 测试点和串口接口。产线用 SWD,把节拍打下来;固件里保留一个最小的串口 Bootloader,只实现擦除、写入、跳转三条命令,代码量控制在 4 KB 以内,平时不占用运行时间,只在特殊情况下才进去。
这个兜底通道的价值在 5.2 节那个坑里体现得淋漓尽致——当固件不小心把调试引脚复用掉之后,是串口把板子救回来的。烧录速度的账要算,但可用性的账更要算。两条路都留着,平时用快的,出事用能进的,这才是工程上比较稳妥的做法。
有一点值得注意:串口 Bootloader 的进入条件要设计得明确,比如上电后检测某个引脚电平,或者检测 Flash 里某个魔数。千万别设计成"上电后先等 500 毫秒看有没有串口命令",那样每次上电都会多出半秒延迟,量产测试的时候这半秒会累积成很可观的数字。
最后再分享一个小技巧。如果你手头只有串口、必须做大批量烧录,可以把固件先做一次简单的差分压缩,只传变化的部分。我在一个 512 KB 固件的项目里这么干过,增量包只有 43 KB,串口烧录时间从 58 秒降到 6 秒出头,比换通道还管用。前提是芯片端要有足够的 RAM 做解压缓冲,而且 Bootloader 得支持按扇区定位写入——这两点在设计初期就要留出来。