Files
wandering1 7efa58d9a1
CI / 静态门禁 (pre-commit) (push) Failing after 25s
CI / 主机侧单测 (MSVC) (push) Successful in 48s
network: 台架 IP 迁到 192.168.31.100,并把重复写死的值收进单一真源
分两段(用户裁定 2026-11-02):先做地址迁移,再把同类字面量收敛成
单一真源,以后再改地址只动一处。

一、IP 迁移 192.168.1.100 → 192.168.31.100(gw 同步 → .31.1)
- 工厂默认:Drivers/BSP/NETCFG/net_cfg.c 的 NET_CFG_DEF_IP/_GW
- 注释/文档示例、主机与板级测试、台架 py 脚本、两份 sim_config.ini
- 字节序教学点同步换算 hex:0x6401A8C0 → 0x641FA8C0(内存
  C0 A8 1F 64),旧版示例引文与历史 Trap 记录按冻结纪律不动
- UI 黄金基准:网口设置 4 态 + shot_info 共 5 张 .bin 由
  check_content_pages.py --update 重刷(渲染值 IP 变了,属预期变更)
- 保留判定:客户点表引用(IEC104/继电保护规划)、芯片手册摘录、
  日期冻结的变更记录行、已退役台架档案 MDK-ARM/retracted_bench_*

二、单一真源收口(A+B+C+D,用户批准全做)
- 新建 test/host/bench_defaults.py:台架 py 默认值唯一定义点
  (DEVICE_IP/DEVICE_GW/PC_IP/NET_BCAST),22 个脚本改为引用;
  MDK-ARM 两探针与 scripts/board_ui_nav.py 经 sys.path shim 引入
- test/board/net_test_task.h 新增 TEST_PC_PEER_IP,板测三处
  PHASE*_REMOTE_IP 改引该宏(保留 -D 覆盖);Phase1 断言改
  memcmp 数组比较,数组各只出现一次
- simulator/tests/check_content_pages.py 从 net_cfg.c 正则解析
  NET_CFG_DEF_* 推导期望值(解析失败即报错、不静默兜底),
  脚本内台架 IP 字面量清零
- 装饰性注释去具体值(net_cfg.h/net_socket.h/net_socket.c/
  net_socket_sim.c);字节序教学 hex 对刻意保留为唯一写值点
- 刻意不动:主机单测夹具 IP(逐文件钉住的任意值是正确测试
  实践)、文档正文 IP(那是文档内容本身)

顺带:scripts/gen_task_table.py 规范串清掉「,B5」——编号清退提交
72b4b094 改了受管区没重跑生成器,task-table 门禁因此一直红

验证:产品 @build 0/0;CH395F 套件开关构建 0/0 后还原产品态;
6 套主机单测 rc=0;content_pages(含宏推导路径)/菜单 8 态/
推流位比对全 PASS;bom-drift 99 无漂移;doc-links/coding-standard/
cppcheck 绿;py_compile 23 文件 + --help 冒烟(含 shim 绝对路径)
2026-11-02 14:46:42 +08:00

492 lines
36 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 实机测试实施指南(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 <id>`) | **当时 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 <serial-assistant 日志路径>`
——自动列 `>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 <addr> <count>` 的 `count` 单位是**字节**(`-R32 0x1000FF00 16` 得 4 个字);
按字理解会把读取**静默截断**。
- `-W32 <addr> <data>` 的 `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/` 寄存器时序 / 模拟器差距清单的硬件专属项 / 配置变更检测与回调 / 网络重建并发 / 启动段 / 内存预算,
> 一律在本文件找到对应批次。