# 实机测试实施指南(On-Board Test Guide) > **文档定位**:本文 = 上板(真机)测试的**执行流程**——按顺序执行。 > 每步给出「目的 → 判据(可观测、量化)→ 命令 → 状态(✅ 已闭环 / ⬜ 待执行)」。 > **已完成事项**(S7、A1~A4)的实施细节与证据**不在本文**,见对应模块文档(§10 状态与去向)。 > **前置铁律**:本文所有步骤**均在接上 MCU / 串口 / ST-LINK 之后**执行; > 无实机时**只读本文,不执行任何烧录**。 > **上游**:`docs/任务规划/README.md`(统一登记簿:P-x / N·S·T-x / > S1x·A-x / 阶段排序 / 诊断基线)· > `子系统设计/看门狗设计说明.md`(§7 验证记录)。 > **约定**:本文事项完成后**先销上游登记**,再把结论写进正文对应小节(本文不再记流水,过程查 `git log`); > 结论与证据沉淀进对应模块文档/问题记录(2026-10-02 台架窗口的完整取证链—— > IWDG PR/RLR 静默错配 / dhara resume 回滚 / ORE 僵尸通道——见 `看门狗设计说明.md` 2026-10-02 变更记录、 > `STM32F4-Base存储架构说明.md` §5.4.1、`控制台单写者日志设计说明.md` §12;修复已随 `930215f` 提交)。 --- ## 1. 执行流程总览 台架窗口就绪后,**按下列顺序执行**(P0 → P1 → P2 → P3): | 步骤 | 事项 | 状态 | 章节 | |---|---|---|---| | **步骤 1** | 环境预检(P0-A,每次上台架必跑) | 常设 | §2 | | **步骤 2** | 产品 `/serial-boot-test` 闭环 + 自检结果判定(P0-B) | ✅ 已闭环 | §3 | | **步骤 3** | IWDG 故障注入 V2/V4 + 长跑 V5(P1-A) | ✅ 已闭环 | §4 | | **步骤 4** | 堆低水位告警实测(P1-B) | ✅ 已闭环 | §5 | | **步骤 5** | GD5F 错误通道回归(P2-A) | ✅ 已闭环 | §6 | | **步骤 6** | 网络稳定性与基线重锁(P2-B / 日志静默观察项 + 队列往返基线待重验) | ◐ 部分闭环(7.1/7.2 ✅;7.3 待 PC peer) | §7 | | **步骤 7** | 工程卫生(P3 + 待核实项) | ✅ 已闭环 | §8 | > **每步出口**:`/keil-build` 0/0 → 一轮 `/serial-boot-test` PASS → ST-LINK 全片回读 `No difference found` → > 上游登记销项 + 把结论写进正文对应小节。 > > **参考**(非执行步骤):§9 执行注意(实测陷阱)· §10 已完成事项与证据去向 · §11 引用闭环。 --- ## 2. 步骤 1:环境预检(P0-A,每次上台架前必跑) > **目的**:避免"改动没问题、台架有问题"的误判(历史上多次把接线/权限问题误判为代码缺陷)。 | # | 检查 | 命令 | 通过判据 | |---|---|---|---| | A1 | 工作区干净 | `git status --short` | 无输出(或有明确待提交项) | | A2 | 产品构建 0/0 | `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py` | 末行 `BUILD_OK: 0 errors, 0 warnings` | | 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 第 1~6 轮台架)→ 264,284 B(第 7~9 轮台架)→ **264,936 B(当前,含 A2/A3/A4 补码)**;明显阶跃 ≈3.7KB 及以上(10-02 实测产品 262,836 → 套件 266,540)= 停在套件态 → 重跑 A2 | | A5 | 全量静态门禁 | `pre-commit run --all-files`(现只覆盖 `pre-commit` 阶段 47 根;manual 阶段三根另跑 `--hook-stage manual `) | **当时 17/17 Passed**(2026-10-03 实测;含新增 `test-cppcheck`) | | 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` 可读 | > ⚠️ **A4 的陷阱**(2026-10-02 实测):`/keil-build` 是**增量**构建,`run_all_suites.py` 跑完 > 套件后 `.map` 会停在**测试套件体积**;仅还原 `test_config.h` 不够,**必须再编一次产品固件** > (`run_all_suites.py` 已在 `finally` 内自动补这一步)。 **预检失败处置**:任一项 FAIL → **不上台架**,先修到全绿(避免"带病测试"浪费台架窗口)。 --- ## 3. 步骤 2:产品 /serial-boot-test 闭环 + 自检结果判定(P0-B) · ✅ 已闭环 > S7 判定已落地并投用;A1~A4 已闭环。**实施细节与真机验证证据见 §10 状态与去向**,本节只给复跑命令与判据。 ### 3.1 执行 `/serial-boot-test` 闭环除串口标记外,还判定设备侧自检结论(v1 只记故障、不记通过): `selftest.log` **缺失 / 0 字节 → 无故障 → PASS**;`filesize/64 > 0`(任一 64B 定长记录) = 有故障 → FAIL,并打印故障条目(时间戳 + 描述)便于定位。 ```bash # 一键闭环(selftest.log 自动 FTP 抓取;无故障记录 = PASS) & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py --require-selftest-pass --device-ip 192.168.31.100 # 或手动联合判定(本轮串口日志 + selftest.log 本地副本) & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/verify_log.py \ --log-path <本轮串口日志> \ --require-selftest-pass \ --selftest-log <下载的 selftest.log 本地副本> # 判据:退出码 0;输出含 "[OK] selftest: no fault record" ``` ### 3.2 自检项清单(S7 判定覆盖面,20 处) > 来源:全库 `syslog_post(SYSLOG_SELFTEST, ...)` 调用点。**这是 S7 的判定覆盖面基准**。 | 文件 | 自检描述串 | |---|---| | `App/app/app_main.c` | `watchdog reset` / `clock chip fault` / `NAND fault` / `ADC fault` | | `App/cfg/cfg_file.c` | `cfg heap alloc fail`(×3) / `cfg template write fail` / `cfg load oversized` / `cfg load read fail` / `cfg save read fail` / `cfg save overflow` / `cfg save write fail` / `cfg poll oversized` | | `App/task/default_task.c` | `filesystem fault`(×2) / `storage fault`(×2) | | `App/task/net_task.c` | `comms fault` / `net init fail` / `net reinit fail` | **联动**:`syslog_post` 的两级自愈(板端测试套件文件级重试 / 主机侧回归错误去重 `s_fault_reported[]`) 决定**持续故障只留第一条**——故 S7 判定"有记录即 FAIL"是**充分**的(不会被去重掩盖)。 --- ## 4. 步骤 3:IWDG 故障注入与长跑(P1-A 收尾) · ✅ 已闭环 > **2026-10-03 台架结论**(实施细节与证据见 `子系统设计/看门狗设计说明.md` 变更记录): > V2/V4/V5 三用例全部通过——V2(任务饿死)声明 stall 后 ≤2.5s `reset cause: IWDG`;V4(关中断死循环)每 ~4.9s 稳定 IWDG 复位(IWDG 独立于中断);V5(30 min 真实负载)**120/120 次 FTP 传输全 PASS、零 IWDG 复位**。 > **遗留观察**:V5 期间 44 次 stall 声明 100% 与 `e02_probe` 探针的 time_sync 30s 掉线+监听重建重合;隔离实验(仅 FTP 120s)**0 次 stall** → 探针场景专属,非一般负载误杀(已登记 `docs/任务规划/登记簿.md`)。 > 来源:`子系统设计/看门狗设计说明.md` §7 验证记录(原实施步骤已并入该文档文末变更记录)。 > 判定状态机与 IWDG 配置(PR/RLR 按设计值写入)已落地并经真机验证(2026-10-02,`930215f`, > 见设计说明 2026-10-02 变更记录);故障注入 + 长跑验收结论见上文 2026-10-03 段落。 ### 4.1 三个验收用例 | # | 用例 | 注入方法 | 通过判据 | |---|---|---|---| | **V2** | 任务饿死 | 临时在 `netTask` 循环插 `while(1);` | 800 ms 后串口 `[ERR] WATCHDOG: task stall mask=0x...`;≤2.5 s 复位;重启后 `[WD] reset cause=watchdog reset` | | **V4** | 关中断死循环 | 临时插 `taskDISABLE_INTERRUPTS(); while(1);` | ≤2.5 s 复位(检验 IWDG **独立于中断**);`cause=watchdog reset` | | **V5** | 长期不误杀(**核心验收项**) | FTP 大文件传输 + UI 菜单遍历 + 配置保存 + 网络重配 **≥30 min** | **零复位**(重点验证 `docs/子系统设计/看门狗设计说明.md` §4.3 白名单是否完备) | > ⚠️ **V5 是核心验收项**:误杀比不杀更有害。必须在**真实业务负载**下跑足时长。 ### 4.2 执行步骤 ```bash # --- V2/V4(故障注入,需临时改码)--- # 1) 基线:跑一轮产品 /serial-boot-test 确认无复位 & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py # 2) 注入:按 `docs/子系统设计/看门狗设计说明.md` §7 在 netTask 循环插 while(1);(或 taskDISABLE_INTERRUPTS(); while(1);) # —— 改完 /keil-build,然后烧录 & '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py & '.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,不写死路径) $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-LINK_CLI.exe).Source } & $cli -c SWD -CmpFile MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex 0x08000000 # 判据:No difference found # 5) 撤销注入,/keil-build 回产品态,再跑一轮 /serial-boot-test 确认正常 # --- V5(长跑,需真实负载)--- # 1) 用 test/host/ftp_speed_test.py / ftp_up_dl_test.py 持续传输大文件 & '.venv\Scripts\python.exe' test/host/ftp_speed_test.py # 2) 同时人工/脚本遍历 UI 菜单 + 反复保存配置 + 触发网络重配 # 3) 持续 ≥30 min,全程串口记录 # 判据:零复位(日志无 "reset cause=watchdog reset");业务不中断 ``` **风险/回滚**: - 故障注入用例**会复位设备多次**——确认设备非在用/非已部署。 - 长跑涉及 NAND 写入(FTP 上传)**消耗 NAND 块**——若设备已接近容量上限,改用只读传输。 - ⚠️ **R5 风险**(`docs/子系统设计/看门狗设计说明.md` §9):NAND 擦写偶发超 800 ms 可能误复位——若 V5 出现偶发复位, 先按设计说明增大容忍次数或把存储操作改分段置位,**不要盲目放宽全局超时**。 --- ## 5. 步骤 4:堆低水位告警实测(P1-B,S11) · ✅ 已闭环 > **2026-10-03 台架结论**:告警代码(`default_task.c` 的 `HEAP_LOW_WATER_B=3072` 门闩 + `[HEAP]` 告警 + syslog 自检)已落地;复用 §4.2 V5 窗口 30 min 长跑,**全程未触发**(`min_ever=4728 B` > 3072 B)→ 记录"当前负载下 min_ever 仍高于阈值"为基线,已更新 `内存管理/RAM使用分析与优化方案.md`。 **现状证据**:2026-10-03 上板复测 `free=8,688 / min_ever=3,712~4,728 B` (两次开机,运行间波动 ≈1KB;初始 `configTOTAL_HEAP_SIZE` 48KB)—— **min_ever 下限已跌破 4KB**,余量不到 8%,新增任务/缓冲可能触发分配失败。 现有代码仅有周期性 `[STACK]` 报告(每周期打印 `heap free/min_ever`),**无阈值告警、不写自检记录**。 **待补代码**(约 15 行 + 1 个编译期常量,在 `defaultTask` 低频循环(LED 闪烁节拍)内): 检查 `xPortGetMinimumEverFreeHeapSize()`,低于阈值(建议 **3072 B**,低于 10-03 实测下限 3,712 B,防运行间波动误报)时 `DBG_ERROR("[HEAP] low water: %u B")` + 写 syslog selftest。 **判据**:台架长跑(复用 §4.2 V5 窗口)下,若发生低水位 → 串口出现告警行 + `selftest.log` 有记录; 若全程未触发 → 记录"当前负载下 min_ever 仍高于阈值"作为基线。 **执行步骤**: ```bash # 1) 代码改动(约 15 行 + 1 个编译期常量)在 defaultTask 低频循环内 # 2) /keil-build 0/0 后,与 §4.2 V5 长跑合并执行 # 3) 长跑结束读取 min_ever: # - 串口 [HEAP] 告警行(若有) # - 或 FTP 取 /log/selftest.log # 4) 更新 docs/内存管理/RAM使用分析与优化方案.md 的 min_ever 基线 ``` **成本/回滚**:约 15 行 + 零额外缓冲;阈值设编译期常量便于按新基线调整。 若长跑中频繁触发但系统正常 → 说明阈值偏紧,据实测值调整(不是缺陷)。 --- ## 6. 步骤 5:GD5F 错误通道统一(P2-A,S9 = 上游 C2) · ✅ 已闭环 > **2026-10-03 台架结论**:四要素(分类/透传/分级/留痕)+ 返回类型统一 `int32_t` 已落地。 > 套件回归:`test/run_all_suites.py --suites gd5f` → **`=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`**(只读模式,4 个破坏性用例按设计跳过);编排器 `[GD5F] -> PASS`。 > **顺带修复(编排器缺陷,非本次代码引入)**:`run_all_suites.py` 原对所有套件统一 `--forbidden "HardFault;assert;[ERR]"`,而 GD5F 的 `TC-GD5F-704`(CCM 非 DMA 可达缓冲)会**预期**打出 `[ERR][NAND] RX DMA buffer not DMA-reachable ... fallback poll`(用例 PASS)→ GD5F 套件**永远被判 FAIL**(自 `2055de0` 起)。按 `verify_log.py` 的说明改为**按套件覆盖 `--forbidden`**(GD5F = `HardFault;assert`),修复后 PASS。 > **来源**:第二档 #1; > `docs/产品需求/产品需求设计文档.md` §7.1(错误码通道四要素骨架,设计基线)。 **现状证据**:`ch395f.c`(1330 行) 与 `gd5f2gq5ue.c`(1496 行) 是同代驱动,但**只有 CH395F** 形成了"分类 + 传播 + 审计"完整体系;GD5F 错误处理散落各函数,`Trap_Records` 仅 198 行。 **统一错误通道四要素**(以 CH395F 为范式): | 要素 | 内容 | |---|---| | ① **分类** | `wait_busy` 区分「超时 / 真故障 / 参数错」——不得混为一个返回码 | | ② **透传** | 底层错误码逐层上报,不吞不猜(禁 `(void)ret`) | | ③ **分级** | 可恢复(重试)/ 不可恢复(上报自检 + 降级)显式判定 | | ④ **留痕** | 不可恢复项写 syslog selftest;可恢复项计数并在 UI/日志可见 | | — | 返回类型统一 `int32_t` | **实施顺序**(数据安全优先): 1. 先建**分类函数**(`gd5f_wait_busy` 内检查三次 HAL 调用)——所有下游判断的基础; 2. 再修**最危险的**:`gd5f_bbt_scan` 失败**放弃持久化**(保护好副本)——**数据安全优先**; 3. 补 **init 全链路检查 + 重试 1~2 次**; 4. 回读 **B0h 校验 ECC 使能**(bit4); 5. `gd5f2gq5ue_read` 的 raw 契约文档化或加 ECCS 检查; 6. 全程在 `GD5F2GQ5UE_Trap_Records.md` 留痕,同步 `GD5F2GQ5UE_Test_Guide.md`。 7. **RAM 预算**:新增错误分支**不得引入新的大缓冲**(先跑 `ram_report.py` 核算)。 **判据**: - **错误通道审计项全部销项**; - **GD5F 套件回归 PASS**: ```bash # 套件回归(需实机) & '.venv\Scripts\python.exe' test/run_all_suites.py --suite gd5f # 判据:=== GD5F Tests: N/M PASSED (failed=0) === 且 N>0 # 只编译验证(离机,改动后随手跑) & '.venv\Scripts\python.exe' test/run_all_suites.py --check-build ``` > ⚠️ **纪律**(呼应 `AGENTS.md` 铁律):自愈逻辑**严禁**把"名字/参数类错误码" > (如 `FR_INVALID_NAME`)归入"卷损坏触发集"——`.fschk` 无限重格式化事故即此类(2026-09-20)。 > ⚠️ **破坏性用例**:GD5F 套件默认只跑只读;`ENABLE_DESTRUCTIVE_TESTS` 跑一次消耗一个 NAND 块 > (实测 95248→95200),**仅在产线首启或明确需要时打开**。 --- ## 7. 步骤 6:网络稳定性与基线重锁(P2-B) · ◐ 部分闭环(7.1/7.2 已闭环;7.3 待 PC peer) ### 7.1 网络重建失败恢复(A2;实施细节见 `网络/Net层阻塞模型架构设计.md`,状态见 §10) · ✅ 正常路径已闭环 **判据**:拔插网线/改 IP 触发重建后—— - 失败路径**写 syslog 自检**(不再零日志); - **30s 周期重试**,不自驻留死循环; - 恢复后各 TCP 服务(FTP 21 等)**据代次号重建监听**,功能正常。 **验证手段**:`test/host/e02_probe.py`(网络探针)+ UI 网口设置页改 IP。 **2026-10-03 台架结论**: - **正常路径 ✅**:V5 长跑(30 min,FTP 512 KB 上/下传)**120/120 PASS、零 IWDG 复位**, 网络重建后的 FTP/time_sync 服务据代次号重建监听、功能正常(证据 `/tmp/v5_soak_serial_20261003.log`)。 - **负向路径(拔线/芯片级 `net_init` 失败)**受**台架拓扑**限制(CH395↔交换机, 拔交换机侧线缆链路仍 UP;芯片级失败需 SPI 故障注入)→ 已登记 `docs/任务规划/登记簿.md` 条目「网络负向场景台架验证」 (① PHY 拔线负向 ② A2 `net_init` 失败分支 ③ A4 短写压测),需专项窗口。 ### 7.2 日志静默观察项:49s 全系统日志静默复现与定位 · ✅ 判据②已满足 / 判据①未复现 **现状证据**(登记簿条目「日志静默观察项」):2026-09-22 台架实测,串口日志 **49s 无行** (testTask/netTask/[STACK] 行全缺),期间 net 栈照常处理外部连接 → **日志管线停滞**, 队列满后各任务日志行被丢;系统功能不受损。 **判据与核实(2026-10-03)**: - **判据②(队列满丢弃计数/告警)→ ✅ 代码已满足**:日志管线**已有**显式丢弃可观测性—— `App/log/console_log.c` 的 `s_con_drop` 计数 + 排空末尾 `[ERR] CONSOLE dropped %lu lines` 提示 + `dbg_console_dropped()` 访问器;`App/log/sys_log_mcu.c` 的队列满/preboot 满分别 打 `DBG_ERROR("queue full, drop: …")` / `("preboot full, drop: …")`。另 UART TX 卡滞 已由**僵尸通道自愈**覆盖(`con_uart_reinit()` + `[WARN] CONSOLE tx fault: UART reinit`, `s_con_tx_fault` 计数)。**"现静默丢行、排障不可见"已不成立**。 - **判据①(复现 49s 并定位卡点)→ 未复现**:对 V5 30 min 长跑日志(6031 条带时间戳行) 做间隔分析,**最大静默 41s**(发生在**开机/烧录边界**,非运行期),**运行期无 49s 静默**。 运行期 >10s 的静默共 24 处,**全部**为 ~28s,且**均落在 `e02_probe` 的 time_sync 30s 挂起客户端窗口内**——与「探针窗口心跳缺口」同源(探针场景专属,非通用负载)。 - **结论**:49s 静默在当前固件 + V5 负载下**不复现**;可观测性缺口已由既有代码补齐。 日志静默观察项降级为**低风险**(保留登记,若再现则借 `[ERR] CONSOLE dropped` 计数定位)。 - **后续(2026-10-25,道F 第二轮 合并评估)**:窗内静默的同源阻塞机制 (`drop_conn` 同步占用 netTask 400-600ms)已随「登记式拆链」修复 (`../../网络/网络自愈架构设计.md` §4.6);日志静默观察项与日志单写者化的合并评估已完成,结论与「无源 vs 真停滞」判别口径落 `../../子系统设计/控制台单写者日志设计说明.md` 文末附录—— 再现静默先按三本账(`dbg_console_dropped/_tx_fault/_tx_timeout`)+ syslog 丢弃 账对照定性,再决定是否开卡;销项条件 = 探针窗口心跳缺口修复版 V5 复跑(e02_probe + FTP ≥30 min)运行期 `task stall` 与真停滞嫌疑静默段双零(2026-10-25 v1.1 起 `MISS start` 降级为诊断行不计销项——见下方判读工具与 `../../子系统设计/看门狗设计说明.md` §6.1)。 - **判读工具 v1.1(2026-10-25 大窗判读 F1 起)**: `'.venv/Scripts/python.exe' scripts/v5_log_gap_report.py ` ——自动列 `>10s` 静默段(`--gap-threshold-s` 可调),每段带判定(NO-SOURCE / PROBE-WINDOW / BOOT-WINDOW / TRUE-STALL-SUSPECT)与贴边证据行。违规口径 = `task stall` 行 >0 **或** 真停滞嫌疑段 >0;退出码 0=达标 / 1=违规 / 2=日志无有效时戳;`--json` 机读、`--report-only` 只出报告、`--selftest` 内嵌合成样例自检(无台架可跑)。**v1.1 变更(判读口径,先文档后脚本)**: ① `MISS start` 降级为**诊断行**(看门狗 v1.5 按位分账后它只是复合缺席边沿, 不代表停喂,`../../子系统设计/看门狗设计说明.md` §6.1)——报告改出归因诊断: 开机良性 / 探针窗内 / 窗外运行期 + 窗外每分钟速率 top;v1.0 把运行期 MISS 计违规属收紧过头(2026-10-25 台架 70min 窗 1262 条经逐窗归因实为风暴复合边沿 与传输窗抖动),予以纠正;② `task stall` 行语义 = v1.5 起「同一任务 800ms 单串真停摆」(行文本不变,`../../子系统设计/看门狗设计说明.md` §5.3),每行都是 真猎物;开机豁免口径(§9 第 8 条)保留、仅作用于 MISS 诊断分类。口径唯一 来源 = `../../子系统设计/控制台单写者日志设计说明.md` 文末附录 + §9 第 8 条 + 看门狗设计说明——改口径先改文档再改脚本(自检断言会拦漂移)。 **执行步骤**:长跑(§4.2 V5)期间周期性打点 + 对照 `CH395F` 无关的 SD2506 侧日志判别卡点。 ### 7.3 队列往返基线待重验 · ⬜ 待 PC peer **现状**:L5 基线(1223/988µs)属**分片时代**;人工目检对该路径逐位不变 → 机制上无影响, 但严格重锁需跑一轮 Phase 13(**需 PC peer**)。 **判据**:Phase 13 窗口跑一轮,基线数值与阈值余量(>8×)对照,重锁数值。 **2026-10-03 台架**:本窗口**无 PC peer**,Phase 13 无法执行 → 保留待办(低风险: 阈值余量 >8×,机制上 人工目检对该路径逐位不变)。 --- ## 8. 步骤 7:工程卫生(P3) · ✅ 已闭环 > S5 / S4 落地(离机可做),待核实项已实机核实并修复。本节留复跑命令与判据。 ### 8.1 S5:`test/` 代码宽松静态检查 **现状证据**:`test/` 完全豁免 pre-commit("自律"要求),而 `test/board/` 下 **6300+ 行 C 代码** (实测 `.c`+`.h` 合计 6,313 行)**且编入固件**。曾出现 `s_rx_buf` 使用先于声明。 **目标**:抓 `test/board/` 固件测试代码的**真实缺陷**(未初始化/越界/空指针), 但不引入存量 style 噪音。 **执行步骤(先跑基线,离机可做)**: ```bash # 1) 基线统计(不需实机) cppcheck --enable=warning --error-exitcode=1 test/ 2>&1 | tee test_cppcheck_baseline.txt # 2) 若存量可控(建议 <20 条)→ 加 test-cppcheck 钩子,只开 warning # 3) 若存量较多 → 先建 suppress 清单(明确每条豁免理由),再挂钩子 # 4) 务必排除模拟器桩/第三方头(test/host/watchdog/stub/ 等) ``` **判据**:`test-cppcheck` 在 CI PASS;新增测试代码越界/未初始化被自动拦截。 > **注意**:若基线告警过多,**保持现状亦可接受**——此时应把该决策写明确("理由:存量 N 条")。 **结论(✅ 已闭环,2026-10-03)**:已加 `test-cppcheck` 钩子(`.pre-commit-config.yaml` `id: test-cppcheck`,**仅开 `warning`、排除模拟器桩/第三方头**)。基线可控, `pre-commit run test-cppcheck --all-files` **PASS**。新增 `test/board/` 代码的 未初始化/越界/空指针问题现由钩子自动拦截。 ### 8.2 S4:用例追溯脚本 **现状**:文档声称 91 个 TC、代码有 222 个 `TEST_CHECK`(断言点数 > 用例数是正常的), 但**双向是否一一对应无人校验**。 **目标**:新增 `[计划] scripts/check_test_traceability.py`—— 输入 `test/board/*_test_task.c` 的 TC 注释 + `docs/测试说明/板级测试/*Test_Guide.md` 的 TC 编号; 输出 ① 文档有、代码无(未实现)② 代码有、文档无(未登记)。 **默认仅告警(warning),不阻断提交**(与 `doc-links` 演进路径一致)。 **判据**:一键列出双向差异清单;差异收敛后再考虑升为门禁。 **结论(✅ 已闭环,2026-10-03)**:`scripts/check_test_traceability.py` 已落地并跑通, **默认仅告警(exit 0)、不阻断提交**。2026-10-26 重打:当前差异 **25 处**(文档有代码无 + 代码有文档无两向), 另有约 1,486 处属"代码有该号、号旁写着人话意图",按「代码即属主登记处」放过——**弱于文档的保证**, 对外举证仍需模块级说明。当年列出的孤儿号(GD5F 那三个 11xx、存储 06 号)已在 2026-10-26 清号批次 里从代码侧删号收口。差异清单可追踪,后续按需补登记;**暂不升门禁**(与 §8.2 目标一致)。 ### 8.3 待核实项:`TEST_SUITE_SELECTED` 未含 `TEST_SUITE_RTC` · ✅ 已核实(非缺陷) **原疑点**(`test/board/test_config.h`):派生标记 `TEST_SUITE_SELECTED` 只含 CH395F / STORAGE / GD5F,未含 RTC;怀疑 RTC 构建下 `testTask` 不被创建 → 套件不执行。 **2026-10-03 实机核实结论**: - **`TEST_SUITE_SELECTED` 是死代码**:`grep -rn TEST_SUITE_SELECTED` 全仓**仅本文件有定义、 无任何使用点**。`Src/freertos.c` **无条件创建 `testTask`**;`test/board/test_config.c` 的 `start_test_task()` **直接按 `TEST_SUITE_RTC` 等宏分支**(不经 `TEST_SUITE_SELECTED`)。 → "testTask 不被创建 → RTC 不执行"的推断**不成立**。 - **RTC 套件确实执行**:选 `TEST_SUITE_RTC` 构建烧录后,串口出现完整 RTC 输出 (`SD2506 test suite started` … `Phase 1 basic: 3/3 PASSED` … `Phase 2 API: 6/6 PASSED`)。 - **真正的缺口 = 缺套件级汇总行**:原 `sd2506_test_run()` 只打 Phase 级汇总,**没有** `=== RTC Tests: N/M PASSED (failed=0) ===` → `run_all_suites.py --require-suite-summary "RTC Tests"` 判 **FAIL**(设备实际 12/12 全过)。属**判据缺口**,非设备缺陷。 **修复(2026-10-03)**: 1. `test/board/sd2506_test_task.c`:各 Phase 进入前**自清零**,末尾输出套件级 `=== RTC Tests: N/M PASSED (failed=0) ===`(复用 `SD2506_TEST_REPORT` 宏,格式与 `test_common.h` 统一)。顺带修正 Phase 级汇总行**累计**问题——原 Phase 2 显示的是 含 Phase 1 的累计值(9/9),现各阶段独立:Phase 1 = 3/3、Phase 2 = 6/6、套件级 = 9/9。 2. `test/board/test_config.h`:`TEST_SUITE_SELECTED` **补齐 RTC** 并注释其"当前无引用", 防止将来真有分支复用时重演"漏 RTC"。 **复跑命令**:`& '.venv\Scripts\python.exe' test/run_all_suites.py --suites RTC` **判据**:`[RTC] -> PASS`、`VERDICT: PASS`,日志含 `=== RTC Tests: 9/9 PASSED (failed=0) ===`。 --- ## 9. 执行注意:实测陷阱(2026-10-02 台架窗口,照做可少绕一天) 1. **写入"复位后消失"的两层原因**(窗口最大坑,根因均已修复,详见模块文档): - **板端测试套件 dhara checkpoint 回滚**(`架构设计/STM32F4-Base存储架构说明.md` §5.4.1):环回绕后 `dhara_journal_resume()` 恢复旧 checkpoint,**之后写的一切整段丢弃** (已修:epoch 锚点改比较两候选的新旧)。 - **主机侧回归 FTL 单页写回缓存**:一次 `disk_write` 里不满整页(2048 B)的写,在**写到另一个 LPN / 整页写 / `CTRL_SYNC`** 之前只存在于 RAM(`nand_ftl.c` 的 `s_cache_buf/s_cache_dirty`)。 但正常 `f_close` 就会走到 `CTRL_SYNC`(FatFs `sync_fs` → `disk_ioctl` → `ftl_flush_cache`), 所以**"写完即关"的路径(日志、配置保存)关文件即落盘,不必再"写另一个文件逼刷"**;会丢的 只有**写事务进行中**掉电/硬复位(含 IWDG 误复位)里最后那笔未跨页的写(≤512 B)—— FTP STOR 传输途中掉电就落在这一档(已登记观察;「写回缓存空闲 flush」于 2026-10-22 裁定暂不做, 缺的是"空闲/掉电前周期 flush",且本机无掉电检测硬件,掉电时点无法补救)。 → 排查写入丢失时**先看开机日志的 head/tail/epoch**(`Map head=… tail=… epoch=…`, 纯内存字段、零 I/O):head 复位后倒退 = 板端测试套件(resume 错);head 正常 = 主机侧回归(缓存未刷)。 (旧口径"清台账/写证据后写另一个文件"的 flush 诱导属 板端测试套件时代的产物:现码 `f_close` 自带 `CTRL_SYNC`,正常写完即关就不需要它;这条诱导只在 `f_close` 失败时才还有兜底意义。) 2. **FTP 路径**:lftpd 相对路径基准是**根目录**(`SIZE selftest.log` → 550),一律用 绝对路径 `/log/xxx`;`LIST /log` 实测列的是**根目录**内容(lftpd 怪癖)——查状态只用 `SIZE` / `RETR`。 3. **0 字节 STOR 的数据连接**存活期仅 ~10~50ms,短于芯片/网络层轮询粒度 → 芯片只见 `DISCONNECTED (chip tcp=0x00)` → accept 失败(425 或控制连接被重置);2026-10-02 实测客户端 `PASV→STOR` 间隔达 **11 s**(数据口 LISTEN 半开)。这是**与看门狗误复位不同的另一类 问题**(CH395F 数据通道);若修复后仍复现 425,转 §7.1 专项。 4. **串口采集铁律**:`open` → `clear` → 烧录/复位 → `wait-idle --timeout 90 --settle-ms 8000 --max-ms 45000` → **直接 Read/Grep `.dsh/skills/serial-assistant/scripts/state/serial.log`** (`log --grep` 子命令不可信);daemon 残留 → `taskkill /F /PID <旧>` 后重新 `open`。 5. **只用 `.venv\Scripts\python.exe`**(PATH 里的 `python` 是 msys64);每次烧录后**必须** ST-LINK 全片回读 `No difference found`。 6. **误复位会使台账 +1 条** `watchdog reset`:取证/修复期间台账只会增长, 属预期(清台账留到最后一次修复后的出口闭环)。 7. **ST-LINK_CLI 取证三硬知识**(完整用法见 `ST-LINK_CLI_复位使用说明.md`): - `-R32` **不会 halt 内核**(`-CoreReg` 才 halt,实测约 30ms/次且可重复)→ 用 `-R32` 做 **无扰动**存活性采样,只在需要 PC 时用 `-CoreReg`(halt 期间 TIM7 ISR 不执行, 会让中断计数出现"假停顿",须用非 halt 读法甄别)。 - `-R32 ` 的 `count` 单位是**字节**(`-R32 0x1000FF00 16` 得 4 个字); 按字理解会把读取**静默截断**。 - `-W32 ` 的 `data` 按**十六进制**解析(写 `1249` 会变成 `0x1249`, 被 12 位 RLR 截断成 `0x249`=585);要写十进制 1249 得传 `0x4E1`。另 `IWDG->KR` 是 **只写**寄存器(读回恒 0),`-W32` 写 KR 必报 `Error!`——那是 ST-LINK 的写-回读 校验失败,**写入其实成功**。 - 现场改寄存器(如 IWDG PR/RLR 验证手段)须在**一次调用**内完成整段键序列 (解锁→参数→键锁)——分多次调用会超过看门狗超时、中途被复位;寄存器随复位丢失, 只能用于验证,不是修复。 8. **开机良性 `MISS start` 行**(2026-10-03 上板):看门狗抽样在**开机屏障等待期**偶见 UI 位单抽样缺席,每 episode 记一条裸 UART `MISS start snap=0x… req=0x… t=…` (`task_watchdog.c` 的停摆起点标记,round-7 取证件)。实测每次开机 2 条 (t≈0.6s / t≈2.8s,snap 仅缺 UI 位),NET_READY 解除后立即恢复——**未达 800ms 停喂 阈值、不触发复位**,属分片心跳与 100ms 抽样边界的固有交错,非回归; 处置:见到该行不必恐慌,**连续**出现 `task stall` 行/复位才是真停摆。 9. **导航类自动化脚本默认零 ENTER**(2026-10-26 台架实证,task-41 收口;口径也见 `启动日志原子性复验流程.md`):装置维护的 **强制复归 / 清除记录 是两段式确认**—— 第一次 ENTER 只把该行标成「确认?」,**第二次 ENTER 才真执行**(复归保护位并写 SOE / 清 SOE·运行·操作记录)。因此任何"认不出屏就按 ENTER 试一下"的回收逻辑,落到这两页上 等于**替操作员按下执行键**;更隐蔽的是它只在画面异常时才发作(新字库缺字、白框、状态行 变化——恰恰是回归最想抓的那几帧),平时零流量、改动时无声。 `scripts/board_ui_nav.py` 的两条硬闸与 `--selftest-quick` 里的 **7 条常驻用例**(假设备 + 内存空白帧 + 按键只记账, **不连 7003、不开串口、不碰台架**,提交钩子 `uinav-selftest` 就跑这档)把它钉成回归: ① UNKNOWN 退避的 ENTER 上限 `BACKOFF_MAX_E`(政策钉死 ≤1,需要第二次就当场停机并 dump 帧, 报错文案点名执行类风险);② `EXEC_PAGE_IDS`(clear / reset)**默认拒绝下钻**且拒绝发生在 **任何键投出去之前**(真机实测:连轨迹文件都不生成),显式 `--allow-exec-pages` 才放行并打警告。 用例还钉住**两个常量本身**:上限被调到 2 以上、清单被清空、或默认值被翻成"允许"—— 都必须在 `--selftest-quick` 里报红(这三条都用"故意改坏再跑"逐条证过能红,不是空跑)。 通用要求:**导航 = 移光标 + 取帧;ENTER 只能出现在明确、非执行类的用例步骤里**; 要看执行类页面的显示正确性,去模拟器黄金参考(那里没有真机后果)。 --- ## 10. 已完成事项与证据去向 > 已完成事项的**实施细节与真机证据不在本文**,按下列去向查阅;本文只保留其复跑命令与判据。 | 事项 | 状态 | 实施细节 / 证据所在 | |---|---|---| | **S7** 自检结果判定 | ✅ 已落地并投用 | `docs/测试说明/流程与规范/自动化测试流程.md`;提交 `4a4cd29` | | **A1** `cfg_file` 堆分配改造 | ✅ 已闭环(含真机持久化) | `子系统设计/配置文件设计说明.md`(实施 v1.11 + 2026-10-03 真机验证记录 + 2026-10-04 独立复跑记录) | | **A2** 网络重建失败恢复 | ✅ 已闭环(正常路径零回归;故障分支见「网络负向场景台架验证」) | `网络/Net层阻塞模型架构设计.md` | | **A3** CH395F 字节命令超时 | ✅ 已闭环(正常路径零回归;故障分支待专项窗口) | — | | **A4** `remo_send` 返回值 | ✅ 已闭环(正常路径零回归;推流压测待专项窗口) | — | | 2026-10-02 台架取证链(IWDG PR/RLR 错配 / dhara resume 回滚 / ORE 僵尸通道) | ✅ 已修 | `子系统设计/看门狗设计说明.md` 2026-10-02 变更记录 · `架构设计/STM32F4-Base存储架构说明.md` §5.4.1 · `子系统设计/控制台单写者日志设计说明.md` §12;提交 `930215f` | > **真机判定汇总**(2026-10-03,本机 MCU,新固件全闭环 `@test` PASS 7/7):A1 ✅ PASS(含配置持久化); > A2/A3/A4 正常路径零回归;芯片级失败分支 / 短写 / 拔线负向场景记 `docs/任务规划/登记簿.md` 条目「网络负向场景台架验证」,待专项台架窗口。 --- ## 11. 与既有文档的引用闭环 | 本文项 | 上游文档 | 下游落地文档 | |---|---|---| | S7 自检判定 | —(原复审项,已闭环 2026-10-02) | `docs/测试说明/流程与规范/自动化测试流程.md`(参数已落地,`4a4cd29`) | | V2/V4/V5 | `子系统设计/看门狗设计说明.md` §7 验证记录 | `子系统设计/看门狗设计说明.md` 变更记录(2026-10-03) | | S11 堆告警 | —(原复审项,已闭环 2026-10-03) | `内存管理/RAM使用分析与优化方案.md`(基线更新) | | S9 GD5F | —(第二档 #1) | `问题记录/GD5F2GQ5UE_Trap_Records.md`、`docs/测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md` | | 日志静默观察项 | `docs/任务规划/登记簿.md` | `网络/Net层阻塞模型架构设计.md`(头阻塞,历史编号) | | 网络重建 | —(原台账项,已闭环 2026-10-02) | `网络/Net层阻塞模型架构设计.md` | | S5/S4 | —(原复审项,已闭环 2026-10-03) | `docs/测试说明/流程与规范/测试开发标准流程.md` | | 模拟器差距 | `docs/测试说明/专项验证/模拟器差距清单.md` §3 决策规则 | 本文为"需回真机"项的执行面 | > **模拟器差距清单 §3 的六条"必须回真机"规则**决定"哪些改动**必须在本规划内验证—— > 改动触及 `Drivers/BSP/` 寄存器时序 / 模拟器差距清单的硬件专属项 / 配置变更检测与回调 / 网络重建并发 / 启动段 / 内存预算, > 一律在本文件找到对应批次。