收敛 reset.bat 到 stlink-flash skill 并删除该批处理
CI / L0 静态门禁 (pre-commit) (push) Has been cancelled
CI / L0.5 主机侧单测 (MSVC) (push) Has been cancelled

stlink_flash.py 新增 hard 子命令(-c SWD RM=Hrst -HardRst PULSE=100),
RM= 被 CLI 拒绝时自动降级为系统复位并打印 RESET_NOTE;至此 reset.bat 的
全部子命令(复位/hard/halt/run/list/flash/cmp)均已由 skill 覆盖。

删除 MDK-ARM/reset.bat(git rm)。@build/@download 两条旧批处理入口至此
全部收敛到 .dsh/skills/ 下的 skill,一个能力一个实现。

同步更新引用:AGENTS.md、README、MDK-ARM/README、7 篇 docs、4 个 skill
(stlink-flash/serial-boot-test/new-machine-setup 及 config.example.json)。

真机验证:hard 子命令 RM= 被拒 → RESET_NOTE → 回退系统复位 → STLINK_RC=0。
This commit is contained in:
2026-10-10 16:41:46 +08:00
parent 5defabcabb
commit 5009a844a9
17 changed files with 82 additions and 171 deletions
+2 -2
View File
@@ -29,12 +29,12 @@ whenToUse: 换新机器/新克隆要把环境搭起来、构建或烧录报"环
```
语义统一:**显式设了但路径无效 = 立即报错不回退**;不设则走 PATH/自动发现。
3. **驱动**:CH340(调试串口)与 ST-Link 的 USB 驱动装好——
`Get-PnpDevice -Class Ports` 能看到 COMx、`reset.bat list` 能看到探针 SN。
`Get-PnpDevice -Class Ports` 能看到 COMx、`stlink_flash.py list` 能看到探针 SN。
4. **验收三连**(全绿才算环境就绪):
```powershell
& '.venv\Scripts\python.exe' .dsh/skills/serial-assistant/scripts/serial_tool.py selftest # 23/23 PASS
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # exit 0
& "MDK-ARM\reset.bat" list # 列出探针
& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py list # 列出探针
```
## 实测坑(本会话两次撞上)
+1 -1
View File
@@ -52,7 +52,7 @@ whenToUse: 需要向目标板烧录或复位后,用串口日志自动判定运
**先跑项目的构建**(本仓库 = `keil-build` skill,其它 Keil 项目照该 skill 套;
非 Keil 项目用项目自己的构建入口),确认 0 错误 0 警告后再进闭环。
档案里的 `flash_cmd`/`readback_cmd` 就是烧录层的入口命令
(本仓库 = `stlink-flash` skill 的 `MDK-ARM\reset.bat flash` / `cmp {hex}`)。
(本仓库 = `stlink-flash` skill 的 `stlink_flash.py flash` / `cmp {hex}`)。
## 第二步:跑闭环
@@ -2,11 +2,11 @@
"portName": "COM6",
"baudRate": 115200,
"flash_cmd": "MDK-ARM\\reset.bat flash",
"flash_cmd": ".venv\\Scripts\\python.exe .dsh\\skills\\stlink-flash\\scripts\\stlink_flash.py flash",
"flash_timeout": 180,
"hex": "MDK-ARM\\STM32F407-Demo\\STM32F407-Demo.hex",
"fw_marker": "fw build: ",
"readback_cmd": "MDK-ARM\\reset.bat cmp {hex}",
"readback_cmd": ".venv\\Scripts\\python.exe .dsh\\skills\\stlink-flash\\scripts\\stlink_flash.py cmp {hex}",
"readback_ok": "no difference found",
"required": "App init OK;Network Init;net init: OK",
@@ -138,7 +138,7 @@ def _split_cmd(cmd_str):
会给含空格的路径正确加引号。旧写法 `['cmd','/c', 串]` 在本仓库(目录名
`DTU Code` 含空格)会被 cmd 的 /c 引号规则玩坏——实测报
`'D:\Code\DTU' is not recognized`,回读校验被假判成"芯片内容不一致"。
路径本身含空格时把该段用引号包住(`"D:\some dir\reset.bat" flash`)。
路径本身含空格时把该段用引号包住(`"D:\some dir\flash_tool.bat" flash`)。
posix=False 是必须的:posix=True 会吃掉 Windows 路径里的反斜杠。
"""
return [p.strip('"') for p in shlex.split(cmd_str, posix=False)]
@@ -416,7 +416,7 @@ def main(argv=None):
print("RUN_TEST_ERROR: no required markers (--required or 'required' in {})".format(CFG_FILE))
return 3
# --flash-cmd 允许带参数(如 "MDK-ARM\reset.bat flash"):拆成 argv,
# --flash-cmd 允许带参数(如 "python flash_tool.py flash"):拆成 argv,
# 存在性只查 exe 段(整串当文件名必然 missing,2026-10-10 实测)
flash_argv = _split_cmd(args.flash_cmd) if args.flash_cmd else None
+11 -5
View File
@@ -15,6 +15,7 @@ whenToUse: 需要向 STM32 目标烧录 .hex/.bin、只复位重启目标、校
& $PY ".dsh\skills\stlink-flash\scripts\stlink_flash.py" flash [hex] # 烧录 + -Rst -Run
& $PY ".dsh\skills\stlink-flash\scripts\stlink_flash.py" cmp [hex] # 回读比对(会复位目标)
& $PY ".dsh\skills\stlink-flash\scripts\stlink_flash.py" reset # 只复位运行
& $PY ".dsh\skills\stlink-flash\scripts\stlink_flash.py" hard # nRST 100ms 脉冲(CLI 拒 RM= 时自动降级为复位)
```
镜像缺省取项目档案 `hex`;ST-LINK_CLI 解析:`--cli` > 档案 `stlink_cli` >
@@ -25,8 +26,11 @@ whenToUse: 需要向 STM32 目标烧录 .hex/.bin、只复位重启目标、校
## 本仓库
仓库原生入口 `MDK-ARM\reset.bat`(flash/cmp/halt/run/list 子命令)与本 skill 脚本
语义一致,两者任选。环境变量 `DTU_STLINK_CLI`(未设回退 PATH;设了但无效 = 报错 exit 2)。
**`@download` = 本 skill**(2026-10-10 起为仓库文档口径)。原 `MDK-ARM\reset.bat`
(`flash`/`cmp`/`halt`/`run`/`list` 子命令)已并入本 skill 的
`flash/cmp/reset/hard/halt/run/list` 并于 **2026-10-10 删除**;`@build`/`@download`
两条旧批处理入口至此全部收敛到 skill(一个能力一个实现)。
环境变量 `DTU_STLINK_CLI`(未设回退 PATH;设了但无效 = 报错 exit 2)。
## 铁律与陷阱(实测教训)
@@ -38,8 +42,9 @@ whenToUse: 需要向 STM32 目标烧录 .hex/.bin、只复位重启目标、校
不一致 = 本次运行作废。
- **`-CmpFile` 连接时会复位目标**:板子当场重打一遍开机日志——在自动闭环里必须排在
**串口日志采集之后**,否则开机日志被截断、启动锚点计数误判成"中途复位"。
- **只重启不烧录**用无参 `-Rst -Run`;nRST 未接线的台架(3 线 SWD)这是唯一复位手段,
`-HardRst`/`RM=` 在部分 CLI 版本(v3.5.0 实测)不可用。
- **只重启不烧录**用 `reset` 子命令(`-c SWD -Rst -Run`);nRST 未接线的台架(3 线 SWD)
这是唯一复位手段——`hard`(`RM=Hrst -HardRst PULSE=100`)在部分 CLI 版本(v3.5.0 实测)
被拒,本 skill 会打印 `RESET_NOTE` 并**自动降级**为这条系统复位(仍按降级结果给 rc)。
- 探针在位先 `list`;`Programming Complete` 但行为像旧固件 → 先怀疑缺 `-Rst -Run`,
再怀疑 hex 没重编(对比 hex 时间戳)。
- 完整复位语义/故障排查(本仓库):`docs/测试说明/ST-LINK_CLI_复位使用说明.md`。
@@ -48,5 +53,6 @@ whenToUse: 需要向 STM32 目标烧录 .hex/.bin、只复位重启目标、校
- 编译 = `keil-build` skill(先 0 错 0 警再烧)。
- `serial-boot-test` 档案里 `flash_cmd` = 本 skill 入口(如
`"MDK-ARM\\reset.bat flash"`)、`readback_cmd` = `"MDK-ARM\\reset.bat cmp {hex}"`、
`".venv\\Scripts\\python.exe .dsh\\skills\\stlink-flash\\scripts\\stlink_flash.py flash"`)、
`readback_cmd` = `"... stlink_flash.py cmp {hex}"`、
`readback_ok` = `no difference found`——闭环脚本已保证"回读排在采集之后"。
@@ -7,6 +7,7 @@
python <skill>/scripts/stlink_flash.py flash [镜像.hex|.bin] # 烧录 + -Rst -Run
python <skill>/scripts/stlink_flash.py cmp [镜像.hex|.bin] [--base 0x08000000]
python <skill>/scripts/stlink_flash.py reset # 只复位运行
python <skill>/scripts/stlink_flash.py hard # nRST 100ms 脉冲(CLI 拒 RM= 时自动降级为系统复位)
python <skill>/scripts/stlink_flash.py halt | run
镜像缺省取项目档案 "hex"(解析顺序同下)。
@@ -20,6 +21,8 @@ ST-LINK_CLI 解析(命令行 > 档案 > 环境 > PATH):
- cmp 成功判据 = 某行以 "No difference found" 开头(原样透传 CLI 输出,
上层 run_boot_test 的 readback_ok 默认值直接可用)
- cmp 连接时会**复位目标**(板子重打开机日志)→ 自动闭环里必须排在日志采集之后
- hard 复位 = `RM=Hrst -HardRst PULSE=100`;v3.5.0 CLI 拒 `RM=` → **自动降级为系统复位**
(打印 RESET_NOTE,仍按降级结果给 rc);本机 nRST 未接线时这条即唯一可用路径
退出码: 0=OK 1=探针/目标错误 2=CLI 未找到/无效 3=用法错误
"""
@@ -105,7 +108,7 @@ def main(argv=None):
pass
ap = argparse.ArgumentParser(description="ST-LINK CLI flash/reset/readback (pure stdlib)")
ap.add_argument("command", choices=["list", "flash", "cmp", "reset", "halt", "run"])
ap.add_argument("command", choices=["list", "flash", "cmp", "reset", "hard", "halt", "run"])
ap.add_argument("image", nargs="?", default=None,
help="image file (.hex/.bin; default: config 'hex')")
ap.add_argument("--cli", default=None, help="ST-LINK_CLI.exe path")
@@ -122,7 +125,7 @@ def main(argv=None):
cli, src = find_cli(args.cli, cfg)
print("CLI: {} ({})".format(cli, src))
if args.command in ("list", "reset", "halt", "run"):
if args.command in ("list", "reset", "hard", "halt", "run"):
if args.image:
print("NOTE: image argument ignored for '{}'".format(args.command))
timeout = args.timeout or 60
@@ -134,6 +137,18 @@ def main(argv=None):
rc, _ = run_cli(cli, ["-c", "SWD", "-Rst", "-Run", "-Q"], timeout)
print("STLINK_RC={}".format(rc))
return 0 if rc == 0 else 1
if args.command == "hard":
print("RESET: nRST pulse 100ms (falls back to system reset if CLI lacks RM=)")
rc, _ = run_cli(cli, ["-c", "SWD", "RM=Hrst", "-HardRst", "PULSE=100", "-Q"],
timeout)
if rc != 0:
# v3.5.0 CLI 帮助与实现不符:RM= 被拒(Unknown debug protocol or option)。
# 回退 = 与 `reset` 同款的系统复位 + 运行;本机 nRST 未接线时这就是唯一可用路径。
print("RESET_NOTE: this ST-LINK_CLI rejects RM= (v3.5.0 known gap), "
"falling back to system reset")
rc, _ = run_cli(cli, ["-c", "SWD", "-Rst", "-Run", "-Q"], timeout)
print("STLINK_RC={}".format(rc))
return 0 if rc == 0 else 1
if args.command == "halt":
rc, _ = run_cli(cli, ["-c", "SWD", "-Rst", "-Halt"], timeout)
print("STLINK_RC={}".format(rc))
+2 -2
View File
@@ -19,8 +19,8 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
- `@test` / `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` 一键闭环(2026-09-20 起为纯 Python 标准库实现,替代 PowerShell 版;`& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py`):开串口+清日志 → 烧录(**`stlink-flash` skill**:`stlink_flash.py flash` = `-P <hex> -Rst -Run`;`flash_cmd` 默认取 `.serialtest/config.json`,本仓库已指向本 skill)→ **固件身份戳**(从刚烧进去的 hex 里抠 `fw build: <DATE> <TIME>` 追加成 Required 标记)→ wait-idle(等 RX 静默 8s,超时 30s,首字节 12s 未到判 `no_data`)→ 提取日志 → **烧录回读校验**(`stlink_flash.py cmp` = `-CmpFile`;不一致 = 本次运行作废 exit 2)→ `.dsh/skills/serial-boot-test/scripts/verify_log.py` 标记校验(含**启动锚点计数**:`reset cause:` 出现 0 次或 >1 次 = FAIL,`--max-boot-count` 调;Forbidden 命中**逐行打印行号+内容**;boot_log 每行带 `[+Nms]` daemon 毫秒时基 → 可查标记顺序 `--require-order` 与开机耗时 `--done-marker`/`--max-boot-ms`,实测 `net init: OK` = **+537ms**);退出码 0=PASS / 1=FAIL / 2=采集或烧录错误(含回读不一致)/ 3=用法/预检错误
- 默认校验标记:Required `App init OK` / `Network Init` / `net init: OK` + **自动追加的固件身份戳 `fw build: <__DATE__> <__TIME__>`**(`App/app/app_main.c` 开机打印,`run_boot_test.py` 从待烧 hex 里抠出该串当 Required——此前所有标记都是固定串,**旧镜像/错镜像照样全中**,"反复重下载、芯片一直是旧镜像"在自动闭环里不可见,2026-10-10 补;`--no-fw-check` 关闭);Forbidden `FAIL` / `ERROR` / `HardFault` / `[ERR]`(**四个 Fault 的崩溃日志都以 `[ERR]` 开头 → 一律判 FAIL**,这是刻意的:出现 Fault 即本次运行不通过);按模块定制标记(网络/存储/RTC/ADC/RS485/UI)见 `docs/测试说明/自动化测试流程.md`
- ⚠️ 旧绝对路径 `D:\Code\DTU 程序\...` 出现即失效(仓库已搬迁),一律改用仓库相对路径
- **下载/烧录铁律(2026-09-20 实测)**:走 `& "MDK-ARM\reset.bat" flash [文件]`(= `ST-LINK_CLI -c SWD -P <文件> -Rst -Run`),烧后 `-Rst -Run` **不可省略**——裸 `-P` 会报 `Programming Complete`,但目标核处于死循环/异常状态时写入并不落地(芯片内容不变;这就是 09/19~09/20 "反复重下载、芯片一直是旧镜像、二分法失效" 的根因);`.hex`/`.bin` 均可直烧(1MB ≈ 6.5s,快于 UV4 路径);只重启不烧录用 `reset.bat` 无参(= `-c SWD -Rst -Run`,本机 3 线接线唯一复位手段:nRST 未接、`-HardRst`/`RM=` 不可用)
- **烧录后必须回读校验**:`& "MDK-ARM\reset.bat" cmp [文件]`(= `ST-LINK_CLI -c SWD -CmpFile <文件> 0x08000000`,全片逐字节比对,`No difference found` 才算真正写入);`run_boot_test.py` 已默认自动跑(`--no-verify-flash` 关闭,不一致 → exit 2 本次运行作废)。⚠ **`-CmpFile` 连接时会复位目标**(2026-10-10 实测:比对瞬间板子重打一遍开机日志)→ 在自动闭环里必须排在**日志采集之后**,否则开机日志被截断且启动锚点计数会误判成"中途复位";CLI 经 `DTU_STLINK_CLI` 环境变量解析(未设时回退 PATH;本机已入 PATH,未设也能工作)(v3.5.0.0,探针 SN `0B250D12A218303030303032`)→ 完整用法/复位语义/故障排查 `docs/测试说明/ST-LINK_CLI_复位使用说明.md`
- **下载/烧录铁律(2026-09-20 实测)**:走 **`stlink-flash` skill** 的 `& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash [文件]`(= `ST-LINK_CLI -c SWD -P <文件> -Rst -Run`),烧后 `-Rst -Run` **不可省略**——裸 `-P` 会报 `Programming Complete`,但目标核处于死循环/异常状态时写入并不落地(芯片内容不变;这就是 09/19~09/20 "反复重下载、芯片一直是旧镜像、二分法失效" 的根因);`.hex`/`.bin` 均可直烧(1MB ≈ 6.5s,快于 UV4 路径);只重启不烧录用 `stlink_flash.py reset`(= `-c SWD -Rst -Run`,本机 3 线接线唯一复位手段);`stlink_flash.py hard`(`RM=Hrst -HardRst PULSE=100`)在 v3.5.0 CLI 上被拒 → **自动降级**为系统复位并打印 `RESET_NOTE`。⚠ **原 `MDK-ARM/reset.bat` 已于 2026-10-10 删除**(子命令已全并入本 skill)
- **烧录后必须回读校验**:`& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py cmp [文件]`(= `ST-LINK_CLI -c SWD -CmpFile <文件> 0x08000000`,全片逐字节比对,`No difference found` 才算真正写入);`run_boot_test.py` 已默认自动跑(`--no-verify-flash` 关闭,不一致 → exit 2 本次运行作废)。⚠ **`-CmpFile` 连接时会复位目标**(2026-10-10 实测:比对瞬间板子重打一遍开机日志)→ 在自动闭环里必须排在**日志采集之后**,否则开机日志被截断且启动锚点计数会误判成"中途复位";CLI 经 `DTU_STLINK_CLI` 环境变量解析(未设时回退 PATH;本机已入 PATH,未设也能工作)(v3.5.0.0,探针 SN `0B250D12A218303030303032`)→ 完整用法/复位语义/故障排查 `docs/测试说明/ST-LINK_CLI_复位使用说明.md`
## Python 环境
+6 -6
View File
@@ -10,7 +10,7 @@
| `STM32F407-Demo.uvprojx` | **Keil 工程文件**(源文件清单、编译选项、目标配置) |
| `STM32F407-Demo.uvoptx` | Keil 用户选项(调试器、断点等) |
| `startup_stm32f407xx.s` | 启动文件(**`Heap_Size EQU 0x200`(512B)在此限定**) |
| `reset.bat` | **复位/烧录/回读**(`ST-LINK_CLI` 封装,见下) |
| `.dsh/skills/stlink-flash/scripts/stlink_flash.py` | **复位/烧录/回读**(`ST-LINK_CLI` 封装,见下;原 `reset.bat` 已于 2026-10-10 删除) |
## 构建与烧录
@@ -18,19 +18,19 @@
# 编译(@build = keil-build skill;相对路径,勿用绝对路径)
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 退出码 0 = 0 errors, 0 warnings
# 烧录 + 复位运行(推荐路径)
& "MDK-ARM\reset.bat" flash <文件.hex|.bin>
# 烧录 + 复位运行(@download = stlink-flash skill)
& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash <文件.hex|.bin>
# 仅复位
& "MDK-ARM\reset.bat"
& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py reset
```
### ⚠️ 烧录铁律
- `reset.bat flash` = `ST-LINK_CLI -c SWD -P <文件> -Rst -Run`——**`-Rst -Run` 不可省略**!
- `stlink_flash.py flash` = `ST-LINK_CLI -c SWD -P <文件> -Rst -Run`——**`-Rst -Run` 不可省略**!
裸 `-P` 会报 `Programming Complete`,但目标核处于异常状态时写入并不落地(芯片内容不变)
- 烧录后**必须回读校验**:`ST-LINK_CLI -c SWD -CmpFile <文件> 0x08000000`(`No difference found` 才算真写入)
- 本机 3 线接线(nRST 未接),**只重启不烧录**用 `reset.bat` 无参
- 本机 3 线接线(nRST 未接),**只重启不烧录**用 `stlink_flash.py reset`
## 台架探针与判读脚本
-110
View File
@@ -1,110 +0,0 @@
@echo off
setlocal
:: ST-LINK CLI location: env var DTU_STLINK_CLI = full path of ST-LINK_CLI.exe.
:: Explicit value is used as-is (invalid value = hard error, no silent fallback);
:: when unset, fall back to PATH (where ST-LINK_CLI.exe).
set "STLINK="
if defined DTU_STLINK_CLI goto stlink_check
where ST-LINK_CLI.exe >nul 2>&1
if errorlevel 1 goto stlink_missing
for /f "delims=" %%i in ('where ST-LINK_CLI.exe 2^>nul') do (
if not defined STLINK set "STLINK=%%i"
)
if not defined STLINK goto stlink_missing
goto stlink_done
:stlink_check
if exist "%DTU_STLINK_CLI%" goto stlink_done
echo ENV_ERROR: DTU_STLINK_CLI is set but does not exist: "%DTU_STLINK_CLI%"
echo Fix the variable value. No fallback to PATH or other installs is attempted.
exit /b 2
:stlink_missing
echo ENV_ERROR: ST-LINK_CLI.exe not found (DTU_STLINK_CLI unset and not on PATH)
echo Set it to the full path of ST-LINK_CLI.exe, e.g.:
echo [Environment]::SetEnvironmentVariable('DTU_STLINK_CLI', 'C:\Tools\STLink\ST-LINK_CLI.exe', 'User')
exit /b 2
:stlink_done
if not defined STLINK set "STLINK=%DTU_STLINK_CLI%"
if exist "%STLINK%" goto stlink_ready
echo RESET_FAIL: ST-LINK_CLI not found at "%STLINK%"
exit /b 2
:stlink_ready
if "%~1"=="" goto :reset_run
if /i "%~1"=="hard" goto :hard
if /i "%~1"=="halt" goto :halt
if /i "%~1"=="run" goto :run
if /i "%~1"=="list" goto :list
if /i "%~1"=="flash" goto :flash
if /i "%~1"=="cmp" goto :cmp
goto :help
:reset_run
echo RESET: system reset and run (SWD)
"%STLINK%" -c SWD -Rst -Run -Q
exit /b %ERRORLEVEL%
:hard
echo RESET: nRST pulse 100ms (falls back to system reset if CLI lacks RM=)
"%STLINK%" -c SWD RM=Hrst -HardRst PULSE=100 -Q
if not errorlevel 1 exit /b 0
echo RESET_NOTE: this ST-LINK_CLI rejects RM= (v3.5.0 known gap), falling back to system reset
"%STLINK%" -c SWD -Rst -Run -Q
exit /b %ERRORLEVEL%
:halt
echo RESET: system reset and halt core (debug; resume with 'run')
"%STLINK%" -c SWD -Rst -Halt
exit /b %ERRORLEVEL%
:run
echo RUN: resume halted core
"%STLINK%" -c SWD -Run
exit /b %ERRORLEVEL%
:list
"%STLINK%" -List
exit /b %ERRORLEVEL%
:flash
if "%~2"=="" set "FW=%~dp0STM32F407-Demo\STM32F407-Demo.hex"
if not "%~2"=="" set "FW=%~2"
if not exist "%FW%" (
echo FLASH_FAIL: firmware not found "%FW%"
exit /b 2
)
echo FLASH: program "%FW%" then reset and run
"%STLINK%" -c SWD -P "%FW%" -Rst -Run -Q
exit /b %ERRORLEVEL%
:cmp
if "%~2"=="" set "FW=%~dp0STM32F407-Demo\STM32F407-Demo.hex"
if not "%~2"=="" set "FW=%~2"
if not exist "%FW%" (
echo CMP_FAIL: firmware not found "%FW%"
exit /b 2
)
echo CMP: readback compare "%FW%" vs device flash
echo CMP_NOTE: connecting resets the target - run this AFTER log capture, not before
"%STLINK%" -c SWD -CmpFile "%FW%" 0x08000000
exit /b %ERRORLEVEL%
:help
echo Usage: reset.bat [command]
echo.
echo (none) System reset and run (restart board via SWD)
echo hard nRST pulse 100ms; auto-falls back to system reset if CLI lacks RM=
echo halt System reset and halt core (in-session read; auto-resumes after disconnect)
echo run Resume a halted core
echo list List connected ST-LINK probes
echo flash [F] Program F (default: latest build hex) then reset and run
echo cmp [F] Readback compare F vs device flash; "No difference found" = write landed
echo (connecting resets the target: run it after log capture, not before)
echo.
echo Exit codes: 0=OK 1=probe or target error 2=usage or tool missing
endlocal
-1
View File
@@ -144,7 +144,6 @@ STM32F4-Base/
│ └── Third_Party/FreeRTOS/ # FreeRTOS 内核
├── MDK-ARM/ # Keil 工程
│ ├── STM32F407-Demo.uvprojx # 工程文件
│ ├── reset.bat # 复位 / 烧录 / 回读(ST-LINK_CLI)
│ ├── *_probe.py # 台架探针(TCP 复连 / FTP 中止等)
│ └── startup_stm32f407xx.s # 启动文件
├── test/ # 测试代码与脚本
@@ -1,7 +1,7 @@
# ST-LINK_CLI 复位使用说明(v3.5.0.0 本机实测版)
> 依据:本机 ST-LINK Utility 4.5.0.0 自带 `ST-LINK_CLI.exe`(CLI 版本 v3.5.0.0,`-h` 全文已实测核对)+ ST 官方文档语义。
> 探针路径:经 `DTU_STLINK_CLI` 环境变量解析(本机设为标准安装位 `C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility\ST-LINK_CLI.exe`;未设时回退 PATH,`reset.bat` 均可用)
> 探针路径:经 `DTU_STLINK_CLI` 环境变量解析(本机设为标准安装位 `C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility\ST-LINK_CLI.exe`;未设时回退 PATH,`stlink_flash.py` 均可用)
> 2026-09-19 已上板实测(ST-LINK/V2,FW V2J37S7,SN `0B250D12A218303030303032`;STM32F407ZGTx 目标,SWD)。
> 2026-09-20 复测(V0.1 板):烧写静默失败根因闭环、完整启动闭环(详见 §10)。
> ⚠️ **本机接线:3 根线(SWDIO / SWCLK / GND),nRST 未接** → 能力边界见 §0,实测记录见 §9。
@@ -35,7 +35,7 @@
- **日常"命令重启板子" = `-c SWD -Rst -Run`(软件系统复位)**。对 STM32F4,SYSRESETREQ 会复位内核 + 几乎所有外设,与 nRST 硬复位的固件级效果等价(差别仅在 nRST 引脚本身不动、调试域保留)——3 线接线场景下这就是复位能力的上限,完全够用。
- **需要 nRST 引脚级操作时**(复位电路/上电时序验证、跑飞后的复位态连接救援)必须补第 4 根线:目标 nRST → 探针 RESET 脚。补线之后注意:本机 v3.5.0.0 CLI 仍不认 `RM=`(ⓞ),届时要走 ST-LINK Utility GUI、升级 ST-LINK Utility(新版 CLI)、或换 J-Link。
- `reset.bat` 的 `hard` 分支已内置回退:本机永远回退到软件复位(exit 仍为 0),**不会把脚本跑断**;将来补线 + 升级 CLI 后自动升级为真 nRST 脉冲,无需改脚本。
- `stlink_flash.py hard` 已内置回退:本机永远回退到软件复位(exit 仍为 0),**不会把脚本跑断**;将来补线 + 升级 CLI 后自动升级为真 nRST 脉冲,无需改脚本。
## 1. 三种"复位"(核心概念)
@@ -72,7 +72,7 @@ ST-LINK_CLI -c SWD -Rst -Halt -SCore -CoreReg :: 复位停核后读核状
ST-LINK_CLI -c SWD -Rst -Halt -R32 0x08000000 4 :: 停核后读向量表前 4 个字
```
ⓞ **一次性连接语义(实测)**:ST-LINK_CLI 是"连接→执行→断开"的一次式进程,**会话结束(断开)时核自动恢复运行**。因此 `reset.bat halt` 单独调用只适合"复位后读状态"场景,断开后板子会继续运行;需要**持续性 halt**(GDB 调试/长时间断点)请走 GDBServer/IDE 路径。
ⓞ **一次性连接语义(实测)**:ST-LINK_CLI 是"连接→执行→断开"的一次式进程,**会话结束(断开)时核自动恢复运行**。因此 `stlink_flash.py halt` 单独调用只适合"复位后读状态"场景,断开后板子会继续运行;需要**持续性 halt**(GDB 调试/长时间断点)请走 GDBServer/IDE 路径。
### ③ 硬件复位:nRST 脉冲 ❌ 本机不可用(nRST 未接线)
@@ -107,7 +107,7 @@ ST-LINK_CLI -c SWD -P "MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex" -Rst -Run
- ⓞ **`.hex` 直接支持已实测**:1MB Intel HEX 编程耗时 **6.5s**,`Programming Complete`(远快于 UV4/CMSIS-DAP 路径);`.bin` 同理(`fromelf --bin` 可转,ARMCC 自带 `C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe`);`.axf` 直读未测,无需。
- 加 `-V` 编程后校验、`-Q` 静默。
- 与 `MDK-ARM/reset.bat flash` 同源(都走 ST-LINK_CLI)。原 `download.bat`(`UV4 -f` 走 CMSIS-DAP)**已于 2026-10-10 删除**,CLI 路径即唯一烧录路径。
- 与 `stlink_flash.py flash` 同源(都走 ST-LINK_CLI)。原 `download.bat`(`UV4 -f` 走 CMSIS-DAP)与 `reset.bat` **均已删除**(2026-10-10),烧录/复位/回读统一由 `stlink-flash` skill 承载。
- ⓞ **静默失败模式(2026-09-20 实测,代价最大的坑)**:省略结尾 `-Rst -Run` 的裸 `-c SWD -P <文件>` 仍报 `Memory programmed in 1s 906ms / Programming Complete`,但芯片 flash **不变**(回读向量表 = 旧镜像)——目标核处于死循环/异常状态时,"Normal 连接、不复位"的会话写入不落地;同文件改用完整 `-P <文件> -Rst -Run` 即正常落地(6.5s / 1MB)。**铁律:任何编程命令必须以 `-Rst -Run` 收尾;任何烧录之后必须 `-CmpFile` 全片回读校验**(详见 §10)。
### ⑥ 不扰目标连接(HOTPLUG,运行中板子的只读检查)✅ 本机可用
@@ -168,7 +168,7 @@ ST-LINK_CLI -c SWD HOTPLUG -Dump 20000000 1000000 dump.bin :: 转储 SRAM
```powershell
& '.venv\Scripts\python.exe' .dsh/skills/serial-assistant/scripts/serial_tool.py open
& '.venv\Scripts\python.exe' .dsh/skills/serial-assistant/scripts/serial_tool.py clear
& "MDK-ARM\reset.bat" # 等价于 ST-LINK_CLI -c SWD -Rst -Run -Q
& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py reset # = ST-LINK_CLI -c SWD -Rst -Run -Q
# 等待开机完成后直接读日志文件校验标记:
Get-Content .dsh\skills\serial-assistant\scripts\state\serial.log -Tail 100
```
@@ -180,9 +180,9 @@ Get-Content .dsh\skills\serial-assistant\scripts\state\serial.log -Tail 100
2. **(已修,2026-10-10)旧版 `wait-idle` 在复位突发期会报 `daemon unresponsive after auto-start: timed out`**:CLI 把**超时**误判成"daemon 不可达",顺手拉起一个绑不上 HTTP 口的实例再把命令撞一次超时(数据其实已落盘)。现 CLI 超时按 `--max-ms` 自动推导、超时只快速失败不再误拉起实例;
3. **日志校验**:`.dsh/skills/serial-assistant/scripts/state/serial.log` 用 Read/Grep 直接查最直观;`serial_tool.py log --grep <标记>` 现已可作判定依据(2026-10-10 修:旧实现先 `--tail`(默认 50)后 grep,开机标记必然查不到——这就是当年"实测 0 命中、语义存疑"的真根因)。
**B. 完整烧写 + 运行**:`stlink-flash` skill 的 `stlink_flash.py flash [xxx.hex]`(等价 `MDK-ARM\reset.bat flash`;= `-c SWD -P <hex> -Rst -Run`;09/19 实测 1MB ≈ 6.5s,09/20 复测 6.47s + 全片 `-CmpFile` 逐字节一致)。**2026-10-10 起 CLI 路径即唯一烧录路径**——原 `download.bat` 的 `UV4 -f` 已删除。
**B. 完整烧写 + 运行**:`stlink-flash` skill 的 `stlink_flash.py flash [xxx.hex]`(= `-c SWD -P <hex> -Rst -Run`;09/19 实测 1MB ≈ 6.5s,09/20 复测 6.47s + 全片 `-CmpFile` 逐字节一致)。**2026-10-10 起 CLI 路径即唯一烧录路径**——原 `download.bat` 的 `UV4 -f` 与 `reset.bat` 均已删除。
**B2. 回读校验**:`MDK-ARM\reset.bat cmp [xxx.hex]`(= `-c SWD -CmpFile <hex> 0x08000000`,2026-10-10 加;默认对最新构建 hex)。判定看输出行首的 `No difference found`(`This file is already downloaded` 是同义提示)。⚠ **连接即复位目标**:实测比对瞬间板子重打一遍开机日志 → 在自动化闭环里回读必须排在**日志采集之后**(`run_boot_test.py` 已按此顺序内置)。
**B2. 回读校验**:`stlink_flash.py cmp [xxx.hex]`(= `-c SWD -CmpFile <hex> 0x08000000`,2026-10-10 加;默认对最新构建 hex)。判定看输出行首的 `No difference found`(`This file is already downloaded` 是同义提示)。⚠ **连接即复位目标**:实测比对瞬间板子重打一遍开机日志 → 在自动化闭环里回读必须排在**日志采集之后**(`run_boot_test.py` 已按此顺序内置)。
**C. 救援**(板子起不来 / 疑似 Flash 问题)——本机 3 线接线的可行子集:
@@ -196,35 +196,36 @@ ST-LINK_CLI -c SWD -P "MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex" -Rst -Run
**D. 运行中检查**:`-c SWD HOTPLUG -SCore` / `-R32` / `-TVolt`(⚠️ 本机 3 线连接下读数无效,见命令表注记)/ `-Dump`。
## 6. `reset.bat`(已落工程 `MDK-ARM\reset.bat`,2026-09-19 上板验证)
## 6. `stlink-flash` skill 的复位/烧录子命令(2026-09-19 上板验证;原 `MDK-ARM\reset.bat` 已于 2026-10-10 删除并全并入本 skill)
用法(全部分支 exit=0 实测通过):
| 调用 | 行为 | 本机实际效果 |
|---|---|---|
| `reset.bat` | `-c SWD -Rst -Run` | ✅ 软件系统复位 + 运行(重启板子) |
| `reset.bat hard` | `-c SWD RM=Hrst -HardRst PULSE=100`,被拒则回退 | ⓞ 本机 `RM=` 被拒 → **自动回退软件复位**(打印 RESET_NOTE,exit=0);补 nRST 线 + 升级 CLI 后自动升级为真 nRST 脉冲 |
| `reset.bat halt` | `-c SWD -Rst -Halt` | ✅ 复位停核(一次性会话,读状态须同命令行附加;断开后核自动恢复运行) |
| `reset.bat run` | `-c SWD -Run` | ✅ 恢复被停核的目标 |
| `reset.bat list` | `-List` | ✅ 枚举探针 |
| `reset.bat flash [F]` | `-c SWD -P F -Rst -Run`(F 缺省 = 最新构建 `MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex`) | ✅ 烧写 + 复位 + 运行(1MB ≈ 6.5s;2026-09-20 复测验证,见 §10) |
| `stlink_flash.py reset` | `-c SWD -Rst -Run` | ✅ 软件系统复位 + 运行(重启板子) |
| `stlink_flash.py hard` | `-c SWD RM=Hrst -HardRst PULSE=100`,被拒则回退 | ⓞ 本机 `RM=` 被拒 → **自动回退软件复位**(打印 RESET_NOTE,exit=0);补 nRST 线 + 升级 CLI 后自动升级为真 nRST 脉冲 |
| `stlink_flash.py halt` | `-c SWD -Rst -Halt` | ✅ 复位停核(一次性会话,读状态须同命令行附加;断开后核自动恢复运行) |
| `stlink_flash.py run` | `-c SWD -Run` | ✅ 恢复被停核的目标 |
| `stlink_flash.py list` | `-List` | ✅ 枚举探针 |
| `stlink_flash.py flash [F]` | `-c SWD -P F -Rst -Run`(F 缺省 = 档案 `hex` = `MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex`) | ✅ 烧写 + 复位 + 运行(1MB ≈ 6.5s;2026-09-20 复测验证,见 §10) |
| `stlink_flash.py cmp [F]` | `-c SWD -CmpFile F 0x08000000` | ✅ 全片回读比对(`No difference found` = 真写入) |
退出码:`0`=成功;`1`=探针/目标错误;`2`=用法或工具缺失。
注意:bat 内 `STLINK` 变量**不带引号**、调用处再加引号(`set VAR="带空格路径"` 会把引号存进变量,导致 `if not exist` 与执行都失败——2026-09-19 实测踩坑并已修复);bat 文件必须 **CRLF** 行尾(LF 会破坏 `:label` 解析)。
退出码:`0`=OK;`1`=探针/目标错误;`2`=CLI 未找到/无效;`3`=用法错误。
注意:档案(`.serialtest/config.json`)解析、`DTU_STLINK_CLI` 工具路径解析(未设回退 PATH;显式设了但无效 = 硬报错)、镜像缺省值均内置于脚本;`stlink_flash.py` 为纯标准库 Python,无原 bat 的引号/CRLF 行尾坑。
## 7. 故障排查速查
| 现象 | 排查 |
|---|---|
| `No ST-LINK detected!`(exit=2) | 探针没插/USB 未枚举/驱动问题。本机已装 STLinkWinUSB 驱动;`reset.bat list` 复核;换 USB 口(避免前端_hub) |
| `[RM=xxx]: Unknown debug protocol or option!`(exit=1) | ⓞ v3.5.0.0 CLI 不支持 `RM=`(帮助与实现不符);**且本机 nRST 未接线**——直接用软件复位 `-Rst`(`reset.bat` 默认分支) |
| `No ST-LINK detected!`(exit=2) | 探针没插/USB 未枚举/驱动问题。本机已装 STLinkWinUSB 驱动;`stlink_flash.py list` 复核;换 USB 口(避免前端_hub) |
| `[RM=xxx]: Unknown debug protocol or option!`(exit=1) | ⓞ v3.5.0.0 CLI 不支持 `RM=`(帮助与实现不符);**且本机 nRST 未接线**——直接用软件复位 `-Rst`(`stlink_flash.py reset`) |
| 连接失败 | 目标供电;SWDIO/SWCLK 接线(本机 3 线,勿依赖 nRST 辅助);芯片休眠 → 加 `LPM`;看门狗反复复位 → 提高连接速度(本机无 `UR` 可用);RDP 被锁 → 只能读有限信息 |
| 复位后程序没跑 | 读复位向量确认 Flash 有效:`-R32 0x08000000 1`(应指向 0x08xxxxxx,`0xFFFFFFFF`=空片);查 BOOT0 状态;看串口是否有输出 |
| 复位后卡死(串口日志中断在半路) | ⓞ 2026-09-19 实测案例:`-c SWD HOTPLUG -SCore -CoreReg` 读 PC/寄存器定位卡点(案例:V2 引脚固件卡在 0x08000FA0 启动循环,见 §9)——**先定位再下结论**,勿直接怀疑探针 |
| 烧录报成功但芯片行为不变 | ⓞ **先怀疑静默写入失败**(2026-09-20 实测):命令缺 `-Rst -Run`(裸 `-P` 在目标核死循环/异常时不落地)或选项字节异常(RDP 字节非法,见 §10.4)。处置:`-CmpFile` 全片比对/回读向量表首字 → 用 `reset.bat flash` 重烧 → 再 `-CmpFile` 复核;勿直接怀疑探针/接线 |
| 烧录报成功但芯片行为不变 | ⓞ **先怀疑静默写入失败**(2026-09-20 实测):命令缺 `-Rst -Run`(裸 `-P` 在目标核死循环/异常时不落地)或选项字节异常(RDP 字节非法,见 §10.4)。处置:`-CmpFile` 全片比对/回读向量表首字 → 用 `stlink_flash.py flash` 重烧 → 再 `-CmpFile` 复核;勿直接怀疑探针/接线 |
| `daemon unresponsive after auto-start`(serial_tool) | ⓞ 复位后几秒内 daemon 忙于排空日志突发;稍等后直接读 `state/serial.log` 文件验证,勿反复重试 |
| `port not found: COMx (available: COMy)`(serial_tool) | ⓞ 端口重新枚举(板子重插常见):改 `.serialtest/config.json` → **kill 旧 daemon 进程** → 重新 `open`(daemon 启动时缓存配置,不重启不生效) |
| `log --grep` 查不到明明存在的标记 | ⓞ 该子命令实测不可靠(0 命中);改用直接读/`Select-String` 查 `state/serial.log` |
| SWD 通信不稳 | 不传 `SWCLK` 用默认时钟(本机实测 4MHz);检查长线/夹子接触 |
| 误改 option bytes | `-rOB` 读回现状;`-OB RDP=0` 等回写后需再复位生效;**RDP=2 会锁死调试+全片擦除,操作前务必确认** |
| 多板混淆 | `reset.bat list` 拿串号,`-c SN=<串号>` 指定 |
| 多板混淆 | `stlink_flash.py list` 拿串号,`-c SN=<串号>` 指定 |
@@ -45,7 +45,7 @@
=== Storage Recovery: 3/3 PASSED (failed=0) ===
```
6. **断电重启**(`MDK-ARM/reset.bat` 无参即可)——本次启动才会重建干净 journal;
6. **断电重启**(`stlink_flash.py reset` 无参即可)——本次启动才会重建干净 journal;
7. 恢复产品构建(两个头文件重新注释)→ `@build` → 烧录 → `@test`,确认:
```
@@ -47,7 +47,7 @@
| A3 | 构建产物为**产品态** | 检查 `test/board/test_config.h` L30-33 | 四个 `TEST_SUITE_*` **全部注释** |
| A4 | `.map` 非测试态 | 看 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map` 的 `ER_IROM1` | 产品态基线随已接受的修复增长:262,100(初版)→ 262,836 B(10-02 round 1~6)→ 264,284 B(round 7~9)→ **264,936 B(当前,含 A2/A3/A4 补码)**;明显阶跃 ≈3.7KB 及以上(10-02 实测产品 262,836 → 套件 266,540)= 停在套件态 → 重跑 A2 |
| A5 | 全量静态门禁 | `pre-commit run --all-files` | **17/17 Passed**(2026-10-03 实测;含新增 S5 `test-cppcheck`) |
| A6 | ST-LINK 探针可见 | `& "MDK-ARM\reset.bat" list`(CLI 经 `DTU_STLINK_CLI` / PATH 解析) | 列出探针 SN `0B250D12A218303030303032` |
| A6 | ST-LINK 探针可见 | `& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py list`(CLI 经 `DTU_STLINK_CLI` / PATH 解析) | 列出探针 SN `0B250D12A218303030303032` |
| A7 | 串口端口正确 | 检查 `.serialtest/config.json` 的 `portName` | 端口单一配置源 = 此文件(当前 COM6,2026-09-21 台架重插后由 COM7 变更);变更后先杀旧 daemon 再 `open` |
| A8 | 串口助手 daemon 可用 | `& '.venv\Scripts\python.exe' .dsh/skills/serial-assistant/scripts/serial_tool.py status` | 返回 JSON,`open: true/false` 可读 |
@@ -127,7 +127,7 @@
# 2) 注入:按设计说明 §10 在 netTask 循环插 while(1);(或 taskDISABLE_INTERRUPTS(); while(1);)
# —— 改完 @build,然后烧录
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py
& "MDK-ARM\reset.bat" flash MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex
& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex
# 3) 抓串口日志,确认 [ERR] WATCHDOG task stall + 复位原因
# 4) 回读校验复位后镜像(ST-LINK_CLI 经 DTU_STLINK_CLI 解析,未设时回退 PATH,不写死路径)
+6 -6
View File
@@ -23,8 +23,8 @@
| `@build` | `keil-build` skill(`.dsh/skills/keil-build/`,自带 `keil_build.py`) | 仅编译验证(0 错误 0 警告) |
| `@download` | `stlink-flash` skill(`.dsh/skills/stlink-flash/`,自带 `stlink_flash.py`) | 编译并烧录(ST-Link) |
| `@test` | `serial-boot-test` skill(`.dsh/skills/serial-boot-test/`) | 完整闭环:build → capture+download → verify → iterate |
| `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | **一键闭环**:open + clear → 烧录 → 固件身份戳(从 hex 抠 `fw build:`)→ wait-idle(静默 8s/超时 30s/首字节 12s)→ 提取 `[RX]`(每行加 `[+Nms]` 时基)→ 回读校验(`reset.bat cmp`)→ verify;`--keep-out` 清旧产物;退出码 0=PASS/1=FAIL/2=采集·烧录·回读错误/3=用法错误 |
| `MDK-ARM/reset.bat` | `MDK-ARM/reset.bat` | ST-LINK CLI 封装:`flash [hex]`(烧 + `-Rst -Run`)/ **`cmp [hex]`(回读比对,2026-10-10 加)**/ 无参复位 / `halt`/`run`/`list`/`hard` |
| `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | **一键闭环**:open + clear → 烧录 → 固件身份戳(从 hex 抠 `fw build:`)→ wait-idle(静默 8s/超时 30s/首字节 12s)→ 提取 `[RX]`(每行加 `[+Nms]` 时基)→ 回读校验(`stlink_flash.py cmp`)→ verify;`--keep-out` 清旧产物;退出码 0=PASS/1=FAIL/2=采集·烧录·回读错误/3=用法错误 |
| `.dsh/skills/stlink-flash/scripts/stlink_flash.py` | `stlink-flash` skill | ST-LINK CLI 封装:`flash [hex]`(烧 + `-Rst -Run`)/ **`cmp [hex]`(回读比对,2026-10-10 加)**/ `reset`(只复位)/ `hard`(nRST 脉冲,CLI 拒 `RM=` 自动降级)/ `halt`/`run`/`list`;原 `MDK-ARM/reset.bat` 已于 2026-10-10 删除并全并入本 skill |
| `.dsh/skills/serial-assistant/scripts/serial_tool.py` | `.dsh/skills/serial-assistant/` | Python 串口助手(常驻 daemon + 薄 CLI):ports/open/send/status/log/clear/wait-idle/selftest;全量日志 `state/serial.log`(20MB 自动截头) |
| `.dsh/skills/serial-boot-test/scripts/verify_log.py` | `.dsh/skills/serial-boot-test/scripts/verify_log.py` | 对日志做标记校验:Required/Forbidden(**命中逐行打印行号+内容**)、启动锚点计数 `--max-boot-count`、原因白名单 `--expect-reset-cause`、标记顺序 `--require-order`、开机耗时 `--done-marker`/`--max-boot-ms`、`--json` 结构化输出;退出码 0/1/2 |
@@ -37,12 +37,12 @@
- `1` = FAIL:存在缺失标记(`[MISS]`)或违规模式(`[HIT]`)
- `2` = 日志文件不可读
- **固件身份戳(2026-10-10 加)**:`App/app/app_main.c` 开机打印 `fw build: <__DATE__> <__TIME__>`(编译期展开成一段**连续 ASCII**,`.hex` 里可原样搜到)。`run_boot_test.py` 从**待烧的 hex** 里抠出该串并追加成 Required 标记 → 芯片里跑的必须就是这份构建。反例实测:把 hex 里的戳改成 `Oct 9 2026 01:00:00` 再跑,其余标记全中但 `[MISS] fw build: Oct 9 2026 01:00:00` → FAIL——这正是过去"反复重下载、芯片一直是旧镜像"在闭环里**完全隐形**的场景。`--no-fw-check` 关闭;hex 里没有该串(旧构建)只 WARN 跳过、不假判。
- **烧录回读(2026-10-10 加)**:采集结束后自动跑 `reset.bat cmp`(= `-CmpFile` 全片逐字节比对),判定按**行首** `No difference found`(进度条与帮助文本里也含该短语,故不能全文匹配);不一致 → `RUN_TEST_ERROR: flash readback mismatch` + 退出码 2(本次运行作废:日志是"某个未知镜像"跑出来的)。⚠ **顺序不能颠倒**——`-CmpFile` 连接时会复位目标(实测板子当场重打一遍开机日志)。`--no-verify-flash` 关闭。
- **烧录回读(2026-10-10 加)**:采集结束后自动跑 `stlink_flash.py cmp`(= `-CmpFile` 全片逐字节比对),判定按**行首** `No difference found`(进度条与帮助文本里也含该短语,故不能全文匹配);不一致 → `RUN_TEST_ERROR: flash readback mismatch` + 退出码 2(本次运行作废:日志是"某个未知镜像"跑出来的)。⚠ **顺序不能颠倒**——`-CmpFile` 连接时会复位目标(实测板子当场重打一遍开机日志)。`--no-verify-flash` 关闭。
- **启动锚点计数(2026-10-10 加)**:`reset cause:` 在一次采集里出现 **0 次**(采集没从复位开始,标记全中也可能只是上一轮残留)或 **>1 次**(板子中途自己复位:IWDG 咬狗 / Fault 后 `NVIC_SystemReset` / 掉电重启)→ FAIL;`--max-boot-count N` 放宽(跑故意复位的套件时用),`--expect-reset-cause 'software;power-on'` 对第一个锚点做原因白名单(SWD 软复位实测为 `software (NVIC_SystemReset)`,CSR=0x14000000)。
- **判定可读性 + 时序(2026-10-10 第三批)**:
- **Forbidden 命中行**:命中时逐行打印 `L<行号>: <内容>`(最多 6 行,剩余报数)。旧实现只说 `forbidden pattern: [ERR] (3 line(s))`,还得人再 grep 一遍才知道是哪几行。
- **`[+Nms]` 相对时基**:`run_boot_test.py` 给 boot_log 每行加行首 `[+Nms]`(daemon 毫秒时基,本次第一行 = +0ms;`--raw-log` 出纯载荷)。MCU 侧时间戳只有秒级、开机头几行还没时钟(`----/--/--`),量不了耗时。
- **开机耗时**:`--done-marker 'net init: OK'`(默认)+ `--max-boot-ms N`。实测基线 **+537 ms**(2026-10-10 台架,`reset.bat flash` 后);默认 `--max-boot-ms 0` = 只报告不判,避免网络重试抖动造成假 FAIL。
- **开机耗时**:`--done-marker 'net init: OK'`(默认)+ `--max-boot-ms N`。实测基线 **+537 ms**(2026-10-10 台架,`stlink_flash.py flash` 后);默认 `--max-boot-ms 0` = 只报告不判,避免网络重试抖动造成假 FAIL。
- **标记顺序**:`--require-order 'App init OK>Network Init>net init: OK'` 按首次出现的**行序**校验;标记全中但顺序颠倒(屏障失序 / 重试补跑)此前完全不可见。
- **`--json`**:同一套判定以 JSON 输出(`required_missing` / `forbidden_hits[].lines` / `boot` / `timing` / `tail` / `verdict` / `exit`),退出码语义不变,供 CI 与上层脚本消费。
- **产物保留**:`--keep-out N`(默认 20)自动清理 `--out-dir` 里的旧 `boot_log_*.txt` / `selftest_log_*.log`(本机曾堆到 31+7 份)。
@@ -80,8 +80,8 @@
| daemon 无响应 / 自动拉起失败 | 查 `.dsh/skills/serial-assistant/scripts/state/daemon.log`;`serial_tool.py restart` 重建 daemon;检查 COM 是否被其他程序占用 |
| 抓到日志缺 `Network Init` | `-SettleMs` 太小(boot 任务间有 ~3-4s 自然静默间隙),用默认 8000ms;2026-10-10 起首字节到达前不启动静默计时(`--first-wait-ms` 默认 12000),首行晚到不再被提前收尾 |
| 烧录后无数据(`no_data`) | 2026-10-10 起 `no_data` = **本次等待期间零字节**(旧实现比 daemon 累计字节,常驻 daemon 下永不触发,只能靠"日志里没有 [RX] 行"兜底);开串口 + clear 必须在烧录**前**完成(`run_boot_test.py` 已保证);核对 COM 号、板子供电,看 `state/serial.log` 有无 SYS 异常行 |
| `[MISS] fw build: <戳>`(其余标记全中) | 芯片里跑的不是这份构建:烧录没落地 / 烧错文件 / hex 未重编译。处置:`reset.bat flash <hex>` 重烧并看 `READBACK: OK`;确认 `@build` 已重跑(戳取自编译时刻) |
| `READBACK: FAIL` / `flash readback mismatch` | 芯片内容与 hex 逐字节不一致(写入未落地、选项字节/RDP 异常、烧错目标)。处置:按 `docs/测试说明/ST-LINK_CLI_复位使用说明.md` §10 排查;`reset.bat flash` 重烧后闭环会自动复核 |
| `[MISS] fw build: <戳>`(其余标记全中) | 芯片里跑的不是这份构建:烧录没落地 / 烧错文件 / hex 未重编译。处置:`stlink_flash.py flash <hex>` 重烧并看 `READBACK: OK`;确认 `@build` 已重跑(戳取自编译时刻) |
| `READBACK: FAIL` / `flash readback mismatch` | 芯片内容与 hex 逐字节不一致(写入未落地、选项字节/RDP 异常、烧错目标)。处置:按 `docs/测试说明/ST-LINK_CLI_复位使用说明.md` §10 排查;`stlink_flash.py flash` 重烧后闭环会自动复核 |
| `boot banner x2 > allowed 1` | 采集窗口内板子自己复位过(IWDG 咬狗 / Fault 后 `NVIC_SystemReset` / 掉电重启)。处置:看判定里逐条打印的 `reset cause:` 定位;跑**故意复位**的套件用 `--max-boot-count N` 放宽 |
| `marker order violated` | 标记全中但**先后顺序颠倒** = 某步在另一步之后补做(就绪屏障失序 / 失败重试补跑)。处置:看判定打印的各标记首次行号定位;`--require-order` 只在需要强约束时启用 |
| `boot time N ms > limit` | 能开机但慢(mount 重试 / PHY 重协商 / 复位重试)。实测基线:`net init: OK` 在 **+537ms**(2026-10-10 台架);默认只报告不判(`--max-boot-ms 0`),要设门禁再给预算 |
@@ -238,7 +238,7 @@ DTU-RemoteLCD 客户端;输出每帧 `req2first / total / recv 批次到达时
# (产品构建 = 全部注释掉)
# 1) 构建(0 错 0 警)
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py
# 2) 一键闭环:开串口+清日志 → 烧录(reset.bat flash,Reset&Run)→ wait-idle → 标记校验
# 2) 一键闭环:开串口+清日志 → 烧录(stlink_flash.py flash,Reset&Run)→ wait-idle → 标记校验
# 硬件层模式无 "Network Init" 标记(netTask 自挂起),且 TEST_REPORT 行含 "failed=0"
# → Required/Forbidden 均用下方定制集(见 §4.4)
& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\run_boot_test.py `
@@ -301,7 +301,7 @@ DTU-RemoteLCD 客户端;输出每帧 `req2first / total / recv 批次到达时
单会话内 7 次数据连接 + 1KB/32KB 往返内容校验,退出码 = 是否全通过。
**D20 的失败窗口在"每次开机后的第一个 FTP 会话"**(稳态会话恒过)→ 复现/验证必须以
**复位开机**为边界(`MDK-ARM\reset.bat` 无参 = 只复位不烧录),首会话必跑、多轮统计。
**复位开机**为边界(`stlink_flash.py reset` = 只复位不烧录),首会话必跑、多轮统计。
### 5.5 台架铁律与工作流陷阱
@@ -10,7 +10,7 @@
| 变量 | 必需性 | 值的形态 | 使用者 | 默认/回退 |
|---|---|---|---|---|
| `DTU_KEIL_UV4` | MCU 构建机必需 | UV4.exe 完整路径,例如 `D:\Tools\Keil\UV4\UV4.exe` | `keil_build.py`(`@build`) | 不设即报错(exit 2);不从 C 盘旧路径猜测 |
| `DTU_STLINK_CLI` | 复位/烧录/回读机必需 | `ST-LINK_CLI.exe` 完整路径,例如 `C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility\ST-LINK_CLI.exe` | `reset.bat` | 未设时回退 PATH(`where ST-LINK_CLI.exe` 首个命中);已设但无效 = 硬报错 |
| `DTU_STLINK_CLI` | 复位/烧录/回读机必需 | `ST-LINK_CLI.exe` 完整路径,例如 `C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility\ST-LINK_CLI.exe` | `stlink_flash.py` | 未设时回退 PATH(`where ST-LINK_CLI.exe` 首个命中);已设但无效 = 硬报错 |
| `DTU_PRECOMMIT_PYTHON` | 可选(覆盖用) | 含 `pre_commit` 模块的 `python.exe` 完整路径 | `ci_local.py` L0、CI `l0-gates` | 未设时按 §1.1 探测(当前解释器 → `.venv` → PATH);已设但无效 = 硬报错 |
| `DTU_GRAPHVIZ_DOT` | 可选(覆盖用) | `dot.exe` 完整路径 | `gen_refgraph.py` | 未设时只查 PATH;无 Graphviz 时降级为"生成 .dot/.json、跳过渲染" |
| `DTU_MSVC_CL` | 可选(覆盖用) | x64 `cl.exe` 完整路径(`bin\Hostx64\x64` 下) | `unit_test_harness.py` L0.5、CI `l05` | 未设时按 VS 安装根自动探测(2019/2022/2026 各 edition);已设但不符 `bin\Hostx64\x64\cl.exe` = 硬报错 |
@@ -66,7 +66,7 @@ Graphviz / Cppcheck 的安装位置差异一律用 `DTU_*` 环境变量表达(
| 变量 | 必需性 | 值的形态 | 使用方 |
|---|---|---|---|
| `DTU_KEIL_UV4` | **必需**(MCU 构建机) | UV4.exe 完整路径 | `keil_build.py`(`@build`) |
| `DTU_STLINK_CLI` | **必需**(用 ST-LINK CLI 的机器;已在 PATH 可不设) | ST-LINK_CLI.exe 完整路径 | `MDK-ARM/reset.bat`(烧录/复位/回读) |
| `DTU_STLINK_CLI` | **必需**(用 ST-LINK CLI 的机器;已在 PATH 可不设) | ST-LINK_CLI.exe 完整路径 | `stlink_flash.py`(烧录/复位/回读) |
| `DTU_PRECOMMIT_PYTHON` | 可选(pre-commit 不在 `.venv`/当前解释器时设;CI runner 建议必设) | 装有 pre-commit 的 python.exe 完整路径 | `scripts/ci_local.py`、CI L0/L0.5 |
| `DTU_CPPCHECK` | 可选(开发机入 PATH 即可;无 PATH 的 CI runner 建议设) | cppcheck.exe 完整路径 | pre-commit cppcheck 钩子、CI L0 |
| `DTU_MSVC_CL` | 可选(VS 标准位置时自动探测) | x64 `cl.exe` 完整路径 | 主机侧单测、CI L0.5 |
@@ -91,11 +91,11 @@ Graphviz / Cppcheck 的安装位置差异一律用 `DTU_*` 环境变量表达(
| 项 | 要求 | 说明 |
|---|---|---|
| Keil MDK-ARM | **v5.x**(须含 ARMCC v5 编译器) | 工程锁定 `uAC6=0`(仅 ARMCC v5,**不支持 armclang**);仅支持此工具链,无其他工具链 |
| 安装路径 | **任意目录** | `keil_build.py` / `reset.bat` 经 `DTU_*` 环境变量解析工具路径(§2.1.1);新机器装完后设置 `DTU_KEIL_UV4` / `DTU_STLINK_CLI` 即可,**无需修改仓库脚本** |
| 安装路径 | **任意目录** | `keil_build.py` / `stlink_flash.py` 经 `DTU_*` 环境变量解析工具路径(§2.1.1);新机器装完后设置 `DTU_KEIL_UV4` / `DTU_STLINK_CLI` 即可,**无需修改仓库脚本** |
| 设备支持 | STM32F407ZGTx + CMSIS Flash 算法 | 工程引用 `STM32F4xx_1024.FLM`(1MB 片内 Flash,0x08000000),由 CMSIS STM32F4 设备包提供 |
| STM32CubeMX | **6.17.0**(工程 `STM32F407-Demo.ioc` 记录的版本) | 修改外设/引脚/时钟配置并再生成 CubeMX 文件;固件包须 **STM32Cube FW_F4 V1.28.3** |
| 命令行构建 | `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`(即 `@build`) | 退出码 0 = 0 错 0 警;失败看 `MDK-ARM/build_log.txt` |
| 命令行烧录 | `& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash`(即 `@download`;原生入口 `MDK-ARM/reset.bat flash` 等价) | 烧后 `-Rst -Run`;判据 `Programming Complete` + 回读 `No difference found` |
| 命令行烧录 | `& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash`(即 `@download`) | 烧后 `-Rst -Run`;判据 `Programming Complete` + 回读 `No difference found` |
> ⚠ **版本一致性关键**:.ioc 记录 `MxCube.Version=6.17.0` + `FirmwarePackage=STM32Cube FW_F4 V1.28.3`,
> 再生成代码必须用此组合——用更新版本的 CubeMX 会把仓库内 HAL 驱动升级、产生大量无关 diff(见 §六 速查)。
@@ -453,7 +453,7 @@ Test-Path $env:DTU_STLINK_CLI # True
| ③ | `& '.venv\Scripts\python.exe' scripts/ci_local.py` | L0 全钩子 Passed + L0.5 单测断言全部通过(当前 278 条),exit 0 | "找不到 MSVC" = VS 未装或装到非标准位置(设 `DTU_MSVC_CL` 可验证覆盖通道) |
| ④ | `& 'tools\refgraph\gen_refgraph.bat'` → `git status` | 重生成后 `docs/images/` **无 diff**(图内容只依赖源码,与本机工具链无关) | 出现 diff = 异常,需查 |
| ⑤ | `& "simulator\build_sim.bat"` | 自动探测 VS → 生成器匹配 → 构建成功(2026-10-02 起模拟器构建可用:配置引擎重构的文件迁移已同步进 `simulator/CMakeLists.txt`) | 报 `Cannot find source file` / `C1083` = CMakeLists 与源码目录再次失步(查文件迁移记录),与 `DTU_*` 变量无关;"未探测到 VS" 见 §2.3 |
| ⑥ | (有 ST-LINK 硬件才执行)`& "MDK-ARM\reset.bat" list` | 列出探针 SN | 只跑 `list`,**不烧录不复位**;无硬件跳过不扣分 |
| ⑥ | (有 ST-LINK 硬件才执行)`& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py list` | 列出探针 SN | 只跑 `list`,**不烧录不复位**;无硬件跳过不扣分 |
**第 3 步:反向测试**(验证"显式值无效 = 硬报错、不静默回退"语义,约 1 分钟)
@@ -524,7 +524,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 项 | 说明 |
|---|---|
| 下载方式 | `& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash`(ST-LINK_CLI:烧写 + Reset & Run;等价 `MDK-ARM/reset.bat flash`) |
| 下载方式 | `& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash`(ST-LINK_CLI:烧写 + Reset & Run) |
| 下载器 | 工程已配 `UL2CM3` 调试驱动 + `STM32F4xx_1024` Flash 算法;**物理下载器按现场选择**(本机为 ST-Link——不占 COM 口,可与串口调试并存);新机器在 Keil `Options for Target → Debug → Settings` 选择与硬件一致的驱动 |
| 调试日志 | USART1 115200;抓开机日志必须**先武装采集再复位**(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py --flash-cmd` 已保证顺序) |
@@ -603,7 +603,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 现象 | 原因 | 处理 |
|---|---|---|
| `keil_build.py` 报 `DTU_KEIL_UV4` 未设/无效 | `DTU_KEIL_UV4` 未设置或指向错误路径(**设完变量须重开终端**) | 设置/修正 `DTU_KEIL_UV4` 为 UV4.exe 完整路径(§2.1.1),重开终端重试 |
| `reset.bat` 报 `ST-LINK_CLI.exe not found` / `ENV_ERROR: DTU_STLINK_CLI` | `DTU_STLINK_CLI` 未设且不在 PATH,或显式值无效 | 设置 `DTU_STLINK_CLI` 为 ST-LINK_CLI.exe 完整路径,或把其目录加入 PATH;显式值无效时**不会**回退,须修值 |
| `stlink_flash.py` 报 `ST-LINK_CLI.exe not found` / `ENV_ERROR: DTU_STLINK_CLI` | `DTU_STLINK_CLI` 未设且不在 PATH,或显式值无效 | 设置 `DTU_STLINK_CLI` 为 ST-LINK_CLI.exe 完整路径,或把其目录加入 PATH;显式值无效时**不会**回退,须修值 |
| Keil 编译报编译器不识别 | 新装 MDK 只有 armclang | 工程锁 `uAC6=0`,必须用带 **ARMCC v5** 的 MDK v5.x |
| CubeMX 再生成后出现大量 diff / 新告警 | 用了比工程记录更新的 CubeMX / 固件包版本 | 用 git 还原生成文件,按 **.ioc 记录的 CubeMX 6.17.0 + STM32Cube FW_F4 V1.28.3** 重新生成 |
| CMake 配置失败:找不到生成器 | 本机 VS 大版本与生成器不匹配 | 统一走 `simulator\build_sim.bat`(自动探测 VS 版本选生成器);手动 cmake 时 `-G` 号须与本机 VS 一致(16=2019 / 17=2022 / 18=2026) |