这是「自己写一个 MCU 烧录上位机」系列的下篇。 上篇《OpenOCD Telnet 协议解析与四步烧录法》解决的是"能烧进去、能读回来、判据不骗人";本篇讲剩下的两块硬骨头:
不依赖 cfg 里的 flash 驱动、直接操作 eFlash 控制器寄存器的全片擦除 ,以及 v2.1 → v2.6 六轮现场迭代 ------每一轮的起点都是现场一句话:"还是不行"。
另有第三篇《如何把技术经验沉淀为 AI 技能》,讲这套流程怎么固化成可复用的技能。
全篇的落脚点是同一件事:怎么知道自己真的做到了 。擦完必须读回 0xFFFFFFFF;上板动作也要写成脚本;验收清单分"离线已证 / 上板已证 / 仍未覆盖"三栏,绝不混。
本篇导航
| 节 | 内容 | 关键判据 |
|---|---|---|
| 一 | 寄存器级全片擦除:时序真值 | 先 W1C 清 FINISH → 触发命令 → 等 FINISH=1,并查 5 个错误位 |
| 二 | 现场故障速查表(擦除 / 独占锁 / 留档类) | 擦除超时、撞锁不重试、留档与代码不同版 |
| 三 | 六轮迭代实录:v2.1 → v2.6 | 每轮:现场症状 → 根因(带证据)→ 改法 → 上板结果 |
| 四 | 真板回归:把"待上板"逐个划掉 | board_test.py 七场景,断言 64 通过 0 失败 |
| 五 | 验收清单与边界声明 | 离线已证 / 上板已证 / 仍未覆盖 |
上篇讲过的四步烧录与故障速查(环境、链路、烧录、读回对比类症状)本篇不重复。
一、全片擦除:寄存器级时序(本篇最硬的一节)
OpenOCD 的 flash erase_sector 能不能用,取决于 cfg 里的 flash 驱动实现得对不对。想"确定擦干净",就要自己能直接操作 eFlash 控制器。以 AS32x601 为例(基址 0x42100000):
1.1 寄存器速查表
| 偏移 | 名称 | 关键位 |
|---|---|---|
| +0x00 | SR 状态 | LOCK b0、BUSY b1、FINISH b2 、OPERR b3、ECC1ERR b4、ECC2ERR b5、WPERR b6、SECKEYERR b7、CLKSWRDY b16 |
| +0x04 | UNLKR 解锁 | 写 0x01020304 再写 0x0A0B0C0D 开锁;写二者的按位取反重新上锁 |
| +0x08 | CR 控制 | CLKFRQ = bits31:16,eFlash 工作时钟(MHz,AXIBus0 频率) |
| +0x0C | CIDR 命令 | 写命令 ID:0x01 扇区擦、0x02 块擦、0x03 保护信息区擦、0x04 全片擦 、0x10 编程 |
| +0x10 / +0x14 | AR / DLR | 地址 / 数据长度(全片擦不需要) |
| +0x30 | CTR 触发 | 写 1 启动命令 |
1.2 正确的擦除序列(照着这个顺序做)
1) reset halt # 目标必须停住
2) mww 0x42100004 0x01020304 # 解锁 KEY1
mww 0x42100004 0x0A0B0C0D # 解锁 KEY2
读 SR:LOCK(b0) 必须为 0,否则解锁失败
3) 读 CR[31:16] = CLKFRQ
- CLKFRQ == 0(芯片刚复位、固件还没配)→ 写 CR 设为 AXIBus0 频率(本板 160MHz 量级)
- CLKFRQ != 0 且与你的设定不一致 → 保留芯片现值(别乱改!)
- 写 CR 后必须等 SR.CLKSWRDY(b16)=1
4) mww 0x42100000 0x00000004 # 清 FINISH(W1C,写 1 清除)
读 SR 直到 BUSY(b1) == 0
5) mww 0x4210000C 0x00000004 # CIDR = 全片擦除
mww 0x42100030 0x00000001 # CTR = 1 启动
6) 轮询 SR 直到 FINISH(b2) == 1 # 全片擦除较慢,超时给 ≥60s
每轮同时检查 OPERR/ECC1ERR/ECC2ERR/WPERR/SECKEYERR,命中即失败
7) 抽样读回:0x01000000 起读若干字,必须全是 0xFFFFFFFF(空片)
8) (可选)mww 0x42100004 写 ~KEY1、~KEY2 重新上锁
1.3 三个最容易搞错的地方
- FINISH 是"清掉 → 命令完成后置 1",不是"等它变 0"。
把方向搞反的典型后果:FINISH 残留为 1 时以为已经完成(秒过、其实命令根本没发),或者一直在等一个永远不会来的 0(白等到超时)。正确做法:先 W1C 清零,触发命令,再等它变回 1。 - 从不查错误位。
OPERR / WPERR / SECKEYERR任意一位被置起,说明擦除/编程实际失败了,这时报"成功"就是事故。 - 时钟没就绪就下命令。
CR.CLKFRQ与实际 AXI 时钟不匹配时,控制器寄存器写入会静默无效 ------命令写进去、SR 也不报错,但 flash 一点没动。所以:先确认CLKFRQ,写完CR必须等CLKSWRDY=1。
擦除是不可恢复操作。擦完芯片就是空片(
0xFFFFFFFF),复位也只是跑飞。合理流程是:擦除(保持 halt)→ 立刻烧录 → 复位运行。另外如果目标固件开了看门狗(AS32x601 的 WWDG 在 tick 死掉时会秒级复位),长操作中途可能被打断,现场要先关狗或缩短单次操作时间。
二、现场故障速查表(下篇:擦除 / 独占锁 / 留档类)
按症状查(环境、链路、烧录、读回对比类症状见《上篇》第五节):
| 症状 | 根因 | 处置 |
|---|---|---|
| 擦除"完成"但数据还在 | FINISH 语义搞反 / 不查错误位 / CLKFRQ 未就绪导致写入静默无效 | 按本篇第一节重做时序;擦完必须抽样读回 0xFFFFFFFF |
| 擦除等 FINISH 一直超时 | 解锁失败(LOCK 仍为 1)、CLKSWRDY 未置位、目标被看门狗复位 |
打印 SR/CR 原值定位;关狗或缩短操作 |
第二个任务点下去被挡;旧版日志出现 第 n/3 次尝试失败: 已有任务正在占用调试链路... |
独占锁:同一时刻只允许一个任务(本地冲突,不是链路坏) | v2.6 起当场报错、不重试:等前一个任务结束再点;别去查线/换线/查供电(见 3.6) |
后台跑脚本,通知说 exit code 0 但脚本其实失败了 |
那个退出码是外层 shell wrapper 的,不是脚本的 | 命令写成 ... > log 2>&1; echo "EXIT=$?",让退出码进日志,看日志尾行判定 |
| 留档日志与盘上代码不是同一版 | 改完脚本没重跑留档(且工具目录不是 git 仓库,连 diff 都没有) | 留档前把 sha256 + 字节数 写进日志头部;改了脚本就重跑重留档(见 4.2) |
三、六轮迭代实录:一个"简单下载工具"是怎么被磨出来的(v2.1 → v2.6)
v2.0 解决了"能不能烧",真正难的是后面六轮 ------ 每一轮的起点都是现场一句话 :"还是不行"、
"报告下载失败其实已经写进去了"、"点了没反应"。下面按轮次记:现场症状 → 根因(带证据)→ 改法 → 上板结果 。
坑都很小,但每一个都长得像"芯片坏了"。
3.1 v2.1:判据写错 ------ "报告失败,其实已写进去,还要手动按复位键"
- 症状 :烧录每次都在
[3/4]报校验失败;板子必须手动按复位键才跑起来。 - 根因 :成功判据写的是
** Verified OK **。这个字符串只有 OpenOCD 内嵌 Tcl 过程program才会 echo
(program内部才去调verify_image);我们是裸调verify_image,它只打印原生行
verified 44908 bytes in 0.115688s (379.084 KiB/s)。判据永远匹配不上 → 每次误报失败 →
第 4 步reset run被跳过(这就是"要手动按复位键"的根因)。 - 改法 :判据换成
verified N bytes in ...s且 N 必须等于文件大小 ;顺带把wrote N bytes里
N 略大于文件大小(≤4KB)判为正常 ------ 驱动按写入边界补齐0xFF(实测44908 → 45056,+148 = 0x200 对齐)。 - 上板 :四步全通 7.2s ;故意改 1 字节的副本被
diff 0 address 0x01000100. Was 0x04 instead of 0xfbNo more differences found.判失败 ✓。
- 教训 :判据只能取"被调命令自己"的输出,不能取你以为它会打的输出。
3.2 v2.2:一个真 bug + 一次给自己下的绊子
- 真 bug(保留至今) :OpenOCD 的 telnet 应答每行行首混入 NUL 字节(
0x00) ,解析按行首锚定 →
整行被丢,0x00+0x42100000: 00000001一个字节都读不出来。改为解析前先剥控制字符。 - 绊子(当轮就撤了,见 3.3) :为解决"目标没停住",加了一个"读回确认"------
reset halt之后用
mdw 0x42100000(eflash SR)自证"停住了"。看着稳妥,实际是拿 DMI/abstract 通道当判据。
3.3 v2.3:撤销探针 ------ "目标其实停住了,被工具误判成没停住"
- 现场症状 :10:13 一次事故日志里 91 行
Warn : target not halted+Unable to halt. dmcontrol=0x00000001, dmstatus=0x00430c82;
随后加了探针的版本在[1/4]就拦死,一个字也下不进去("还是不行"说的就是它)。 - 根因 :那个探针读寄存器走的是 abstract/DMI 通道,链路边缘会超时
(Timed out after 2s waiting for busy to go low (abstractcs=0x2001001)/
Failed to read memory via program buffer/progbuf=failed, sysbus=skipped (unsupported size), abstract=failed)。
通道超时 ≠ 目标没停住 ------ 于是工具把"已经停住"的目标判成"没停住"。 - 改法 :探针整个撤掉,
reset halt之后不再发任何额外命令 ;判据回归最朴素:只认被调命令输出里明确的
Unable to halt/target not halted/Hart unexpectedly reset。同时删掉一条自伤签名 :
Timed out after Ns waiting for busy曾被我写进"没停住"判据 ------ 它正是触发误判的那条。 - 保留的守门(零副作用) :只在
[2/4]写入步的输出里出现上述明确文案时立即中止 ------
10:13 事故里那 91 行target not halted恰好落在写入步。 - 上板 :10:41 四步全通 5.2s (
wrote 45056/verified 44908/reset run OK);10:46 复验 5.3s。 - 教训 :"正向确认"看着稳妥,在通道不稳的板子上会变成新的拦路虎 ;判据要"零副作用"------
能只见证、不打扰被调命令。
3.4 v2.4:把"连接"从界面按钮收进任务里(顺手抓到一个真并发 bug)
- 需求:去掉"连接 / 断开 / 连接状态"三个按钮 ------ 不想先连再点,点"开始"就该干活。
- 改法 :三个页签点"开始"时自动连接 ,任务完成或失败后自动断开 (断开放在
finally,
异常路径也不留残留openocd.exe);路径预检挪到点按钮那一刻同步做;连接与断开都打进当前页签日志,
每次任务另存一份logs/openocd_*.log(保留最近 10 份)。 - 顺带加固 :链路加独占锁 (第二个任务被挡,见 3.6)。真机实测代价:每个任务多花 4--6s 在
OpenOCD 起停上(烧录整任务 11.4s、擦除整任务 5.1s,均含连接与断开)。 - E2E 抓到并修掉的真 bug(离线 33 项全绿也没抓到它) :
Session.open()原本在工作线程 里回读 Tk
输入框(le_exe.get())→ 抛RuntimeError: main thread is not in main loop,任务 1.2s 就"失败"收尾,
一步都没走 。改法:主线程先取快照(Session.sync()),工作线程只读快照、绝不回头摸控件 ;
离线自测补一条回归项session_open_never_touches_gui_getters。 - 教训 :离线全绿 ≠ 真机可用 ------ 这个 bug 只有把真界面(真 Tk + 真线程)跑起来才会暴露。
3.5 v2.5:失败重试 ------ 把"偶发"和"真错"分开
- 需求 :链路边缘偶发失败(
dmstatus=0x0、Hart unexpectedly reset、failed read at 0x11洪泛)时别一次判死,多给两次机会。 - 实现 :
MAX_ATTEMPTS = 3、RETRY_DELAY_S = 2.0(都在main.py顶部,想改改这两个常量)。三个关键决定:- 重试粒度 = 整个操作 (不是单个步骤):每次失败都断开 → 重连 → 从头再跑一遍 。
理由:半死的连接复用不了,新起一个 OpenOCD 进程才有干净的 telnet/DMI 状态。 - 参数类错误不进重试环 :文件不存在 / 地址越界 / 路径无效在点按钮那一刻就被同步拦下
(_start/_pre_check),不会对着"写错的路径"白跑 3 次;纯本地的对比页不占链路,失败不重试。 - 失败可见 :日志依次写
第 n/3 次尝试失败: ...、---- 第 n/3 次尝试(上次失败:...)----、
第 n/3 次尝试成功;用尽则已连续 3 次失败,停止重试(不再自动重连)。
- 重试粒度 = 整个操作 (不是单个步骤):每次失败都断开 → 重连 → 从头再跑一遍 。
- 代价(如实说) :最坏耗时 ≈ 3 × (单次超时 + 连接时间) + 2 × 2s;每次尝试各一份 OpenOCD 日志
(3 次尝试 = 3 个时间戳,按时间顺序连起来看就知道"败在哪一次、哪一步")。 - 上板(注入式验证,不靠"碰运气遇偶发") :
① 一次成功:日志没有 任何"第 n/3 次尝试"字样,只连 1 次 ✓;
② 注入第 1 次失败:第 1/3 次尝试失败: 测试注入的失败(第1次)→---- 第 2/3 次尝试(上次失败:...)----
→第 2/3 次尝试成功,共连接 2 次 (证实"先断再连")、不弹错误框,全程 17.2s ✓;
③ 坏 cfg 连吃 3 次:日志写全 1/3、2/3、3/3 +已连续 3 次失败,停止重试(不再自动重连),
共连接 3 次、无第 4 次 ,全程 17.6s ,弹窗带 OpenOCD 原始报错尾巴 ✓;
④ 收尾:还原 cfg 再烧一次 PASS(10.8s),证明"重试用尽"不留后遗症 ✓。 - 教训 :想验证"重试能救回偶发",别等偶发自己发生 ------ 注入一次失败,把"第 2 次成功"
变成可复现的用例;同时用"连接次数"作断言(1 次 / 2 次 / 正好 3 次),比数日志行可靠。
3.6 v2.6:撞锁该"重试"还是"立刻报错" ------ 一次产品决策
- 症状 :烧录在跑时再点"读取",旧版不是当场报错,而是白等约 4 秒 才弹错,而且日志里留下 3 行
第 n/3 次尝试失败: 已有任务正在占用调试链路...------ 看着像链路坏了,很容易把人引去查线、查供电。 - 根因(两处代码) :① 锁在
Session.open()用acquire(blocking=False)抢,抢不到就抛异常;
② v2.5 的重试外壳把任何 异常都当"这次尝试失败"。锁冲突正好走这条通道,
于是本地冲突被当成了链路故障。 - 把选项摆清(这一段是方法论) :
A 保留自动补跑 (代价:延迟报错 + 把本地冲突伪装成链路故障)/
B 认出"占用"就当场报错,不重试不等待 /
C 锁改可等待、真排队 (功能最强,但要给第二个任务加"等待中"状态,偏离"点一下、成或败都立刻有结论"的心智)。
关键事实:"自动补跑"窗口只有约 5 秒 (3 次尝试落在 T = 1s / 3s / 5s),而烧录要 10.5s、擦除 5s
→ 现实里几乎总以弹错收场,只是比"立刻报错"晚 4 秒。用户选 B。 - 改法(可复用的模式) :给"本地冲突"一个专用异常
LinkBusyError(在拿锁失败处抛它),
重试外壳里单独except LinkBusyError→ 写一行日志 +raise,且必须放在通用except Exception之前 。
要点:"要不要重试"由异常类型决定,而不是去匹配错误文案 ------ 文案会变,类型不会。 - 上板 :场景 6 断言 8/0 :被挡任务的日志只有一行新文案(无
第 n/3)、
根本没去连 OpenOCD (logs/里该任务 0 份日志 = 连接前就被拦下)、先起的烧录照常跑完、结束无残留。 - 教训 :这类"要不要重试 / 要不要弹窗"是产品语义 ,不是 bug 修法 ------ 别自己拍板改码,
把选项原样摆给用户。解释要三件套:① 一句话说清在问什么;② 贴代码位置 (哪一行、什么行为);
③ 给具体时间线(谁先谁后、每秒发生什么)+ 每个选项的代价 + 你推荐哪个及理由。
四、真板回归:把"待上板"那一栏逐个划掉(board_test.py 七场景)
此前版本结尾留了一栏"必须上板确认(本机无板,未验证) "。补上板之后,那 5 项的去向是:
3 项关闭、1 项部分关闭、1 项明确转入后期迭代 。做法不是"手工点一遍看看",而是把上板动作也写成脚本。
4.1 从"假对端"到"真板回归脚本"
- 分工 :《上篇》第六节的假对端负责证明"顺序对、判据对、失败会报错 "(离线,随时能跑);
上板脚本负责证明"真硬件上确实如此"。两层证据都不能省,替换不了。 - 做法 :脚本驱动真正的界面 (弹窗自动确认,代替人点按钮),跑完打印
断言 通过 N / 失败 M- 每场景
[PASS]/[FAIL]+ 退出码。一条命令全跑,约 3~6 分钟。
- 每场景
| # | 场景 | 判 PASS 的关键点 |
|---|---|---|
| 1 | 烧录 + 校验 | 四步全通;自动连接恰好 1 次 ;没有"第 n/3 次尝试"字样(一次就成) |
| 2 | 读取 + 对比 | 读回 44908 字节与固件逐字节一致 ,日志 对比通过:两个文件完全一致 |
| 3 | 本地对比页 | 同两文件 → 完全一致;改 1 字节 → 发现 1 处差异 + 首个差异偏移: 0x10;且不占链路 |
| 4 | 重试成功率 | 注入第 1 次失败 → 第 2/3 次尝试成功,共连接 2 次,不弹错误框 |
| 5 | 重试用尽 | 坏 cfg → 写全 1/3、2/3、3/3 + 已连续 3 次失败,停止重试;连接正好 3 次(无第 4 次) |
| 6 | 互斥锁 | 烧录在跑时第二个任务被挡(日志/弹窗出现"占用")、无产出,第一个任务照常跑完;v2.6 起还断言不重试 |
| 7 | 全片擦除闭环 | 备份 45056B(确认非空片)→ 擦除 → 读回全 0xFF → 烧回复原 → 整片读回与固件逐字节一致 |
- 真机结果(2026-09-14) :全 7 场景
[PASS]、断言 通过 64 / 失败 0、收尾 openocd 残留=False、退出码 0。 - 实测耗时(本机、链路 1MHz) :整片擦除整任务 5.0s (其中"等 FINISH=1"仅 0.1s ,
SR=0x00000004,
默认finish_timeout=120s余量充足);单次烧录整任务 10.5s ;重试成功(2 次)17.2s ;
重试用尽(3 次)17.6s;全套 7 场景 3~6 分钟。 - 安全护栏(写进脚本,不靠自觉) :跑前预检(固件在不在、路径有效、有没有程序占着链路、再读 16 字节冒烟),
不过就退出码 2 ;场景 7 先成功备份 45056B 才允许整片擦除 ,备份失败即中止、绝不裸擦 ;
场景 5 临时换坏 cfg,跑完或异常退出都还原,不写脏tool_config.json;脚本开头就警告
"会覆盖0x01000000、场景 7 会整片擦除,别对着量产件或别人的板子跑"。
4.2 三个"看着通过、其实没通过"的坑(都真踩过)
上板脚本最危险的不是写不出来,而是它会给你一个假绿的结论:
- 后台跑命令,通知里的
exit code 0不是脚本的退出码。
用python board_test.py > log 2>&1丢进后台,收到的"正常退出"是外层 shell wrapper 的 ------
脚本自己EXIT=1也照样报 0。正确写法 :python board_test.py > log 2>&1; echo "EXIT=$?",
把退出码写进日志,读日志尾行判定。 - 留档日志与盘上代码不是同一版。
改完脚本接着调,留档的还是改之前跑的那份(而这个工具目录不是 git 仓库 ,连 diff 都没有)
→ 你手里的"证据"其实对不上代码。纪律 :留档前把sha256 + 字节数写进日志头部;
改了脚本就重跑重留档,别在旧文件上继续叠(真要叠就写明哪一段属哪个指纹)。 - 断言写成"瞬时状态"或"我以为是的原因"。
踩过的三条:对比判据分支写错(把"尾部空白"判成不一致)、锁断言写成"此刻锁是否被占"
(抓不住"被挡下"这件事)、场景结论没跟着断言走(断言失败场景还打 PASS)。
这三个都表现为"脚本报错,但工具其实是对的" ------ 所以脚本一失败,先查脚本自身,再怀疑工具。
一句话总结这一节:上板回归的价值不在"跑了",而在"这一份结论钉在哪个代码版本上"。
五、验收清单与边界声明(2026-09-14 更新)
好文档的标准是标明边界 。这套工具的验收分三栏,绝不混:离线已证 / 上板已证 / 仍未覆盖。
已离线实证(不需要板子,随时能重跑)
| # | 项目 | 结论 |
|---|---|---|
| 1 | 离线自测整体 | 53 项全通过(v2.0 时 33 项:v2.1 +6、v2.2 +3、v2.3 +2、v2.4 +7、v2.5 +4、v2.6 +1) |
| 2 | 失败路径必须报错 | 喂 ** Verify Failed **、Error: ... 进去,必抛异常并保留原始输出 |
| 3 | 反向证据 | FINISH 永不置位时必须超时报错(证明等的是"置 1") |
| 4 | 路径含空格 / 括号 / $ |
统一花括号转义,命令字符串断言通过 |
| 5 | 读回对比引擎 | "程序区一致 + 尾部空白"不误报;差异全量计数 |
| 6 | 不弹 OpenOCD 黑窗口 | 断言 CREATE_NO_WINDOW + stdin=DEVNULL |
| 7 | 环境自检与启动 | --check 三行 OK;Python 3.12 实测能出窗口 |
| 8 | 任务前后自动连接/断开 | 成功与失败两条路径都断言;连接失败时不干活、不留连接、锁必须释放 |
| 9 | open() 绝不摸 Tk 控件 |
回归项 session_open_never_touches_gui_getters(就是真机 1.2s 失败那个 bug) |
| 10 | 撞锁不重试(v2.6) | 断言 events == ["open"] + 日志无"次尝试" + 耗时 < 1.5s(把"立即反馈"写成可测断言) |
已上板实证(真板 + J-Link,链路 1MHz,2026-09-14)
| # | 项目 | 结果 |
|---|---|---|
| 1 | 四步烧录 | 全通 5.2s / 5.3s (两轮):wrote 45056、verified 44908(N = 文件大小)、reset run 自动执行 |
| 2 | 失败判据 | 故意改 1 字节 → diff 0 address 0x01000100. Was 0x04 instead of 0xfb 判失败 |
| 3 | 全片擦除 | 整任务 5.0~5.1s ;[6/8] 等 FINISH 仅 0.1s;抽样与整片读回全 0xFF;擦后烧回复原并逐字节复核 |
| 4 | 四页签 + 自动连断 | 读取 45056B ✓ / 擦除 ✓ / 整片读回全 0xFF ✓ / 烧回复原 ✓;每步无 openocd.exe 残留 |
| 5 | 失败重试 | 一次成功不加壳 / 第 2 次成功 17.2s (共连 2 次)/ 3 次用尽 17.6s(共连 3 次,无第 4 次) |
| 6 | 互斥锁(v2.6) | 被挡并提示"已有任务正在占用调试链路";不重试、不等 2s、没去连 OpenOCD ,断言 8/0 |
| 7 | 整机回归 | board_test.py 全 7 场景 [PASS]、断言 64/0、退出码 0、无残留 |
仍未覆盖(不是缺陷,是覆盖边界 ------ 明确登记,留到后续迭代)
| # | 项目 | 为什么先不做 |
|---|---|---|
| I1 | 芯片带固件(WWDG 已开)时"擦除 → 复位运行"是否被看门狗打断 | 属固件侧行为,要一块已烧使能 WWDG 的板子按序复现,与"简单下载"主功能无关 |
| I2 | 1.8MB 大文件的烧录 / 读取 | 手边无该量级固件;180s / 60s 超时是按大文件留的余量,未实测 |
| I3 | 连续 5 轮"擦除 → 烧录 → 读回"长跑稳定性 | 属压力测试;单次功能已验证 |
| I4 | 链路真实间歇性失败的"重试救回率 "统计(如 8MHz 下 dmstatus=0x0 那类) |
需要现场自然发生的失败样本;重试机制本身已用注入失败验证(场景 4/5) |
| --- | 链路层残余:同一块板 10:25 能连能停住 → 10:26 / 10:28 变 JTAG scan chain interrogation failed: all ones + Unsupported DTM version: 15,8000/1000 kHz 都一样 |
属链路/目标侧(供电、复位循环、接线、VTref),不是工具判断;现场需确认:断电重上电能否一次过、换短线/换 USB 口、JTAG 引脚是否被固件复用 |
结论怎么写才诚实:"离线已证""上板已证""仍未覆盖"一定分三栏,绝不混。
把待确认的写成"已验证",早晚会在客户现场被打脸。上表 I1~I4 是本轮主动不做,不是做不了。
免责与边界
- 全片擦除不可恢复:执行前请确认芯片里没有需要保留的标定/参数区;工具默认擦完保持 halt,方便接着烧录。
- 不同芯片的寄存器地址、位定义、命令 ID 都要以该芯片的手册与 SDK 为准,本篇的 AS32x601 数值不能照搬。
- 文中给出的时序与判据均来自 SDK 驱动源码与真实抓包;本篇标注"已验证"的结论都对应具体版本号(v2.1--v2.6)
与实测日期,改了代码就要重新取证,旧日志不能冒充当前版本。 - 文中所有"耗时"都是本机 2026-09-14 在链路 1MHz 下的实测值,换适配器 / 换链路会变。
写在最后
这类"上位机工具"的坑有个共同点:它们都会给你一个"看起来成功"的结果------烧完不复位、擦除报成功、对比误判不一致、GUI 静默起不来。所以真正值钱的不是"能点按钮",而是四件事:
- 每一步都有明确判据,没看到成功标志就判失败;
- 失败要留证据(OpenOCD 原始输出 + 双份日志);
- 把踩过的坑写进技能,让下一次(包括 AI 帮你做的时候)不再踩------具体做法见《如何把技术经验沉淀为 AI 技能》;
- 把结论钉在代码版本上 :留档前写
sha256 + 字节数,改了代码就重跑重留档,后台跑批要让退出码进日志 ------
否则你手里那份"通过"的报告,可能对应的是已经不存在的代码。
把这四件事做完,工具就从"能用"变成"可信",而技能则保证"可信"是可复制的。
*(文中 AS32x601 相关数值来自厂商 SDK 驱动与实测抓包;离线验证方法与上板脚本见上篇第六节与本篇第四节。)