Merge branch 'v2-on-ui' into task/archsplit-sw-hw
# Conflicts: # docs/架构设计/软件架构与运行时规格.md
This commit is contained in:
+6
-6
@@ -40,8 +40,8 @@
|
||||
|
||||
> **曾经的盲区已关闭为强制路径(2026-10-25,「体积门禁 CI 强制路径」残余销项,`task/A5-size-gate`→564715c)。**
|
||||
> `mem-numbers` 钩子的事实源是 Keil 构建产物 `MDK-ARM/**/*.map`(已 gitignore),CI 刻意不跑
|
||||
> `@build` → 该钩子在远端只会被跳过(现以 `SKIP` 名单诚实显示 Skipped,不再伪装 Passed)。
|
||||
> 补上的强制路径 = **同码凭证**:本地 `@build` 0/0 后 `ram_baseline.py --update` 顺手盖
|
||||
> `/keil-build` → 该钩子在远端只会被跳过(现以 `SKIP` 名单诚实显示 Skipped,不再伪装 Passed)。
|
||||
> 补上的强制路径 = **同码凭证**:本地 `/keil-build` 0/0 后 `ram_baseline.py --update` 顺手盖
|
||||
> `scripts/size_attestation.json`(三道前置:`.map` 可解析 / `.map` 不早于指纹内源码 mtime /
|
||||
> 受管区 ≤92%);CI 无需 Keil,静态门禁作业末尾的「体积门禁」步用 `scripts/check_size_gate.py --ci`
|
||||
> 复核两件事——**工作树指纹 == 凭证指纹**(改了代码从没测过 = 拦)+ **按凭证数字复跑 92% 红线**。
|
||||
@@ -51,7 +51,7 @@
|
||||
> (只复核同码性与红线复算);编译器选项(`.uvprojx`)不在指纹内;凭证由本地真实构建产生,
|
||||
> 链路本身不经 Keil 授权就无法伪造,但"盖戳后换机器环境"的差异只能靠 mtime 前置兜。
|
||||
>
|
||||
> 本地兜底动作不变(每次 `@build` 后):`ram_baseline.py --update`(含盖戳)+
|
||||
> 本地兜底动作不变(每次 `/keil-build` 后):`ram_baseline.py --update`(含盖戳)+
|
||||
> `check_mem_numbers.py --scan`。若 runner 以后装了 Keil,`mem-numbers` 会在有 `.map` 时照常执行。
|
||||
|
||||
---
|
||||
@@ -657,7 +657,7 @@ runner 是一台干净的 Windows,**下面这些必须装**,否则 job 会
|
||||
| **cppcheck** | 🔴 必需 | 同上(`cppcheck --version` 探测不过) | `winget install Cppcheck.Cppcheck` |
|
||||
| **pre-commit** | 🔴 必需 | 同上(`python -m pre_commit --version` 探测) | `pip install pre-commit` 或 `uv tool install pre-commit` |
|
||||
| **MSVC(VS + Windows SDK)** | 🟡 建议 | 缺则 `host-unit-tests` **告警降级跳过**(不硬失败,但 主机侧纯逻辑单测裸奔) | `winget install Microsoft.VisualStudio.2022.BuildTools`,勾"使用 C++ 的桌面开发" |
|
||||
| Keil / ST-LINK | ⛔ 不需要 | CI 刻意不跑 `@build` | — |
|
||||
| Keil / ST-LINK | ⛔ 不需要 | CI 刻意不跑 `/keil-build` | — |
|
||||
| 开发板 / 串口 | ⛔ 不需要 | 板端测试套件/主机侧回归/人工目检不上 CI | — |
|
||||
|
||||
Git 另算:`git --version` 任意版本即可(你已在用)。
|
||||
@@ -1066,7 +1066,7 @@ date +%s # 对比 last_online
|
||||
| `static-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) |
|
||||
| `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §5.3 |
|
||||
| `l05` 日志出现 `::warning::runner 未安装 MSVC` | VS 装在非默认路径或未装 | 见 §4.2(默认路径 / 同步改两处根目录) |
|
||||
| `mem-numbers` 显示 `Skipped` | 正常(CI 无 Keil,无 `.map`) | 无需处理;但注意 **CI 未守体积门禁**(见 §0.0),改代码后本地 `@build` + `check_mem_numbers.py --scan` 兜底 |
|
||||
| `mem-numbers` 显示 `Skipped` | 正常(CI 无 Keil,无 `.map`) | 无需处理;但注意 **CI 未守体积门禁**(见 §0.0),改代码后本地 `/keil-build` + `check_mem_numbers.py --scan` 兜底 |
|
||||
| 主机侧纯逻辑单测显示 `2 runner(s), X/Y ...` 但 X<Y | 有单测用例未通过 | 看该模块的 `::error::unit test failed` 行与其 `ci-artifacts/host-unit-<模块>.log` |
|
||||
| 主机侧纯逻辑单测报 `produced no '=== ... N/M PASSED ===' summary line` | 该模块运行器未输出汇总行(改了 harness 或运行器崩溃) | 核对 `test/host/common/unit_test_harness.py` 的 `summary_marker` 与该模块 `TestBuild` 是否一致 |
|
||||
| `Tee-Object` 步骤报 `TerminatorExpectedAtEndOfString` / 字符串缺少终止符 | `run:` 块混入了非 ASCII(GBK 解码错位) | 见文件头 🔴🔴;`run:` 内必须纯 ASCII |
|
||||
@@ -1211,7 +1211,7 @@ runner 的日志走 **stderr**,于是上面两个日志文件都栽在这两
|
||||
带 `stages: [manual]` 的三根(`cppcheck` / `test-cppcheck` / `test-coverage`)也不在其中,
|
||||
靠 static-gates 的「手动阶段门禁」一步逐根 `--hook-stage manual` 跑(新增 manual 钩子须同步加进
|
||||
该步的 `$hooks` 列表,否则只声明不执行);
|
||||
- **`@build` / 板端测试套件 / 主机侧回归**:永远留在本地,不要往 CI 加(需 Keil 授权与真机);
|
||||
- **`/keil-build` / 板端测试套件 / 主机侧回归**:永远留在本地,不要往 CI 加(需 Keil 授权与真机);
|
||||
- **runner 长时间不可用**时:CI 只是"多一道网",**提交前的本地 `ci_local.py` 仍是 静态门禁的唯一保障**,
|
||||
不能因"已经配了 CI"放宽(见 [docs/任务规划/登记簿.md](../任务规划/登记簿.md) 的条目「CI 远端闭环未验证」)。
|
||||
|
||||
|
||||
+4
-4
@@ -45,7 +45,7 @@ docs/
|
||||
| 2 | [`项目架构总览.md`](架构设计/项目架构总览.md) | 六层分层、启动时序、存储/网络/UI 三栈、目录组织 |
|
||||
| 3 | [`项目启动顺序管理.md`](架构设计/项目启动顺序管理.md) | Event Flags 就绪屏障(取代魔法延时)的任务启动模型 |
|
||||
| 4 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) | 开发机/目标板/现场部署的完整环境核对清单 |
|
||||
| 5 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) → 构建 | 跑通 `@build` / `@test`(见根 `AGENTS.md`) |
|
||||
| 5 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) → 构建 | 跑通 `/keil-build` / `/serial-boot-test`(见根 `AGENTS.md`) |
|
||||
| 6 | [`测试开发标准流程.md`](测试说明/流程与规范/测试开发标准流程.md) | 测试分层 静态门禁~保护判据与门禁决策表 |
|
||||
| 7 | [`嵌入式C语言代码规范_V1.0.md`](代码规范/嵌入式C语言代码规范_V1.0.md) | 命名/格式/注释规范(提交门禁强制) |
|
||||
| 8 | [`项目架构总览.md`](架构设计/项目架构总览.md) → 按需 | 进入具体子系统设计文档 |
|
||||
@@ -125,7 +125,7 @@ docs/
|
||||
| 文档 | 内容 |
|
||||
|---|---|
|
||||
| [`测试开发标准流程.md`](测试说明/流程与规范/测试开发标准流程.md) | 测试开发总流程:规划→板端套件→主机工具→门禁→沉淀;门禁决策表 |
|
||||
| [`自动化测试流程.md`](测试说明/流程与规范/自动化测试流程.md) | `@test` 闭环细节:命令 / 串口标记 / 失败模式 |
|
||||
| [`自动化测试流程.md`](测试说明/流程与规范/自动化测试流程.md) | `/serial-boot-test` 闭环细节:命令 / 串口标记 / 失败模式 |
|
||||
| [`测试文档模板规范.md`](测试说明/流程与规范/测试文档模板规范.md) | 测试文档模板与命名规则(TM / TC / DP) |
|
||||
|
||||
**单元测试/**(主机侧纯逻辑单测说明与覆盖率口径)
|
||||
@@ -150,7 +150,7 @@ docs/
|
||||
| [`GD5F2GQ5UE_Test_Guide.md`](测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md) | GD5F2GQ5UE SPI NAND 存储测试 |
|
||||
| [`SD2506_Test_Guide.md`](测试说明/板级测试/SD2506_Test_Guide.md) | SD2506 RTC 测试 |
|
||||
| [`FTP_Test_Guide.md`](测试说明/板级测试/FTP_Test_Guide.md) | FTP 服务测试 |
|
||||
| [`实机测试实施指南.md`](测试说明/板级测试/实机测试实施指南.md) | **上板(真机)测试执行流程**(按顺序:环境预检 → `@test`+自检判定 → IWDG 注入/长跑 → 堆告警 → GD5F 错误通道 → 网络基线 → 工程卫生;每步含判据/命令/状态);已完成事项证据去向见 §10 |
|
||||
| [`实机测试实施指南.md`](测试说明/板级测试/实机测试实施指南.md) | **上板(真机)测试执行流程**(按顺序:环境预检 → `/serial-boot-test`+自检判定 → IWDG 注入/长跑 → 堆告警 → GD5F 错误通道 → 网络基线 → 工程卫生;每步含判据/命令/状态);已完成事项证据去向见 §10 |
|
||||
|
||||
**专项验证/**(针对一个缺陷或一次复验的台架专项流程;做完即定稿)
|
||||
|
||||
@@ -174,7 +174,7 @@ docs/
|
||||
|
||||
| 目录 | 内容 |
|
||||
|---|---|
|
||||
| [`boot_log_data/README.md`](测试说明/测试数据/boot_log_data/README.md) | `@test` 产出的**开机串口日志快照**存档(跨构建 `[STACK]` 栈 / 堆水位对照,39 个文件) |
|
||||
| [`boot_log_data/README.md`](测试说明/测试数据/boot_log_data/README.md) | `/serial-boot-test` 产出的**开机串口日志快照**存档(跨构建 `[STACK]` 栈 / 堆水位对照,39 个文件) |
|
||||
| [`m2_data/README.md`](测试说明/测试数据/m2_data/README.md) | 链路时延 M2 安静档采集数据(JSON + 串口 `[LAT]` 片段,含逐帧尾批明细与方法学约束) |
|
||||
| `m3_data/` | 链路时延 M3 各档原始 JSON(quiet / stress / pending / 尾批实验;无独立 README,口径见 [`链路延迟测试说明.md`](测试说明/专项验证/链路延迟测试说明.md) 附表) |
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ OK 67325 bytes -> ...\Drivers\BSP\UI\ui_font_cn_12.c
|
||||
## 6. 编译验证
|
||||
|
||||
```bash
|
||||
@build # = keil-build skill(.dsh/skills/keil-build/scripts/keil_build.py),调用 UV4 全量重建
|
||||
/keil-build # = keil-build skill(.dsh/skills/keil-build/scripts/keil_build.py),调用 UV4 全量重建
|
||||
```
|
||||
|
||||
要求:`0 Error(s), 0 Warning(s)`。重新生成/修改字体或 `lv_conf.h` 后务必重建验证。当前已验证通过,`ui_font_cn_12.c` 正常编入。
|
||||
|
||||
@@ -258,7 +258,7 @@ typedef struct {
|
||||
以及「禁写终身次数」「跨开机累计不做」这两条口径,落在 `Drivers/BSP/UI/ui_page_runreport.c` 文件头;口径出处只在
|
||||
`App/util/boot_stats.h` 四数台账与本节各述一次(同一事实只在一处详述,`docs/文档规范.md` 同此要求)。
|
||||
- 代价(本批
|
||||
`@build` 实测,0 错 0 警):
|
||||
`/keil-build` 实测,0 错 0 警):
|
||||
`Program Size` 由本批前
|
||||
Code=235536 RO=26392 RW=1572 ZI=136924 变为 Code=236456 RO=28124 RW=1876 ZI=137084(ROM 合计 +2,652 B)
|
||||
→ **RAM 合计 +464 B**(RW +304 / ZI +160)。逐对象(.map):5 个记录页各 60 B
|
||||
@@ -271,7 +271,7 @@ typedef struct {
|
||||
随帧发布不新增拷贝节拍。水位:
|
||||
`RW_IRAM1` 62.2% / `RW_CCM` 87.9%(阈值 92%),
|
||||
`mem-budget` PASS,
|
||||
台账已由 `scripts/ram_baseline.py --update` / `--write` 回填(docs/内存管理/RAM预算台账.md)。 `@build` 后回填),未触 `mem-budget` 92% 红线。
|
||||
台账已由 `scripts/ram_baseline.py --update` / `--write` 回填(docs/内存管理/RAM预算台账.md)。 `/keil-build` 后回填),未触 `mem-budget` 92% 红线。
|
||||
|
||||
> 另一处落地细化:`ui_data_rt_t` 除 `yc[]`(语义通道序基波有效值)外还带一条 `ph[]` 相量实例槽带(`ph_n` 给实际槽数)。原因是谐波(`Ia` 的二次/五次)无法在"语义通道"下标空间里表达——同一通道的谐波会与基波撞位,而交流量页要显示它们(`docs/UI设计/页面扩充详细方案.md` §3.1.1 第 4/5 行)。两条带的元素同形(数值 + 品质),页面行表按需选一条。
|
||||
|
||||
@@ -606,7 +606,7 @@ typedef struct {
|
||||
|
||||
**结论**:代价 = 约 1.5 KB 静态 + 每 200 ms 一次 memcpy,换来整个 UI 层的数据可及性与一条
|
||||
可测的写路径。区域水位(RW_IRAM1 / RW_CCM / Flash)**不在本文复制**——那是受管快照,
|
||||
由 `scripts/ram_baseline.py` 从每次 `@build` 的 `.map` 生成,第二处写死就是一份会过期的
|
||||
由 `scripts/ram_baseline.py` 从每次 `/keil-build` 的 `.map` 生成,第二处写死就是一份会过期的
|
||||
真源(本仓已有一条门禁在钉这个,口径见 `docs/内存管理/RAM使用分析与优化方案.md` §2.1 与
|
||||
`docs/内存管理/RAM预算台账.md`)。本次新增约 1.5 KB 静态 + 一条 LVGL 池定时器,量级无压力。
|
||||
|
||||
@@ -617,7 +617,7 @@ typedef struct {
|
||||
| 步 | 内容 | 验证 | 依赖 |
|
||||
|---|---|---|---|
|
||||
| **步骤1** | `ui_data.h/.c` + 主机单测(无任何消费者) | `test/host/ui_data` 全绿;`layer-deps` 无新增 | 无 |
|
||||
| **步骤2** | `App/app/ui_data_feed.c` + `ui_task.c` 接线(每轮调 tick) | 串口打印首帧(临时锚点);`@build` 0/0 | 步骤1 |
|
||||
| **步骤2** | `App/app/ui_data_feed.c` + `ui_task.c` 接线(每轮调 tick) | 串口打印首帧(临时锚点);`/keil-build` 0/0 | 步骤1 |
|
||||
| **步骤3** | 交流量 / 直流量回炉(接快照 + 行表加 `src/dot/unit`) | 模拟器固定帧 golden 更新;台架看真实码值变化 | 步骤2 |
|
||||
| **步骤4** | 遥信量 + 定值/控制字/软压板查看(4 页) | 主站改定值 → 页面 200 ms 内变 | 步骤3 |
|
||||
| **步骤5** | 写路径(请求/状态)+ 整定 4 页 | 就地改定值 → 保存 → 掉电重启 → 值保持 | 步骤4。**已落**(道F task-1 落本层队列与状态机、task-2 落整定 4 页与模拟器侧,2026-10-26);「掉电重启值保持」只能上板收口,见 §12 判据 6 |
|
||||
|
||||
@@ -21,7 +21,7 @@ flowchart TD
|
||||
P2 --> C2{"输出图 ↔ 参考图<br/>同构?"}
|
||||
C2 -- "否: 按根因修正(修代码 / 回 P0 修规格)→ 再出图" --> P2
|
||||
C2 -- "是(Gate: 导航符合按键语义 + 自动化回归 PASS)" --> P3["P3 MCU 验证<br/>→ 日志 + 上机目检结论"]
|
||||
P3 -- "Gate: @build 0 错 0 警 + @test PASS + 上机目检通过" --> P4(["P4 归档<br/>→ 受影响章节现状化/台账 + 随代码提交(代码+字体+字集+规格+文档)"])
|
||||
P3 -- "Gate: /keil-build 0 错 0 警 + /serial-boot-test PASS + 上机目检通过" --> P4(["P4 归档<br/>→ 受影响章节现状化/台账 + 随代码提交(代码+字体+字集+规格+文档)"])
|
||||
```
|
||||
|
||||
> 小改动(文案/间距/纯交互/字体重生成)走「小改动快速通道」一节(见文末第十节),无需全流程。
|
||||
@@ -33,7 +33,7 @@ flowchart TD
|
||||
| P0 需求录入 + 设计规格 | 提供图片(每批 1~3 屏);说明各屏作用与菜单位置;回答问题;确认布局参数表 | 图片分析、界面登记、字体度量核对、字符集核对、布局计算(验证等式)、规格制作 | 台账 + `_说明.md`(参数表/字符集/问题/映射/翻译要点) |
|
||||
| P1 规格→代码 | (可选)抽查代码 | 逐参数翻译、字体重生成、内存预算、双端注册 | 代码 + 字体 |
|
||||
| P2 模拟器 | **视觉确认**(窗口模式/出图),指出差异 | 构建、离线出图、出图与原图逐项比对、依规格改码 | 比对结论 |
|
||||
| P3 MCU | **真机目检**(DTU-RemoteLCD) | @build / @test 执行、日志解析 | 日志 + 结论 |
|
||||
| P3 MCU | **真机目检**(DTU-RemoteLCD) | /keil-build / /serial-boot-test 执行、日志解析 | 日志 + 结论 |
|
||||
| P4 归档 | — | 把受影响章节改成改动后的现状 + 更新台账 | 文档 |
|
||||
|
||||
> 用户视觉确认点由旧"浏览器打开 HTML 样机"改为 **P2 模拟器窗口/出图**:P0 确认**参数表**(数值层面),P2 确认**渲染结果**(视觉层面);渲染结果与意图不符时按 §5.1 根因修正(代码错 → 修代码;参数表错 → 回 P0 修规格再修代码)。
|
||||
@@ -248,8 +248,8 @@ flowchart TD
|
||||
|
||||
## 六、P3:MCU 验证
|
||||
|
||||
1. `@build` → **0 错误 0 警告**(AGENTS.md 强制;有警告看 `MDK-ARM/build_log.txt`)
|
||||
2. `@test`(一键 `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):
|
||||
1. `/keil-build` → **0 错误 0 警告**(AGENTS.md 强制;有警告看 `MDK-ARM/build_log.txt`)
|
||||
2. `/serial-boot-test`(一键 `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):
|
||||
- 采集窗口**先于烧录武装**(脚本 `--flash-cmd` 已保证顺序)
|
||||
- Required:`UI port init: OK;UI menu init: OK` + 本界面定制标记(按屏增补,如页头标题字样)
|
||||
- Forbidden:`FAIL;ERROR;HardFault`
|
||||
@@ -257,8 +257,8 @@ flowchart TD
|
||||
4. 串口日志:池水位、任务栈高水位(`uxTaskGetStackHighWaterMark`,uiTask 预算 16384 words)
|
||||
|
||||
### Gate(进入 P4)
|
||||
- [ ] @build 0错0警
|
||||
- [ ] @test PASS
|
||||
- [ ] /keil-build 0错0警
|
||||
- [ ] /serial-boot-test PASS
|
||||
- [ ] 真机目检通过(用户确认)
|
||||
|
||||
---
|
||||
@@ -278,7 +278,7 @@ flowchart TD
|
||||
2. **按界面族批量**:
|
||||
- 列表页(复用现有级联框自绘架构)→ 数据显示页(数值行框架)→ 参数编辑页(编辑/确认交互)→ 曲线/状态页
|
||||
- 每族先做**族内第一屏**产出可复用的页面框架,再批量铺同族界面
|
||||
3. **每批收尾**:字体统一重生成(各屏字符集**并集**,一次生成)→ 全量回归(已完成界面重跑 @test)
|
||||
3. **每批收尾**:字体统一重生成(各屏字符集**并集**,一次生成)→ 全量回归(已完成界面重跑 /serial-boot-test)
|
||||
4. 字符集只增不减;字体文件每次**全量重生成**,禁止手工拼合
|
||||
|
||||
### 8.1 规格投入分流(按屏型)
|
||||
@@ -384,8 +384,8 @@ P2 [ ] 模拟器构建 0 错误
|
||||
[ ] shot.png 与参考图/规格参数表逐项比对(几何/文本/状态);不符按根因(修码/修规格)再出图(§5.1)
|
||||
[ ] 导航行为验证(窗口模式按键)
|
||||
[ ] 自动化回归:check_menu_rows.py + check_content_pages.py PASS
|
||||
P3 [ ] @build 0 错 0 警
|
||||
[ ] @test PASS(Required:____ / Forbidden:FAIL;ERROR;HardFault)
|
||||
P3 [ ] /keil-build 0 错 0 警
|
||||
[ ] /serial-boot-test PASS(Required:____ / Forbidden:FAIL;ERROR;HardFault)
|
||||
[ ] 真机目检通过
|
||||
P4 [ ] UI_设计架构.md 受影响章节已改成现状(不记流水)
|
||||
[ ] 台账状态 → 已完成(代码+字体+字符集+规格+文档 一并提交)
|
||||
|
||||
@@ -48,7 +48,7 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓
|
||||
3. 文本一律用静态字符串(`lv_label_set_text_static`),不 strdup
|
||||
4. UI 相关文件为 **UTF-8 带 BOM**(`Drivers/BSP/UI/*`、`ui_task.c`、`Lib/lv_conf.h`),其余源码无 BOM;禁止 PowerShell 整文件重写(编码铁律,见 AGENTS.md)
|
||||
5. 模拟器与 MCU **共用** UI 源码(`ui_menu*.c` / `ui_font_cn_12.c`),禁止在 `simulator/` 下复制副本;新增文件须同时注册进 Keil `.uvprojx` 与模拟器 CMakeLists(双端注册)
|
||||
6. 验证顺序:模拟器预览 → `@build`(0 错 0 警)→ `@test`(Required 加 `UI port init: OK;UI menu init: OK`)
|
||||
6. 验证顺序:模拟器预览 → `/keil-build`(0 错 0 警)→ `/serial-boot-test`(Required 加 `UI port init: OK;UI menu init: OK`)
|
||||
7. **`LV_DRAW_SW_COMPLEX` 必须为 1**(两端均显式声明):主界面顶部"开口浅 V"装饰含斜线,`lv_line` 的斜段仅在复杂渲染器下绘制(`draw_line_skew`),关闭则只画水平线、两腿消失。且 `LV_DRAW_SW_CIRCLE_CACHE_SIZE` 须非零(默认 4),否则零长数组编译报错(C2466)
|
||||
8. **I1 绘制缓冲必须含 palette 头(2026-09-21 实证,别再踩)**:LVGL 9.3 索引色格式的 draw buffer 布局是 `[palette 8B][pixel data stride×h]`——
|
||||
`lv_draw_buf_create()` 按 `stride×h + LV_COLOR_INDEXED_PALETTE_SIZE(cf)×4` 分配,`lv_draw.c:497 → lv_draw_buf_goto_xy()` 取绘制指针时先 `+= palette`,`lv_refr.c get_max_row()` 也从 `data_size` 里扣掉 palette。
|
||||
|
||||
+3
-3
@@ -114,7 +114,7 @@ sequenceDiagram
|
||||
L-->>U: 0(成功)
|
||||
```
|
||||
|
||||
- 日志标记:`UI menu init: OK`(uiTask 打印)——`@test` 回归可作 Required 标记。
|
||||
- 日志标记:`UI menu init: OK`(uiTask 打印)——`/serial-boot-test` 回归可作 Required 标记。
|
||||
- LVGL 池实测(模拟器 16 KB 池):根框建完 `used=5368 B`(旧行对象实现为 13312 B,自绘 + 容器收敛省 8.3 KB);最深四级级联 `used=6040 B`(增量 1256 B = 3 个子框体),远小于池余量。
|
||||
|
||||
### 1.6 层间不变量(每层"绝不做")
|
||||
@@ -787,8 +787,8 @@ ESC/LEFT : d>0 → menu_back() (根框 d=0 不响
|
||||
|---|---|
|
||||
| 模拟器无头 | `lvgl_sim.exe --screenshot shot.bin` / `--keys <seq> shot.bin`;I1 原始像素(160×160 = 3200 B)转 PNG(PIL)目检 |
|
||||
| 对照参考图 | 7 状态(根页 + 6 个一级子框)逐一对照参考图 01~07;反色行/框几何用像素统计核验(偏差见 §8.2 注 5) |
|
||||
| 编译 | `@build`(ARMCC,0 错 0 警,强制) |
|
||||
| 真机 | `@test`(Required 含 `UI port init: OK;UI menu init: OK`;屏幕内容仍需人工目检) |
|
||||
| 编译 | `/keil-build`(ARMCC,0 错 0 警,强制) |
|
||||
| 真机 | `/serial-boot-test`(Required 含 `UI port init: OK;UI menu init: OK`;屏幕内容仍需人工目检) |
|
||||
|
||||
### 8.2 陷阱与保真度说明
|
||||
|
||||
|
||||
@@ -571,7 +571,7 @@ void ui_w_soe_refresh(ui_w_soe_t *inst, uint16_t row, char *name, char *valu
|
||||
| 步 | 内容 | 验证 |
|
||||
|---|---|---|
|
||||
| 0-1 | `ui_data` + 主机单测 | `test/host/ui_data` 全绿 |
|
||||
| 0-2 | `ui_data_feed` + `ui_task` 接线 | 首帧串口锚点;`@build` 0/0 |
|
||||
| 0-2 | `ui_data_feed` + `ui_task` 接线 | 首帧串口锚点;`/keil-build` 0/0 |
|
||||
| 0-3 | `ui_w_editor`(数值编辑器) | 模拟器可改值 |
|
||||
| 0-4 | `ui_page_action`(确认操作页) | 模拟器确认/取消 |
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</style>
|
||||
</defs>
|
||||
<text x="440.0" y="22" text-anchor="middle" class="title">FreeRTOS 任务栈预算(9 任务;栈全部从 56KB FreeRTOS 堆分配,堆位于 CCM)</text>
|
||||
<text x="440.0" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-10-22 @build + 同日真机 [STACK] 水位(data/stack_report_20261022.md);最坏 = max(静态最坏链, 实测峰值);判定线 = 余量 ≥ max(1024B ISR, 配置×30%)</text>
|
||||
<text x="440.0" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-10-22 构建 + 同日真机 [STACK] 水位(data/stack_report_20261022.md);最坏 = max(静态最坏链, 实测峰值);判定线 = 余量 ≥ max(1024B ISR, 配置×30%)</text>
|
||||
<text x="190" y="54" class="legend-text" fill="#555">浅色条 = 分配栈 深色条 = 最坏占用(max(静态最坏, 实测峰值)) 下方文字 = 实测余量/判定</text>
|
||||
<text x="186" y="72" text-anchor="end" dominant-baseline="central" class="label" fill="#333">defaultTask</text>
|
||||
<rect class="bar-seg" x="190" y="64" width="203.1" height="16" fill="#BBDEFB" rx="3"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -7,7 +7,7 @@
|
||||
.bar-seg { stroke:#fff; stroke-width:0.5; }
|
||||
</style>
|
||||
</defs>
|
||||
<text x="410.0" y="24" text-anchor="middle" class="title">Flash (1MB) 使用布局 — 2026-10-23 @build</text>
|
||||
<text x="410.0" y="24" text-anchor="middle" class="title">Flash (1MB) 使用布局 — 2026-10-23 构建</text>
|
||||
<rect class="bar-seg" x="60" y="50" width="150.8" height="40" fill="#2196F3" rx="2"/>
|
||||
<text x="135.4" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">代码段 (Code) 208.7KB</text>
|
||||
<rect class="bar-seg" x="210.7952117919922" y="50" width="13.3" height="40" fill="#4CAF50" rx="2"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -7,7 +7,7 @@
|
||||
.bar-seg { stroke:#fff; stroke-width:0.5; }
|
||||
</style>
|
||||
</defs>
|
||||
<text x="410.0" y="24" text-anchor="middle" class="title">SRAM (192KB) 使用布局 — 2026-10-22 @build</text>
|
||||
<text x="410.0" y="24" text-anchor="middle" class="title">SRAM (192KB) 使用布局 — 2026-10-22 构建</text>
|
||||
<text x="60" y="42" class="label" fill="#555">主 SRAM RW_IRAM1 @0x20000000(128KB,DMA 可达)</text>
|
||||
<rect class="bar-seg" x="60" y="50" width="95.3" height="44" fill="#4CAF50" rx="2"/>
|
||||
<text x="107.6" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">LVGL 池 16KB</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
+13
-13
@@ -54,7 +54,7 @@
|
||||
| **IWDG** | Independent Watchdog,独立看门狗(LSI 时钟源) |
|
||||
| **就绪屏障** | 用 CMSIS-RTOS2 Event Flags 表达"子系统就绪"的启动顺序机制 |
|
||||
| **双端** | MCU(Keil/ARMCC)与 PC 模拟器(CMake/MSVC)共用同一份 UI/配置源码 |
|
||||
| **@build / @test** | 仓库约定的构建 / 自动化测试命令别名 |
|
||||
| **/keil-build / /serial-boot-test** | 仓库约定的构建 / 自动化测试命令别名 |
|
||||
| **语义取数与有效性 / 标定折算 / 全周傅氏相量 / 测频与遥测快照 / 保护判据** | 采集链路分层:语义取数与有效性(语义取数与有效性)→ 标定折算(标定折算)→ 全周傅氏相量(全周傅氏相量)→ 测频与遥测快照(测频与遥测快照)→ 保护判据(保护判据) |
|
||||
| **全周傅氏** | 单频 DFT 取基波相量,窗长 = 整周波(20 点);幅值即基波有效值 |
|
||||
| **相量** | `{fRe, fIm, fAm, fAmSQ}`,实部 / 虚部 / 幅值 / 有效值 |
|
||||
@@ -170,7 +170,7 @@ flowchart LR
|
||||
| G2 | **可维护**:分层解耦、单一事实源、双端复用、门禁强制 | 六层架构、就绪屏障、生成器(菜单/字体/任务表)、pre-commit 钩子族 |
|
||||
| G3 | **可移植**:UI/配置源码 MCU 与 PC 模拟器共用 | 平台后端抽象(`*_mcu.c` / `*_sim.c`),核心零平台依赖 |
|
||||
| G4 | **可扩展**:新子系统/新配置段/新页面按模板接入 | 事件位扩展、段注册三步、页面插件描述符、`App/protocol/` 预留 |
|
||||
| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `@build` / `@test` / 静态门禁 / 主机侧纯逻辑单测 / 台架验证 |
|
||||
| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `/keil-build` / `/serial-boot-test` / 静态门禁 / 主机侧纯逻辑单测 / 台架验证 |
|
||||
|
||||
**设计原则**(贯穿全项目):
|
||||
|
||||
@@ -1296,11 +1296,11 @@ mac=00:00:00:00:00:00
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 端口 | UART5(RS232#1:PC12 TX / PD2 RX,经 U9 隔离 → U13 → CN1 端子),115200 8N1;台架端子接法见 `docs/子系统设计/屏板链路设计说明.md` §3.2 |
|
||||
| 用途 | printf 日志 + `@test` 采集通道 |
|
||||
| 用途 | printf 日志 + `/serial-boot-test` 采集通道 |
|
||||
| 配置源 | `.serialtest/config.json`(单一配置源) |
|
||||
| 工具 | 仓库内置 Python 串口助手(`.dsh/skills/serial-assistant/`,常驻 daemon + CLI) |
|
||||
|
||||
**启动标记(`@test` 判据)**:
|
||||
**启动标记(`/serial-boot-test` 判据)**:
|
||||
- Required:`App init OK` / `Network Init` / `net init: OK` / `PHY: 100M FULL`
|
||||
- Forbidden:`FAIL` / `ERROR` / `HardFault` / `[ERR]`
|
||||
|
||||
@@ -1409,7 +1409,7 @@ mac=00:00:00:00:00:00
|
||||
| 网络重建生效 | 2~5 s(含等链路 ≤3 s) | 配置设计 |
|
||||
| UI 刷新 | 10 ms 循环 | `ui_task_func` |
|
||||
| IWDG 停喂到复位 | ≤2.5 s | IWDG 设计 |
|
||||
| **采样节拍** | **1000 Hz**(每 10 s 帧号 +10000,零丢帧) | 台架 `@test` |
|
||||
| **采样节拍** | **1000 Hz**(每 10 s 帧号 +10000,零丢帧) | 台架 `/serial-boot-test` |
|
||||
| **单拍采集消费** | ≤10 µs(DMA 搬运后实测 8 µs) | `adc_task` 实测 |
|
||||
| **相量刷新** | 1 ms(每拍整窗重算 5 实例) | `phasor_run` |
|
||||
| **遥测快照周期** | 200 ms(经过时间分频) | `meas_tick` |
|
||||
@@ -1461,14 +1461,14 @@ mac=00:00:00:00:00:00
|
||||
| 静态门禁 | pre-commit 钩子族:格式 / 编码 / 静态分析 / 文档链接 / 体积 / 生成物新鲜度 / 编号 | 本地 / CI |
|
||||
| 主机侧纯逻辑单测 | MSVC 离线编译真实源码 | 本地 / CI |
|
||||
| 板端测试套件 | 板端套件(testTask 串口 `[PASS]/[FAIL]` 汇总) | 本地(需真机 + 串口) |
|
||||
| 主机侧回归 | 模拟器像素 / 池水位回归 + 真机 `@test` 闭环(含探针) | 本地(模拟器免硬件;`@test` 需真机 + ST-LINK) |
|
||||
| 主机侧回归 | 模拟器像素 / 池水位回归 + 真机 `/serial-boot-test` 闭环(含探针) | 本地(模拟器免硬件;`/serial-boot-test` 需真机 + ST-LINK) |
|
||||
|
||||
**构建 / 测试命令**:
|
||||
|
||||
```powershell
|
||||
# 构建(@build):0 错 0 警
|
||||
# 构建(/keil-build):0 错 0 警
|
||||
& '.venv\Scripts\python.exe' .dsh/skills/keil-build/scripts/keil_build.py # 退出码 0=PASS / 1=构建失败 / 2=环境错误
|
||||
# 自动化测试(@test):串口采集 → 烧录 → 日志校验
|
||||
# 自动化测试(/serial-boot-test):串口采集 → 烧录 → 日志校验
|
||||
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py # 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法错误
|
||||
# 全库门禁
|
||||
pre-commit run --all-files
|
||||
@@ -1499,7 +1499,7 @@ pre-commit run --all-files
|
||||
| 构建 | Keil MDK-ARM v5(ARMCC) | CMake + SDL2 + MSVC |
|
||||
| 共用源码 | `Drivers/BSP/UI/*`、`Drivers/BSP/NETCFG/net_cfg.c`、`App/cfg/cfg_file.c` | 同左 |
|
||||
| 平台后端 | `*_mcu.c`(CH395F/FatFS) | `*_sim.c`(Winsock/宿主文件) |
|
||||
| 验证 | `@build` 0/0 → `@test` 串口闭环 | `check_menu_rows.py` + `check_content_pages.py`(像素黄金参考) |
|
||||
| 验证 | `/keil-build` 0/0 → `/serial-boot-test` 串口闭环 | `check_menu_rows.py` + `check_content_pages.py`(像素黄金参考) |
|
||||
|
||||
> **新源文件必须双端注册**(Keil `.uvprojx` + `simulator/CMakeLists.txt`),否则不参与编译。
|
||||
|
||||
@@ -1718,7 +1718,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
|
||||
| 项 | 判据 |
|
||||
|---|---|
|
||||
| 构建 | `@build` → `BUILD_OK: 0 errors, 0 warnings`(退出码 0) |
|
||||
| 构建 | `/keil-build` → `BUILD_OK: 0 errors, 0 warnings`(退出码 0) |
|
||||
| 静态门禁 | `pre-commit run --all-files` 全部通过(钩子族见 §7.4) |
|
||||
| 主机侧单测 | **全部套件全绿**(看门狗、时钟、配置、相量、采集层、标定、遥测、测频、定值、元件、运行参数、104、SOE 等;套件与断言数随代码演进,以 `.pre-commit-config.yaml` 与 `test/host/` 为准) |
|
||||
| 栈门禁 | `scripts/stack_report.py` 9/9 PASS(余量 ≥ max(1024 B, 30%)) |
|
||||
@@ -1726,7 +1726,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
| 双端构建 | Keil 与 CMake 均 0 错 0 警 |
|
||||
| UI 回归 | 菜单行 + 内容页像素黄金 + 导航自检全 PASS |
|
||||
|
||||
### 10.2 上电自检(`@test`)
|
||||
### 10.2 上电自检(`/serial-boot-test`)
|
||||
|
||||
| 层 | 判据 |
|
||||
|---|---|
|
||||
@@ -1737,13 +1737,13 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
| Forbidden 标记 | 无 `FAIL` / `ERROR` / `HardFault` / `[ERR]`(四个 Fault 的崩溃日志都以 `[ERR]` 开头,出现即不通过) |
|
||||
| 自检记录 | `selftest: no fault record` |
|
||||
| 固件身份 | `fw build` 身份戳匹配 + 烧录后回读 `No difference found` |
|
||||
| 退出码 | `@test` → 0(PASS) |
|
||||
| 退出码 | `/serial-boot-test` → 0(PASS) |
|
||||
|
||||
### 10.3 现场验收清单
|
||||
|
||||
| # | 项目 | 方法 | 通过标准 |
|
||||
|---|---|---|---|
|
||||
| 1 | 上电自启动 | 断电上电 + 串口采集 | `@test` PASS,无 Forbidden 标记 |
|
||||
| 1 | 上电自启动 | 断电上电 + 串口采集 | `/serial-boot-test` PASS,无 Forbidden 标记 |
|
||||
| 2 | 网络连通 | PC ping 设备 IP | 通(PHY 100M FULL) |
|
||||
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
|
||||
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
|
||||
|
||||
@@ -793,7 +793,7 @@ tTimer_NoUI 无压无流判据(CN_
|
||||
> **一条物理前置(已完成 2026-10-29)+ 一条实现纪律**:
|
||||
> ① **台架串口线已挪位**——日志口迁到 RS-232 侧后,台架那根线改接 CN1 的 RS232#1 端子
|
||||
> (`CN1.10` 接适配器 TXD、`CN1.12` 接适配器 RXD、`CN1.7` 或 `CN1.8` 接 GND),台架日志口
|
||||
> 因此由 `COM6` 变为 **`COM3`**,`.serialtest/config.json` 的 `portName` 已同步,`@test` 与
|
||||
> 因此由 `COM6` 变为 **`COM3`**,`.serialtest/config.json` 的 `portName` 已同步,`/serial-boot-test` 与
|
||||
> `serial-assistant` 不必再手工指端口。这些端子是 **RS-232 电平**,TTL 的 CH340 直连不了,
|
||||
> 用的是 USB 转 RS-232 适配器;接法与端子出处见 `docs/子系统设计/屏板链路设计说明.md` §3.2。
|
||||
> **这条要在第 16 条动代码之前完成。**
|
||||
|
||||
@@ -145,7 +145,7 @@ git commit
|
||||
| 一把梭(上面多数静态项 + 主机单测) | `& '.venv\Scripts\python.exe' scripts/ci_local.py` | 非 0 就别抢跑(但它**不含** `stack_report` / `ram_baseline`,数字与安全边界要另跑) |
|
||||
|
||||
> ⚠ **这张表为什么必须存在**:`stack_report` 与 `ram_baseline --gate` 这类**根本不在钩子清单里**(它们要 Keil 构建产物,
|
||||
> 只能靠人在 `@build` 后跑),所以「人漏跑一次」直接等于「这道检查这一次不存在」。想机械化,先要解决「磁盘上的
|
||||
> 只能靠人在 `/keil-build` 后跑),所以「人漏跑一次」直接等于「这道检查这一次不存在」。想机械化,先要解决「磁盘上的
|
||||
> `.htm` / `.map` 属于最后一次构建的人」这个归因问题——与 §2.2「stash 覆盖面」那条同源,不是加一根钩子就完事
|
||||
> (`mem-budget` 今天红在「台账与 `.map` 不同码」正是同一个形状)。
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ flowchart TD
|
||||
|---|---|
|
||||
| 跑 `scripts/ram_report.py` 与 `scripts/stack_report.py` 前后各一次,回填 `scripts/ram_baseline.py --update` | 录波环与落盘缓冲约 5.9 KB 静态增量,**大于 4 KB 触发 R6 规则**(`docs/内存管理/内存内容分配策略.md` §6 台账与 R6);CCM 只剩七千余字节 → 落位必须进 SRAM1(`docs/子系统设计/故障录波设计说明.md` §7) |
|
||||
| 新 .c 双端注册 | `new-source-file`:`MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 都是显式文件列表 |
|
||||
| `@build` 0 错误 0 警告 | 任何代码改动后的固定动作 |
|
||||
| `/keil-build` 0 错误 0 警告 | 任何代码改动后的固定动作 |
|
||||
| 主机侧单测新增三套并挂总闸 | 环、段格式、触发状态机都是纯逻辑,能被 `test-coverage` 真跑;别把该测的部分留给台架 |
|
||||
| 菜单与字库生成物重跑 | 新增叶子改叶数 → 同步 `docs/UI设计/页面补齐规划.md` 与 `docs/任务规划/UI补齐.md` 的口径闭合,重跑 `tools/menu_gen/gen_menu.py` 与中文字库链,过 `generated-fresh` 与 `font-charset` |
|
||||
| 台账回填与文档现状化 | `docs/任务规划/README.md` 登记簿销项时同步清理引用;`docs/子系统设计/故障录波设计说明.md` §11 的三处口径要回写 `docs/任务规划/数据可信.md` |
|
||||
|
||||
+4
-4
@@ -240,7 +240,7 @@ M4 的六条退出判据(逐条对齐现状,别整格打勾):
|
||||
`adc_cal_prot_blocked()`,`App/task/protect_task.c:245` 消费并打 INFO + 记 SOE)。
|
||||
|
||||
- 与"自检失败 / 采样故障闭锁"同构(`PROT_HF_LOCK_MASK` 那一套),**不新增机制**。
|
||||
- 现有台架 `@test` 的期望会因此变化——**这是有意的**,任务卡里同步更新判据。
|
||||
- 现有台架 `/serial-boot-test` 的期望会因此变化——**这是有意的**,任务卡里同步更新判据。
|
||||
- 台架调试要旁路只能走编译期开关 `DTU_BENCH`,不进 cfg、不进 104。
|
||||
⚠ **`DTU_BENCH` 只旁路这一处**:它**不**旁路 §2.3 #1 的测频幅值门,也不产生任何读数出口。
|
||||
|
||||
@@ -320,7 +320,7 @@ typedef struct {
|
||||
| **波形轮** | 读数是否只跟基波走 | 同幅值改方波、三角 | 同上 | 期望值 = **基波分量有效值**(同峰值下的基波 / 峰值比:方波 ≈ **0.90**,闭式 4/π ÷ √2 = 0.900、20 点整周期 DFT 实算 0.904;三角 ≈ **0.573**,闭式 8/π² ÷ √2 = 0.573、实算 0.578),**不是**真有效值(方波 = 1.000、三角 = 0.583 峰值);偏差 ≤2 %。算术与逐波形对照表见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的波形卡。顺带把"这台仪表读的是基波"这条口径钉进文档——用户拿真有效值表去对会以为差了 10 % |
|
||||
| **相位轮** | 相位 | UA 与 UN 两路**同片**(都走 TPAFE)注同幅、同相 | **定量档**:录波段文件 `test/host/wave_read.py fft`(浮点重算基波相位;段内表序 `UA` = 0、`UN` = 3);**定性档**:`phasor(hb)` 各行 `Re=` / `Im=` | **定量档**:同段内两路相位差 ≤2°,把发生器相位改 90° 后差值跟到 ≈90°。**定性档只判"跟随"**(差值落进 80°~100°)——`phasor` 出口是 ×0.1 定点,1.000 Vrms 时每格 0.1 V,角度量化最坏 ≈ atan(0.1 / 0.707) ≈ 8°,判不动 2°(§4.6.3)。⚠ 段与发生器**无绝对相位锁定**(触发瞬间钉在段内点 159),只能比**同段内两路**的相对差,跨段比相位或拿发生器相位当绝对基准都不成立。**只许比同片内**——跨转换器无同刻性(§9),STM32 那五路还有 1 拍乒乓滞后 |
|
||||
| **测频轮** | 测频 | 同幅值轮的 1 V/50 Hz | `freq(hb):` 与 104 `0x401D` | **预期就是"不出数"**(幅值门 50 V 在引脚域恒不过,§2.3 #1)→ 这条的判据是"如实报不可测"(`freq_valid=0`、UI `Hz` 显示 `--`、104 上送 IV),**不是**报出一个错误的 50.00。想真验 45~55 Hz 精度(PC 侧判据 0.02 Hz)须临时把 `FREQ_AMP_MIN` 降到引脚域量级(如 0.5 V)单跑一轮 → §7 #8 + §8 风险行 |
|
||||
| **削顶轮** | 削顶与品质路由(**负向用例**) | 给 UB(R52 / `AIN2`)注 **DC −1.00 V 并保持 ≥5 s**;或 1.000 Vrms 而频率 **2.0 Hz** 的正弦——两档都能造出"连续贴同一侧轨带"。⚠ **注 50 Hz 同幅正弦造不出锁存,且这是预期而非故障**:1 kHz 下一个周波只 20 点、正半周必落中带,而 `adc_health_rail_watch()` 任何一拍回中带就把计数清零(`App/algo/adc_health.c`),连续贴底最长 ≈ 209/360 × 20 ≈ 11 拍 < 20。TPAFE 侧在 ±2 V 判据上限内造不出这条(轨带 ±32112 码 ≈ ±4.9 V),只有 STM32 那五路能做 | `cal:` 行的 `rail=` / `edge=`;`rail: edge=…` 的 `[ERR]` 行;104 `0x4012`;段文件档另加 `wave_read.py shape` 的三把尺子(`--strict` / 再加 `--clip-superset` / 不带),逐条配法见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的削顶卡 | 负半周钉下轨 → **连续 20 拍同侧后该路贴轨位锁存**(`ADC_RAIL_N_RUN = 20`,`App/algo/adc_health.h:174`)、`edge` 起沿、104 上该路 IV 且值清零。锁存另有**释放沿前提**:该通道此前须出现过中带样本 → **先上电见基线摆动、再开发生器输出**,开机就注会测出假不通过。撤掉注入后 `rail` 仍非 0 是 sticky 语义(只有 `adc_health_rail_init()` / 复位清)——**复位后仍不清才是 bug**。⚠ 这条会**故意造出 `[ERR]`**,而 `@test` 的 forbidden 标记含 `[ERR]` → 人工判读、排在最后单独一轮(§8 第一行) |
|
||||
| **削顶轮** | 削顶与品质路由(**负向用例**) | 给 UB(R52 / `AIN2`)注 **DC −1.00 V 并保持 ≥5 s**;或 1.000 Vrms 而频率 **2.0 Hz** 的正弦——两档都能造出"连续贴同一侧轨带"。⚠ **注 50 Hz 同幅正弦造不出锁存,且这是预期而非故障**:1 kHz 下一个周波只 20 点、正半周必落中带,而 `adc_health_rail_watch()` 任何一拍回中带就把计数清零(`App/algo/adc_health.c`),连续贴底最长 ≈ 209/360 × 20 ≈ 11 拍 < 20。TPAFE 侧在 ±2 V 判据上限内造不出这条(轨带 ±32112 码 ≈ ±4.9 V),只有 STM32 那五路能做 | `cal:` 行的 `rail=` / `edge=`;`rail: edge=…` 的 `[ERR]` 行;104 `0x4012`;段文件档另加 `wave_read.py shape` 的三把尺子(`--strict` / 再加 `--clip-superset` / 不带),逐条配法见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的削顶卡 | 负半周钉下轨 → **连续 20 拍同侧后该路贴轨位锁存**(`ADC_RAIL_N_RUN = 20`,`App/algo/adc_health.h:174`)、`edge` 起沿、104 上该路 IV 且值清零。锁存另有**释放沿前提**:该通道此前须出现过中带样本 → **先上电见基线摆动、再开发生器输出**,开机就注会测出假不通过。撤掉注入后 `rail` 仍非 0 是 sticky 语义(只有 `adc_health_rail_init()` / 复位清)——**复位后仍不清才是 bug**。⚠ 这条会**故意造出 `[ERR]`**,而 `/serial-boot-test` 的 forbidden 标记含 `[ERR]` → 人工判读、排在最后单独一轮(§8 第一行) |
|
||||
| **谐波轮** | 谐波轨 | 基波 + 20 % 二次 + 20 % 五次(IA 上注)。⚠ 峰值预算:1.000 Vrms 基波 + 两个 20 % 分量的最坏对齐 ≈ 1.98 V 已顶 ±2 V 红线 → 基波用 **0.800 Vrms**、二次与五次各 0.160 Vrms | **定量档**:段文件 `test/host/wave_read.py fft --ch 4 --all`(`IA` = 段内表序 4,直接给基波与 2 / 5 次谐波 RMS,单位 mV);**旁证**:UI 交流量页 `Ia2` / `Ia5`(1 mV)与 `phasor(hb) C04-IA-F2` / `C05-IA-F5`(×0.1 定点,只证这根轨不是恒 0) | 谐波轨读数 = 注入分量(**±2 %,走定量档**;周期行档判不动:0.160 Vrms 在 ×0.1 出口只有 1~2 格)。⚠ 这两根轨**目前没有任何下游消费者**(`prot_elm.c` 无引用、`meas` 只算 `harm=1`),所以谐波轮只证"算得对",**不证"有人用"**——出口归 §9 的谐波轨立项 |
|
||||
|
||||
**先做幅值 / 波形 / 削顶三轮**(纯读数、无跨通道约定),相位 / 谐波次之,测频轮视 §7 #8 的裁定。每条的两档跑法(周期行标量档 /
|
||||
@@ -433,9 +433,9 @@ flowchart TD
|
||||
|
||||
| 风险 | 触发条件 | 缓解 |
|
||||
|---|---|---|
|
||||
| **削顶负向用例被当成固件 bug** | 做削顶轮时 STM32 那五路必然钉下轨 → `rail: edge=…` 是 `[ERR]` 行,而 `@test` 的 forbidden 标记含 `[ERR]` → 整轮被判 FAIL | 判读规则写进流程文档:"做削顶轮的这一轮 `[ERR] rail:` 属预期,只人工判读、不计入 `@test` 结论",或把这五路排在最后单独一轮并事先声明。同时别忘 `cl=0xE6` 是**来源派生**(§2.6),不是本轮新削出来的 |
|
||||
| **削顶负向用例被当成固件 bug** | 做削顶轮时 STM32 那五路必然钉下轨 → `rail: edge=…` 是 `[ERR]` 行,而 `/serial-boot-test` 的 forbidden 标记含 `[ERR]` → 整轮被判 FAIL | 判读规则写进流程文档:"做削顶轮的这一轮 `[ERR] rail:` 属预期,只人工判读、不计入 `/serial-boot-test` 结论",或把这五路排在最后单独一轮并事先声明。同时别忘 `cl=0xE6` 是**来源派生**(§2.6),不是本轮新削出来的 |
|
||||
| **拿发生器的精度当标准用** | 用发生器自报 Vrms 当基准值去比 | 基准 = 万用表 AC 档实测(§7 #9);发生器只当"波形干净、频率可锁"的信号源,它自己的幅值误差正是要被校的量 |
|
||||
| **临时改了 `FREQ_AMP_MIN` 忘了还原** | 走 §7 #8 的 B 方案 | 改动落在产品头文件里、会编进固件。单列一条收尾判据:还原后 `@build` 0 错误 0 警告 + `git diff` 里 `App/algo/freq.h` 必须为空;并在 §7 #8 那行留一句"本轮台架已还原" |
|
||||
| **临时改了 `FREQ_AMP_MIN` 忘了还原** | 走 §7 #8 的 B 方案 | 改动落在产品头文件里、会编进固件。单列一条收尾判据:还原后 `/keil-build` 0 错误 0 警告 + `git diff` 里 `App/algo/freq.h` 必须为空;并在 §7 #8 那行留一句"本轮台架已还原" |
|
||||
| **峰值当成有效值,顶到量程外** | 只看 Vrms 数字:1.000 Vrms 峰值已 1.414 V | 接线前按**峰值**核一遍:TPAFE 侧 ≤±2 V、STM32 侧峰值必须 <3.3 V(且负半周本就钉轨,只作削顶轮用) |
|
||||
| **发生器与板不共地 / 地环** | 两台设备各自接电网地 | 单点共地,必要时发生器浮地;读数漂或出现与幅度无关的偏置先查这里 |
|
||||
| **非整 50 Hz 注入被当成 `meas` 抖动 bug** | 注 49.5 / 50.5 Hz | `meas` 是**瞬时基波有效值透传、200 ms 是上送节拍不是平均窗**(§4.5)→ 快照轨会有拍频式周期间跳动,属预期。测频腿要扫频时读 `freq(hb)`,别读 `meas:` |
|
||||
|
||||
+20
-20
@@ -14,7 +14,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
> 已按合并后的 `.map` 同步(RW_IRAM1 75,272 / ER_IROM1 232,512 / RW_CCM 57,376 / Flash 233,704)。
|
||||
> 变更:v1.2 数据现状全量同步——§2.1/§2.2/§5/§6/§8.6 等"现状"数据全部更新为 2026-09-21 lftpd 静态化后的最新 map 与实测值;清除 2026-09-11 历史口径残留(历史快照保留于 `data/`)
|
||||
> 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2)
|
||||
> 数据来源:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(`@build` 重建)+ `scripts/ram_report.py`(静态台账)+ `scripts/stack_report.py`(静态调用图 × 运行时栈水位)+ 源码静态分析 + 网络调研
|
||||
> 数据来源:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(`/keil-build` 重建)+ `scripts/ram_report.py`(静态台账)+ `scripts/stack_report.py`(静态调用图 × 运行时栈水位)+ 源码静态分析 + 网络调研
|
||||
> **本文档是内存管理主文档**(2026-09-21 合并):原《STM32F4-Base 内存使用分析报告》(现状审计/快照)与《内存使用分析规划》(执行规划,已全部闭环)已并入本文;**区域放置与 DMA 可达性规范**单列于 `内存内容分配策略.md`(本文只引用、不重复)。
|
||||
> 快照产物:`data/ram_report_<日期>.md`(静态台账)、`data/stack_report_<日期>.md`(栈预算)。
|
||||
|
||||
@@ -29,7 +29,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
- F407 另有 **64KB CCM RAM**:**已于 2026-09-21 启用**并把 FreeRTOS 堆迁入(§4.1.2)
|
||||
- 起步时没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋
|
||||
|
||||
当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成;2026-10-24 `@build`):RW_IRAM1 **75,632 / 131,072 B(57.7%)**、RW_CCM **57,376 / 65,280 B(87.9%)**。✅ **任务栈:`stack_report.py` 9/9 PASS**(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位,`defaultTask` 最坏 3024B/5120(余 40%)、`protectTask` 872B/2048(余 57%),见 `data/stack_report_20261022.md`);**卡点已从 SRAM1 移到 CCM**(余 7,904 B)。历史:`defaultTask` 曾于 2026-10-04 达 3664B/4096 ≈10%,曾登记在案,2026-10-10 削链后销项(详见"2026-10-10 复测"小节)。
|
||||
当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成;2026-10-24 `/keil-build`):RW_IRAM1 **75,632 / 131,072 B(57.7%)**、RW_CCM **57,376 / 65,280 B(87.9%)**。✅ **任务栈:`stack_report.py` 9/9 PASS**(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位,`defaultTask` 最坏 3024B/5120(余 40%)、`protectTask` 872B/2048(余 57%),见 `data/stack_report_20261022.md`);**卡点已从 SRAM1 移到 CCM**(余 7,904 B)。历史:`defaultTask` 曾于 2026-10-04 达 3664B/4096 ≈10%,曾登记在案,2026-10-10 削链后销项(详见"2026-10-10 复测"小节)。
|
||||
|
||||
本方案三个目标:
|
||||
|
||||
@@ -116,7 +116,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
> timeSyncTask 链 **1264 B**(同一控制台回显尾),512 words 余 784 B < ISR 底线 → **抬栈**
|
||||
> defaultTask 1024→**1280**、timeSyncTask 512→**640**(堆 +1536 B,台账 §6 已登记);
|
||||
> 台架实测 `heap free=16400 / min_ever=8464`(与预算 −1536 吻合,>3072 告警线),
|
||||
> `stack_report` 9 任务 0 FAIL + `@test` PASS(开机 641 ms)。瘦链候选(§4 ①②)刻意未做。
|
||||
> `stack_report` 9 任务 0 FAIL + `/serial-boot-test` PASS(开机 641 ms)。瘦链候选(§4 ①②)刻意未做。
|
||||
|
||||
| 任务 | 配置栈 | 字节 | 实际职责 | 2026-10-10 实测峰值 / 静态最坏链 |
|
||||
|---|---|---|---|---|
|
||||
@@ -126,7 +126,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
| testTask | `2048*4` | 8KB | 测试套件(产品构建不创建;存储套件深链需要) | 未创建 / 静态 496 B |
|
||||
| defaultTask | `1280*4` | 5KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2152 B(余 41%)/ 静态 **3016 B**(栈余量收口时抬栈 1024→1280,2026-10-12) |
|
||||
| timeSyncTask | `640*4` | 2.5KB | TCP 时间同步 | 616 B(余 50%)/ 静态 **1264 B**(栈余量收口时抬栈 512→640,2026-10-12) |
|
||||
| adcTask | `640*4` | 2.5KB | TPAFE5160 采样消费 + 全周傅氏相量 + 有效性校验(TIM3 1 kHz 节拍,B1→全周傅氏相量→「采样有效性校验」 2026-10-11) | **952 / 2560**(余 1608 B / 62%,台架 `@test` 实测 2026-10-11);静态 **1320 B**(2026-10-12 复测,保护判据接线后) |
|
||||
| adcTask | `640*4` | 2.5KB | TPAFE5160 采样消费 + 全周傅氏相量 + 有效性校验(TIM3 1 kHz 节拍,B1→全周傅氏相量→「采样有效性校验」 2026-10-11) | **952 / 2560**(余 1608 B / 62%,台架 `/serial-boot-test` 实测 2026-10-11);静态 **1320 B**(2026-10-12 复测,保护判据接线后) |
|
||||
| iec104Task | `640*4` | 2.5KB | IEC104 上行服务(2404 监听 + link 状态机,B4 2026-10-12) | 未上板 / 静态 **1456 B**(拆链→重建监听→net_bind→SPI 故障→日志尾,日志主导;512 words 时余 592 B < ISR 底线 → 抬至 640,余 1104 B / 43%) |
|
||||
| protectTask | `512*4` | 2KB | 保护判据节拍(prot_elm_run 消费者 + 「自检失败闭锁」采样闭锁,B5 2026-10-12) | 未上板 / 静态 **872 B**(float 判据 + phasor 快照 + 日志尾,余 1176 B / 57%) |
|
||||
| idle(内核) | `configMINIMAL_STACK_SIZE=128` | 0.5KB | 空闲 | 未观测(未取句柄) |
|
||||
@@ -197,7 +197,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
| 菜单根框建完 | 5,368 B | 33% | `simulator/tests/check_menu_rows.py` 的 `MENU_POOL_BASELINE` |
|
||||
| 开页-关页往返(一次性残留 +80 B) | 5,448 B | 33% | `simulator/tests/check_content_pages.py` 的 `POOL_AFTER_ROUNDTRIP` |
|
||||
| **最重页面峰值**(网口设置保存态) | **6,568 B** | **40%** | 同上脚本的 `pool after keys: used=… free=7,080` |
|
||||
| **真机开机**(2026-10-15,16 KB 池,`@test` PASS) | **3,192 B** | 21% | 台架日志 `menu build done: total=15020 free=11828 used=3192` |
|
||||
| **真机开机**(2026-10-15,16 KB 池,`/serial-boot-test` PASS) | **3,192 B** | 21% | 台架日志 `menu build done: total=15020 free=11828 used=3192` |
|
||||
| 真机开机(2026-09-21,40 KB 池时代) | 2,976 B | — | `ui_menu.c` 的 `lv_mem_monitor` 日志 `menu build done: used=2976` |
|
||||
|
||||
⚠ 16,384 B 建池后 `lv_mem_monitor` 报的 `total` 是 **15,020 B**——TLSF 控制块与对齐元数据吃掉 ≈1,364 B(约 8%);核算池余量用 15,020,不是 16,384。
|
||||
@@ -227,7 +227,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
> **2026-10-11 裁剪 LVGL 死代码(原审查报告的三条裁剪建议,已销项)**:`Lib/lv_conf.h` 显式关掉 10 个 `LV_DRAW_SW_SUPPORT_*`(只留 I1)、5 个未用控件类(label / button / buttonmatrix / bar / textarea)、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX` / `LV_USE_GRID` → **`ER_IROM1` 274,724 → 210,148 B(省 64,576 B = 旧镜像的 23.5%)**,Flash 占比 26.3% → **20.1%**。
|
||||
> 对象级对照(`.map` 组件表,Code+RO+RW):`blend_to_*` 40,582 → 1,338(其中 `blend_to_i1` 自身 5,272 → 1,338——它按**源**格式的分支也一并消失);控件类 13,397 → 0;`lv_theme_simple` 866 → 0;`lv_draw_sw_transform` 4,340 → 598;`lv_theme_mono` 1,400 → 1,036;镜像内对象数 227 → 212。实测比原报告估算(54,630 B)多省约 10 KB,差额正是这些"锚点消失后连带消失的分支"。
|
||||
> ⚠ **配套本地补丁**:`lv_draw_sw_blend_to_i1.c` 的 `lv_color16_from_u16` 上游 9.3.0 漏了定义处守卫(唯一使用点全在 `#if LV_DRAW_SW_SUPPORT_RGB565_SWAPPED` 内)→ 关掉该格式后 ARMCC 报 `#177-D declared but never referenced`(破坏"0 警告"门禁)。已按使用点补同一守卫,升级 LVGL 时若上游已修可去掉。
|
||||
> 验证:ui-verify 五步全过——**11 页内容黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,LVGL 池水位 5,440 → 5,368 B(−72 B,样式表更小,一次性残留仍 80 B);`@build` 0 错 0 警;`@test` 真机 PASS。详见 `../UI设计/UI_设计架构.md` §4.1.4 第 10 条。
|
||||
> 验证:ui-verify 五步全过——**11 页内容黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,LVGL 池水位 5,440 → 5,368 B(−72 B,样式表更小,一次性残留仍 80 B);`/keil-build` 0 错 0 警;`/serial-boot-test` 真机 PASS。详见 `../UI设计/UI_设计架构.md` §4.1.4 第 10 条。
|
||||
> **长效防复发**:已建门禁 `scripts/check_lv_conf_sync.py`(pre-commit 钩子 `lv-conf-sync`)——比对两份 `lv_conf.h` 的**生效值**(未定义则取 `lv_conf_internal.h` 兜底默认),并抓无效宏名与重复定义;首次跑揪出两端共 26 个 9.3 不存在的选项、3 处重复定义、10 项靠默认值隐式生效的开关,已清理(本次清理对镜像体积中性)。详解见 `docs/门禁说明/lv-conf-sync.md`(含四类判定、端专属白名单与首次落地实测)。
|
||||
|
||||
> ⚠ **MicroLIB 的 `_fp_digits` 与双精度 helper(约 1,018 B)删不掉,别再去搜 `%f`**:全库 `%f` 命中 **0**,
|
||||
@@ -325,7 +325,7 @@ void vApplicationMallocFailedHook(void)
|
||||
|
||||
行为:挂在 defaultTask 主循环——首次 5s 后报告一次(bring-up 深链 mount/mkfs/探针/cfg/syslog
|
||||
均已跑过),此后每 2s 采样,**只在出现更深峰值时打印**:既不破坏串口静默窗口,也不漏掉
|
||||
之后才走到的更深分支(卷修复、配置保存等)。输出纯 ASCII,便于 `@test` 采集:
|
||||
之后才走到的更深分支(卷修复、配置保存等)。输出纯 ASCII,便于 `/serial-boot-test` 采集:
|
||||
|
||||
```
|
||||
[STACK] <task> peak_used=<B>/<cfg> free_min=<B> (<pct>%)
|
||||
@@ -335,7 +335,7 @@ void vApplicationMallocFailedHook(void)
|
||||
要点:
|
||||
|
||||
- `uxTaskGetStackHighWaterMark` 返回**任务创建以来**的最小剩余栈(单位 word=4B);`used = 配置 − 剩余` 即该任务历史峰值栈占用。
|
||||
- **测量必须覆盖全功能路径**(高水位是历史最小值,只反映跑过的路径):全菜单遍历、FTP 上传/下载大文件、CH395F 断链重连、NAND 格式化、时间同步。建议 `@test` 全回归 + 1 小时浸泡。
|
||||
- **测量必须覆盖全功能路径**(高水位是历史最小值,只反映跑过的路径):全菜单遍历、FTP 上传/下载大文件、CH395F 断链重连、NAND 格式化、时间同步。建议 `/serial-boot-test` 全回归 + 1 小时浸泡。
|
||||
- 未覆盖 idle/timer(内核静态小栈、纯循环,未取句柄);构建期静态侧见 §4.0.6。
|
||||
|
||||
#### 4.0.3 map 静态占用报告脚本
|
||||
@@ -360,7 +360,7 @@ void vApplicationMallocFailedHook(void)
|
||||
#### 4.0.6 静态栈分析 + 汇总工具(构建期,零目标依赖;2026-09-21 落地)
|
||||
|
||||
**UV4 每次构建已自动生成静态调用图** `MDK-ARM/STM32F407-Demo/STM32F407-Demo.htm`
|
||||
(标题 "Static Call Graph",随 `@build` 刷新,**无需**给 armlink 加 `--callgraph`)。每个函数
|
||||
(标题 "Static Call Graph",随 `/keil-build` 刷新,**无需**给 armlink 加 `--callgraph`)。每个函数
|
||||
条目自带 `Stack size N bytes`(该函数自身帧)与 `Max Depth = N`(其子树最坏累计深度)
|
||||
以及完整调用链——即"不烧板子也能看到最坏链有多深、多宽"。
|
||||
|
||||
@@ -459,7 +459,7 @@ void vApplicationMallocFailedHook(void)
|
||||
`name[49]/num[16]/rev[16]` 改 static + 原地反转、去掉 `HAL_Delay`(断言现场 tick 未必可信)。
|
||||
|
||||
代价与复核:静态 BSS **+4,128 B**(ZI 133,524 → 137,652;RW_IRAM1 64.9% → 68.1%),Flash 反而 −36 B;
|
||||
FreeRTOS 堆不变(`free=17248 / min_ever=9312`)。验证链:`@build` 0/0 → `@test` 真机 PASS(含烧录回读)
|
||||
FreeRTOS 堆不变(`free=17248 / min_ever=9312`)。验证链:`/keil-build` 0/0 → `/serial-boot-test` 真机 PASS(含烧录回读)
|
||||
→ `stack_report.py` **6/6 PASS**。
|
||||
|
||||
顺带揪出并修掉的**两个静态虚高源**:① `dbg_console_drain` 故障路径调 `syslog_post` →
|
||||
@@ -540,7 +540,7 @@ Linker run 时刻**——commit hash 不是稳定身份(重写正文即换 has
|
||||
#### 4.1.1 LVGL 内存池 40KB → 16KB(✅ 已执行 2026-10-15,省 24,576 B)→ 8KB(待评估)
|
||||
|
||||
- 依据:屏幕仅 160×160×1bpp,菜单为轻量控件树;LVGL 内存池只装对象/样式/字符串缓冲,与像素缓冲无关。40KB 来自模板默认值,无适配依据。
|
||||
- **已落地**:`Lib/lv_conf.h` 与 `simulator/lv_conf.h` 同步改 `(16 * 1024)`——两端必须同值,`lv-conf-sync` 门禁按**生效值**比对。实测水位见 §2.6.2(最重页面峰值 6,568 B = 池的 40%);`@build` 0 错 0 警,ZI 精确 −24,576 B → SRAM1 由 74.9% 降到 **56.1%**(余 57,504 B)。
|
||||
- **已落地**:`Lib/lv_conf.h` 与 `simulator/lv_conf.h` 同步改 `(16 * 1024)`——两端必须同值,`lv-conf-sync` 门禁按**生效值**比对。实测水位见 §2.6.2(最重页面峰值 6,568 B = 池的 40%);`/keil-build` 0 错 0 警,ZI 精确 −24,576 B → SRAM1 由 74.9% 降到 **56.1%**(余 57,504 B)。
|
||||
- 验证口径(本次即用它):`simulator/tests/check_menu_rows.py`(菜单 8 状态像素 + 池基线 5,368 B)+ `simulator/tests/check_content_pages.py`(当时 11 页黄金参考逐字节 + 往返池水位 5,448 B)+ 真机 `menu build done: used=` 日志。
|
||||
- **再降 8KB 的前置**:全量 UI 回归(PC 模拟器 + 真机)无 `LV_ASSERT_MALLOC`/分配失败;若 8KB 不够就停在 16KB,不勉强。
|
||||
|
||||
@@ -596,7 +596,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
|
||||
|
||||
缩减后任务栈合计 38,912 → 21,504 B(**省 17,408 B / ~17 KB**,全部在 CCM 堆内,主 SRAM 不受影响)。
|
||||
|
||||
每改一批 → `@build` + 全功能回归 + 复测高水位(`free_min` 应 ≥ 新栈×30%),不满足则回调。
|
||||
每改一批 → `/keil-build` + 全功能回归 + 复测高水位(`free_min` 应 ≥ 新栈×30%),不满足则回调。
|
||||
|
||||
#### 4.1.4 lftpd 大栈局部变量 → 静态缓冲(✅ 2026-09-21 完成;ftpTask 峰值 3480 → 1184 B)
|
||||
|
||||
@@ -613,7 +613,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
|
||||
|
||||
- **结果**:ftpTask 实测峰值 **3480 → 1184 B**(余量 616 → **2912 B / 71%**,判定 PASS);静态调用图 2528 → 472 B;帧宽榜中 lftpd 全部退出前 15。
|
||||
- **代价(2026-09-21 实施时)**:静态 BSS **+4,616 B**(RW_IRAM1 77,432 → 82,048 B / 128 KB;CCM 与 Flash 不变)。
|
||||
- **验证**:`@build` 0/0 → 烧录 → `@test` PASS → `ftp_up_dl_test.py` 10/10 PASS + `ftp_speed_test.py --size 128` 逐字节一致(功能与搬迁前完全一致,反证无别名/重入问题)。
|
||||
- **验证**:`/keil-build` 0/0 → 烧录 → `/serial-boot-test` PASS → `ftp_up_dl_test.py` 10/10 PASS + `ftp_speed_test.py --size 128` 逐字节一致(功能与搬迁前完全一致,反证无别名/重入问题)。
|
||||
- **结论:原 ① 方案(ftpTask 涨到 1600 words)不再需要**,栈保持 1024 words(4096 B)。
|
||||
|
||||
> lftpd 属第三方代码(pre-commit 排除范围),只做最小侵入改动(每处仅加 `static` + 单行注释),改动点在本台账登记。
|
||||
@@ -645,8 +645,8 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
|
||||
1. **RAM 预算台账**(✅ **2026-10-12 落地** `docs/内存管理/RAM预算台账.md`,模板见 §8.2):记录每个 Region/缓冲/栈的计划值、实测值、变更历史;每次改内存相关代码必须更新
|
||||
2. **AGENTS.md 增补**(✅ 2026-09-21 已完成:"内存预算紧张"条目已含 `ram_report.py`/`stack_report.py` 数据源与"改栈前后各跑一次"要求)
|
||||
3. **pre-commit 门禁**(✅ **2026-10-12 落地** `mem-budget` 钩子 = `scripts/ram_baseline.py --gate`):钩子解析最新 map,RW_IRAM1 使用率 >92% 或 RW_CCM 超限 → 提交拦截并附 Top-10 RW+ZI 对象表
|
||||
4. **@test 扩展**:`verify_log.py` 增加可选 Required 标记 `"[MEM] heap free"`,把堆余量下限纳入自动化回归
|
||||
5. **map diff 常态化**:每次 `@build` 后跑 `ram_report.py`,ZI 增量 >1KB 的改动必须在提交说明中解释
|
||||
4. **/serial-boot-test 扩展**:`verify_log.py` 增加可选 Required 标记 `"[MEM] heap free"`,把堆余量下限纳入自动化回归
|
||||
5. **map diff 常态化**:每次 `/keil-build` 后跑 `ram_report.py`,ZI 增量 >1KB 的改动必须在提交说明中解释
|
||||
|
||||
## 5. 预期效果
|
||||
|
||||
@@ -668,17 +668,17 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
|
||||
|
||||
| 步骤 | 内容 | 验证 | 状态 |
|
||||
|---|---|---|---|
|
||||
| 步骤 1 | 0.1 + 0.2(溢出钩子 + `stack_probe_poll` 栈水位探针,`Src/freertos.c` USER CODE 区) | `@build` 0错0警;`@test` 串口出现每任务 `[STACK]` 报告 | ✅ 2026-09-21 |
|
||||
| 步骤 1 | 0.1 + 0.2(溢出钩子 + `stack_probe_poll` 栈水位探针,`Src/freertos.c` USER CODE 区) | `/keil-build` 0错0警;`/serial-boot-test` 串口出现每任务 `[STACK]` 报告 | ✅ 2026-09-21 |
|
||||
| 步骤 2 | 0.3 `ram_report.py` + 基线 | 脚本输出与 map 人工核对一致 | ✅ 2026-09-21(`data/ram_report_20260921.md`) |
|
||||
| 步骤 3 | 全功能回归跑数(菜单/FTP/网络/格式化/浸泡 1h) | 拿到各任务 `free_min` 与堆 free 底值 | ◐ boot + FTP 路径已实测(09-21);全菜单遍历 + 1h 浸泡待补 |
|
||||
| 步骤 4 | §4.1.4 lftpd 静态化 + §4.2.1 malloc 修复 | FTP STOR/RETR 大文件 + 长路径回归 | ✅(§4.2.1 于 2026-09-10、§4.1.4 于 2026-09-21 完成) |
|
||||
| 步骤 5 | §4.1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 | ✅ **2026-10-15**(11 页黄金参考 + 池水位 5,448 B + `@test` 真机 `used=3192`;实测峰值 6,568 B = 池的 40%,§2.6.2) |
|
||||
| 步骤 5 | §4.1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 | ✅ **2026-10-15**(11 页黄金参考 + 池水位 5,448 B + `/serial-boot-test` 真机 `used=3192`;实测峰值 6,568 B = 池的 40%,§2.6.2) |
|
||||
| 步骤 6 | §4.1.2 CCM(scatter + section + 门禁宏) | map 显示 `RW_CCM`;关 `APP_CCM_ENABLE` 也能编译通过 | ⏸ **暂缓(2026-10-15 裁定)**;已完成部分仅 `RW_CCM` + 堆迁入(09-21),缓冲 section 迁移与门禁宏不再排期 |
|
||||
| 步骤 7 | §4.1.3 栈缩减(按步骤 3 数据)+ 复测 | 每任务 `free_min ≥ 新栈×30%`,无溢出钩子触发 | ⏸ **暂缓(2026-10-15 裁定)**(目标值已按 09-21 实测修正,重开时可直接用) |
|
||||
| 步骤 8 | §4.2.2 堆 48K→32K + 复测 | 全负载 `heap free ≥ 8KB` | ⏸ **暂缓(2026-10-15 裁定)**;⚠ 2026-10-12 B5 反向扩容至 56 KB,重开时须重跑 `stack_report.py` + 上板 `min_ever` |
|
||||
| 步骤 9 | §4.1.5 小项 + 台账/门禁/AGENTS.md | `@build` + `@test` 全绿 | ◐ AGENTS.md 内存条目已增补(09-21);台账 / 趋势 / `mem-budget` 三件套 ✅ 2026-10-12(**常开,不受暂缓影响**) |
|
||||
| 步骤 9 | §4.1.5 小项 + 台账/门禁/AGENTS.md | `/keil-build` + `/serial-boot-test` 全绿 | ◐ AGENTS.md 内存条目已增补(09-21);台账 / 趋势 / `mem-budget` 三件套 ✅ 2026-10-12(**常开,不受暂缓影响**) |
|
||||
|
||||
每步都必须 `@build` 通过(0 错误 0 警告)后再进入下一步;涉及 UI 的步骤用 `simulator` 先过一遍再上实机。
|
||||
每步都必须 `/keil-build` 通过(0 错误 0 警告)后再进入下一步;涉及 UI 的步骤用 `simulator` 先过一遍再上实机。
|
||||
|
||||
## 7. 风险与注意事项
|
||||
|
||||
@@ -758,7 +758,7 @@ V1 链接视图(map:Grand Totals / Image component sizes / Symbol Table)
|
||||
|
||||
V4 逐项相加须与 V1 的 `Image component sizes` 逐 .o 吻合;不等则以 V1 为准并记录差异原因。
|
||||
|
||||
**数据来源清单**:`STM32F407-Demo.sct`(区域)、`Inc/FreeRTOSConfig.h`(堆/优先级/静态分配开关)、`cmsis_os2.c:472-476`(**栈大小单位 = 字节**:内部 `/ sizeof(StackType_t)`)、`Src/freertos.c`(任务/队列/事件组属性)、`Lib/lv_conf.h`、`Lib/FatFs/ffconf.h`、`test/board/test_config.h`(构建口径;`App/app/app_cfg.h` 自 2026-10-22 起无宏,不再是口径来源)、`Drivers/BSP/**` + `App/**`(缓冲语义/用途/DMA 归属);**权威数据 = `@build` 重建后的 `.map`**(历史 map 不得用于结论)。
|
||||
**数据来源清单**:`STM32F407-Demo.sct`(区域)、`Inc/FreeRTOSConfig.h`(堆/优先级/静态分配开关)、`cmsis_os2.c:472-476`(**栈大小单位 = 字节**:内部 `/ sizeof(StackType_t)`)、`Src/freertos.c`(任务/队列/事件组属性)、`Lib/lv_conf.h`、`Lib/FatFs/ffconf.h`、`test/board/test_config.h`(构建口径;`App/app/app_cfg.h` 自 2026-10-22 起无宏,不再是口径来源)、`Drivers/BSP/**` + `App/**`(缓冲语义/用途/DMA 归属);**权威数据 = `/keil-build` 重建后的 `.map`**(历史 map 不得用于结论)。
|
||||
|
||||
### 8.5 图表与台账重生成
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
无 `.map` 时自适应跳过(CI 侧跳过 `mem-numbers` 的边界 = `scripts/check_size_gate.py` 的 docstring,唯一正文)。
|
||||
- **阈值依据**:92% 由 2026-09 体系评审提出、2026-10-12 落地为门禁(提出时的报告条目已随销项删除,闭环说明见 `RAM使用分析与优化方案.md` §4 阶段 3「长效机制」与本文)——留 ~8% 给突发缓冲与栈抬升,
|
||||
触线即触发排期,而不是静默逼近溢出。
|
||||
- **软提醒**(不拦提交):tsv 末行落后于 `.map`(忘跑 `--update`)、`.map` 早于最新源码(忘 `@build`)。
|
||||
- **软提醒**(不拦提交):tsv 末行落后于 `.map`(忘跑 `--update`)、`.map` 早于最新源码(忘 `/keil-build`)。
|
||||
- **子系统归集为参考值**:按对象名前缀归集(口径见 §2),合计与区域总量可差几十字节
|
||||
(链接器填充行与 `UNINIT_CCM` 等小区分段所致);区域行才是门禁事实源。
|
||||
|
||||
@@ -81,7 +81,7 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名
|
||||
|
||||
## 4. 趋势基线(`scripts/ram_baseline.tsv`)
|
||||
|
||||
- **每次 `@build` 后**:`& '.venv\Scripts\python.exe' scripts/ram_baseline.py --update`
|
||||
- **每次 `/keil-build` 后**:`& '.venv\Scripts\python.exe' scripts/ram_baseline.py --update`
|
||||
追加一行(Code/RO/RW/ZI + 三区水位 + Top-10 RW+ZI 对象;同日重跑覆盖当日行)。
|
||||
- **看趋势 / 找增量**:`scripts/ram_baseline.py --trend`——高亮较上一行 RW+ZI 增长 >1 KB 的对象,
|
||||
即 `docs/内存管理/RAM使用分析与优化方案.md` §4.0.3 "ZI 增量 >1KB 必须在提交说明中解释"的机械化;提交说明直接抄 `--trend` 输出行。
|
||||
|
||||
@@ -143,8 +143,8 @@ LVGL 集合明细(均在 SRAM):LVGL 池 16,384B(`lv_mem_core_builtin.o`
|
||||
|
||||
任何 scatter / CCM / 堆大小变更后,依次通过以下全部关卡方可认定生效:
|
||||
|
||||
1. `@build`(0 错误 0 警告)
|
||||
2. 产品构建 `@test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):标记校验 PASS,且**零 `[ERR]`**(默认 Forbidden 已含 `[ERR]`)
|
||||
1. `/keil-build`(0 错误 0 警告)
|
||||
2. 产品构建 `/serial-boot-test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):标记校验 PASS,且**零 `[ERR]`**(默认 Forbidden 已含 `[ERR]`)
|
||||
3. 存储套件(`test_config.h` → `TEST_SUITE_STORAGE`):只读模式实测 **3/3 PASSED**(破坏性关,卷内容不变);开启 `ENABLE_DESTRUCTIVE_TESTS` 后为全量集成用例(2026-09-20 实测 **20/20 PASSED**,含 TC-STORAGE-01/02/03/05/06)
|
||||
4. GD5F 套件(`TEST_SUITE_GD5F`):只读模式实测 **16/16 PASSED**(含只读的守卫负路径 TC-GD5F-704);开启 `ENABLE_DESTRUCTIVE_TESTS` 后 **68/68 PASSED**,并确认日志出现 2 条守卫负路径 `[ERR]`(TC-GD5F-704/705)——破坏性模式运行须放宽 Forbidden(`--forbidden "FAIL;HardFault"`)
|
||||
5. 恢复跑(`ENABLE_STORAGE_RECOVERY_TESTS`):期望 **3/3 PASSED**
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
| # | 方案 | 省 RAM | 区域 | 风险 | 验证 |
|
||||
|---|---|---:|---|---|---|
|
||||
| `s_copy_buf` 分块 | `s_copy_buf` 2048→512:GC 拷贝改分段 read→program_load(`program_load`/`read_from_cache` 支持列地址);纯 RAM 优化(GC 拷贝暂存减半) | **−1,536B(可选,不计入当前预期)** | SRAM1 | 低(仅动拷贝机制) | 待 RAM 余量需要时再议 |
|
||||
| lftpd 缓冲收紧 | lftpd 缓冲收紧:response 512→256、read_buffer 512→256、arg_buf 512→256、name 256→128 ×2(本卷仅 8.3 短名,路径深度浅);数据块 2×1024 与 message 不动 | **−1,024B** | SRAM1 | 低(FTP 客户端实测行长远低于新值) | `@build` + FTP 上传/下载/LIST 闭环 + `@test` |
|
||||
| lftpd 缓冲收紧 | lftpd 缓冲收紧:response 512→256、read_buffer 512→256、arg_buf 512→256、name 256→128 ×2(本卷仅 8.3 短名,路径深度浅);数据块 2×1024 与 message 不动 | **−1,024B** | SRAM1 | 低(FTP 客户端实测行长远低于新值) | `/keil-build` + FTP 上传/下载/LIST 闭环 + `/serial-boot-test` |
|
||||
| `FF_FS_TINY` 共享窗 | `FF_FS_TINY=1`:删每 FIL 私有的 512B buf,文件数据走 `FatFs->win` 共享窗 | 静态 **−528B**;且栈上每处 FIL 峰值 **−528B**(defaultTask 最坏链 2,776→~2,250,实测峰 2,136→~1,600) | SRAM1 + CCM 栈 | 中(所有文件 I/O 经 512B 窗 bounce,FTP 吞吐 −20~40%;LIST 与文件读交错只影响速度) | 存储套件 + FTP 吞吐实测 + 日志/配置闭环;**与 RAM方案 实施步骤 7 的缩栈捆绑**(直接支撑 defaultTask 降档) |
|
||||
| `APP_CCM_ENABLE` 迁 CCM | `APP_CCM_ENABLE` 门(与 RAM方案 实施步骤 6 同宏):①CPU-only 缓冲迁 CCM——lftpd static 3.6K + cfg_file 0.7K + FATFS win 0.6K + lftpd_io ≈ **−4.9K** 零代价;②DMA 页缓冲(nand_ftl 4.6K + gd5f 位图/页 0.8K)迁 CCM 走轮询 ≈ **−5.4K**,需实测吞吐 | **−10.3K** | SRAM1→CCM | 低①/中② | 分段实施;每步跑 `ram_report.py` + `@test` + FTP 吞吐 |
|
||||
| `APP_CCM_ENABLE` 迁 CCM | `APP_CCM_ENABLE` 门(与 RAM方案 实施步骤 6 同宏):①CPU-only 缓冲迁 CCM——lftpd static 3.6K + cfg_file 0.7K + FATFS win 0.6K + lftpd_io ≈ **−4.9K** 零代价;②DMA 页缓冲(nand_ftl 4.6K + gd5f 位图/页 0.8K)迁 CCM 走轮询 ≈ **−5.4K**,需实测吞吐 | **−10.3K** | SRAM1→CCM | 低①/中② | 分段实施;每步跑 `ram_report.py` + `/serial-boot-test` + FTP 吞吐 |
|
||||
| NAND copy-back | NAND copy-back 命令(0x15→0x10)替代主机搬页:省 `s_copy_buf` 的拷贝用途 + 减半 SPI 流量 | −512B(`s_copy_buf` 分块后) | SRAM1 | 高(片上 ECC 在 copy-back 下的语义须台架实证) | 仅评估,暂缓 |
|
||||
| lftpd 数据块减半 | lftpd 数据块 2×1024→512 | −1,024B | SRAM1 | 吞吐 −~30% | RAM 仍紧时才做 |
|
||||
| 共享静态 FIL | syslog/cfg 共享一个静态 FIL(同任务串行) | 净≈0(静态+528/栈−528) | — | 不推荐:`FF_FS_TINY` 那条已覆盖同一收益面 | — |
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
|---|---|
|
||||
| 主机侧纯逻辑单测 | `test/host/iec104/`:APCI 重组/坏帧、STARTDT 握手、总召全流程(含 RBT/分片/预算)、COV、k/w 窗、t1/t2/t3(假时基)、遥控两段式与 PN、YK12 软压板两段转真 + 置脏(B7)、定值两段写 + set_sealed 后 selfcheck、C_RD(含本地项不可寻址)、C_CS(含闰年)、错地址/错类型 EVI;**板端默认源真实链**(ring→phasor→freq→meas 快照,非桩):值/品质透传、CLIPPED→IV、0x401D 绑频(收敛 ±0.02 Hz / 未新鲜 IV)、死区运行时化 + 2 点确认/回摆撤销、seq 去重(104 改读快照与频率点绑定,TC-IEC104-025~028)+ 运行参数段路由/两段式/RMW(TC-IEC104-029)+ `[comm]` 段真链(道G task-8,TC-IEC104-031~033:注册/出厂模板/沉降重载/带外自愈/setter 守卫/ca 端到端 = C_RD 正答与旧写死 ca=1 转 COT13+EVI);`test/host/iec104_serv/` 钉端口与 ca 取值来源(TC-I104SRV-001b/c);`test/host/cfg_file/` 钉段/键容量上限(TC-CONFIG-056/057);`test/host/run_par/`:[run_par] 注册/落盘往返/守卫/系数直通合法带(TC-RUNPAR-001~008);`test/host/prot_set/`:blob 往返 + 损坏整表拒绝(all-or-nothing)|
|
||||
| 静态门禁 | `iec104-unit-test` 钩子 + cppcheck/coding-standard |
|
||||
| 台架 | `@test` 锚点(104 服务启动/监听行);**PC 主站客户端 `test/host/iec104_master.py`(纯标准库,B6 2026-10-12 就绪)**:STARTDT/总召四组+actterm/C_SE 定值两段写→YX15·16 变位(COT=3)/恢复复归/YK11 两段正答/C_RD 读回,台架往返 **18/18 PASS**(证据 `../测试说明/测试数据/boot_log_data/20261012_B6_iec104_roundtrip_log.txt`);**B7 持久化模式 `--b7-pre/--b7-post/--b7-mark/--b7-check`**(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、**定值与压板态跨复位保持**(含非默认 7.5 往返),**21/21 + 13/13 + 4/4+4/4 PASS**(证据 `../测试说明/测试数据/boot_log_data/20261012_B7_iec104_persist_log.txt`);**B9 SOE 腿**(同工具同轮回归):自发 YX 断言 **M_SP_TB_1 带 ts**(工具解析 type 30/1 双型)+ FTP 下载 `/log/soe.log` 经 `test/host/soe/decode_soe.py` 解码断言(校验和/seq 单调/CP56↔epoch UTC 交叉),pre 21/21 + post 13/13 不回归;lib60870 级第三方全流程归 B10 |
|
||||
| 台架 | `/serial-boot-test` 锚点(104 服务启动/监听行);**PC 主站客户端 `test/host/iec104_master.py`(纯标准库,B6 2026-10-12 就绪)**:STARTDT/总召四组+actterm/C_SE 定值两段写→YX15·16 变位(COT=3)/恢复复归/YK11 两段正答/C_RD 读回,台架往返 **18/18 PASS**(证据 `../测试说明/测试数据/boot_log_data/20261012_B6_iec104_roundtrip_log.txt`);**B7 持久化模式 `--b7-pre/--b7-post/--b7-mark/--b7-check`**(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、**定值与压板态跨复位保持**(含非默认 7.5 往返),**21/21 + 13/13 + 4/4+4/4 PASS**(证据 `../测试说明/测试数据/boot_log_data/20261012_B7_iec104_persist_log.txt`);**B9 SOE 腿**(同工具同轮回归):自发 YX 断言 **M_SP_TB_1 带 ts**(工具解析 type 30/1 双型)+ FTP 下载 `/log/soe.log` 经 `test/host/soe/decode_soe.py` 解码断言(校验和/seq 单调/CP56↔epoch UTC 交叉),pre 21/21 + post 13/13 不回归;lib60870 级第三方全流程归 B10 |
|
||||
|
||||
## 8. V0.1 限制与待办(如实登记)
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ flowchart TB
|
||||
|
||||
- 台架须用 **USB 转 RS-232 适配器**(TTL 的 CH340 直连不了,且 RS-232 的 ±电平可能打坏 `U13` TPT3232E 或适配器):
|
||||
适配器 `TXD` → `CN1.10`(板子 `RS232_RX1`)、`RXD` → `CN1.12`(板子 `RS232_TX1`)、`GND` → `CN1.7` 或 `CN1.8`(`CGND`)。
|
||||
- 台架串口因此是 **`COM3`**,`.serialtest/config.json` 的 `portName` 已按此配置,`@test` 与 `serial-assistant` 不必手工指端口。
|
||||
- 台架串口因此是 **`COM3`**,`.serialtest/config.json` 的 `portName` 已按此配置,`/serial-boot-test` 与 `serial-assistant` 不必手工指端口。
|
||||
- RS232#1 **没有 TTL 出口**(`ST_TX1`/`ST_RX1` 只连 `U9` 隔离器与 MCU),唯一外部出口是 CN1 的 RS-232 端子;
|
||||
`U14` 是 RS232#3 的网口座,本通道用不上。
|
||||
|
||||
@@ -259,7 +259,7 @@ flowchart TB
|
||||
|
||||
## 10. 坑账本
|
||||
|
||||
1. **`@test` 的 forbidden 标记(`FAIL;ERROR;HardFault;[ERR]`)大小写不敏感**——日志字段名带 fail / error / abort
|
||||
1. **`/serial-boot-test` 的 forbidden 标记(`FAIL;ERROR;HardFault;[ERR]`)大小写不敏感**——日志字段名带 fail / error / abort
|
||||
会把启动自检判红,本模块统计行用 `kerr=` / `abor=` / `off=` 命名。
|
||||
2. **`ReceiveToIdle_IT` 是一次性的**:漏重挂 = 链路永久静默,且没有任何报错;ORE 不自己清,一次噪声打死接收。
|
||||
3. **NE/FE 非阻塞、ORE 阻塞**:重挂判据用 `RxState == READY` 而不是错误位;台架"NE 每帧出现 + `rx_ok` 照涨"
|
||||
|
||||
+10
-10
@@ -2,7 +2,7 @@
|
||||
|
||||
> **版本**:v1.8(已实施;修订史见文末 §11「实施时间线」)
|
||||
> **日期**:2026-10-30(v1.8:控制台端口由 USART1 改为 **UART5/RS232#1**,全文端口口径同步——物理口与台架端子见 `屏板链路设计说明.md` §2 / §3.2;v1.7 于 2026-10-26:崩溃上报归一进单写者 + 多段裸发收口,道G task-26;v1.6 同日:未注册期裸发加「行级单一所有者」;v1.5 于 2026-10-24;初版 v1.0 于 2026-09-25)
|
||||
> **状态**:已实施并验证——`@build` 0 错误 0 警告、`@test` 硬件闭环 PASS(2026-09-25,见 §10);设计 2026-09-24 加固实施(当时位于 `Src/usart.c` USER CODE 区),2026-09-25 实现迁出至 `App/log/`(HAL 生成文件不含应用代码);同日写者任务体自 `Src/freertos.c` 迁至 `App/task/default_task.c`(与其余 5 任务文件同址归位)
|
||||
> **状态**:已实施并验证——`/keil-build` 0 错误 0 警告、`/serial-boot-test` 硬件闭环 PASS(2026-09-25,见 §10);设计 2026-09-24 加固实施(当时位于 `Src/usart.c` USER CODE 区),2026-09-25 实现迁出至 `App/log/`(HAL 生成文件不含应用代码);同日写者任务体自 `Src/freertos.c` 迁至 `App/task/default_task.c`(与其余 5 任务文件同址归位)
|
||||
> **关联文档**:`项目架构总览.md` · `运行与自检记录设计说明.md`(姊妹模块:持久化日志,同一写者任务) · `docs/测试说明/专项验证/链路延迟测试说明.md`(D6 日志税 / 步骤 8 非阻塞日志,需求出处) · AGENTS.md「平台约束」
|
||||
|
||||
## 1. 背景:实测故障与根因
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
| # | 症状 | 实测证据 | 后果 |
|
||||
|---|------|---------|------|
|
||||
| F1 | 日志行字符级交错 | 崩溃前两行日志逐字符交错:`"[2026-[2026-0909-24 15:2-24 15:27:37:39]…"` | @test 必检标记可能假性丢失、日志不可读 |
|
||||
| F1 | 日志行字符级交错 | 崩溃前两行日志逐字符交错:`"[2026-[2026-0909-24 15:2-24 15:27:37:39]…"` | /serial-boot-test 必检标记可能假性丢失、日志不可读 |
|
||||
| F2 | HardFault(脏指针直接进入 UART) | BFAR=0x10010000(恰为 CCM 末尾+1),PC 落在 `HAL_UART_Transmit` 的缓冲读取处 | 崩溃上报路径自身再崩;被破坏的字符串指针被 UART 逐字节读取 |
|
||||
|
||||
前兆:2026-09-22 链路延迟测试实测**开机连续丢行**(多任务并发 `printf` → 非重入 `fputc`/`HAL_UART_Transmit` 丢整行,~2s 自恢复;`链路延迟测试说明.md` §9.2)——同类竞态的温和形态,当时列为待办(日志非阻塞化)。
|
||||
@@ -36,7 +36,7 @@ HAL 并发语义(本仓库实现实测,`stm32f4xx_hal_uart.c:1135`):轮
|
||||
| G2 | 指针可控:进入 `HAL_UART_Transmit` 的缓冲恒受本模块控制 | 调用方指针只经拷贝入环、到不了 UART;写者发出的缓冲恒为环形缓冲或模块本地静态缓冲 |
|
||||
| G3 | 不新增任务、不用 C 库堆、不新增内核对象 | 复用既有 defaultTask 作写者;环形缓冲 = 4KB 静态 BSS;复用 `sys_evt` 事件标志位 |
|
||||
| G4 | 内核启动前 / 写者未注册 / ISR / 异常钩子路径不降级 | 回退:有界直发(`dbg_console_write_raw`,≤256B 截断、不等待、不死锁、零内核对象) |
|
||||
| G5 | 缓冲满丢行必须可观测 | 丢弃计数 + 写者刷出 `[ERR] CONSOLE dropped N lines` —— 含 `[ERR]` 模式,`@test` Forbidden 判 FAIL,丢失永不静默 |
|
||||
| G5 | 缓冲满丢行必须可观测 | 丢弃计数 + 写者刷出 `[ERR] CONSOLE dropped N lines` —— 含 `[ERR]` 模式,`/serial-boot-test` Forbidden 判 FAIL,丢失永不静默 |
|
||||
|
||||
### 2.2 约束
|
||||
|
||||
@@ -114,7 +114,7 @@ flowchart LR
|
||||
| 写者未注册(内核启动 → attach) | 任何任务都可能记日志(2026-10-26 起这是**常态而非违规**) | **整行独占**(行级单一所有者,§4.4.1):同一时刻只有一个上下文在写 `CON_UART->DR`,抢不到独占的整行转入环形缓冲延后发 → 交错结构性消除,且不丢行 |
|
||||
| 写者已注册(稳态) | 任务上下文唯一调用者 = 写者 | 任务上下文零竞争;残余窗口 = ISR 直发撞写者发送中(丢失、不挂死,§7) |
|
||||
|
||||
**纪律(2026-10-10 固化,2026-10-10 / 2026-10-26 两次台架实证其不足)**:周期 / 常驻任务**不得**在写者注册前记日志——用 `dbg_console_writer_ready()`(§5)门控自身日志(`adcTask` 的首帧与 10 s 心跳、`protectTask` 的启动锚点行即按此门控——后者 2026-10-12 B6 台架实证:B5 接线时启动行未门控,注册前裸发被交错损坏致 `@test` 锚点 MISS,见 §11 变更记录)。内核前代码仍可直接裸发(单线程,无并发)。
|
||||
**纪律(2026-10-10 固化,2026-10-10 / 2026-10-26 两次台架实证其不足)**:周期 / 常驻任务**不得**在写者注册前记日志——用 `dbg_console_writer_ready()`(§5)门控自身日志(`adcTask` 的首帧与 10 s 心跳、`protectTask` 的启动锚点行即按此门控——后者 2026-10-12 B6 台架实证:B5 接线时启动行未门控,注册前裸发被交错损坏致 `/serial-boot-test` 锚点 MISS,见 §11 变更记录)。内核前代码仍可直接裸发(单线程,无并发)。
|
||||
>
|
||||
> ⚠ **自愿门控挡不住边沿留痕**——它在 2026-10-12(B6,启动锚点)与 2026-10-26(未标定闭锁边沿,见 §4.4.1)两次被同一个窗口咬到,
|
||||
> 因为"这条行要不要留痕"是**业务边沿**决定的,调用方无法预知它是否落在未注册期。2026-10-26 起该窗口改由**机制**兜住
|
||||
@@ -156,7 +156,7 @@ flowchart TD
|
||||
|
||||
- **整行原子入环**:拷贝与 head 推进在同一次 `taskENTER_CRITICAL` 内完成 → 写者永不见"半行";临界区时长有界(≤192B 拷贝,微秒级)。
|
||||
- **自等待规避**:写者自身记录日志时先 `drain` 腾空(否则"等写者腾出的空间"会等自己,死锁);腾空后入环必立即成功。
|
||||
- **丢弃可观测**:丢弃累计 `s_con_drop`,写者每次 drain 后把累计值刷成 `[ERR]` 行(§4.3)——`[ERR]` 触发 `@test` Forbidden 判定,丢行永不静默。
|
||||
- **丢弃可观测**:丢弃累计 `s_con_drop`,写者每次 drain 后把累计值刷成 `[ERR]` 行(§4.3)——`[ERR]` 触发 `/serial-boot-test` Forbidden 判定,丢行永不静默。
|
||||
- **唤醒**:入环后置 `SYS_EVT_CONSOLE_TX` 命令位 → 写者立即被唤醒(零时延);写者循环另有 50ms 超时兜底(事件位异常也不滞留日志)。
|
||||
|
||||
### 4.3 写者路径(`attach` / `drain`)
|
||||
@@ -250,7 +250,7 @@ sequenceDiagram
|
||||
2. 判据**只用原子性**,不碰 `[ERR]` 计数:① 崩溃行**一整行**逐字节完整(`[ERR] MEMFAULT PC=… LR=… xPSR=… R0..R3=… CFSR=… HFSR=…` 字段齐全、无邻居行字节插入);② 该行在本次启动日志里**恰好出现一次**(既不丢也不重);③ `CONSOLE dropped` = 0。首个字符应为 `[`;前面若出现 ≤1 个移位寄存器残留字节,记为已知残余,不判失败。
|
||||
3. ⚠ **不得在同一条腿要求 `[ERR]` = 0**:崩溃上报本身就以 `[ERR]` 起头,在同一路径上要求「无 `[ERR]`」等于要求这条路径别工作(本次就是这么被台架纠正的)。`[ERR]` 零命中的判据只属于腿 B 之外的**常规回归腿**(3~5 次仅复位不重烧 + 两行整行锚定)。
|
||||
4. **故意复位的注入腿不得并入「连续 N 次仅复位不重烧」的计数**:注入会主动 `NVIC_SystemReset()`,混进常规序列会把正常行为判成异常(栈溢出钩子同理)。两条腿分开跑、分开记台账。
|
||||
5. `@test` 在注入腿**只在命令行运行时**放宽(故障注入的既有台架口径见 `docs/测试说明/板级测试/实机测试实施指南.md` §4,其中已写明"故障注入用例会复位设备多次 → 确认设备非在用/非已部署"):放宽 `--forbidden` / `--max-boot-count`;**不改仓库配置**(改了就等于把"永不出崩溃日志"设成期望状态)。
|
||||
5. `/serial-boot-test` 在注入腿**只在命令行运行时**放宽(故障注入的既有台架口径见 `docs/测试说明/板级测试/实机测试实施指南.md` §4,其中已写明"故障注入用例会复位设备多次 → 确认设备非在用/非已部署"):放宽 `--forbidden` / `--max-boot-count`;**不改仓库配置**(改了就等于把"永不出崩溃日志"设成期望状态)。
|
||||
|
||||
### 4.5 `fputc` 重定向(MicroLIB `printf`)
|
||||
|
||||
@@ -267,7 +267,7 @@ sequenceDiagram
|
||||
| 单行 192B(最坏) | ≈17 ms | `DBG_CON_LINE_MAX` |
|
||||
| 生产者最长等待 | 200 ms(200 × 1ms tick) | ≈9 个发送块的时间,稳态下环恒被及时排空 |
|
||||
| 写者兜底周期 | 50 ms | 事件位失效时日志滞留上限 |
|
||||
| 实测日志时延 | 事件唤醒级(无丢失) | 2026-09-25 @test:67 行开机日志完整、无交错、无丢行告警 |
|
||||
| 实测日志时延 | 事件唤醒级(无丢失) | 2026-09-25 /serial-boot-test:67 行开机日志完整、无交错、无丢行告警 |
|
||||
|
||||
## 5. API 参考
|
||||
|
||||
@@ -304,7 +304,7 @@ sequenceDiagram
|
||||
|
||||
| 失效 | 行为 | 可观测性 | 残余风险 |
|
||||
|------|------|---------|---------|
|
||||
| 缓冲满(日志突发 / 写者卡滞) | 生产者等 200ms 后丢行 | `s_con_drop` 累计 → 写者刷 `[ERR] CONSOLE dropped N lines`(@test FAIL,永不静默) | — |
|
||||
| 缓冲满(日志突发 / 写者卡滞) | 生产者等 200ms 后丢行 | `s_con_drop` 累计 → 写者刷 `[ERR] CONSOLE dropped N lines`(/serial-boot-test FAIL,永不静默) | — |
|
||||
| 写者未注册(内核前) | 单线程直发 | — | 无(无并发) |
|
||||
| 未注册期其他任务直发 | **已消除**(2026-10-26):整行独占,抢不到就整行入环延后发(§4.4.1) | 让路不计数;只有"抢不到且环也满"才进 `s_con_drop` → `[ERR] CONSOLE dropped N lines` | 残余 = 让路行在流里位置后移(内容与直发逐字节相同);主机单测 `TC-CONLOG-170 至 TC-CONLOG-179` 钉死 |
|
||||
| 崩溃上报(四个 Fault)原走独立 HAL 发送口 | **已归一**(2026-10-26 task-26):`hard_fault_report` 的 14 段裸发改走 `dbg_console_write_raw`(整行独占**不**用在这里——异常态空转,见 §4.4.1);ORE 快速失败(= 证据整条蒸发)与 `HAL_GetTick`/`HAL_Delay` 的 tick 依赖随之消失 | 崩溃行 `[ERR] <TAG> PC=… CFSR=…` 现在是单写者输出;实机验证设计见 §4.4.1 末(待台架窗口) | 极早期(`MX_UART5_Init()` 之前)崩溃仍完全静默(NULL 短路);ORE 脏标志本身未消除(§12) |
|
||||
@@ -410,7 +410,7 @@ sequenceDiagram
|
||||
- `项目架构总览.md` —— 六层分层 / 启动时序 / 6 任务(写者任务与就绪屏障位置)
|
||||
- `运行与自检记录设计说明.md` —— sys_log 持久化日志(姊妹模块:同写者任务、不同通道)
|
||||
- `docs/测试说明/专项验证/链路延迟测试说明.md` —— D6 日志税 / 步骤 8 非阻塞日志(需求出处与 09-22 丢行实证)
|
||||
- `docs/测试说明/流程与规范/自动化测试流程.md` —— `@test` 标记体系(Required / Forbidden 模式,`[ERR]` 语义)
|
||||
- `docs/测试说明/流程与规范/自动化测试流程.md` —— `/serial-boot-test` 标记体系(Required / Forbidden 模式,`[ERR]` 语义)
|
||||
- AGENTS.md —— 平台约束(内核前禁内核对象 / C 堆禁令 / 事件位语义 / 内存预算)
|
||||
|
||||
## 附录:日志静默观察项 × 步骤 8 合并评估(2026-10-25,道F 批 2 / task-18,零代码改动)
|
||||
@@ -457,7 +457,7 @@ D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏
|
||||
- 判据②(静默 → 可观测)= **已具备**:控制台三本账 `dbg_console_dropped /
|
||||
dbg_console_tx_fault / dbg_console_tx_timeout` + syslog 的
|
||||
`queue full, drop` / `preboot full, drop` / `hold-over drop`
|
||||
(`App/log/sys_log_mcu.c`);任何“静默丢行”现形为 `[ERR]` → `@test` FAIL。
|
||||
(`App/log/sys_log_mcu.c`);任何“静默丢行”现形为 `[ERR]` → `/serial-boot-test` FAIL。
|
||||
- 判据①(复现 49s 并定位):V5 30min 未复现;实测(`docs/测试说明/板级测试/实机测试实施指南.md` §7.2)
|
||||
运行期 >10s 静默 24 处**全部落在 e02_probe 窗口、长度 ≈28s**——其
|
||||
同源阻塞机制(`drop_conn` 同步占用 netTask 400-600ms)已随登记式拆链改造修复
|
||||
|
||||
@@ -323,7 +323,7 @@ flowchart TD
|
||||
| 跨工具链分叉(实证,写进来是为了下一个模块别重踩) | 两份录波 .c 只 include 自身头(链上只有 stdint.h)时,ARMCC 报 22 条 identifier "NULL" is undefined;而主机 MSVC / gcc 会传递性把 stddef.h 拉进来,**主机单测完全照不出这个分叉** → 新增模块需要用 NULL 就自己 include stddef.h(与 `App/algo/sample_ring.c` 同口径,别塞公共头) |
|
||||
| 板级 | 信号发生器注 1 V / 50 Hz → 手动一段 → FTP 拉回 → PC 脚本量出周波数、幅值、极性与注入一致;再把 PC 重算的基波有效值与板上 `meas:` 与 104 浮点 YC 对表。**判据只量四样:周波数、幅值、极性、钉轨路数——不比绝对相位**(理由见 §4.2「段内点序」:触发瞬间在工频周期内任意落点,段字节里没有绝对时间参考;真要比相位必须让发生器给同步触发,本波不做)。**钉轨对表的合格判据 = 头位图 ⊇ 实测钉轨路数,且差异必须能由注入条件解释**——因为 `clip_mask` 是按来源派生的链路可信性标记(§6.2 偏移 24 行),不是本段实测值:只加直流或小幅注入时那五路本就不该钉轨,硬取等式会假红。**同段另起一条强观察项**:V0.1 下这五路若在做**交流注入**时反而没钉轨,先怀疑接线或该路根本没被驱动——这是告警面,不是失败面(等式仍作为默认尺子留在 PC 侧 `shape --strict` 里,因为它有这个信号价值;要放行差异用那条判据的解释权,不靠改尺子)。**§5.2 第四条的板级观察项(代码与单测面已有、板级未测)**:bench 连续自抓期间人为把卷占住 2 s 以上(例如同一段跑 FTP 大文件读),看串口 `wave:` 行的 `err` 是否加一、是否只出现**一条** `[ERR] … seg drop: ring abandoned` 留痕(不是每拍刷屏)、且段号不推进(下一段仍写同号文件) |
|
||||
| UI | `ui-verify` 五步;回放页入 `ui-golden` 黄金参考(**参考图一旦入库只能改代码对齐,禁止刷参考掩盖**);新叶走 `gen_menu.py` 重生成 + 字库链 |
|
||||
| 构建 | `@build` 要求 0 错误 0 警告;新 .c 双端注册走 `new-source-file`(`MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 两处显式列表) |
|
||||
| 构建 | `/keil-build` 要求 0 错误 0 警告;新 .c 双端注册走 `new-source-file`(`MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 两处显式列表) |
|
||||
| 台账 | R6 三处同步 + `scripts/ram_baseline.py --update` + `mem-numbers` 与 `mem-budget` 钩子 |
|
||||
| 板级(读路径耗时,2026-10-29 补) | 进回放页后**连续切段 + 连续切道**,读 `task_prof` 里 uiTask 的单轮工作耗时峰值与 800 ms 窗的余量;**不要为此新加埋点**(现成读数够用)。判读:峰值贴着窗走就是读路径该再加节流(先想缓存与分次,别去动心跳窗) |
|
||||
| 图与生成物 | 本设计文档的配图生成器受 `generated-fresh` 逐字节幂等约束;脚本与产物同一笔提交 |
|
||||
|
||||
@@ -284,7 +284,7 @@ static HAL_StatusTypeDef iwdg_deferred_start(const IWDG_HandleTypeDef *a_h) { (v
|
||||
|
||||
| 通道 | 现状 |
|
||||
|---|---|
|
||||
| 串口 | 每次开机第一行区打 `[APP] reset cause: <类型> (CSR=0x…)`(`@test` 已把它当启动锚点并校验次数与期望值) |
|
||||
| 串口 | 每次开机第一行区打 `[APP] reset cause: <类型> (CSR=0x…)`(`/serial-boot-test` 已把它当启动锚点并校验次数与期望值) |
|
||||
| `selftest.log` | **看门狗/窗口看门狗复位 → `syslog_post(SYSLOG_SELFTEST, "watchdog reset")`**(只记故障,符合既有约定) |
|
||||
| `run.log` | 仍是"上电 / 重启"二分类(靠末条 epoch 时间窗推断),**未接入硬件复位原因**——细粒度原因不上 `run.log`/UI(避免中文字符集重生成),见 §9 O2 |
|
||||
| `/log/wdev.log` | 停喂现场证据(`sys_log_evidence()`,>8 KB 先 unlink 重建,防长跑无界增长) |
|
||||
@@ -406,7 +406,7 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗
|
||||
|
||||
| 标记 | 时机 | 含义 |
|
||||
|---|---|---|
|
||||
| `[APP] reset cause: <类型> (CSR=0x%08X)` | `app_main_init` 早期 | 复位原因 + `RCC_CSR` 原值(`@test` 启动锚点,出现 0 次或 >1 次判 FAIL) |
|
||||
| `[APP] reset cause: <类型> (CSR=0x%08X)` | `app_main_init` 早期 | 复位原因 + `RCC_CSR` 原值(`/serial-boot-test` 启动锚点,出现 0 次或 >1 次判 FAIL) |
|
||||
| `PREVRUN seg=… seq=… t=…` | 开机(裸 UART) | 上一轮 defaultTask 最后进段(magic 命中才打,一次复位只报一次) |
|
||||
| `TICKRUN uw=… n=… duw=…` | 紧随其后 | 上一轮 HAL 时基终值;`duw≈0` → TIM7 与主循环同刻停摆;`duw≈2000+` → 另有阻塞点 |
|
||||
| `[WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms` | `task_wd_start()` | IWDG 已启动(真机自检必查标记) |
|
||||
@@ -417,7 +417,7 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗
|
||||
| `[ERR] HARDFAULT / MEMFAULT / BUSFAULT / USAGEFAULT PC=… CFSR=… [BFAR=] [MMFAR=]` | 4 个 Fault | 崩溃现场 + 故障名归因 |
|
||||
| `[ERR] STACK OVERFLOW task=<name>` / `[ERR] MALLOC FAILED …` | 内核钩子 | 栈溢出(复位)/ 堆耗尽(仅告警) |
|
||||
|
||||
> **与 `@test` 禁项的关系**:`run_boot_test.py` 的 Forbidden 含 `[ERR]` → **任何崩溃日志都会让本次运行判 FAIL**(刻意)。四个 Fault 打各自名字的意义在于**归因**,不必再从笼统 `HARDFAULT` 反推 `CFSR` 子域。
|
||||
> **与 `/serial-boot-test` 禁项的关系**:`run_boot_test.py` 的 Forbidden 含 `[ERR]` → **任何崩溃日志都会让本次运行判 FAIL**(刻意)。四个 Fault 打各自名字的意义在于**归因**,不必再从笼统 `HARDFAULT` 反推 `CFSR` 子域。
|
||||
> 日志标签:`[APP]` = `app_main.c` 的 `DBG_TAG`;`[WD]` = `task_watchdog.c` 的 `DBG_TAG`。全部纯 ASCII(规避 ARMCC CP936 字符串陷阱)。
|
||||
|
||||
### 6.2 判读顺序(现场"设备反复重启")
|
||||
@@ -433,7 +433,7 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗
|
||||
|
||||
| # | 场景 | 结果 | 证据 / 备注 |
|
||||
|---|---|---|---|
|
||||
| V1 | 正常启动、长期不复位 | ✅ 常态成立 | 每次 `@test` 开机日志均含 `[WD] armed:`;2026-10-10 复测开机 524 ms 内完成、无 stall |
|
||||
| V1 | 正常启动、长期不复位 | ✅ 常态成立 | 每次 `/serial-boot-test` 开机日志均含 `[WD] armed:`;2026-10-10 复测开机 524 ms 内完成、无 stall |
|
||||
| V2 | **任务饿死**(netTask 插 `while(1)`) | ✅ **2026-10-03 闭环** | 800 ms 声明 `task stall: mask=0x00000001 req=0x00000037` → ≤2.5 s IWDG 复位(`reset cause: IWDG`,`RCC_CSR=0x24000000`)。首轮曾不复位,根因是证据落盘自身喂狗 → 加门闩(§5.3) |
|
||||
| V3 | 断言失败 | 🟡 未单独注入 | 宏与钩子已在用(`configASSERT` 全内核可达);行为经编译与代码路径核对,未做定向注入用例 |
|
||||
| V4 | **关中断死循环** | ✅ **2026-10-03 闭环** | `__disable_irq()` + `while(1)` → 每 ~4.9 s 稳定复位(20+ 周期,`IWDG`)→ 证实 IWDG 独立于中断 |
|
||||
@@ -533,7 +533,7 @@ python scripts/check_cubemx_survival.py --write # 确认改动无误后重建
|
||||
|
||||
- **清单 21 项**覆盖 P0/P1 全部防线(iwdg 延后启动、4 个 Fault Handler 采集、故障使能、两个加固宏、断言改造 ×2、看门狗模块与工程挂载、复位原因锁存、HAL 驱动与使能宏、`.ioc` 关键项、钩子落点三项)。
|
||||
- **漂移检测**:各项内容哈希存 `.cubemx_survival_baseline.json`;有意改动确认后 `--write` 重建。
|
||||
- ⚠ `MDK-ARM/*.uvprojx` **排除在漂移检测之外**(`no_baseline=True`)——该文件每次 `@build` 后被 UV4 改写(XML 归一化 + `PackID` 降级),参与检测会每次构建后都被拦;只保留"源文件仍挂载"断言。
|
||||
- ⚠ `MDK-ARM/*.uvprojx` **排除在漂移检测之外**(`no_baseline=True`)——该文件每次 `/keil-build` 后被 UV4 改写(XML 归一化 + `PackID` 降级),参与检测会每次构建后都被拦;只保留"源文件仍挂载"断言。
|
||||
- **注释过滤**:断言前剥掉 C 注释,故 `protected` 项的 `must_contain` **只能断言代码符号**(锚点注释会被剥掉,断言它等于没断言)。
|
||||
- 已挂 pre-commit `cubemx-survival`(改 `.ioc`/`Src/`/`Inc/`/`task_watchdog`/`.uvprojx` 触发)。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> **版本**:v1.3(§4.4 新增"卷锁 fail-open 边沿补投自检记录"一条,道G task-40;v1.2 为 §4.3 随七类记录更新,v1.1 的原始设计口径不变)
|
||||
> **日期**:2026-09-14(v1.2 更新 2026-10-26)
|
||||
> **状态**:步骤 1–9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 @test 待上电回归(§8.2)
|
||||
> **状态**:步骤 1–9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 /serial-boot-test 待上电回归(§8.2)
|
||||
> **关联文档**:`项目架构总览.md` · `STM32F4-Base存储架构说明.md` · `系统墙钟时间维护说明.md` · `UI设计/内容页架构.md`(如存在)· `docs/UI设计/mockups/`(待机显示页面.jpg / 运行记录页.jpg / 自检记录页.jpg / 密码页面实际机器的示例图片.jpg)
|
||||
|
||||
## 1. 背景与需求
|
||||
@@ -186,7 +186,7 @@ defaultTask(唯一写者,挂载成功后):
|
||||
复位 latch 后故障复发仍会再投(否则第二次开机起永久静默)。文本形如
|
||||
`vollock failopen=3`(累计值就是证据,不带态号),落 `selftest.log`。
|
||||
⚠ 三条约束(与 task-23「日志让路不计 `[ERR]`」同一根判据):① 不在失败调用点就地打日志;
|
||||
② 不打 DBG 错级、不打 `[ERR]`——`[ERR]` 是"设备出故障"的唯一信号通道(`@test` 的 Forbidden
|
||||
② 不打 DBG 错级、不打 `[ERR]`——`[ERR]` 是"设备出故障"的唯一信号通道(`/serial-boot-test` 的 Forbidden
|
||||
也依赖它),塞进可观测性事件就同时不再是故障、不再是禁令;③ 记录自带的时间戳是
|
||||
**被观测到的时刻**(粒度 = 一轮 `syslog_drain()`),不是发生时刻,所以本模块刻意不再另造
|
||||
`obs_first_ms`/`obs_last_ms` 字段去"看起来更精确"。主机判据:`TC-SYSLOG-137` 至
|
||||
@@ -253,7 +253,7 @@ defaultTask 挂载成功、排空待发队列**之后**写一条:
|
||||
```
|
||||
|
||||
- 阈值 `SYSLOG_REBOOT_WINDOW_S = 600`:断电 <10min 会被误判为重启——无掉电检测硬件时该歧义不可消除,取工程折中并在此声明。
|
||||
- 串口标记(供 @test):`runlog boot entry: 装置上电记录 / 装置重启记录`(`sys_log_mcu.c` `DBG_INFO`,与落盘描述串同源)。
|
||||
- 串口标记(供 /serial-boot-test):`runlog boot entry: 装置上电记录 / 装置重启记录`(`sys_log_mcu.c` `DBG_INFO`,与落盘描述串同源)。
|
||||
|
||||
### 5.4 R4 自检记录(v1 项表)
|
||||
|
||||
@@ -311,7 +311,7 @@ defaultTask 挂载成功、排空待发队列**之后**写一条:
|
||||
| 记录页像素回归 | `--keys` 导航至 node 39/40 → 截图 + 文本断言(时间戳行/描述行/页脚分页),纳入 ui-verify 门禁 |
|
||||
| 密码错误提示 | `--keys` 输错密码序列 → 截图断言"密码输入错误"可见;再按方向键 → 提示消失 |
|
||||
|
||||
### 8.2 真机(@test 串口标记)
|
||||
### 8.2 真机(/serial-boot-test 串口标记)
|
||||
|
||||
Required:`App init OK`、`Network Init`、`net init: OK`、**`runlog boot entry:`**(装置上电记录 / 装置重启记录)、**`UI standby: ready (idle=60s)`**;
|
||||
Forbidden:`FAIL`/`ERROR`/`HardFault`。
|
||||
@@ -341,5 +341,5 @@ Forbidden:`FAIL`/`ERROR`/`HardFault`。
|
||||
| 步骤 5 | 待机屏 `ui_standby`(空闲检测 + 覆盖层)+ 键挂钩 | ui_menu/ui_content_page 各一行通知 |
|
||||
| 步骤 6 | 记录页 ×2 + 内容页框架接线(枚举/描述符表/node 映射) | ui_page_runlog/selftest、ui_content_page.h/.c、ui_menu.c |
|
||||
| 步骤 7 | 双端注册新文件(uvprojx + CMakeLists)+ 构建 0/0 | new-source-file skill |
|
||||
| 步骤 8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 @test | 测试通过记录 |
|
||||
| 步骤 8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 /serial-boot-test | 测试通过记录 |
|
||||
| 步骤 9 | 文档同步(项目架构总览、AGENTS.md、本文档状态) | docs |
|
||||
|
||||
@@ -495,13 +495,13 @@ void adc_cfg_init(void)
|
||||
| 阶段 | 内容 | 涉及文件 | 验收标准 |
|
||||
|---|---|---|---|
|
||||
| **P1** | 设计文档 + 架构图 | 本文档 + `gen_cfg_arch_svg.py` + SVG | 用户确认 §2 决策 |
|
||||
| **P2** | FS 线程安全 + 时间戳 | `ffconf.h`、`Lib/FatFs/ffsystem.c`(新)、`App/util/sys_clock.c`(sys_clock_get_time;移入中间件层)、`uvprojx`、架构总览 §1.1(sys_clock 改归 ④) | `@build` 0/0;`@test` 启动 PASS(无死锁) |
|
||||
| **P3** ✅ 2026-09-14 | cfg_file 引擎(双端)+ net_cfg 集成 + 旧档迁移 | `App/cfg/cfg_file.{h,c}`、`App/cfg/cfg_file_store.h`、`App/cfg/cfg_file_store_mcu.c`(新)、`simulator/sim/cfg_file_store_sim.c`(新)、`net_cfg.{h,c}`(段注册 + 引擎化 load/save)、`net_cfg_legacy.c`(新,NCFG 迁移)、删 `net_cfg_store*`、`uvprojx`、`CMakeLists.txt` | Keil `@build` 0/0;模拟器生成 `sim_config.ini` 且编辑回读一致;旧档 NCFG 自动迁移三态正确 —— **已实测通过**(工厂模板 / 编辑回读 / 非法键回退 / 迁移三态 / 双端 0 警告;`cfg_file.c` 含中文注释块 → UTF-8 **带 BOM**) |
|
||||
| **P4** | 轮询 + D15 协议 + 测试标记 | `net_task.c`(沉降去抖轮询 + 落盘收敛 + 串口标记)、`lftpd.c`(STOR tmp+rename) | `@build`;`@test` 启动新标记(`cfg file: loaded OK` 等);FTP 上传无半截可见(rename 原子) |
|
||||
| **P2** | FS 线程安全 + 时间戳 | `ffconf.h`、`Lib/FatFs/ffsystem.c`(新)、`App/util/sys_clock.c`(sys_clock_get_time;移入中间件层)、`uvprojx`、架构总览 §1.1(sys_clock 改归 ④) | `/keil-build` 0/0;`/serial-boot-test` 启动 PASS(无死锁) |
|
||||
| **P3** ✅ 2026-09-14 | cfg_file 引擎(双端)+ net_cfg 集成 + 旧档迁移 | `App/cfg/cfg_file.{h,c}`、`App/cfg/cfg_file_store.h`、`App/cfg/cfg_file_store_mcu.c`(新)、`simulator/sim/cfg_file_store_sim.c`(新)、`net_cfg.{h,c}`(段注册 + 引擎化 load/save)、`net_cfg_legacy.c`(新,NCFG 迁移)、删 `net_cfg_store*`、`uvprojx`、`CMakeLists.txt` | Keil `/keil-build` 0/0;模拟器生成 `sim_config.ini` 且编辑回读一致;旧档 NCFG 自动迁移三态正确 —— **已实测通过**(工厂模板 / 编辑回读 / 非法键回退 / 迁移三态 / 双端 0 警告;`cfg_file.c` 含中文注释块 → UTF-8 **带 BOM**) |
|
||||
| **P4** | 轮询 + D15 协议 + 测试标记 | `net_task.c`(沉降去抖轮询 + 落盘收敛 + 串口标记)、`lftpd.c`(STOR tmp+rename) | `/keil-build`;`/serial-boot-test` 启动新标记(`cfg file: loaded OK` 等);FTP 上传无半截可见(rename 原子) |
|
||||
| **P5** ✅ 2026-09-14 | 模拟器迁移 | `simulator/sim/cfg_file_store_sim.c`(新)、删 `net_cfg_store_sim.c`、`simulator/main.c`(2s 轮询 + `--cfgtest` 自检)、`CMakeLists.txt` | sim 构建 0 警告 + 4 场景无头 PASS(工厂 / 有效旧档迁移 / 无效旧档保留 / UI 无头);`sim_netcfg.bin` 自动迁移 + 轮询沉降重载 + 外科保存无损回环 —— **已实测通过** |
|
||||
| **P6** | 文档同步 | `网络配置说明.md`(§0/3/6/7 重写)、`AGENTS.md`、`README.md`、`项目架构总览.md` + `gen_arch_svg.py`、`内容页架构.md`、BSD 指南、`simulator/README.md`、`ui_page_net_cfg.c` 头注释、`docs/测试说明/` | pre-commit 全绿;全库无"Flash"旧表述残留 |
|
||||
| **P7** | FTP 闭环脚本 | `[计划] test/ftp_config_test.py`(尚未创建,见 §11.2 设计稿)+ 测试说明 | 真机闭环 PASS(§11.2) |
|
||||
| **P8** | 总回归 + 提交 | `@build` + `@test` + 模拟器 ui-verify + `pre-commit --all-files` | 0 错误 0 警告;全 PASS |
|
||||
| **P8** | 总回归 + 提交 | `/keil-build` + `/serial-boot-test` + 模拟器 ui-verify + `pre-commit --all-files` | 0 错误 0 警告;全 PASS |
|
||||
|
||||
**建议提交边界**:C1 = P2+P3(FS 线程安全 + 配置引擎)/ C2 = P4+P5(网络配置迁移 + 模拟器)/ C3 = P6+P7(文档 + 测试脚本);P8 发现的收尾问题并入最近一次提交。
|
||||
|
||||
@@ -511,7 +511,7 @@ void adc_cfg_init(void)
|
||||
|
||||
| 串口标记 | 场景 | 属性 |
|
||||
|---|---|---|
|
||||
| `cfg file: loaded OK` | 上电加载成功 | Required(P4 起纳入 `@test`) |
|
||||
| `cfg file: loaded OK` | 上电加载成功 | Required(P4 起纳入 `/serial-boot-test`) |
|
||||
| `cfg file: factory template written` | 首启 / 删文件 | 条件(手工) |
|
||||
| `cfg file: legacy NCFG migrated` | 旧文件迁移 | **已作废**(2026-10-10 旧档迁移删除,该标记不再产生) |
|
||||
| `cfg file: saved OK` | UI 保存 | 条件(手工) |
|
||||
|
||||
+2
-2
@@ -165,7 +165,7 @@
|
||||
|
||||
1. **昵称式引用**(「内存方案 §2.1」)与正文里的裸文件名散文 → 落盘后 `git grep -n '<旧文件名>'` 扫一遍;
|
||||
2. **生成物**(`docs/images/引用关系图.*`)不改写 → 重跑 `& 'tools\refgraph\gen_refgraph.bat'`;
|
||||
3. 改的是 `.c/.h` 且**新增了目录** → 还要把目录加进 `uvprojx` 的 `<IncludePath>`(→ `new-source-file` skill),随后 `@build` 验证。
|
||||
3. 改的是 `.c/.h` 且**新增了目录** → 还要把目录加进 `uvprojx` 的 `<IncludePath>`(→ `new-source-file` skill),随后 `/keil-build` 验证。
|
||||
4. ~~搬整目录时会改写目录内部文件的内容~~ **已修**(2026-10-30,回归见 `--selftest` 案例 A):根因是
|
||||
「相对引用文件所在目录」那条解析规则不检验目标是否存在,于是内部文件里**不是路径的斜杠片段**被
|
||||
当成旧目录下的子路径并插上目录名 —— 分层那次 `1/1 PASSED` 改成 `test_data/1/1 PASSED`、日志日期占位
|
||||
@@ -262,7 +262,7 @@
|
||||
```
|
||||
|
||||
> **改栈流程**:先改 `Src/freertos.c` 的 `osThreadAttr_t` → 跑 `gen_task_table.py --write` → 提交。
|
||||
> **改内存流程**:先改代码 + `@build` 出 `.map` → 跑 `check_mem_numbers.py --write` → 提交。
|
||||
> **改内存流程**:先改代码 + `/keil-build` 出 `.map` → 跑 `check_mem_numbers.py --write` → 提交。
|
||||
> 钩子报不一致时**不要手改受管区**——跑 `--write` 让脚本重算。
|
||||
|
||||
#### 陈旧数字扫描(`mem-numbers --scan`)
|
||||
|
||||
@@ -610,9 +610,9 @@ FTP 根目录 = 卷根,lftpd 是卷的网络文件入口(`Drivers/BSP/NET/lf
|
||||
lftpd 原先把全部异常路径一律按 error 级打(串口上带 `[ERR]`),其中**九处其实由外部输入决
|
||||
定**:客户端关窗口、连上就走、PASV 之后不建数据连接、运维改 IP 触发监听重建。代价实测两条:
|
||||
|
||||
1. `@test` 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀)——"FileZilla 关了
|
||||
1. `/serial-boot-test` 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀)——"FileZilla 关了
|
||||
个窗口"就能把一次启动测试判 FAIL。实测就是这样踩到的:抓自检台账基线那次 FTP 会话结束后,
|
||||
设备打 `[ERR] [FTP] error reading next command`,本次 `@test` 直接 FAIL。
|
||||
设备打 `[ERR] [FTP] error reading next command`,本次 `/serial-boot-test` 直接 FAIL。
|
||||
2. 任何对 21 端口"连一下就断开"的外部行为,都能**无限制造固件级 ERROR 日志**——日志面与门禁
|
||||
面都可被外部输入驱动,这在工业现场是过不了审的。
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
| **USART1(U5 直连 TTL)** | UART | PA9 TX / PA10 RX | **700000 8N1 → 屏板链路**(TTL 直连、无 DE,`../产品需求/产品需求设计文档.md` §5.16.2);PA10 双向均已接线;日志已迁 UART5 |
|
||||
| **UART4(RS-485)** | UART | PC10 TX / PC11 RX / **PA15 DE** | 半双工通信(V0.1 轮换后 RS485#4 = UART4,`../硬件平台/V0.1_网表详细分析报告.md` §4.2) |
|
||||
| **USART2(RS-232 #3)** | UART | PD5 TX / PD6 RX | 经 U10 隔离 → U13 TPT3232E → CN1;固件目前未占用(日志实际走 UART5/RS232#1) |
|
||||
| **UART5(RS-232 #1)** | UART | PC12 TX / PD2 RX | 经 U9 隔离 → U13 → CN1;**日志口**(115200,`@test` 采集通道;台架端子 `../子系统设计/屏板链路设计说明.md` §3.2) |
|
||||
| **UART5(RS-232 #1)** | UART | PC12 TX / PD2 RX | 经 U9 隔离 → U13 → CN1;**日志口**(115200,`/serial-boot-test` 采集通道;台架端子 `../子系统设计/屏板链路设计说明.md` §3.2) |
|
||||
| **状态 LED ×6** | GPIO | PC5 / PB0 / PB1 / PB2 / PF11 / PF12 | **低电平点亮** |
|
||||
| SWD | — | PA13 SWDIO / PA14 SWCLK | V0.1 已修复 V0.0 的 SWCLK 引脚缺陷 |
|
||||
| 调试口 | — | CN2(XH-3A,1=SWDIO/2=SWCLK/3=GND) | V0.1 由 H2 排针改为 XH 端子 |
|
||||
|
||||
+3
-3
@@ -351,15 +351,15 @@ STM32F4-Base/
|
||||
```mermaid
|
||||
flowchart LR
|
||||
SRC["共享源码<br/>Drivers/BSP/UI/*<br/>Drivers/BSP/NETCFG/net_cfg.c<br/>App/cfg/cfg_file.c"]
|
||||
SRC --> MCU["MCU 端<br/>Keil MDK-ARM (ARMCC v5)<br/>@build → BUILD_OK"]
|
||||
SRC --> MCU["MCU 端<br/>Keil MDK-ARM (ARMCC v5)<br/>/keil-build → BUILD_OK"]
|
||||
SRC --> SIM["PC 模拟器<br/>CMake + SDL2 (MSVC)<br/>lvgl_sim.exe --keys"]
|
||||
MCU --> TEST["@test:串口采集 + 烧录 + 日志校验"]
|
||||
MCU --> TEST["/serial-boot-test:串口采集 + 烧录 + 日志校验"]
|
||||
SIM --> REG["UI 回归:<br/>check_menu_rows.py<br/>check_content_pages.py"]
|
||||
```
|
||||
|
||||
| 目标 | 工具链 | 验证 |
|
||||
|---|---|---|
|
||||
| MCU 固件 | Keil MDK-ARM v5(`@build` = `keil-build` skill) | `@build` 0 错 0 警 → `@test` 串口闭环 |
|
||||
| MCU 固件 | Keil MDK-ARM v5(`/keil-build` = `keil-build` skill) | `/keil-build` 0 错 0 警 → `/serial-boot-test` 串口闭环 |
|
||||
| PC 模拟器 | CMake + SDL2 + MSVC(`simulator/build/`) | `check_menu_rows.py` + `check_content_pages.py`(文本断言 + 像素黄金参考) |
|
||||
|
||||
**纯逻辑模块第三端**:录波(`wave_ring`/`wave_store`)、保护判据(`prot_set`/`prot_elm`)、遥测快照(`meas`)、SOE 编解码(`soe_rec`)、屏板协议层(`sb_proto`)等零平台依赖,被 `test/host/` 原样 `#include` 免桩直跑(覆盖率总闸全量真跑)——主机侧就是它们的"第三端"。
|
||||
|
||||
@@ -328,9 +328,9 @@ TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性,
|
||||
| 信号发生器 | 正弦 / 方波 / 三角,**并有 DC 档**;频率可锁 50.000 Hz、也能设到 2 Hz;幅度 0~3 Vpp | **只要求"波形干净、幅度稳、频率锁得住"**,它的刻度不当基准(`docs/任务规划/数据可信.md` §7 #9)。DC 档是给削顶轮改法 A 用的 |
|
||||
| ⚠ 发生器输出端接设置 | 接板子时设成**高阻负载**,不要按 50 Ω 端接 | 按 50 Ω 端接而实际接的是高阻板输入 → 面板值与真实电压差 2 倍;这是台架"差一倍"最常见来源 |
|
||||
| 万用表 AC 档 | 能读到 mV;**记明型号与响应类型**(真 RMS / 均值响应) | 基准就是它;波形轮那一步两种表的读数都与基波期望差很多,不记响应类型就说不清是谁错 |
|
||||
| 板子 | 产品构建(`test/board/test_config.h` 四个 `TEST_SUITE_*` 全注释)+ `@build` 0 错误 0 警告 + 烧录 `-Rst -Run` 与回读校验 | → `stlink-flash` skill 与 AGENTS.md「构建与验证」 |
|
||||
| 板子 | 产品构建(`test/board/test_config.h` 四个 `TEST_SUITE_*` 全注释)+ `/keil-build` 0 错误 0 警告 + 烧录 `-Rst -Run` 与回读校验 | → `stlink-flash` skill 与 AGENTS.md「构建与验证」 |
|
||||
| `[adc_cal]` 状态 | **保持未标定**(`eng=0`),别为"读数好看"先写标定值 | 现在 `meas:` / `phasor` 的量纲是 **ADC 引脚电压 V**(`App/algo/meas.h` 文件头的 A-lite 口径);标定一生效就换成二次值,本节全部期望值作废 |
|
||||
| 串口 | 全程抓 RX 落盘:用 `serial-assistant` skill(`open` → `clear` → 跑 → 读日志文件或 `log --tail`) | 日志文件就是证据本体,§8.10 归档归档的就是它;**长跑别用 `@test`**——它绑定一轮启动语义(静默 8 s、总超时 30 s、`max_boot_count` = 1,且 forbidden 含 `[ERR]`,而削顶轮故意造 `[ERR]`),约 135 min 的 session 必然超时或误判;daemon 全量写 `state/serial.log`(20 MB 上限自动截头保尾 10 MB)→ `docs/测试说明/流程与规范/自动化测试流程.md` 长时间采集一节 |
|
||||
| 串口 | 全程抓 RX 落盘:用 `serial-assistant` skill(`open` → `clear` → 跑 → 读日志文件或 `log --tail`) | 日志文件就是证据本体,§8.10 归档归档的就是它;**长跑别用 `/serial-boot-test`**——它绑定一轮启动语义(静默 8 s、总超时 30 s、`max_boot_count` = 1,且 forbidden 含 `[ERR]`,而削顶轮故意造 `[ERR]`),约 135 min 的 session 必然超时或误判;daemon 全量写 `state/serial.log`(20 MB 上限自动截头保尾 10 MB)→ `docs/测试说明/流程与规范/自动化测试流程.md` 长时间采集一节 |
|
||||
| 网口(可选) | 要读 104 那一路就插网线、PC 与板同网段(出厂 192.168.1.100),跑现成主站 `test/host/iec104_master.py`(`--ip` / `--port` / `--addr`) | 104 是浮点无量化 + 带品质,是 `meas:` 之外的第二条腿 |
|
||||
| FTP 客户端 | 走现成 21 端口取段文件(出厂 `[ftp]` 账号,连续 5 次失败锁 60 s) | 段文件是相位 / 谐波轮定量档的样本出口;段文件名 `/wave/WAVE<NN>.BIN`(00~99 轮转),**取完立刻改名归档**,否则下一次触发把它盖掉 |
|
||||
| 录波触发路径 | 产品构建里唯一**免改代码**的路径 = UI 菜单第 26 叶「手动录波」确认页(`Drivers/BSP/UI/ui_page_wave_manual.c` → `App/task/ui_task.c:605`) | 保护启动上升沿也算一次触发,但保护判据现处**未标定闭锁**态(AGENTS.md「现状」),别指望它自动来段;bench 定时自抓在双层编译门 `DTU_BENCH` 且 `WAVE_BENCH_AUTOCAP` 非 0 之内(默认 0,`App/task/adc_task.c:153`),开它就走 §8.9 的还原判据 |
|
||||
@@ -389,7 +389,7 @@ flowchart LR
|
||||
|
||||
```text
|
||||
日期 ____ / 操作人 ____ / 板子序列号 ____
|
||||
固件 commit ________(@build 0 错误 0 警告:是 / 否)
|
||||
固件 commit ________(/keil-build 0 错误 0 警告:是 / 否)
|
||||
发生器:型号 ______ 输出端接 = 高阻 / 50 Ω 波形 ____ 频率设定 ____
|
||||
万用表:型号 ______ AC 响应类型 = 真 RMS / 均值 共地方式 = 单点 / 发生器浮地
|
||||
注入点:路名 ____(端子侧 U5 / 引脚侧外串 ____ Ω)
|
||||
@@ -521,7 +521,7 @@ flowchart TD
|
||||
| 读数 | `freq(hb): f=.. valid=..`;UI 交流量页 `Hz`;104 `0x401D` |
|
||||
| 期望(A 档 = 现状) | **就是不出数**:幅值门 `FREQ_AMP_MIN 50.0f`(`App/algo/freq.h:62`,`App/algo/freq.c:231` 拿它与基波有效值比)在引脚电压域恒不过 → `valid=0`、`f=0`、UI `Hz` 显 `--`、104 `0x401D` 上送 IV |
|
||||
| 判据 | **如实报不可测**,而不是报一个看起来对的 50.00。若门限没改却报出 50.00 → 反而是 bug,停下查 `App/algo/freq.c` |
|
||||
| B 档(要用户先点头) | 临时把 `FREQ_AMP_MIN` 由 `50.0f` 降到 `0.5f` → 重编(`@build`,要 build 锁)+ 重烧 → 扫 45.000 / 50.000 / 55.000 Hz 三点,PC 侧判据 0.02 Hz → 跑完**必须还原**,收尾判据见 §8.9 |
|
||||
| B 档(要用户先点头) | 临时把 `FREQ_AMP_MIN` 由 `50.0f` 降到 `0.5f` → 重编(`/keil-build`,要 build 锁)+ 重烧 → 扫 45.000 / 50.000 / 55.000 Hz 三点,PC 侧判据 0.02 Hz → 跑完**必须还原**,收尾判据见 §8.9 |
|
||||
| 坑 | 非整 50 Hz 时 `meas:` 会拍频式跳动,属预期 → **扫频读 `freq(hb)`,别读 `meas:`**(`docs/任务规划/数据可信.md` §8 风险表同一条);测频的取源顺序是先 `UA`(相量实例 C02)再退 `IA`(C01)(`App/algo/freq.c:47`)——本卡注 `UA` 走的就是 UA 源;若哪天只注 `IA`,测频会跟着 IA 走,别以为它锁在 UA |
|
||||
|
||||
记录表(A 档):
|
||||
@@ -627,8 +627,8 @@ flowchart TD
|
||||
|
||||
### 8.7 `[ERR]` 的判读规则(削顶轮单独跑、单独归档)
|
||||
|
||||
- 削顶轮会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `@test` 的 forbidden 标记含
|
||||
`[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `@test` 的标记校验当结论**:改人工判读,并在归档文件名与
|
||||
- 削顶轮会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `/serial-boot-test` 的 forbidden 标记含
|
||||
`[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `/serial-boot-test` 的标记校验当结论**:改人工判读,并在归档文件名与
|
||||
抬头里写明「预期 [ERR]:贴轨锁存负向用例」;§8.6 已把它排在最后单独一轮(`docs/任务规划/数据可信.md` §8 风险表「削顶负向用例被当成固件 bug」行同一条)。
|
||||
- 除这一条 `[ERR] rail:` 之外,**任何 `[ERR]` 都是不通过**;四个 Fault 的崩溃日志也以 `[ERR]` 开头 →
|
||||
出现 Fault 立即停线取现场(`docs/子系统设计/看门狗设计说明.md` §10),别再继续注。
|
||||
@@ -659,9 +659,9 @@ flowchart TD
|
||||
|
||||
| 临时改动 | 收尾判据 |
|
||||
|---|---|
|
||||
| 测频轮 B 档:`FREQ_AMP_MIN` 50.0f → 0.5f(`App/algo/freq.h:62`) | 还原 + `@build` 0 错误 0 警告 + **`git diff` 里 `App/algo/freq.h` 必须为空** + 在 `docs/任务规划/数据可信.md` §7 #8 那行留一句"本轮台架已还原" |
|
||||
| 测频轮 B 档:`FREQ_AMP_MIN` 50.0f → 0.5f(`App/algo/freq.h:62`) | 还原 + `/keil-build` 0 错误 0 警告 + **`git diff` 里 `App/algo/freq.h` 必须为空** + 在 `docs/任务规划/数据可信.md` §7 #8 那行留一句"本轮台架已还原" |
|
||||
| 相位轮改倍数:`log_phasors()` 的 `* 10.0f` → `* 1000.0f`(`App/task/adc_task.c:209`) | 同上,`git diff` 里 `App/task/adc_task.c` 必须为空 |
|
||||
| 为 bench 定时自抓开过编译门(`DTU_BENCH` + `WAVE_BENCH_AUTOCAP` 非 0,`App/task/adc_task.c:153`) | 还原成默认 0 + `@build` 0 错误 0 警告 + `git diff` 里 `App/task/adc_task.c` 必须为空(产品镜像里这段必须零存在) |
|
||||
| 为 bench 定时自抓开过编译门(`DTU_BENCH` + `WAVE_BENCH_AUTOCAP` 非 0,`App/task/adc_task.c:153`) | 还原成默认 0 + `/keil-build` 0 错误 0 警告 + `git diff` 里 `App/task/adc_task.c` 必须为空(产品镜像里这段必须零存在) |
|
||||
| 只动接线与发生器、没动代码 | `git status` 除归档目录外干净 |
|
||||
| 全程 | `test/board/test_config.h` 四个套件宏保持全注释(产品构建);烧录仍须 `-Rst -Run` + 回读校验 |
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
| `--selftest` | 跑内置 **22 条**判别用例后退出,无需日志与硬件(含下表三计数分类器) | "不可能失败的 PASS 就是空跑"——用例里含交错、缺行首、残留 1/2、配对错序,以及**"旧子串口径在同一条缺陷行上判 PASS"** 这一条盲区证明 |
|
||||
|
||||
用法示例(一次仅复位启动的原子性判定)。四个行级开关**也已接进 `run_boot_test.py`**,
|
||||
所以 @test 闭环里直接加 `--exact ... --pair ... --allow-residual 1 --head-char '['` 即可,无需单独跑本脚本:
|
||||
所以 /serial-boot-test 闭环里直接加 `--exact ... --pair ... --allow-residual 1 --head-char '['` 即可,无需单独跑本脚本:
|
||||
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\verify_log.py `
|
||||
@@ -75,9 +75,9 @@
|
||||
2. **出像**:像必须来自**已提交 commit + 干净工作树**。主树脏(别人在飞)时走仓外只读 detached worktree:
|
||||
`git worktree add --detach <_wt>/bench-<ID>-<sha> <sha>` + `mklink /J <wt>\.venv <main>\.venv`;
|
||||
建树后 `git -C <wt> status --short` 必须为空,全程只读、不 commit、不跑生成器。
|
||||
3. **@build**:0 错误 0 警告;随后**从 .hex 二进制里抠身份戳**(不是从 build_log 里读),与之后线上读到的
|
||||
3. **/keil-build**:0 错误 0 警告;随后**从 .hex 二进制里抠身份戳**(不是从 build_log 里读),与之后线上读到的
|
||||
`fw build:` 逐字对上——这是"烧进去的就是这份像"的唯一硬证据。
|
||||
4. **烧录启动**:`@test` 已保证 flash(`-Rst -Run`) → 采日志 → 回读(`-CmpFile`) 的次序(`-CmpFile` 会复位目标,
|
||||
4. **烧录启动**:`/serial-boot-test` 已保证 flash(`-Rst -Run`) → 采日志 → 回读(`-CmpFile`) 的次序(`-CmpFile` 会复位目标,
|
||||
必须排最后)。
|
||||
5. **N 次仅复位对照**(N≥3):每次 `clear`(记下 gen,证明窗口不重叠)→ `stlink_flash.py reset`(`-c SWD -Rst -Run`,
|
||||
**零重烧**)→ `wait-idle` → 取日志逐块存档。"保存的值重启后还在"用重烧去验是**无意义**的。
|
||||
@@ -98,7 +98,7 @@
|
||||
所以"跑 5 次正常启动、[ERR] 零命中"**证不到 IPSR**——那是定义上的必然,是空跑。
|
||||
IPSR 主张交给主机侧套件(假 USART + IPSR + 临界区深度水位);要硬件证据就得由代码道**提交触发器**,
|
||||
判据写"崩溃遗言一整行、恰好一次、`dropped=0`",并按 `../测试数据/boot_log_data/README.md` §2「故障注入日志」
|
||||
运行时放宽 `@test`(`--forbidden` 去掉 `[ERR]`、`--max-boot-count 2`)——**不改仓库配置**。
|
||||
运行时放宽 `/serial-boot-test`(`--forbidden` 去掉 `[ERR]`、`--max-boot-count 2`)——**不改仓库配置**。
|
||||
|
||||
## 5. 栈判据(task-32 定稿:三档分权,不再是"待定")
|
||||
|
||||
@@ -145,7 +145,7 @@ SHA 只当坐标(2026-10-26 那天 SHA 被 amend 挪了三轮)。同镜像
|
||||
## 7. 台架卫生清单(每一条都是实测踩过的,不是设想)
|
||||
|
||||
1. **每个台架窗口开头、以及每一圈复位之前**:`serial_tool.py open` + `status` 断言 `port.ready=true`,并**观察 `rx_lines` 在几秒内增长**。
|
||||
实测:常驻 daemon 会死(今晚 pid 18020 跑了 156589 s 后没了),而 `status` 会**自动拉起一个新 daemon**,于是它一直"正常"地报 `ready=false / rx_lines=0` —— 端口状态**不会**跨小时存活,`@test` 开过也不算数。
|
||||
实测:常驻 daemon 会死(今晚 pid 18020 跑了 156589 s 后没了),而 `status` 会**自动拉起一个新 daemon**,于是它一直"正常"地报 `ready=false / rx_lines=0` —— 端口状态**不会**跨小时存活,`/serial-boot-test` 开过也不算数。
|
||||
2. **`serial_tool.py log / status / clear` 返回 JSON,不是纯文本**。按 `[RX]` 行首过滤纯文本会得到**恒 0 行**且看起来"跑成功了"。
|
||||
3. **Forbidden 令牌不得落在英文单词内部**:裸 `Fault` 命中 `de`+`fault` → 打到 `[FTP] using factory default password` 与 `[STACK] default peak_used`(实测假红两次)。四个 Fault 的崩溃日志都以 `[ERR]` 开头,列 `[ERR]` 就够,要加就加 `MEMFAULT/BUSFAULT/USAGEFAULT/HardFault`。
|
||||
4. **工具输出别混流**:非零退出时 stdout(结果)与 stderr(诊断)拼接后再 `JSON.parse` 必崩;解析只吃 stdout。
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
1. `test/board/test_config.h`:取消注释 `TEST_SUITE_STORAGE`;
|
||||
2. `test/board/storage_test.h`:取消注释 `ENABLE_STORAGE_RECOVERY_TESTS` 与 `STORAGE_RECOVERY_FULL_WIPE`;
|
||||
3. `@build`(要求 0 错误 0 警告);
|
||||
3. `/keil-build`(要求 0 错误 0 警告);
|
||||
4. 采集顺序 `open` → `clear` → 烧录 → `wait-idle`(擦除 2044 块约 4 秒,静默判据给 20~30s);
|
||||
5. 确认日志:
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
```
|
||||
|
||||
6. **断电重启**(`stlink_flash.py reset` 无参即可)——本次启动才会重建干净 journal;
|
||||
7. 恢复产品构建(两个头文件重新注释)→ `@build` → 烧录 → `@test`,确认:
|
||||
7. 恢复产品构建(两个头文件重新注释)→ `/keil-build` → 烧录 → `/serial-boot-test`,确认:
|
||||
|
||||
```
|
||||
[NAND_FTL] No valid map found, creating fresh (err=3)
|
||||
@@ -74,14 +74,14 @@
|
||||
- **判据:只删显式点名的文件,绝不 glob**。清理脚本里写死 27 个文件名逐个 `DELE`;保留清单
|
||||
必须含 **`/config.ini`**(网口与 FTP 鉴权配置)、**`/PROT_SET.BIN`**(保护定值 blob,
|
||||
`PROT_SET_BLOB_SZ` = 176 B,删了等于把定值清零)、**`/log`** 目录(运行与自检记录)。
|
||||
- 三步验收:① 清完 `LIST` 只剩 `LOG` + `CONFIG.INI` + `PROT_SET.BIN`;② `@test` PASS(定值巡检
|
||||
- 三步验收:① 清完 `LIST` 只剩 `LOG` + `CONFIG.INI` + `PROT_SET.BIN`;② `/serial-boot-test` PASS(定值巡检
|
||||
与配置加载都在这条链上);③ 跑 `test/host/ftp_list_fill_test.py` 确认 FTP 面正常。
|
||||
- 这属于**动作 D(清文件)**,与本文 §1 的 A(重建 BBT + 格式化)/ B(全片擦除)/ C(容量恢复)
|
||||
都不同:A/B 会丢卷内容,D 不动文件系统结构。
|
||||
|
||||
### 4.2 自检台账清零,重建联合判定审计基线(2026-10-22 实测)
|
||||
|
||||
`@test --require-selftest-pass --selftest-scope all`(累计口径)在台架上**永远 FAIL**:
|
||||
`run_boot_test.py --require-selftest-pass --selftest-scope all`(累计口径)在台架上**永远 FAIL**:
|
||||
`/log/selftest.log` 里躺着 29 条历史记录,`2026-10-10 09:24:12 → 2026-10-14 06:55:02`,
|
||||
组成为 **16 × `watchdog reset` + 13 × `heap low water`**——都是看门狗引入期与堆迁移期
|
||||
留下的,两件事后来都已结案。新判据(只看本次新增)已不受影响,但**审计口径要能用,就得
|
||||
@@ -97,7 +97,7 @@
|
||||
256 种字节值全部出现、前 64 字节全 `FF` → 就是一份 0..255 图案的写盘校验填充,没有任何
|
||||
产品语义;留档后再 `DELE`(判据同 §4.1:先知道删的是什么)。
|
||||
- 清理后 `/log` 只剩三条、**全是产品数据**:`run.log`(运行记录)、`wdev.log`(掉电取证)、
|
||||
`soe.log`(SOE 动作记录)。之后再跑 `@test --require-selftest-pass` → **PASS**
|
||||
`soe.log`(SOE 动作记录)。之后再跑 `run_boot_test.py --require-selftest-pass` → **PASS**
|
||||
(699 ms、`SELFTEST_BASELINE: 既有记录 0 条`)。
|
||||
- 验收(两条判定路径都要过):`--require-selftest-pass` → `SELFTEST_BASELINE: 既有记录 0 条
|
||||
(status=absent)` → PASS;`--selftest-scope all` → `selftest: no fault record` → **PASS**
|
||||
|
||||
+12
-12
@@ -13,7 +13,7 @@
|
||||
|
||||
> 模拟器覆盖的是 **「纯逻辑 + 平台抽象之上」的那一层**:UI 场景/渲染/导航、配置引擎、日志记录格式、
|
||||
> `net_*` BSD API 之上的协议状态机。它**不覆盖**「硬件时序 / 中断 / 真实复位 / CH395F 与 NAND 芯片行为」。
|
||||
> 因此 **模拟器回归与板端测试套件 / 主机侧回归(`@test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。
|
||||
> 因此 **模拟器回归与板端测试套件 / 主机侧回归(`/serial-boot-test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。
|
||||
|
||||
---
|
||||
|
||||
@@ -34,16 +34,16 @@
|
||||
## 2. 差距清单
|
||||
|
||||
> **列含义**:`模拟器行为` / `真机行为` 描述差异本身;`影响` 说明它会让什么 bug 逃过模拟器回归;
|
||||
> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`@test` / 板端套件 / 目检)。
|
||||
> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`/serial-boot-test` / 板端套件 / 目检)。
|
||||
|
||||
### 2.1 网络(`net_socket_sim.c` vs `net_socket.c`)
|
||||
|
||||
| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||||
|---|---|---|---|---|
|
||||
| **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | accept 轮询计时失真、lftpd 控制通道 errno 分类、timeSync errno 分级等在模拟器上无法复现 | `@test` + FTP 套件;ACCEPT 决定不 pending 化(2026-10-15),不再专门建模 |
|
||||
| **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) |
|
||||
| **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | accept 轮询计时失真、lftpd 控制通道 errno 分类、timeSync errno 分级等在模拟器上无法复现 | `/serial-boot-test` + FTP 套件;ACCEPT 决定不 pending 化(2026-10-15),不再专门建模 |
|
||||
| **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `/serial-boot-test` 网络重配置用例(UI 改 IP 免重启生效) |
|
||||
| **消息泵缺失**:~~`net_recv_to` 缺失~~(**已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 |
|
||||
| **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten` + 对账形态③④自动重建(无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` |
|
||||
| **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten` + 对账形态③④自动重建(无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `/serial-boot-test` |
|
||||
| **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 |
|
||||
| **非阻塞语义模拟** | 创建即 `FIONBIO`(Winsock 句柄恒非阻塞,UI 主循环安全底线);**阻塞/非阻塞按控制块 `blocking` 属性在包装层模拟**(`net_recv` 无限轮询等待 / `net_recv_nb` / `NET_MSG_DONTWAIT` 立即 WOULDBLOCK,`net_setblocking` 双端同名) | 中断驱动 + 状态机 + 挂起 recv(等待睡任务通知,netTask 零占用) | 外部语义**对齐**(登记以说明"为何模拟器能测非阻塞路径与显式预算路径");内部挂起模型(无 pending 机制)仍不建模 | —— |
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
|
||||
| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||||
|---|---|---|---|---|
|
||||
| **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `@test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §4.1 |
|
||||
| **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `/serial-boot-test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §4.1 |
|
||||
| **mtime 粒度不同** | 宿主文件 mtime(**秒级**) | FAT 打包时间(`fdate<<16\|ftime`,**分钟级**) | `cfg_file_poll` 的 `(size, mtime)` 变更令牌在两端触发时机不同;分钟级粒度下的"同分钟内两次改动"在真机可能被判为无变化 | 板端 STORAGE 套件;`配置文件设计说明.md` §变更检测 |
|
||||
| **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `@test`(配置保存 → UI 生效) |
|
||||
| **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `/serial-boot-test`(配置保存 → UI 生效) |
|
||||
| **「清除记录」两端时序不同** | `syslog_request_clear()` 在调用内**同步**删除宿主文件(`sim_syslog.c` 单线程,没有卷锁、没有队列),返回后状态立刻是 `OK`/`FAIL`,`SYSLOG_CLEAR_WAIT` 永不出现 | 非写者上下文只置标志,真正的 `f_unlink` 排在 defaultTask 的 `syslog_drain()` 尾部(卷锁 try:拿不到锁就顺延,见 `App/log/sys_log_mcu.c`) | 「按了按钮 → 下一拍才见结果」这条异步链、卷占用顺延、以及删除与写者重建的交错,在模拟器上**结构上不存在**;UI 页面若把 WAIT 当失败,模拟器上看不出来 | 主机单测 `test/host/sys_log_mcu/`(TC-SYSLOG-110..122:请求侧零盘触点 / 落地只在 drain / 卷占用顺延 / 幂等 / 不误删;TC-SYSLOG-133/133b:清除成功投一条操作记录、清 oper 自己时不投)+ 台架装置维护页目检 |
|
||||
| **两条投递口在宿主端合一** | `syslog_post_quiet()` = `syslog_post()` 的别名(直写宿主文件,没有队列/待发数组),`syslog_post_dropped()` **恒返 0** | 两个口分开且判据不同:quiet 的调用链上不许内联日志行缓冲(protectTask / iec104Task 的静态最坏链硬线),丢弃只累加计数、由 defaultTask 在 `syslog_drain()` 里按差值补留痕 | 五类记录(事故 / 告警 / 启动 / 操作 / 遥控)在模拟器上**永不丢弃**,"队列满丢记录"、"丢了几条"、"投递侧不许打日志"这三件事在模拟器上结构上不可见;UI 若把"投了"当成"落盘了",模拟器看不出来 | 主机单测 `test/host/sys_log_mcu/`(TC-SYSLOG-132 / 134 / 135:队列侧与启动前数组侧各自的计数与零留痕)+ `scripts/stack_report.py` 静态最坏链(真机侧唯一手段) |
|
||||
| **宿主 cwd 的记录文件互相污染** | 七类记录在模拟器落**当前工作目录**的 `sim_<id>.log`(如 `sim_oper.log`);`sim_post_oper()` 在时钟保存 / 网口保存 / 定值保存等**动作页**用例里真投记录 | 每条记录进 `/log/` 下各自 8.3 短名文件,跨页不互相影响(同一进程内不同页读的是同一份 NAND 文件,本来就该看见彼此写入的记录) | 回归若在**同一个 tmp 目录**跑全部页,前面动作页写的 3 条操作留痕会让「操作记录」页拍不到空态 → 空态判据被环境污染掩盖(真机上没有这个问题:写者只有 defaultTask 与事件边沿) | `simulator/tests/check_content_pages.py` 的 `FRESH_CWD` 表:记录 5 页各在独立空目录里跑(其余 55 页环境不变);手工跑 `lvgl_sim.exe` 前先确认当前目录没有 `sim_*.log`/`sim_config.ini` 残留 |
|
||||
@@ -63,11 +63,11 @@
|
||||
|
||||
| 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 |
|
||||
|---|---|---|---|---|
|
||||
| **IWDG 硬件行为** | **不编译看门狗模块**(`simulator/` 无 `task_watchdog.c`,也无 IWDG 桩) | 独立看门狗,LSI 时钟驱动,超时真复位 | **真实复位行为、LSI 漂移、`RCC_CSR` 标志均不可建模**;位图/判定逻辑也不在模拟器内跑——改由主机侧单测覆盖 | 真机 `@test`(复位原因日志 + 故障注入 V2/V4)+ 看门狗主机侧纯逻辑单测(39 断言)<br>`看门狗设计说明.md` §4.7 / §7 |
|
||||
| **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `@test` |
|
||||
| **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `@test` |
|
||||
| **IWDG 硬件行为** | **不编译看门狗模块**(`simulator/` 无 `task_watchdog.c`,也无 IWDG 桩) | 独立看门狗,LSI 时钟驱动,超时真复位 | **真实复位行为、LSI 漂移、`RCC_CSR` 标志均不可建模**;位图/判定逻辑也不在模拟器内跑——改由主机侧单测覆盖 | 真机 `/serial-boot-test`(复位原因日志 + 故障注入 V2/V4)+ 看门狗主机侧纯逻辑单测(39 断言)<br>`看门狗设计说明.md` §4.7 / §7 |
|
||||
| **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `/serial-boot-test` |
|
||||
| **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `/serial-boot-test` |
|
||||
| **SD2506 RTC** | 无 | I2C RTC:时间读写、闹钟、温度、电池电压 | 时间源相关(`sys_clock_init` 降级路径)在模拟器上不可触发 | 板端 RTC 套件(18 断言点)+ `sys_clock` 主机侧纯逻辑单测(已覆盖算术面) |
|
||||
| **开机账本 boot_stats(上电次数 / 复位原因快照)** | **不编译** `App/util/boot_stats.c`(依赖 SD2506 用户 SRAM 与 `RCC_CSR`);UI「运行报告」页的四个数在模拟器上是 `simulator/main.c` 的**固定假数据**(`boot_count=7`、`seq_state=延续`、`rst_cause=上电复位`、104 四枚 3 / 128 / 0 / 4096、`uptime_s=0`) |`boot_stats_init()` 读 SRAM → 自增 → 写回 → 快照驻留;四个 104 观测计数由 iec104Task 真实累加(跨重连不清零、断电归零) | 「自增有没有真落 SRAM」「电池域掉电后是否落 RESTARTED」「写失败是否只少计不重复」「104 计数是否跨重连累计」四类事在模拟器上**结构上不可见**;黄金参考只拍得到显示口径(范围标签在不在屏上、值有没有串行、`boot_ready=0` 是否显 `--` 而不是 0) | 主机单测 `test/host/boot_stats/`(SRAM 状态机与四态判定)+ 真机 `@test`:上电一次看 `boot_count` +1、拔电池后落 RESTARTED、IWDG 复位后 `rst_cause=IWDG`、主站连一次看 `conn_up` +1 |
|
||||
| **开机账本 boot_stats(上电次数 / 复位原因快照)** | **不编译** `App/util/boot_stats.c`(依赖 SD2506 用户 SRAM 与 `RCC_CSR`);UI「运行报告」页的四个数在模拟器上是 `simulator/main.c` 的**固定假数据**(`boot_count=7`、`seq_state=延续`、`rst_cause=上电复位`、104 四枚 3 / 128 / 0 / 4096、`uptime_s=0`) |`boot_stats_init()` 读 SRAM → 自增 → 写回 → 快照驻留;四个 104 观测计数由 iec104Task 真实累加(跨重连不清零、断电归零) | 「自增有没有真落 SRAM」「电池域掉电后是否落 RESTARTED」「写失败是否只少计不重复」「104 计数是否跨重连累计」四类事在模拟器上**结构上不可见**;黄金参考只拍得到显示口径(范围标签在不在屏上、值有没有串行、`boot_ready=0` 是否显 `--` 而不是 0) | 主机单测 `test/host/boot_stats/`(SRAM 状态机与四态判定)+ 真机 `/serial-boot-test`:上电一次看 `boot_count` +1、拔电池后落 RESTARTED、IWDG 复位后 `rst_cause=IWDG`、主站连一次看 `conn_up` +1 |
|
||||
| **TPAFE5160 ADC** | 无 | SPI ADC + BUSY EXTI 中断 | 中断驱动采集链路 | 待功能实现(上游 B1) |
|
||||
| **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) |
|
||||
| **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6 已落地) |
|
||||
@@ -87,7 +87,7 @@
|
||||
|
||||
## 3. 什么时候必须回真机(决策规则)
|
||||
|
||||
模拟器回归通过后,**满足以下任一条件即必须走真机**(`@test` / 板端套件):
|
||||
模拟器回归通过后,**满足以下任一条件即必须走真机**(`/serial-boot-test` / 板端套件):
|
||||
|
||||
1. **改动触及硬件访问层**:`Drivers/BSP/` 下任何驱动的寄存器/时序/中断代码
|
||||
2. **改动触及 §2.3 硬件专属差距的任一子系统**的初始化或运行时行为
|
||||
|
||||
@@ -222,7 +222,7 @@ DTU-RemoteLCD 客户端;输出每帧 `req2first / total / recv 批次到达时
|
||||
|
||||
产品代码(`net_socket.c`/`ui_net_sink.c`/`ui_task.c`/`ui_port.c`)内各数行打点,
|
||||
经静态环形缓冲(总计 <1.5KB BSS)上报;**产品构建下该宏未定义,探针代码编译消失**,
|
||||
两种构建配置都必须过 `@build`(0 错 0 警)。
|
||||
两种构建配置都必须过 `/keil-build`(0 错 0 警)。
|
||||
|
||||
- 多打点站点必须用 `lat_probe_add_seg()` 按段名过滤(探针窗口是串行全局窗口,直接打点会跨段污染,D18 教训);
|
||||
- 启用探针的构建必须在用例入口调 `lat_time_init()`(漏调 = 全 0 假绿,D19 教训;`med==0` 判无效兜底)。
|
||||
@@ -729,7 +729,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
|
||||
|
||||
## 11. 约束核对(AGENTS.md)
|
||||
|
||||
- 每个测试阶段/修复 `@build`(0 错 0 警)+ `@test` 硬件闭环;
|
||||
- 每个测试阶段/修复 `/keil-build`(0 错 0 警)+ `/serial-boot-test` 硬件闭环;
|
||||
- 新 `.c` 注册:`test/*.c` 仅 MCU 端编译 → **只注册 `MDK-ARM/STM32F407-Demo.uvprojx`**(Test 组;
|
||||
模拟器 CMake 不含 test 文件,与 `ch395f_test_task.c` 等现状一致);
|
||||
- 新增 C 字符串字面量**纯 ASCII**(`[LAT]` 报告行均为英文),中文只写注释;
|
||||
@@ -737,7 +737,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
|
||||
- `testTask` 栈 2048 words:用例函数只用几十字节局部变量;
|
||||
- 套件互斥宏体系不动:Phase 12 挂 HW 组,13/14 挂 NET 组,`#error` 自动防两层同启;
|
||||
- 非破坏性:不改 IP/卷/BBT;HW 层阶段独占 CH395F(netTask 自挂起)→ 标注"台架专用";
|
||||
- 生产代码探针全部在 `#ifdef TEST_LATENCY_PROBE` 内,产品构建编译消失,两种构建都须过 `@build`。
|
||||
- 生产代码探针全部在 `#ifdef TEST_LATENCY_PROBE` 内,产品构建编译消失,两种构建都须过 `/keil-build`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -411,7 +411,7 @@ uint16_t ch395f_link_change_count(void);
|
||||
对账循环使用"),假件要保留"对未在用槽位也能拿到项"的行为,否则对账兜底路径测不到。
|
||||
|
||||
**验收顺序**:`net_listener` 绿 → 立刻登记基线(哪怕只有它一个)→ `net_pending` → `net_events`;
|
||||
每步都要 @build 0/0 + 全部套件绿 + --update-baseline + 补建 `TC-HOST-*` 族(本规格落地时族数
|
||||
每步都要 /keil-build 0/0 + 全部套件绿 + --update-baseline + 补建 `TC-HOST-*` 族(本规格落地时族数
|
||||
16 → 22、套件 16 → 28,实测见上方落地进度),并且**先补族再往文档里引这些号**,否则 `id-check` 会按台账外新账卡提交
|
||||
(本会话已被 `TC-NETCFG-028` 实测拦过一次)。
|
||||
|
||||
@@ -538,7 +538,7 @@ uint16_t ch395f_link_change_count(void);
|
||||
- 自愈触发集里混着 `FR_INVALID_NAME` / `FR_NO_PATH` / `FR_NO_FILE` / `FR_INVALID_OBJECT`——**比原报告的 6 号严重**:这类码下 unlink 自己的文件必然同样失败(同名同因),后果是**每轮排空删一次自己的历史日志且永远修不好**,正是 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 第 10 条严禁的归集。已新增代码侧唯一入口 `s_is_selfheal_code()`:触发集收敛为 `{FR_DISK_ERR, FR_INT_ERR, FR_NO_FILESYSTEM, FR_DENIED}`,逐码依据(含 `Lib/FatFs/ff.c` 行号)与显式排除清单写在资料块里;`soe_store.c` 侧同源但**取更保守子集**。TC-SYSLOG-101/101b/102/103/103b、TC-SOESTORE-022b。
|
||||
|
||||
**本轮刻意没做(后续项,含理由)**:① UDP 改走 `netMsgQueue`(需先设计 per-task 结果槽:`net_msg_t` 按值拷入队列、错误码只搭任务通知值回传,AGENTS 明文禁止给 `net_msg_t` 加回传字段,且必须配真机回归);
|
||||
② `simulator/sim/sim_syslog.c:171` 那条与 3 号**同源的不可达截断支**(本轮编译验证不到它——模拟器走 CMake+VS,不在 `@build` 覆盖内,不交没编译过的改动);③ `syslog_post` 的**丢弃计数**(属新增对外可见信息,本轮没有实测频率支撑阈值);
|
||||
② `simulator/sim/sim_syslog.c:171` 那条与 3 号**同源的不可达截断支**(本轮编译验证不到它——模拟器走 CMake+VS,不在 `/keil-build` 覆盖内,不交没编译过的改动);③ `syslog_post` 的**丢弃计数**(属新增对外可见信息,本轮没有实测频率支撑阈值);
|
||||
④ `dbg_console_tx_timeout()` 的**读端接线**(现无生产调用者;候选位置 `task_watchdog.c` 的 `STALL snap=… req=… h=… mg=… cf=… seg=…` 是**被解析的证据行**,加字段要连解析端与台架判据一起改,本批不碰台架);⑤ 给 104 加来源地址日志(现网 `iec104_serv.c` 根本不打印来源 IP、无 `getpeername/ntoa`,**没有输出点可看**)。
|
||||
|
||||
**必须上台架复验(本轮按指示未碰台架;主机测覆盖不到的部分逐条列出)**:
|
||||
@@ -594,7 +594,7 @@ uint16_t ch395f_link_change_count(void);
|
||||
`net_listener` 125/126、`lftpd` 304/473、`iec104_link` 367/405 …);每套 runner 的断言数改前后相同
|
||||
(如 `iec104` 97/97、`sys_clock` 100/100、`adc_input` 103/103、`net_cfg` 45/45)。**基线本轮不需要重登**(生产码零改动)。
|
||||
|
||||
⚠ **`@build` 对本轮没有覆盖力**:`test/host/**` 不进 Keil 编译面也不进模拟器 CMake,字面量改动只能靠
|
||||
⚠ **`/keil-build` 对本轮没有覆盖力**:`test/host/**` 不进 Keil 编译面也不进模拟器 CMake,字面量改动只能靠
|
||||
16 个 `*-unit-test` 钩子 + `test-cppcheck` + `test-coverage` 这三根闸验证——**别把"编译过了"当测试面改动的证据**。
|
||||
|
||||
**本轮新手法(值得抄进任何"批量改标签"作业)**:
|
||||
@@ -714,9 +714,9 @@ uint16_t ch395f_link_change_count(void);
|
||||
|
||||
| 类别 | 体量 | 为什么不做 | 那它靠什么把关 |
|
||||
|---|---|---|---|
|
||||
| 芯片驱动(寄存器/时序) | `ch395f.c`(801)、`gd5f2gq5ue.c`(689)、`sd2506.c`(264)、`tpafe5160.c`(157)、`rs485.c`(79) = **1,990 行** | 把 HAL 与寄存器假到底 ≈ **重写一遍驱动**,测的是自己的桩而不是硬件行为(SPI 分频必须 8、DMA 流、I2C 时序在桩里怎么编都"对") | 板级套件(`board-test-suite` skill)+ `@test` 真机日志 + 4 个 Fault 现场;陷阱逐条登记在 `docs/问题记录/CH395F_Trap_Records.md`、`docs/问题记录/GD5F2GQ5UE_Trap_Records.md`、`docs/问题记录/SD2506_Trap_Records.md` |
|
||||
| 芯片驱动(寄存器/时序) | `ch395f.c`(801)、`gd5f2gq5ue.c`(689)、`sd2506.c`(264)、`tpafe5160.c`(157)、`rs485.c`(79) = **1,990 行** | 把 HAL 与寄存器假到底 ≈ **重写一遍驱动**,测的是自己的桩而不是硬件行为(SPI 分频必须 8、DMA 流、I2C 时序在桩里怎么编都"对") | 板级套件(`board-test-suite` skill)+ `/serial-boot-test` 真机日志 + 4 个 Fault 现场;陷阱逐条登记在 `docs/问题记录/CH395F_Trap_Records.md`、`docs/问题记录/GD5F2GQ5UE_Trap_Records.md`、`docs/问题记录/SD2506_Trap_Records.md` |
|
||||
| UI 侧 | 17 个 `.c` = **1,942 行** | 与 PC 模拟器是**同一份源码**,行为回归已由 `ui-golden`(菜单 8 状态 + 内容页 66 张像素黄金参考)覆盖;再写单测是第三份重复投资 | `ui-verify` skill 五步 + `docs/UI设计/UI_自动化测试流程.md` |
|
||||
| 任务编排 | `default_task.c`(281)、`adc_task.c`(171)、`net_task.c`(123)、`protect_task.c`(90)、`ui_task.c`(71)、`rs485_task.c`(11) = **747 行** | 每个只有 4~5 个函数,主体是"按顺序调别人";真逻辑在被调模块里(那些已测或在 P1/P2)。给编排层写单测最容易写成"断言我调了几次"的自证 | 真机启动锚点计数 + IWDG `required_mask`(`@test`)+ `docs/架构设计/项目启动顺序管理.md` |
|
||||
| 任务编排 | `default_task.c`(281)、`adc_task.c`(171)、`net_task.c`(123)、`protect_task.c`(90)、`ui_task.c`(71)、`rs485_task.c`(11) = **747 行** | 每个只有 4~5 个函数,主体是"按顺序调别人";真逻辑在被调模块里(那些已测或在 P1/P2)。给编排层写单测最容易写成"断言我调了几次"的自证 | 真机启动锚点计数 + IWDG `required_mask`(`/serial-boot-test`)+ `docs/架构设计/项目启动顺序管理.md` |
|
||||
| 内核前装配 | `app_main.c`(135) | 被测对象本身就是"`osKernelStart` 之前不许调内核 API"的时序,主机侧无法忠实复现(PC 上没有那个约束) | 同上 + `cubemx-survival` 审计 |
|
||||
|
||||
**四个数字对得上**:`47 = 13(P1) + 5(P2) + 29(P3)`、`9,657 = 2,617 + 2,226 + 4,814`。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **模块**:Task Watchdog(IWDG + 任务心跳位图,`App/util/task_watchdog.c`)
|
||||
> **测试类型**:**主机侧纯逻辑单元测试**(见 §1.5)——在 PC 上离线验证
|
||||
> 判定状态机,零硬件、零烧录、秒级、不占 MCU 的 RAM。
|
||||
> **状态**:✅ 已落地(47 断言,`@build` 0/0,已挂 pre-commit 钩子)
|
||||
> **状态**:✅ 已落地(47 断言,`/keil-build` 0/0,已挂 pre-commit 钩子)
|
||||
> **日期**:2026-10-01(2026-10-25 v1.5 按位分账改造;改造史与设计依据见 `docs/子系统设计/看门狗设计说明.md` 变更记录——本文档不再设变更记录节)
|
||||
> **关联文档**:
|
||||
> [`../../子系统设计/看门狗设计说明.md`](../../子系统设计/看门狗设计说明.md)(被测行为的设计依据)、
|
||||
@@ -55,7 +55,7 @@ test/host/watchdog/watchdog_test.c
|
||||
| `wd_platform_tick_ms()` | `HAL_GetTick()` | 返回单测可控的 `s_tick` |
|
||||
|
||||
> **生产端零增量**:三个钩子在 MCU 构建下就是原来的寄存器写 / HAL 调用,行为逐字一致,
|
||||
> 无额外分支、无额外符号(`@build` 0/0 已验)。平台默认实现由
|
||||
> 无额外分支、无额外符号(`/keil-build` 0/0 已验)。平台默认实现由
|
||||
> `#ifndef TASK_WATCHDOG_UNIT_TEST` 守卫;测试专用复位 `task_wd_test_reset()`
|
||||
> 仅在主机侧编译(MCU 构建零代码、零符号)。
|
||||
|
||||
@@ -93,7 +93,7 @@ test/host/watchdog/watchdog_test.c
|
||||
| **门禁层级** | **主机侧纯逻辑单测**(介于静态门禁与板端测试套件之间):主机侧、秒级、纯逻辑,比板端套件便宜、比静态检查更强 |
|
||||
| **pre-commit 钩子** | `watchdog-unit-test`(本地钩子,`.pre-commit-config.yaml` §7) |
|
||||
| **汇总行** | `=== Task Watchdog Tests: N/N PASSED (failed=0) ===` |
|
||||
| **与板端测试的关系** | 互补:本单测保证**判定逻辑**正确;板端 `@test` 故障注入(V2/V4,见 `docs/子系统设计/看门狗设计说明.md` §7 验证记录)保证**真机复位链路**正确 |
|
||||
| **与板端测试的关系** | 互补:本单测保证**判定逻辑**正确;板端 `/serial-boot-test` 故障注入(V2/V4,见 `docs/子系统设计/看门狗设计说明.md` §7 验证记录)保证**真机复位链路**正确 |
|
||||
|
||||
---
|
||||
|
||||
@@ -174,4 +174,4 @@ if (s_miss_streak >= WD_MISS_TOLERANCE) { DBG_ERROR("[WD] task stall: ..."); }
|
||||
| 长跑(主机侧回归) | ≥30 min 无假复位(`miss_count` 稳定) | ✅ 已台架验收(V5,30 min / 120×FTP 零复位,2026-10-03) |
|
||||
|
||||
> 本单测**不能**替代板端验证:IWDG 实际溢出时间、LSI 频率漂移、`SHCSR`/复位原因
|
||||
> 寄存器的真机行为,仍需 `@test` 台架闭环确认。
|
||||
> 寄存器的真机行为,仍需 `/serial-boot-test` 台架闭环确认。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **模块**:System Clock(墙钟时间维护,`App/util/sys_clock.c`)
|
||||
> **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证时间换算与
|
||||
> 墙钟基准逻辑,零硬件、零烧录、秒级、不占 MCU 的 RAM。
|
||||
> **状态**:✅ 已落地(20 用例 / 100 断言点,`@build` 0/0,已挂 pre-commit 钩子)
|
||||
> **状态**:✅ 已落地(20 用例 / 100 断言点,`/keil-build` 0/0,已挂 pre-commit 钩子)
|
||||
> **日期**:2026-10-02
|
||||
> **关联文档**:
|
||||
> [`../../../AGENTS.md`](../../../AGENTS.md)(平台约束)、
|
||||
@@ -234,7 +234,7 @@ static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d) { /* m: 1-based
|
||||
|
||||
> 注:`5J ≡ -2J (mod 7)`,取 `5J` 保证各项非负(无符号 `%` 安全)。
|
||||
> 修复后经 Python `datetime` 独立核对 **1970~2099 全部 47,482 天零错误**;
|
||||
> `@build` 0 错误 0 警告。
|
||||
> `/keil-build` 0 错误 0 警告。
|
||||
|
||||
### 5.5 校验数据来源
|
||||
|
||||
@@ -252,4 +252,4 @@ TC-SYSCLK-019 的期望星期由 Python `datetime.date(...).weekday()` 独立生
|
||||
| 板端测试套件 / 主机侧回归 | 真机 SD2506 I2C 读写、掉电保持、与 PC 对时联动 | 见 [`SD2506_Test_Guide.md`](../板级测试/SD2506_Test_Guide.md) |
|
||||
|
||||
> 本单测**不替代**板端验证:I2C 时序、BCD 编解码、掉电保持、温度读等真机行为,
|
||||
> 仍需 `@test` 台架闭环确认。
|
||||
> 仍需 `/serial-boot-test` 台架闭环确认。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **模块**:Config File Engine(通用配置文件引擎,`App/cfg/cfg_file.c`)
|
||||
> **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证 INI 解析、
|
||||
> 值类型编解码、段注册校验与 load/save/poll 契约,零硬件、零烧录、毫秒级、不占 MCU 的 RAM。
|
||||
> **状态**:✅ 已落地(20 用例 / 139 断言点,`@build` 0/0,已挂 pre-commit 钩子)
|
||||
> **状态**:✅ 已落地(20 用例 / 139 断言点,`/keil-build` 0/0,已挂 pre-commit 钩子)
|
||||
> **日期**:2026-10-02
|
||||
> **关联文档**:
|
||||
> [`../../../AGENTS.md`](../../../AGENTS.md)(平台约束 · FatFs/内存铁律)、
|
||||
@@ -269,7 +269,7 @@ if (s_encode(p_key, ..., v, sizeof(v)) >= 0) {
|
||||
}
|
||||
```
|
||||
|
||||
修复后 `TC-CONFIG-041` / `TC-CONFIG-042` 转 PASS,`@build` 0 errors 0 warnings。
|
||||
修复后 `TC-CONFIG-041` / `TC-CONFIG-042` 转 PASS,`/keil-build` 0 errors 0 warnings。
|
||||
|
||||
### 5.5 建议后续动作
|
||||
|
||||
|
||||
@@ -1121,7 +1121,7 @@ CLOSE 才落 00。与 TRAP-NET-07 的 `0x05/0x00` 驻留签名同源;`sock_res
|
||||
WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 TRAP-NET-09 open 重试吸收。
|
||||
|
||||
**运行方法**:`ch395f_test.h` 启用 `ENABLE_PHASE15_TESTS`(自动进入硬件层模式,
|
||||
勿与 NET 层 Phase 混选)→ `@build` → 烧录 → 串口看 `=== Phase 15: 20/20 PASSED ===`。
|
||||
勿与 NET 层 Phase 混选)→ `/keil-build` → 烧录 → 串口看 `=== Phase 15: 20/20 PASSED ===`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
## 3. 编译与运行(固件侧)
|
||||
|
||||
- **默认构建即可提供 FTP Server**,无需切换测试套件。
|
||||
- 任何改动后必须执行 `@build`(即 `keil-build` skill:`.dsh/skills/keil-build/scripts/keil_build.py`),确认 **0 Error(s) 0 Warning(s)** 再烧录(遵循 AGENTS.md 铁律)。
|
||||
- 任何改动后必须执行 `/keil-build`(即 `keil-build` skill:`.dsh/skills/keil-build/scripts/keil_build.py`),确认 **0 Error(s) 0 Warning(s)** 再烧录(遵循 AGENTS.md 铁律)。
|
||||
- 若需跑**自动化 FTP 文件传输用例**:在 `test/board/test_config.h` 选择 `TEST_SUITE_CH395F`,并在 `test/board/ch395f_test.h` 打开 `ENABLE_PHASE8_TESTS`(文件传输阶段),重新编译烧录。
|
||||
- 烧录后打开串口(115200bps)观察:
|
||||
- `net_init` 完成、PHY link up;
|
||||
@@ -192,7 +192,7 @@
|
||||
|
||||
- **串口日志 + `DBG_*`**:在 `lftpd.c` / `net_socket` 关键路径加 `DBG_ERROR/INFO` 打印(注意编码安全铁律——只用 Edit 改中文源文件,禁止用 `Get/Set-Content` 整文件重写)。
|
||||
- **抓包**:`ftp_capture.py` 或 Wireshark 过滤 `tcp.port==21`。
|
||||
- **编译验证**:`@build` 必须 0/0 再烧录(AGENTS.md 铁律)。
|
||||
- **编译验证**:`/keil-build` 必须 0/0 再烧录(AGENTS.md 铁律)。
|
||||
- **套件切换**:`test/board/test_config.h` 改 `TEST_SUITE_CH395F` / `TEST_SUITE_GD5F` / `TEST_SUITE_STORAGE`(三选一,互斥)。
|
||||
|
||||
---
|
||||
|
||||
@@ -93,7 +93,7 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10`
|
||||
|
||||
1. 在 `test/board/test_config.h` 取消注释所需的 `TEST_SUITE_*` 宏(如 `TEST_SUITE_GD5F`),
|
||||
需要写/擦类用例时再打开 `ENABLE_DESTRUCTIVE_TESTS`;
|
||||
2. 编译(`@build`)确认 0 错误 0 警告;
|
||||
2. 编译(`/keil-build`)确认 0 错误 0 警告;
|
||||
3. 烧录后串口观察 `mode: ...` 横幅、`[NAND-TEST]` 日志与各 TC 的 `PASS/FAIL`,结尾打印
|
||||
`=== GD5F2GQ5UE Tests: X/Y PASSED ===`;
|
||||
4. 不选任何 `TEST_SUITE_*` 时为正常产品构建(`ch395fTestTask` 空闲,不跑测试)。
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
| 步骤 | 事项 | 状态 | 章节 |
|
||||
|---|---|---|---|
|
||||
| **步骤 1** | 环境预检(P0-A,每次上台架必跑) | 常设 | §2 |
|
||||
| **步骤 2** | 产品 `@test` 闭环 + 自检结果判定(P0-B) | ✅ 已闭环 | §3 |
|
||||
| **步骤 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 |
|
||||
|
||||
> **每步出口**:`@build` 0/0 → 一轮 `@test` PASS → ST-LINK 全片回读 `No difference found` →
|
||||
> **每步出口**:`/keil-build` 0/0 → 一轮 `/serial-boot-test` PASS → ST-LINK 全片回读 `No difference found` →
|
||||
> 上游登记销项 + 把结论写进正文对应小节。
|
||||
>
|
||||
> **参考**(非执行步骤):§9 执行注意(实测陷阱)· §10 已完成事项与证据去向 · §11 引用闭环。
|
||||
@@ -51,7 +51,7 @@
|
||||
| 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 实测):`@build` 是**增量**构建,`run_all_suites.py` 跑完
|
||||
> ⚠️ **A4 的陷阱**(2026-10-02 实测):`/keil-build` 是**增量**构建,`run_all_suites.py` 跑完
|
||||
> 套件后 `.map` 会停在**测试套件体积**;仅还原 `test_config.h` 不够,**必须再编一次产品固件**
|
||||
> (`run_all_suites.py` 已在 `finally` 内自动补这一步)。
|
||||
|
||||
@@ -59,13 +59,13 @@
|
||||
|
||||
---
|
||||
|
||||
## 3. 步骤 2:产品 @test 闭环 + 自检结果判定(P0-B) · ✅ 已闭环
|
||||
## 3. 步骤 2:产品 /serial-boot-test 闭环 + 自检结果判定(P0-B) · ✅ 已闭环
|
||||
|
||||
> S7 判定已落地并投用;A1~A4 已闭环。**实施细节与真机验证证据见 §10 状态与去向**,本节只给复跑命令与判据。
|
||||
|
||||
### 3.1 执行
|
||||
|
||||
`@test` 闭环除串口标记外,还判定设备侧自检结论(v1 只记故障、不记通过):
|
||||
`/serial-boot-test` 闭环除串口标记外,还判定设备侧自检结论(v1 只记故障、不记通过):
|
||||
`selftest.log` **缺失 / 0 字节 → 无故障 → PASS**;`filesize/64 > 0`(任一 64B 定长记录)
|
||||
= 有故障 → FAIL,并打印故障条目(时间戳 + 描述)便于定位。
|
||||
|
||||
@@ -121,11 +121,11 @@
|
||||
|
||||
```bash
|
||||
# --- V2/V4(故障注入,需临时改码)---
|
||||
# 1) 基线:跑一轮产品 @test 确认无复位
|
||||
# 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);)
|
||||
# —— 改完 @build,然后烧录
|
||||
# —— 改完 /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
|
||||
|
||||
@@ -135,7 +135,7 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L
|
||||
& $cli -c SWD -CmpFile MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex 0x08000000
|
||||
# 判据:No difference found
|
||||
|
||||
# 5) 撤销注入,@build 回产品态,再跑一轮 @test 确认正常
|
||||
# 5) 撤销注入,/keil-build 回产品态,再跑一轮 /serial-boot-test 确认正常
|
||||
|
||||
# --- V5(长跑,需真实负载)---
|
||||
# 1) 用 test/host/ftp_speed_test.py / ftp_up_dl_test.py 持续传输大文件
|
||||
@@ -172,7 +172,7 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L
|
||||
**执行步骤**:
|
||||
```bash
|
||||
# 1) 代码改动(约 15 行 + 1 个编译期常量)在 defaultTask 低频循环内
|
||||
# 2) @build 0/0 后,与 §4.2 V5 长跑合并执行
|
||||
# 2) /keil-build 0/0 后,与 §4.2 V5 长跑合并执行
|
||||
# 3) 长跑结束读取 min_ever:
|
||||
# - 串口 [HEAP] 告警行(若有)
|
||||
# - 或 FTP 取 /log/selftest.log
|
||||
|
||||
+26
-26
@@ -7,7 +7,7 @@
|
||||
|
||||
| 文档 | 内容 |
|
||||
|---|---|
|
||||
| `docs/测试说明/流程与规范/自动化测试流程.md` | @test 闭环细节(命令 / 标记 / 失败模式) |
|
||||
| `docs/测试说明/流程与规范/自动化测试流程.md` | /serial-boot-test 闭环细节(命令 / 标记 / 失败模式) |
|
||||
| `docs/测试说明/流程与规范/测试文档模板规范.md` | 测试文档模板、命名规则(TM / TC / DP) |
|
||||
| `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试 / 采集工具与陷阱(Python 串口助手) |
|
||||
| `docs/测试说明/测试工具/串口助手使用指南.md` | 串口助手命令用法 / 工作流 / 排障 |
|
||||
@@ -25,10 +25,10 @@
|
||||
|
||||
| 名称 | 载体 | 抓住什么 |
|
||||
|---|---|---|
|
||||
| 静态门禁 | pre-commit 钩子(cppcheck / 代码规范 / 乱码 / 空白 / gitlint)+ `@build` | 编码规范、静态缺陷、任何编译警告 |
|
||||
| 静态门禁 | pre-commit 钩子(cppcheck / 代码规范 / 乱码 / 空白 / gitlint)+ `/keil-build` | 编码规范、静态缺陷、任何编译警告 |
|
||||
| **主机侧纯逻辑单测** | `test/host/<模块>/run_unit_test.py` + MSVC 离线编译真实源码(`#include` 纳入)+ 平台桩 | **纯逻辑模块的边界穷举**(节流窗口、计数阈值、整数回绕)——板端无法覆盖、无需硬件 |
|
||||
| 板端测试套件 | testTask + `test/board/test_config.h` 套件 | 驱动 / 协议功能(串口 `[PASS]/[FAIL]` + `=== ... PASSED` 汇总) |
|
||||
| 主机侧回归 | `@test` 串口日志标记 + LVGL 模拟器像素 / 池水位回归 | 启动序列、UI 渲染、LVGL 内存池水位 |
|
||||
| 主机侧回归 | `/serial-boot-test` 串口日志标记 + LVGL 模拟器像素 / 池水位回归 | 启动序列、UI 渲染、LVGL 内存池水位 |
|
||||
| 人工目检 | 上机屏幕、PC 端交互工具 | 视觉内容、自动化无法覆盖的场景 |
|
||||
|
||||
> **主机侧纯逻辑单测与主机侧回归的区别**:主机侧回归的模拟器回归依赖 SDL / LVGL 与图形输出;主机侧纯逻辑单测是**纯计算**单测
|
||||
@@ -43,9 +43,9 @@
|
||||
|
||||
| 改动类型 | 必跑门禁 |
|
||||
|---|---|
|
||||
| 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 `@build` |
|
||||
| App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 `@test`(模块标记) |
|
||||
| **有主机侧纯逻辑单测的模块(如看门狗)** | G0(含该模块单测钩子,自动)+ G1 `@build`(+ 台架项另按需 G2) |
|
||||
| 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 `/keil-build` |
|
||||
| App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 `/serial-boot-test`(模块标记) |
|
||||
| **有主机侧纯逻辑单测的模块(如看门狗)** | G0(含该模块单测钩子,自动)+ G1 `/keil-build`(+ 台架项另按需 G2) |
|
||||
| UI(菜单 / 内容页 / 字体 / 样式 / 几何) | G0 + G1 + G2 + G3 ui-verify 五步 |
|
||||
| UI 新增中文文案 | 另需 font-regen(字体重生成 + 回归),**先于提交** |
|
||||
| `test/` 套件或 PC 端工具代码 | G1 + G2(套件标题标记 / 工具自测) |
|
||||
@@ -74,7 +74,7 @@ G0~G3 命令详见 §6 / §7;ui-verify 完整命令见 `UI_自动化测试流
|
||||
### 2.3 三条铁律
|
||||
|
||||
1. **先写判定标准,再写测试代码** —— 文档里先写"什么算过 / 什么算失败"(精确到返回值 / 日志字符串 / 像素参考),再实现,避免"测了但不知道算不算过"。
|
||||
2. **判定标准必须可机器判定** —— 必须能落到:串口日志标记(→ `@test -Required`)、返回值 / 寄存器值(→ `TEST_CHECK`)、像素参考(→ 模拟器回归)、池水位(→ 基线常量)之一。无法机器判定的标准只能放 人工目检人工层。
|
||||
2. **判定标准必须可机器判定** —— 必须能落到:串口日志标记(→ `run_boot_test.py --required`)、返回值 / 寄存器值(→ `TEST_CHECK`)、像素参考(→ 模拟器回归)、池水位(→ 基线常量)之一。无法机器判定的标准只能放 人工目检人工层。
|
||||
3. **每条设计原则必须有对应用例** —— 覆盖表中每个 DP-xx 至少一个 TC;新功能设计时先定 DP,再设计 TC。
|
||||
|
||||
---
|
||||
@@ -170,7 +170,7 @@ static void phaseN_run(void) {
|
||||
}
|
||||
```
|
||||
|
||||
**套件运行结束行的 `=== <名称> Tests: N/N PASSED (failed=0) ===` 即该套件 `@test` 回归的默认标记**(各套件确切字符串见对应模块 Test_Guide,如 `=== Storage Tests:` / `=== GD5F2GQ5UE Tests:`)。
|
||||
**套件运行结束行的 `=== <名称> Tests: N/N PASSED (failed=0) ===` 即该套件 `/serial-boot-test` 回归的默认标记**(各套件确切字符串见对应模块 Test_Guide,如 `=== Storage Tests:` / `=== GD5F2GQ5UE Tests:`)。
|
||||
|
||||
### 3.3 编码约定
|
||||
|
||||
@@ -204,9 +204,9 @@ static void phaseN_run(void) {
|
||||
2. `test/board/test_config.h`:补 `TEST_SUITE_XXX` 注释开关 + 互斥 `#error` 加项 + 分发分支(include + `TEST_SUITE_NAME`);
|
||||
3. `test/board/test_config.c`:`start_test_task()` 加分发分支;
|
||||
4. **双端注册核查**(`new-source-file` skill):新 `.c` 必须加入 `MDK-ARM/STM32F407-Demo.uvprojx`;test/ 代码仅 MCU 端编译,**不**登记模拟器 CMakeLists(UI / App 共用代码才需双端);
|
||||
5. `@build` → 0 错 0 警;
|
||||
5. `/keil-build` → 0 错 0 警;
|
||||
6. 立 `docs/测试说明/<模块>_Test_Guide.md`(§2 模板);
|
||||
7. `@test` 以套件标题行为 `-Required` 标记跑通回归。
|
||||
7. `/serial-boot-test` 以套件标题行为 `-Required` 标记跑通回归。
|
||||
|
||||
### 3.6 多套件回归编排(`test/run_all_suites.py`,2026-10-01 加)
|
||||
|
||||
@@ -229,7 +229,7 @@ static void phaseN_run(void) {
|
||||
|
||||
#### 3.6.1 `--check-build`:只编译不烧录(2026-10-02 加)
|
||||
|
||||
**解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `@build` 天天跑,
|
||||
**解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `/keil-build` 天天跑,
|
||||
但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件
|
||||
7 个配置实例)。
|
||||
|
||||
@@ -239,9 +239,9 @@ static void phaseN_run(void) {
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 做什么 | 逐套件:改 `test_config.h` 宏 → `@build` → 记 PASS/FAIL;结束后无条件还原 |
|
||||
| 做什么 | 逐套件:改 `test_config.h` 宏 → `/keil-build` → 记 PASS/FAIL;结束后无条件还原 |
|
||||
| **不需要** | MCU / 串口 / ST-LINK(**纯离机**,可在 CI 或任何有 Keil 的机器上跑) |
|
||||
| 判定 | `@build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL |
|
||||
| 判定 | `/keil-build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL |
|
||||
| RAM 溢出 | "编译过但链接失败"这类问题会**自然暴露**为 `L6406E`——这正是想要的 |
|
||||
| 退出码 | 同完整模式:0=全 PASS / 1=有套件编不过 / 2=用法缺文件 |
|
||||
|
||||
@@ -310,7 +310,7 @@ static void phaseN_run(void) {
|
||||
|
||||
**约束**:
|
||||
|
||||
- 平台层的抽取必须**零行为变化、零额外开销**(MCU 端默认实现即原逻辑;`@build` 须 0/0);
|
||||
- 平台层的抽取必须**零行为变化、零额外开销**(MCU 端默认实现即原逻辑;`/keil-build` 须 0/0);
|
||||
- 测试专用钩子(如静态态复位)用 `#ifdef <模块>_UNIT_TEST` 守卫,MCU 构建不编入(零符号);
|
||||
- 单测**不能替代**板端验证:真机寄存器 / 复位原因 / 时序仍须走板端测试套件 / 主机侧回归台架闭环。
|
||||
|
||||
@@ -348,7 +348,7 @@ static void phaseN_run(void) {
|
||||
- **test/ 自动化脚本为纯 Python 标准库**(2026-09-20 由 PowerShell 迁移:绕开控制台代码页/执行策略陷阱,全部 I/O 显式 UTF-8/字节级处理;`requirements.txt` 仍须纯 ASCII——pip 按 GBK 代码页解码);
|
||||
- **Python 统一用仓库 `.venv`**;第三方依赖必须进 `requirements.txt`(纯 ASCII);
|
||||
- 落位:板端交互工具 → `test/`;模拟器回归 → `simulator/tests/`;
|
||||
- 新工具引入新的"通过标记"时,必须登记到 §6.2 标记表,供 `@test` 闭环复用。
|
||||
- 新工具引入新的"通过标记"时,必须登记到 §6.2 标记表,供 `/serial-boot-test` 闭环复用。
|
||||
|
||||
---
|
||||
|
||||
@@ -386,13 +386,13 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
|
||||
|
||||
---
|
||||
|
||||
## 6. 自动化闭环(@test)
|
||||
## 6. 自动化闭环(/serial-boot-test)
|
||||
|
||||
### 6.1 流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[代码改完] --> B["G1 @build(0 错 0 警,否则修复重编)"]
|
||||
A[代码改完] --> B["G1 /keil-build(0 错 0 警,否则修复重编)"]
|
||||
B --> C["run_boot_test.py:烧录前武装采集(daemon 全量日志兜底)"]
|
||||
C --> D["@download 烧录(Reset & Run 触发重启)"]
|
||||
D --> E[提取日志 + verify_log 标记校验]
|
||||
@@ -477,7 +477,7 @@ flowchart TD
|
||||
| 代码变更影响判定标准 | 立即把模块 Test_Guide **正文**改到与代码一致(现状说了算,不再记流水) |
|
||||
| 有意改视觉 | 刷黄金参考 + 重生成目检 PNG + 把新口径写进 `UI_自动化测试流程.md` 正文对应小节 |
|
||||
| 新增测试工具 / 新标记 | `自动化测试流程.md` 命令清单登记(§4.2) |
|
||||
| 栈 / 堆预算相关改动或基线重采 | 把 `@test` 的 boot_log 快照(`%TEMP%\serial-boot-test\boot_log_*.txt`)存入 `docs/测试说明/测试数据/boot_log_data/` 并在其 §3 清单登记(防临时目录被清理 / 长跑日志截头丢 `[STACK]` 行) |
|
||||
| 栈 / 堆预算相关改动或基线重采 | 把 `/serial-boot-test` 的 boot_log 快照(`%TEMP%\serial-boot-test\boot_log_*.txt`)存入 `docs/测试说明/测试数据/boot_log_data/` 并在其 §3 清单登记(防临时目录被清理 / 长跑日志截头丢 `[STACK]` 行) |
|
||||
|
||||
> 文档与代码一体两面:**代码改了文档没同步 = 未完成**。
|
||||
|
||||
@@ -488,8 +488,8 @@ flowchart TD
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["文档:规划 + 判定标准<br/>(Test_Guide)"] --> B["代码:套件 / 工具实现<br/>+ 双端注册核查"]
|
||||
B --> C["G1 @build<br/>0 错 0 警"]
|
||||
C --> D["G2 @test<br/>采集→烧录→校验"]
|
||||
B --> C["G1 /keil-build<br/>0 错 0 警"]
|
||||
C --> D["G2 /serial-boot-test<br/>采集→烧录→校验"]
|
||||
D -->|PASS| E["G3 ui-verify<br/>(UI 改动时)"]
|
||||
E --> F["人工目检上机目检"]
|
||||
F --> G["提交<br/>G0 门禁 + 逻辑提交"]
|
||||
@@ -514,21 +514,21 @@ flowchart LR
|
||||
| **静态门禁** | ✅ | `pre-commit run --all-files`(提交期 47 根快钩子)+「手动阶段门禁」步逐根 `--hook-stage manual` 跑 `cppcheck` / `test-cppcheck` / `test-coverage` | 纯 Python + cppcheck,零硬件 |
|
||||
| **主机侧纯逻辑单测** | ✅ | `test/host/**/run_unit_test.py`(MSVC 离线编译) | 纯标准库 + `cl.exe`,零硬件 |
|
||||
| 板端测试套件 | ⛔ | `test/board/` | 需真机 + 串口 + ST-LINK |
|
||||
| 主机侧回归 `@test` 闭环 | ⛔ | 烧录 + 串口采集 | 同上 |
|
||||
| 主机侧回归 `/serial-boot-test` 闭环 | ⛔ | 烧录 + 串口采集 | 同上 |
|
||||
| 人工目检 | ⛔ | 上机屏幕 | 人的判断 |
|
||||
|
||||
**明确不做的两件事(都是刻意为之)**:
|
||||
|
||||
1. **不跑 `@build`(Keil UV4)**——需本机 Keil 授权,runner 上不具备。静态门禁的 cppcheck + 代码规范
|
||||
1. **不跑 `/keil-build`(Keil UV4)**——需本机 Keil 授权,runner 上不具备。静态门禁的 cppcheck + 代码规范
|
||||
已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(`AGENTS.md`)。
|
||||
2. **`mem-numbers` 钩子按 `.map` 有无自适应跳过**——它的事实源是 `MDK-ARM/**/*.map`
|
||||
(Keil 构建产物,已 `.gitignore`),无 `@build` 即无 `.map`。故 workflow 在**检测不到
|
||||
(Keil 构建产物,已 `.gitignore`),无 `/keil-build` 即无 `.map`。故 workflow 在**检测不到
|
||||
`.map` 时自动跳过该钩子**(`SKIP=mem-numbers`,钩子显示 `Skipped` 而非 `Passed`——
|
||||
是"不适用",不是"通过");有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。
|
||||
|
||||
> 🔴 **门禁盲区(2026-10-02 记录)**:跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**——
|
||||
> 新增任务/栈/缓冲/代码导致的体积增长**不会在 CI 被拦**。故该跳过以 `::warning::` 明示
|
||||
> (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `@build`(0/0)
|
||||
> (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `/keil-build`(0/0)
|
||||
> → `python scripts/check_mem_numbers.py --scan`。详见
|
||||
> [`../../CI/远程CI完整指南.md`](../../CI/远程CI完整指南.md) §0.0。
|
||||
|
||||
@@ -578,7 +578,7 @@ CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runn
|
||||
- **`.map` 自适应与 CI 完全一致**:本地有 `.map` 就照常跑 `mem-numbers`,没有就跳过。
|
||||
- 退出码:`0` 通过 / `1` 门禁或单测失败 / `2` 环境错误。
|
||||
|
||||
> ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`@build`、板端测试套件/主机侧回归台架项都只存在于本地。
|
||||
> ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`/keil-build`、板端测试套件/主机侧回归台架项都只存在于本地。
|
||||
> 提交前跑一遍 `ci_local.py` 仍是**强制**动作,见 §7.1(`pre-commit run --all-files` 只覆盖提交期 47 根,
|
||||
> 走不到 `commit-msg` 与 manual 阶段)。
|
||||
|
||||
@@ -620,7 +620,7 @@ CI 就静默停摆(2026-10-11 实测 run 16~21 共 12 个 job 零日志)。
|
||||
| `docs/测试说明/板级测试/SD2506_Test_Guide.md` | RTC(TC-RTC-xxx) |
|
||||
| `docs/测试说明/板级测试/FTP_Test_Guide.md` | FTP 服务(手动 + 阶段 8 自动化) |
|
||||
| `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试工具(Python 串口助手) |
|
||||
| `docs/测试说明/流程与规范/自动化测试流程.md` | @test 闭环细节 |
|
||||
| `docs/测试说明/流程与规范/自动化测试流程.md` | /serial-boot-test 闭环细节 |
|
||||
| `docs/测试说明/流程与规范/测试文档模板规范.md` | 测试文档模板与命名 |
|
||||
| `docs/测试说明/单元测试/看门狗单元测试说明.md` | 看门狗判定状态机(主机侧纯逻辑单测) |
|
||||
| `docs/测试说明/单元测试/系统时钟单元测试说明.md` | 系统时钟换算(主机侧纯逻辑单测) |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 测试文档模板规范
|
||||
|
||||
本文档定义本项目测试文档的统一模板与编写规范,确保各模块(CH395F、GD5F2GQ5UE、SD2506 RTC、存储等)测试文档格式一致、可追溯,并能直接接入 `@test` 自动化闭环与门禁体系。
|
||||
本文档定义本项目测试文档的统一模板与编写规范,确保各模块(CH395F、GD5F2GQ5UE、SD2506 RTC、存储等)测试文档格式一致、可追溯,并能直接接入 `/serial-boot-test` 自动化闭环与门禁体系。
|
||||
|
||||
---
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
| 文档 | 关系 |
|
||||
|---|---|
|
||||
| `测试开发标准流程.md` | 测试开发总流程(规划 → 实现 → 门禁 → 沉淀);本文是其"测试规划(文档先行)"环节的可执行模板 |
|
||||
| `自动化测试流程.md` | `@test` 闭环命令、参数与失败模式;本文"回归接入"章节引用它 |
|
||||
| `自动化测试流程.md` | `/serial-boot-test` 闭环命令、参数与失败模式;本文"回归接入"章节引用它 |
|
||||
| `docs/UI设计/UI_自动化测试流程.md` | UI 类改动的模拟器回归门禁 |
|
||||
| `docs/问题记录/<模块>_Trap_Records.md` | 陷阱记录;测试文档"已知陷阱"章节引用它 |
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
### 1.2 设计原则覆盖表(原则短语 → 描述 → 验证用例)
|
||||
### 1.3 测试环境(拓扑、工具链、硬件版本、PC 端依赖)
|
||||
### 1.4 启用方法(编译开关 + 运行命令)
|
||||
### 1.5 回归接入(@test 标记 / PC 端脚本 / 门禁层级)
|
||||
### 1.5 回归接入(/serial-boot-test 标记 / PC 端脚本 / 门禁层级)
|
||||
|
||||
## 2~N. 阶段 x:[阶段名]
|
||||
- 阶段元信息表(含阶段宏)
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| `@test` 标记 | 套件结尾汇总行(如 `=== Storage Tests:`)或阶段标题行;**必须与串口实际输出逐字一致**(`verify_log.py` 子串匹配) |
|
||||
| `/serial-boot-test` 标记 | 套件结尾汇总行(如 `=== Storage Tests:`)或阶段标题行;**必须与串口实际输出逐字一致**(`verify_log.py` 子串匹配) |
|
||||
| 禁止模式 | 默认 `FAIL;ERROR;HardFault;assert`,可按模块增补 |
|
||||
| PC 端脚本 | `test/*.py` 工具清单与调用方式 |
|
||||
| 门禁层级 | 板端测试套件 / 主机侧回归(串口标记 + 模拟器回归)/ 人工目检,见 `测试开发标准流程.md` §1 |
|
||||
@@ -76,7 +76,7 @@
|
||||
| **类型** | 独立运行 / 需 PC 配合 / 需物理操作 / 在 xxTask 中运行 |
|
||||
| **耗时** | 预估执行时间 |
|
||||
| **入口** | 代码入口函数或宏定义 |
|
||||
| **出口** | 阶段结束标志(如串口输出内容,即 `@test` 可校验的标记) |
|
||||
| **出口** | 阶段结束标志(如串口输出内容,即 `/serial-boot-test` 可校验的标记) |
|
||||
| **阶段宏** | 对应使能宏(如 `ENABLE_PHASE1_TESTS`) |
|
||||
|
||||
---
|
||||
@@ -195,7 +195,7 @@ TC-{MOD}-{NNN}
|
||||
|
||||
| 通道 | 判定方 | 适用 |
|
||||
|---|---|---|
|
||||
| 串口日志标记 | `@test` / `verify_log.py` 子串匹配 | 启动序列、阶段标题行、关键事件 |
|
||||
| 串口日志标记 | `/serial-boot-test` / `verify_log.py` 子串匹配 | 启动序列、阶段标题行、关键事件 |
|
||||
| 返回值 / 寄存器 / 数据 | 板端 `XXX_TEST_CHECK` 断言 | 驱动命令、协议交互、数据完整性 |
|
||||
| 像素参考 | LVGL 模拟器回归(`simulator/tests/`) | UI 渲染、字体、样式 |
|
||||
| 内存池水位 | 模拟器池基线 / 往返常量 | LVGL 对象泄漏、结构漂移 |
|
||||
@@ -256,7 +256,7 @@ TC-{MOD}-{NNN}
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| `@test` 标记 | `=== XXX Tests:`(套件结尾汇总行的确切字符串) |
|
||||
| `/serial-boot-test` 标记 | `=== XXX Tests:`(套件结尾汇总行的确切字符串) |
|
||||
| PC 端脚本 | `test/xxx_test.py` |
|
||||
| 门禁层级 | 板端测试套件 + 主机侧回归 |
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
## 总体流程
|
||||
|
||||
```
|
||||
@test 命令触发
|
||||
/serial-boot-test 触发
|
||||
│
|
||||
├─ Step 0 预检 .venv + serial_tool(selftest/ports 确认 COM)
|
||||
├─ Step 1 构建验证 keil-build skill(keil_build.py) (0 错误 0 警告才继续)
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
| 名称 | 位置 | 作用 |
|
||||
|---|---|---|
|
||||
| `@build` | `keil-build` skill(`.dsh/skills/keil-build/`,自带 `keil_build.py`) | 仅编译验证(0 错误 0 警告) |
|
||||
| `/keil-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 |
|
||||
| `/serial-boot-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]` 时基)→ 回读校验(`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 自动截头) |
|
||||
@@ -30,14 +30,14 @@
|
||||
|
||||
## 判定标准
|
||||
|
||||
- **构建**:`@build` 退出码 0(0 错误 0 警告),否则不进入硬件环节。
|
||||
- **构建**:`/keil-build` 退出码 0(0 错误 0 警告),否则不进入硬件环节。
|
||||
- **烧录**:`@download` 退出码 0(`run_boot_test.py` 内部调用)。
|
||||
- **日志**:`verify_log.py` 退出码
|
||||
- `0` = PASS:所有 `-Required` 标记均出现,且无 `-Forbidden` 模式命中
|
||||
- `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-11 加)**:`__DATE__`/`__TIME__` 只在**该 TU 被重编**时才刷新——增量构建下只切 `test/board/test_config.h` 的套件宏**不会**重编 `app_main.c`,于是产品镜像与测试镜像顶着同一个戳(实测:切宏后的测试构建与产品构建戳同为 `Oct 11 2026 14:44:11`,只有回读校验才发现镜像不同)。两条防线:① `app_main.c` `#include "test_config.h"` 建立头依赖 → 改宏必重编本 TU、戳必刷新;② 戳串追加构建变体 ` suite=<TEST_SUITE_NAME> dstr=<on|off>`(`FW_BUILD_VARIANT`)→ 产品 = `suite=NONE dstr=off`、CH395F 测试 = `suite=CH395F dstr=on`,**即便戳时间相同也能一眼分辨**。实测:切宏前后 `Oct 11 2026 20:36:11 suite=NONE dstr=off` → `Oct 11 2026 20:36:40 suite=CH395F dstr=on`;`@test` 闭环对新格式匹配通过(Required 行就是完整戳串,脚本按"标记后连续可打印 ASCII"抠取,无需改脚本)。
|
||||
- **戳覆盖构建配置(2026-10-11 加)**:`__DATE__`/`__TIME__` 只在**该 TU 被重编**时才刷新——增量构建下只切 `test/board/test_config.h` 的套件宏**不会**重编 `app_main.c`,于是产品镜像与测试镜像顶着同一个戳(实测:切宏后的测试构建与产品构建戳同为 `Oct 11 2026 14:44:11`,只有回读校验才发现镜像不同)。两条防线:① `app_main.c` `#include "test_config.h"` 建立头依赖 → 改宏必重编本 TU、戳必刷新;② 戳串追加构建变体 ` suite=<TEST_SUITE_NAME> dstr=<on|off>`(`FW_BUILD_VARIANT`)→ 产品 = `suite=NONE dstr=off`、CH395F 测试 = `suite=CH395F dstr=on`,**即便戳时间相同也能一眼分辨**。实测:切宏前后 `Oct 11 2026 20:36:11 suite=NONE dstr=off` → `Oct 11 2026 20:36:40 suite=CH395F dstr=on`;`/serial-boot-test` 闭环对新格式匹配通过(Required 行就是完整戳串,脚本按"标记后连续可打印 ASCII"抠取,无需改脚本)。
|
||||
- **烧录回读(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 第三批)**:
|
||||
@@ -105,7 +105,7 @@
|
||||
| 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 未重编译。处置:`stlink_flash.py flash <hex>` 重烧并看 `READBACK: OK`;确认 `@build` 已重跑(戳取自编译时刻)。⚠ 若戳**命中**但里面的 `suite=` 与本次要跑的套件不符(想跑 CH395F 却显示 `suite=NONE dstr=off`)→ 烧的是产品镜像,`@build` 不会替你切宏(→ `board-test-suite` skill) |
|
||||
| `[MISS] fw build: <戳>`(其余标记全中) | 芯片里跑的不是这份构建:烧录没落地 / 烧错文件 / hex 未重编译。处置:`stlink_flash.py flash <hex>` 重烧并看 `READBACK: OK`;确认 `/keil-build` 已重跑(戳取自编译时刻)。⚠ 若戳**命中**但里面的 `suite=` 与本次要跑的套件不符(想跑 CH395F 却显示 `suite=NONE dstr=off`)→ 烧的是产品镜像,`/keil-build` 不会替你切宏(→ `board-test-suite` skill) |
|
||||
| `READBACK: FAIL` / `flash readback mismatch` | 芯片内容与 hex 逐字节不一致(写入未落地、选项字节/RDP 异常、烧错目标)。处置:按 `docs/测试说明/测试工具/ST-LINK_CLI_复位使用说明.md` §7 排查;`stlink_flash.py flash` 重烧后闭环会自动复核 |
|
||||
| `boot banner x2 > allowed 1` | 采集窗口内板子自己复位过(IWDG 咬狗 / Fault 后 `NVIC_SystemReset` / 掉电重启)。处置:看判定里逐条打印的 `reset cause:` 定位;跑**故意复位**的套件用 `--max-boot-count N` 放宽 |
|
||||
| `marker order violated` | 标记全中但**先后顺序颠倒** = 某步在另一步之后补做(就绪屏障失序 / 失败重试补跑)。处置:看判定打印的各标记首次行号定位;`--require-order` 只在需要强约束时启用 |
|
||||
@@ -118,13 +118,13 @@
|
||||
## UI 屏幕自动化
|
||||
|
||||
- **初始化回归**:`App/task/ui_task.c` 已有 `UI port init: OK` / `UI menu init: OK` / `UI net sink: started`
|
||||
打印(见 `@test` 命令示例);将其加入 `-Required` 即可经串口自动回归 UI 初始化。
|
||||
打印(见 `run_boot_test.py` 命令示例);将其加入 `--required` 即可经串口自动回归 UI 初始化。
|
||||
- **屏幕内容回归**:由 LVGL 模拟器像素回归覆盖(文本级断言 + 66 张黄金参考 + 池水位门禁,
|
||||
见 `docs/UI设计/UI_自动化测试流程.md`);上机屏幕仍需人工目检(见 `测试开发标准流程.md` §1)。
|
||||
|
||||
## 扩展用法
|
||||
|
||||
- **单模块回归**:`@test` 后接 `-Required` 定制,例如网络回归用
|
||||
- **单模块回归**:`run_boot_test.py` 后接 `--required` 定制,例如网络回归用
|
||||
`-Required "App init OK;Network Init;net init: OK;PHY: 100M FULL"`。
|
||||
- **测试套件构建**:`test/board/test_config.h` 开启某个 `TEST_SUITE_*` 后,把套件打印的
|
||||
`=== ... Test:` 标题作为 Required 标记,配合 `test/` 下各用例输出判断。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Python 串口助手(serial_tool)使用指南
|
||||
|
||||
> **定位**:面向使用者的操作手册——命令怎么敲、输出怎么看、常见场景怎么组合、出问题怎么排查。
|
||||
> **关联文档**:设计决策与实现细节见 `串口助手设计说明.md`;@test 自动化闭环见 `自动化测试流程.md`;`serial-assistant` skill 有一页速查(用到自动加载,AGENTS.md 不再重复收录)。
|
||||
> **关联文档**:设计决策与实现细节见 `串口助手设计说明.md`;/serial-boot-test 自动化闭环见 `自动化测试流程.md`;`serial-assistant` skill 有一页速查(用到自动加载,AGENTS.md 不再重复收录)。
|
||||
> **版本**:v1.1(与 `.dsh/skills/serial-assistant/scripts/serial_tool.py` 当前实现一致,2026-09-15)
|
||||
|
||||
## 1. 它是什么(60 秒版)
|
||||
@@ -54,7 +54,7 @@
|
||||
| `status` | 全状态一条 JSON(含 `state`、`rx_age_ms` = 距最后一个 RX 字节的毫秒数,`null` = 这个 daemon 还没收过字节) | 是 |
|
||||
| `log` | 读日志文件(`--tail N` / `--grep P`) | **否**(直接读文件,daemon 死了也能用) |
|
||||
| `clear` | 清空日志 + 代际 +1 | 是 |
|
||||
| `wait-idle` | 等 RX 静默(@test 的收尾判据) | 是 |
|
||||
| `wait-idle` | 等 RX 静默(/serial-boot-test 的收尾判据) | 是 |
|
||||
| `start` / `stop` / `restart` | daemon 生命周期 | — |
|
||||
| `selftest` | 无头自检(行拆分/解码/截头算法,无需硬件) | 否 |
|
||||
|
||||
@@ -198,7 +198,7 @@ Select-String -Path '.dsh\skills\serial-assistant\scripts\state\serial.log' -Pat
|
||||
- **首字节门(2026-10-10 加)**:首字节到达**之前**不启动静默计时——旧实现从进入即计时,板子第一行比 `--settle-ms` 来得晚就提前收尾("开机日志缺尾巴"的另一半成因)。`--first-timeout-ms`(0 = 同 `--max-ms`)到点仍零字节 → `no_data` 快速失败。
|
||||
- **`rx_bytes` 是本次增量**(旧实现回的是 daemon 累计值,常驻 daemon 下动辄百万,无判读价值)。
|
||||
- **CLI 超时自动推导**:未显式带 `--timeout` 时按 `--max-ms/1000 + 5` 取值,不再需要手工加(旧版固定 5s 会在 daemon 还在等时掐断连接 → CLI exit 2 + daemon.log 刷 `ConnectionAbortedError`)。
|
||||
- @test 流程(`run_boot_test.py`)用它作为"烧录后日志收齐"的判据(`--first-wait-ms` 默认 12000)。
|
||||
- /serial-boot-test 流程(`run_boot_test.py`)用它作为"烧录后日志收齐"的判据(`--first-wait-ms` 默认 12000)。
|
||||
|
||||
### 3.9 `start` / `stop` / `restart` — daemon 生命周期
|
||||
|
||||
@@ -273,7 +273,7 @@ Select-String -Path '.dsh\skills\serial-assistant\scripts\state\serial.log' -Pat
|
||||
... serial_tool.py close
|
||||
```
|
||||
|
||||
### 5.2 抓一次开机日志(手动版 @test)
|
||||
### 5.2 抓一次开机日志(手动版 /serial-boot-test)
|
||||
|
||||
```powershell
|
||||
... serial_tool.py open --port COM6
|
||||
@@ -322,7 +322,7 @@ Get-Content '.dsh\skills\serial-assistant\scripts\state\daemon.log' -Tail 30 #
|
||||
| `baudRate` | 默认波特率 | 115200 |
|
||||
| `httpPort` | daemon HTTP 端口(一般不动) | 18765 |
|
||||
|
||||
**换端口只改这个文件**(`@test` 与手动调试都读它)。`httpPort` 冲突时才需要改。
|
||||
**换端口只改这个文件**(`/serial-boot-test` 与手动调试都读它)。`httpPort` 冲突时才需要改。
|
||||
|
||||
## 7. 故障排查
|
||||
|
||||
|
||||
+10
-10
@@ -14,7 +14,7 @@
|
||||
|
||||
| # | 缺陷 | 影响 |
|
||||
|---|---|---|
|
||||
| F1 | 二进制绑定**用户档案**:`opencode.json` 的 `mcp.serial` 指向 `C:\Users\16241\AppData\Local\serial-mcp-server\*.exe`,本机实际用户为 **Administrator**(该档案不存在,二进制缺失) | serial MCP 工具不可用、@test 采集步骤断裂;换机器/换用户无法复现 |
|
||||
| F1 | 二进制绑定**用户档案**:`opencode.json` 的 `mcp.serial` 指向 `C:\Users\16241\AppData\Local\serial-mcp-server\*.exe`,本机实际用户为 **Administrator**(该档案不存在,二进制缺失) | serial MCP 工具不可用、/serial-boot-test 采集步骤断裂;换机器/换用户无法复现 |
|
||||
| F2 | **无历史缓冲**:`read` 只能读调用**之后**到达的字节 | 抓开机日志被迫走"采集窗口 + first-byte 触发 + EOF 重试"仪式(6 个参数 + JSON 解析 + CH340 释放后重开陷阱特判) |
|
||||
|
||||
Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机器/用户可复现);常驻 daemon 把**全量** RX/TX 持续写入日志文件,历史 = 日志文件本身,采集窗口仪式整体消失。
|
||||
@@ -28,7 +28,7 @@ Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机
|
||||
| G3 | Agent 一切操作 = 带硬超时(5s)的短命令;串口所有权归常驻 daemon |
|
||||
| G4 | 一条 `status` 命令看全状态(daemon/端口/计数/最后 RX/日志文件/错误) |
|
||||
| G5 | 随时清空日志(原子 + 代际号,新旧内容可区分) |
|
||||
| G6 | @test 一键流程切换新工具:open → clear → 烧录 → 读文件 → verify,无采集窗口 |
|
||||
| G6 | /serial-boot-test 一键流程切换新工具:open → clear → 烧录 → 读文件 → verify,无采集窗口 |
|
||||
| G7 | 全流程**不关端口**(ST-Link 烧录不占 COM),从根上规避 CH340 释放/重开陷阱 |
|
||||
|
||||
## 2. 需求清单
|
||||
@@ -45,7 +45,7 @@ Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机
|
||||
| R8 | 日志 20MB 上限,超限**原地截头**保留尾部 10MB(不重开端口/不 rename) | P0 | 补充(用户确认) |
|
||||
| R9 | 开端口不翻转 DTR/RTS(防 STM32 自动复位电路误复位),可配置 | P0 | 补充 |
|
||||
| R10 | 串口读错/拔线 → daemon 存活,state=error,可重开;daemon 被杀可自恢复 | P0 | 补充 |
|
||||
| R11 | @test 集成:`run_boot_test.py` 采集步改写 + `verify_log.py` 兼容 + 旧文档/配置迁移 | P0 | 用户场景 |
|
||||
| R11 | /serial-boot-test 集成:`run_boot_test.py` 采集步改写 + `verify_log.py` 兼容 + 旧文档/配置迁移 | P0 | 用户场景 |
|
||||
| R12 | `send`(TX 进日志)、`log --tail/--grep`、`start/stop/restart`、`selftest` 无头自检 | P1 | 补充 |
|
||||
|
||||
## 3. 总体方案
|
||||
@@ -84,7 +84,7 @@ flowchart LR
|
||||
| `send` | 发 TX 字节(进日志);默认**精确字节**不加换行 | 位置参数文本(UTF-8)或 `--hex 0D0A`;`--nl` 追加 CRLF | `{"ok":true,"bytes":12}` |
|
||||
| `status` | 全状态(见 §4.2) | — | 见 §4.2 |
|
||||
| `log` | 读日志(**先过滤后截尾**,2026-10-10) | `--tail N`(默认 50;`0` = 全部)`--grep P` `-i` `--regex` | `{"ok":true,"gen":8,"lines":["…"],"scanned":N,"matched":M}` |
|
||||
| `wait-idle` | 等 RX 静默(@test 收尾判据) | `--settle-ms`(默认 8000)`--max-ms`(默认 30000)`--first-timeout-ms`(默认同 max) | `{"ok":true,"rx_bytes":本次增量,"idle":bool,"waited_ms":N,"reason":"idle\|no_data\|timeout"}` |
|
||||
| `wait-idle` | 等 RX 静默(/serial-boot-test 收尾判据) | `--settle-ms`(默认 8000)`--max-ms`(默认 30000)`--first-timeout-ms`(默认同 max) | `{"ok":true,"rx_bytes":本次增量,"idle":bool,"waited_ms":N,"reason":"idle\|no_data\|timeout"}` |
|
||||
| `clear` | 清空日志:原地截断 + 写 `[SYS] log cleared (gen=N+1)` | — | `{"ok":true,"gen":8}` |
|
||||
| `start` / `stop` / `restart` | daemon 生命周期 | — | `{"ok":true,"pid":…}` / stop 后 `{"ok":true,"stopped":true}` |
|
||||
| `selftest` | 无头自检(行拆分器/解码回退/截头算法 单测回环,无需硬件) | — | 0=PASS |
|
||||
@@ -175,13 +175,13 @@ flowchart LR
|
||||
| 重开退避 | 错误恢复路径重开端口前 ≥1.5s 退避(CH340 特性);正常流程不重开 |
|
||||
| `stop` | 关端口(flush 尾行)→ 写 `[SYS] port closed` → 退出 |
|
||||
| 进程被强杀 | Windows 进程退出自动释放 COM 端口;下次命令自动拉起新实例(`open` 幂等路径) |
|
||||
| `wait-idle` 判据(2026-10-10 修) | `no_data` = **本次等待期间零字节**(旧实现比的是 daemon 累计 `rx_bytes` 是否为 0 → 常驻 daemon 下永不触发,板子全程静默也误报 `idle:true`,@test 的 no_data 拦截形同虚设);**首字节到达前不启动 settle 计时**(`first_timeout_ms` 兜底,默认 = `max_ms`);返回 `rx_bytes` = 本次增量 |
|
||||
| `wait-idle` 判据(2026-10-10 修) | `no_data` = **本次等待期间零字节**(旧实现比的是 daemon 累计 `rx_bytes` 是否为 0 → 常驻 daemon 下永不触发,板子全程静默也误报 `idle:true`,/serial-boot-test 的 no_data 拦截形同虚设);**首字节到达前不启动 settle 计时**(`first_timeout_ms` 兜底,默认 = `max_ms`);返回 `rx_bytes` = 本次增量 |
|
||||
| CLI 超时与断连噪声(2026-10-10) | `wait-idle` 未显式带 `--timeout` 时按 `max_ms/1000 + 5` 自动推导(旧版固定 5s 会在 daemon 还在等时掐断连接);handler 侧 `ConnectionError`(客户端已断开)不再往 daemon.log 打 traceback(本机曾累积 16 次);`daemon.log` 超 1MB 在拉起新实例前原地保尾 256KB |
|
||||
| 配置读取(2026-10-10) | 按 `utf-8-sig` 读(容忍 BOM,与 `run_boot_test.py` 同口径);文件缺失 / JSON 语法错 / `baudRate`·`httpPort` 非整数不再静默回退成"没配端口",错误文本进 `configError`,由 `status` / `ports` / `open` 显式回显 |
|
||||
|
||||
## 7. 集成与迁移(@test 切换)
|
||||
## 7. 集成与迁移(/serial-boot-test 切换)
|
||||
|
||||
### 7.1 新 @test 流程(`run_boot_test.py` 采集步改写,退出码约定不变:0/1/2/3)
|
||||
### 7.1 新 /serial-boot-test 流程(`run_boot_test.py` 采集步改写,退出码约定不变:0/1/2/3)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -193,7 +193,7 @@ flowchart LR
|
||||
```
|
||||
|
||||
- 对比旧流程:6 个采集窗口参数 + JSON 解析 + EOF 重试 **全部删除**;采集 = 端口常开 + 读文件
|
||||
- `verify_log.py` 与纯文本日志契约一致(2026-09-20 随 @test 迁移 Python,判定语义不变)
|
||||
- `verify_log.py` 与纯文本日志契约一致(2026-09-20 随 /serial-boot-test 迁移 Python,判定语义不变)
|
||||
- 参数保留:`-Port` `-BaudRate` `-FlashCmd` `-Required` `-Forbidden` `-OutDir`;删除 `-DurationMs` 等窗口参数,新增 `-SettleMs`(静默判据,**默认 8000**——boot 任务间有 3~4s 自然静默间隙,2000 会截断)`-MaxWaitMs`(默认 30000)
|
||||
|
||||
### 7.2 配套变更清单
|
||||
@@ -230,7 +230,7 @@ flowchart LR
|
||||
| A4 | `stop` 后强杀 daemon(taskkill)→ 再发任意命令 | 自动拉起成功,`open` 幂等路径无 CH340 EOF 错误 |
|
||||
| A5 | 日志灌至 >20MB | 自动截头至 ~10MB,写入连续、无重开端口,`[SYS] truncated` 行存在 |
|
||||
| A6 | 运行中拔 CH340 → `status` | daemon 存活,`state=error`;重插 + `open` 恢复 |
|
||||
| A7 | **@test E2E ×3**(含一轮"上轮端口未关"场景) | 三轮全 PASS(Required 标记齐、无 Forbidden),boot 日志完整 |
|
||||
| A7 | **/serial-boot-test E2E ×3**(含一轮"上轮端口未关"场景) | 三轮全 PASS(Required 标记齐、无 Forbidden),boot 日志完整 |
|
||||
| A8 | `selftest` | 0=PASS |
|
||||
|
||||
## 10. 实施步骤(按序执行)
|
||||
@@ -241,7 +241,7 @@ flowchart LR
|
||||
| 步骤 2 | daemon 核心:HTTP 服务 + 串口读线程 + 行日志写(含 clear/截头/DTR 策略) | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(daemon 部分) | `start→status→send→stop` 手动走通 ✅ |
|
||||
| 步骤 3 | CLI 全子命令 + 自动拉起 + 超时契约 + `selftest` | serial_tool.py(CLI 部分) | `selftest` 0=PASS + 命令走查 ✅ |
|
||||
| 步骤 4 | 验收 A1–A6、A8 | 测试通过记录 | A1–A5/A8 PASS ✅(本机无 CH340,A6 待硬件) |
|
||||
| 步骤 5 | @test 集成:`run_boot_test.py` 采集步改写 + 参数收敛 | run_boot_test.py | 负路径(端口缺失→exit 2)+ 假烧录全链路 + verify 正/负判定 ✅;**A7 正路径待 CH340 接入** |
|
||||
| 步骤 5 | /serial-boot-test 集成:`run_boot_test.py` 采集步改写 + 参数收敛 | run_boot_test.py | 负路径(端口缺失→exit 2)+ 假烧录全链路 + verify 正/负判定 ✅;**A7 正路径待 CH340 接入** |
|
||||
| 步骤 6 | 文档/配置迁移(§7.2 全清单) | docs + 配置 | 全库 grep 无 serial-mcp-server 残留引用(本文 §12 工具历史除外) ✅ |
|
||||
|
||||
提交:全部完成后单提交(pre-commit 门禁;纯宿主侧 Python,不涉及 Keil 构建)。
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# 启动日志(boot_log)存档
|
||||
|
||||
本目录保存 `@test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`)产出的**开机串口日志快照**,
|
||||
本目录保存 `/serial-boot-test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`)产出的**开机串口日志快照**,
|
||||
用于**跨构建的栈 / 堆水位对照**(解析其中的 `[STACK]` 行)。
|
||||
|
||||
## 1. 为什么需要存档
|
||||
|
||||
- `@test` 默认把 boot_log 写到临时目录 `%TEMP%\serial-boot-test\boot_log_<时间戳>.txt`——**会被系统清理**;
|
||||
- `/serial-boot-test` 默认把 boot_log 写到临时目录 `%TEMP%\serial-boot-test\boot_log_<时间戳>.txt`——**会被系统清理**;
|
||||
- 台架长跑的 20MB 串口日志**截头会丢掉早期 `[STACK]` 水位行**(开机首次约 5s 后才报告);
|
||||
- 两者叠加导致**无法事后回溯**"改代码前后"的栈水位变化。
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
| 命名 | `<日期>_<构建标识>_boot_log.txt`,例:`20261005_prod_none_boot_log.txt` |
|
||||
| 构建标识 | 建议含:产品 / 测试套件(`prod_none` / `suite_ch395f` …)+ 负载(`idle` / `v5_30min` / `ftp_stress`) |
|
||||
| 随文件登记 | 在下方 §3 清单登记日期、构建、负载、关键 `[STACK]` 水位(如 defaultTask 峰值) |
|
||||
| 入库门槛 | **只存有对照价值的快照**——常规 `@test` 通过日志不必入库 |
|
||||
| 入库门槛 | **只存有对照价值的快照**——常规 `/serial-boot-test` 通过日志不必入库 |
|
||||
| 故障注入日志 | 含**刻意产生**的 `[ERR]` 行(如兜底门闭锁)的日志用自定义标记集采集:`--required` 加闭锁/复归两行、`--forbidden` 去掉 `[ERR]` 保留 `FAIL;ERROR;HardFault`。此类日志的 PASS 只代表注入行为符合预期,不代表标记集等价于日常门禁 |
|
||||
| 证据可达性 | 台账**只准引用已入库的文件**。被忽略规则挡住的产物(如全局 `*.bin`)要么显式 force-add 入库,要么把行内引用改写成「本机产物(不入库)」——**两者都不做 = 让台账行指着仓库里不存在的证据**,按图索骥的人只能拿到 File Not Found |
|
||||
|
||||
|
||||
+18
-18
@@ -19,7 +19,7 @@ Python 环境、以及 `docs/任务规划/` 这类**全局唯一登记簿**都
|
||||
**做法**:每个任务一棵 `git worktree` + 一条任务分支,任务之间物理隔离;干完由固定命令合回主干。
|
||||
|
||||
**范围**:只解决「开发期隔离 + 回收期合并」的机械流程。**不改变**既有的分支纪律
|
||||
(push 需许可)、门禁口径(pre-commit 51 钩子 + `@build` + `@test`)与文档约定。
|
||||
(push 需许可)、门禁口径(pre-commit 52 钩子 + `/keil-build` + `/serial-boot-test`)与文档约定。
|
||||
|
||||
---
|
||||
|
||||
@@ -108,7 +108,7 @@ D:/Code/DTU Code/
|
||||
| 分支命名 | `task/<ID>-<slug>` | `worktree/<name>` |
|
||||
|
||||
两条**并发红线由本方案单方守住**(插件不感知):`build`(UV4 单实例)与 `bench`(COM6 / ST-LINK /
|
||||
设备 IP 唯一)这两把锁的位置与 TTL 口径见 §7.1 / §7.2——插件树里跑 `@build` 时,`keil_build.py`
|
||||
设备 IP 唯一)这两把锁的位置与 TTL 口径见 §7.1 / §7.2——插件树里跑 `/keil-build` 时,`keil_build.py`
|
||||
会照旧自持自释,持有者名从 cwd 反推为 `pw:<name>`;`bench` 仍须手工占同一个名。共享联接(§3.3)
|
||||
插件不会建,由本 skill 的 `plugin adopt <name>` 补(执行体命令行见 §8)。
|
||||
|
||||
@@ -137,7 +137,7 @@ D:/Code/DTU Code/
|
||||
磁盘从来不是这条线的硬约束——实测一棵纯净检出 **84.0 MiB / 2857 个受控文件**(含 LFS smudge
|
||||
后的真图;`docs/` 独占 46 MiB),而 `.venv` 与 `simulator/SDL2` 走目录联接共享、不随树复制;同日 D
|
||||
盘余 1.4 TB,**8 棵全开的最坏估算也只有约 3.2 GB**(每棵再叠一次构建产物:Keil 中间件 289 MB + 模拟器 35 MB)。
|
||||
真正随并行数上涨的是**排队与合并**:`@build` 与台架 `@test` 各被一把独占锁串行化(见 §5),`done`
|
||||
真正随并行数上涨的是**排队与合并**:`/keil-build` 与台架 `/serial-boot-test` 各被一把独占锁串行化(见 §5),`done`
|
||||
的合并必须串行(§5 下方说明),而每多一棵树把 `sync` 追平与冲突面乘一遍。所以这条线现在是**管
|
||||
理半径闸门**而非磁盘闸门:调到 8 = 把「同时盯几棵」交回人,两把锁的排队代价用 `$TW lock status` 与
|
||||
§7 的台架台账观测;脑子里的名额够不够,看的是锁队列长度,不是磁盘。
|
||||
@@ -152,8 +152,8 @@ D:/Code/DTU Code/
|
||||
| 编码 | 在新空间里改代码 | 可并行 |
|
||||
| 单测产物 | `%TEMP%\dtu_host\<工作树哈希>\` 分家 + 每跑独占 `runs\<pid>-<ns>\` | **不需要锁,但必须按树分家**(旧固定共享名跨树互踩:A 树可能执行 B 树编出的同名 exe,summary 相同不可辨;build/bench 是真独占,继续用锁) |
|
||||
| 门禁 | `scripts/ci_local.py`(静态门禁 + 主机侧纯逻辑单测,纯软件) | **可并行** |
|
||||
| 构建 | `@build`(UV4 增量,需持 `build` 锁) | **必须串行** |
|
||||
| 台架 | `@test` / 板级套件(需先持台架锁,见 §7) | **必须串行** |
|
||||
| 构建 | `/keil-build`(UV4 增量,需持 `build` 锁) | **必须串行** |
|
||||
| 台架 | `/serial-boot-test` / 板级套件(需先持台架锁,见 §7) | **必须串行** |
|
||||
| 提交 | 本地 `git commit`(多行正文用 `-F <UTF-8 无 BOM 文件>`) | 可并行 |
|
||||
| `sync` | `rebase` 追平主干,把冲突控制在任务分支上 | 可并行 |
|
||||
| `done` | 预演 → 合入主干 → 残留审计 → 重生成产物 → 门禁 → 清理工作树 | **必须串行** |
|
||||
@@ -175,7 +175,7 @@ D:/Code/DTU Code/
|
||||
| `docs/任务规划/`(整个文件夹) | 全项目唯一登记簿,每个任务都要销项 | 由主工作树统一改,任务分支不碰 |
|
||||
| `AGENTS.md`、`docs/README.md`(索引/登记类) | 每个任务都要在 AGENTS.md 的「按主题查哪份文档」表、`docs/README.md` §9.2 环境表、`docs/README.md` 的文档索引表里**各加一行** → 同一张表的新增行两任务必撞(2026-10-12 实测:主工作树里本任务的行与他任务的行已并排混在**同一 hunk**)。变更记录那一路**不再是冲突面**:2026-10-28 起新文档一律不设变更记录节(`docs/文档规范.md` §5.1),存量也只剩 5 篇台账/证据文档 | 由主工作树统一追加;提交时**只 `git add` 本任务的行**(`git apply --cached` 选择性入库),勿整文件 `git add` |
|
||||
| `AGENTS.md` 的 `TASK_TABLE` 块 | 生成物,两个任务都抬栈就冲突 | 合并后重跑 `scripts/gen_task_table.py` **重新生成**,不手工并 |
|
||||
| `MDK-ARM/STM32F407-Demo.uvprojx` | 新源文件注册 + UV4 自动改写 | 手工并 `<File>` 条目 → 重跑 `registration-check` → `@build` |
|
||||
| `MDK-ARM/STM32F407-Demo.uvprojx` | 新源文件注册 + UV4 自动改写 | 手工并 `<File>` 条目 → 重跑 `registration-check` → `/keil-build` |
|
||||
| `.pre-commit-config.yaml` | 新增主机单测要加钩子 | 并集处理 |
|
||||
| `docs/内存管理/RAM使用分析与优化方案.md`(及其 `mem-numbers` 快照) | `mem-numbers` 快照 | **绝不手工合数字**,合并后 `scripts/check_mem_numbers.py --write` 重生成 |
|
||||
|
||||
@@ -204,7 +204,7 @@ D:/Code/DTU Code/
|
||||
|
||||
### 7.1 台架锁
|
||||
|
||||
以下物理资源**各只有一份**,故 `@test` / 板级套件 / 烧录必须串行:
|
||||
以下物理资源**各只有一份**,故 `/serial-boot-test` / 板级套件 / 烧录必须串行:
|
||||
|
||||
- 串口 `COM6` @115200
|
||||
- ST-LINK SN `0B250D12A218303030303032`
|
||||
@@ -219,7 +219,7 @@ D:/Code/DTU Code/
|
||||
|
||||
**TTL 按锁分设**(2026-10-12):`LOCK_TTL_HOURS = {"build": 1.0, "bench": 6.0}`。
|
||||
口径 = 兜底窗口应与**操作真实时长**同量级:编译是秒~分钟级(`keil_build.py` 自持自释,
|
||||
窗口 ≈ UV4 运行时长),1h 已足够宽松;bench 是**会话级**(flash→`@test`→探针),才配得上 6h。
|
||||
窗口 ≈ UV4 运行时长),1h 已足够宽松;bench 是**会话级**(flash→`/serial-boot-test`→探针),才配得上 6h。
|
||||
持有者还可用 `lock acquire ... --ttl <小时>` **自报**更精确的窗口——
|
||||
`keil_build.py` 即按其 `--timeout` 自报(`timeout/3600 + 15min`,下限 0.5h)。
|
||||
|
||||
@@ -230,11 +230,11 @@ D:/Code/DTU Code/
|
||||
|
||||
### 7.2 构建
|
||||
|
||||
`@build`(UV4 增量)在新空间里是**独占目录**,但 **UV4 不能两个实例同时 `-b`**(2026-10-12 用户确认,
|
||||
`/keil-build`(UV4 增量)在新空间里是**独占目录**,但 **UV4 不能两个实例同时 `-b`**(2026-10-12 用户确认,
|
||||
无需实测)——因此构建**同样纳入串行锁**(`build`),与台架锁并列。
|
||||
|
||||
> 结论:任务期真正可并行的只有**编码与静态门禁**(`ci_local.py` 的静态门禁 / 主机侧纯逻辑单测,纯软件、零硬件);
|
||||
> `@build` 与烧录/台架都必须排队。
|
||||
> `/keil-build` 与烧录/台架都必须排队。
|
||||
|
||||
### 7.3 并行道与写范围规则(规划层 ↔ 执行层映射)
|
||||
|
||||
@@ -264,7 +264,7 @@ D:/Code/DTU Code/
|
||||
|
||||
## 8. SKILL 设计
|
||||
|
||||
**名称**:`task-worktree` **文档别名**:`@task`——**只是沿用 `@build` / `@test` 惯例的文档记号,没有任何执行机制**:shell 里敲 `@task` 直接 command not found,`dsh task` 会被当 profile 名报错(2026-10-30 实测);DSH 会话里的显式调用是 `/task-worktree`,可执行形态只有下面的执行体命令行。
|
||||
**名称**:`task-worktree` **文档别名**:`@task`——**只是文档记号,没有任何执行机制**:shell 里敲 `@task` 直接 command not found,`dsh task` 会被当 profile 名报错;DSH 会话里的显式调用是 `/task-worktree`,可执行形态只有下面的执行体命令行。
|
||||
|
||||
**执行体**:`.dsh/skills/task-worktree/scripts/task_worktree.py`(纯标准库,
|
||||
照 `keil_build.py` 的写法,整目录可拷到别的项目复用)。**已落地并端到端实测**(见 §11)。
|
||||
@@ -298,19 +298,19 @@ D:/Code/DTU Code/
|
||||
| 层级 | 内容 | 时机 |
|
||||
| ------- | ------------------------------------ | ------------------------------ |
|
||||
| 任务期 | `scripts/ci_local.py`(静态门禁 + 主机侧纯逻辑单测,可并行) | 每个任务各自跑 |
|
||||
| 任务期 | `@build`(0 错 0 警,**持 `build` 锁串行**) | 每个任务各自跑 |
|
||||
| **新空间首次** | `@build` 一次,生成 `.map` | 见下表下注释:不先构建,体积类门禁必然红 |
|
||||
| 任务期(可选) | `@test` / 板级套件(持 `bench` 锁) | 需真机验证的任务 |
|
||||
| 回收期 | 重生成产物 → `ci_local.py` → `@build` | `done` 内强制(`--skip-gates` 可跳过) |
|
||||
| 任务期 | `/keil-build`(0 错 0 警,**持 `build` 锁串行**) | 每个任务各自跑 |
|
||||
| **新空间首次** | `/keil-build` 一次,生成 `.map` | 见下表下注释:不先构建,体积类门禁必然红 |
|
||||
| 任务期(可选) | `/serial-boot-test` / 板级套件(持 `bench` 锁) | 需真机验证的任务 |
|
||||
| 回收期 | 重生成产物 → `ci_local.py` → `/keil-build` | `done` 内强制(`--skip-gates` 可跳过) |
|
||||
| 人工 | push 许可 | 回收后 |
|
||||
|
||||
> ⚠ **新开工作树后先 `@build` 一次再谈门禁**(实测 2026-10-25):`.map` 是每棵树各自的构建产物、
|
||||
> ⚠ **新开工作树后先 `/keil-build` 一次再谈门禁**(实测 2026-10-25):`.map` 是每棵树各自的构建产物、
|
||||
> 不入版本库,所以依赖它的事实源的门禁——`mem-numbers`、`mem-budget`、以及 `ci_local.py` 的
|
||||
> 体积门禁——在从未构建过的新空间里**必然红**。这不是代码缺陷,脚本此时也会把这句原因打出来
|
||||
> (`check_mem_numbers.py` / `ram_baseline.py` 的提示文案)。顺带一条同源提醒:`done` 的门禁
|
||||
> 排在 `@build` 之前(见下文陷阱条目),同一族误报在回收期也会重现一次。
|
||||
> 排在 `/keil-build` 之前(见下文陷阱条目),同一族误报在回收期也会重现一次。
|
||||
|
||||
**`done` 的完成判据**:主干上 `git merge --no-ff` 成功 + `ci_local.py` 全绿 + `@build` 0 错 0 警
|
||||
**`done` 的完成判据**:主干上 `git merge --no-ff` 成功 + `ci_local.py` 全绿 + `/keil-build` 0 错 0 警
|
||||
|
||||
- 残留审计无死规则/重复条目 + 工作树已移除。
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
| 变量 | 必需性 | 值的形态 | 使用者 | 默认/回退 |
|
||||
|---|---|---|---|---|
|
||||
| `DTU_KEIL_UV4` | MCU 构建机必需 | UV4.exe 完整路径,例如 `D:\Tools\Keil\UV4\UV4.exe` | `keil_build.py`(`@build`) | 不设即报错(exit 2);不从 C 盘旧路径猜测 |
|
||||
| `DTU_KEIL_UV4` | MCU 构建机必需 | UV4.exe 完整路径,例如 `D:\Tools\Keil\UV4\UV4.exe` | `keil_build.py`(`/keil-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` | `stlink_flash.py` | 未设时回退 PATH(`where ST-LINK_CLI.exe` 首个命中);已设但无效 = 硬报错 |
|
||||
| `DTU_PRECOMMIT_PYTHON` | 可选(覆盖用) | 含 `pre_commit` 模块的 `python.exe` 完整路径 | `ci_local.py` 静态门禁、CI `static-gates` | 未设时按 §1.1 探测(当前解释器 → `.venv` → PATH);已设但无效 = 硬报错 |
|
||||
| `DTU_GRAPHVIZ_DOT` | 可选(覆盖用) | `dot.exe` 完整路径 | `gen_refgraph.py` | 未设时只查 PATH;无 Graphviz 时降级为"生成 .dot/.json、跳过渲染" |
|
||||
|
||||
@@ -65,7 +65,7 @@ Graphviz / Cppcheck 的安装位置差异一律用 `DTU_*` 环境变量表达(
|
||||
|
||||
| 变量 | 必需性 | 值的形态 | 使用方 |
|
||||
|---|---|---|---|
|
||||
| `DTU_KEIL_UV4` | **必需**(MCU 构建机) | UV4.exe 完整路径 | `keil_build.py`(`@build`) |
|
||||
| `DTU_KEIL_UV4` | **必需**(MCU 构建机) | UV4.exe 完整路径 | `keil_build.py`(`/keil-build`) |
|
||||
| `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 静态门禁 / 主机侧纯逻辑单测 |
|
||||
| `DTU_CPPCHECK` | 可选(开发机入 PATH 即可;无 PATH 的 CI runner 建议设) | cppcheck.exe 完整路径 | pre-commit cppcheck 钩子、CI 静态门禁 |
|
||||
@@ -94,7 +94,7 @@ Graphviz / Cppcheck 的安装位置差异一律用 `DTU_*` 环境变量表达(
|
||||
| 安装路径 | **任意目录** | `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\keil-build\scripts\keil_build.py`(即 `/keil-build`) | 退出码 0 = 0 错 0 警;失败看 `MDK-ARM/build_log.txt` |
|
||||
| 命令行烧录 | `& '.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`,
|
||||
@@ -189,7 +189,7 @@ python tools/font_gen/extract_cn.py # 任意 python3:
|
||||
|---|---|---|
|
||||
| Python 串口助手 | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(仓库内置,.venv + pyserial) | 常驻 daemon + 薄 CLI + 全量日志文件;无需单独安装,`pip install -r requirements.txt` 即就绪 |
|
||||
| 调试串口 | USART1(PA9 TX / PA10 RX),115200 8N1 | **端口单一配置源 `.serialtest/config.json`**(本机 COM6);换端口只改这个文件 |
|
||||
| 一键测试 | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`(即 `@test`,`& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py`) | 开串口 + 清日志 → 烧录 → wait-idle(静默 8s/超时 30s)→ 提取 → 标记校验;默认端口取 `.serialtest/config.json` / 115200,退出码 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法错误 |
|
||||
| 一键测试 | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`(即 `/serial-boot-test`,`& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py`) | 开串口 + 清日志 → 烧录 → wait-idle(静默 8s/超时 30s)→ 提取 → 标记校验;默认端口取 `.serialtest/config.json` / 115200,退出码 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法错误 |
|
||||
|
||||
> 采集 = 端口常开 + daemon 全量日志(`.dsh/skills/serial-assistant/scripts/state/serial.log`),无采集窗口/first-byte/EOF 重试仪式。
|
||||
> 命令契约与陷阱见 `docs/测试说明/测试工具/串口助手设计说明.md`、`docs/测试说明/流程与规范/自动化测试流程.md`。
|
||||
@@ -423,7 +423,7 @@ workflow 见 `.gitea/workflows/ci.yml`;覆盖边界与判定细节见 [`../测
|
||||
| Actions 开关 | 实例 `[actions] ENABLED = true` | site admin 配置;未开启则 push 不触发任何 job |
|
||||
| runner OS | **Windows**(`runs-on: windows`) | labels 须含 `windows`;与开发机同平台,避免脚本语法分叉 |
|
||||
| runner 软件 | Python 3.x、cppcheck、pre-commit、**MSVC**(VS C++ 工具链 + Windows SDK) | 前三个缺 → `static-gates` **硬失败**(`::error::`);MSVC 缺 → `host-unit-tests` **降级跳过并告警**(不硬失败,但等于主机侧纯逻辑单测裸奔)。runner 以 SYSTEM 服务运行,**用户级 PATH/变量不可见**:自动发现失败时用**机器级** `DTU_PRECOMMIT_PYTHON` / `DTU_CPPCHECK` / `DTU_MSVC_CL` 注入(见 `docs/CI/远程CI完整指南.md` §4.1.3) |
|
||||
| Keil | **不需要** | 刻意不跑 `@build`(需授权);`.map` 缺失时 `mem-numbers` 钩子自动跳过 |
|
||||
| Keil | **不需要** | 刻意不跑 `/keil-build`(需授权);`.map` 缺失时 `mem-numbers` 钩子自动跳过 |
|
||||
| `mem-numbers` | 有 `.map` 就跑,无则 `SKIP` | Keil 构建产物 `.map` 已 gitignore;自建带 Keil 的 runner 会照常跑该钩子 |
|
||||
|
||||
**runner 就绪前的过渡**:CI 只是"多一道网",**不能替代本地门禁**。提交前仍须在本地跑:
|
||||
@@ -501,13 +501,13 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
|
||||
| CH395F 以太网 | SPI2 | PB12 CS / PB13 SCK / PB14 SDO / PB15 SDI | PHY 强制 100M 全双工;SPI 分频系数必须为 8(PCB 设计约束,速度受限) |
|
||||
| GD5F2GQ5UE NAND(2 Gbit) | SPI1 | PE0 CS / PB3 SCK / PB4 MISO / PB5 MOSI / PB8 WP / PE1 HOLD | FatFS(FAT32)+ dhara FTL;上电无卷自动 `f_mkfs` |
|
||||
| TPAFE5160 ADC | 并行 16 位 | PG0~PG15 数据 / **PC6 FRSTDATA** / **PC7 BUSY(EXTI9_5)** / **PC8 RD** / **PC9 CONVST** / **PA8 OS2 · PA11 OS1 · PA12 OS0** | **编译期常驻**(无裁剪开关;原 `APP_ADC_ENABLE` 未接线占位宏已于 2026-10-22 删除) |
|
||||
| USART1(调试串口) | UART | PA9 TX / PA10 RX | 115200 8N1;printf 日志 + `@test` 采集通道 |
|
||||
| USART1(调试串口) | UART | PA9 TX / PA10 RX | 115200 8N1;printf 日志 + `/serial-boot-test` 采集通道 |
|
||||
| UART4(RS-485) | UART | **PC10 TX / PC11 RX / PA15 DE** | 半双工 115200;`app_main.c` 以 `rs485_init(&g_rs485, &huart4, ST_DIR4_GPIO_Port, ST_DIR4_Pin)` 绑定;**编译期常驻**(无裁剪开关;原 `APP_RS485_ENABLE` 占位宏已删) |
|
||||
| 状态 LED ×6 | GPIO | **PC5 / PB0 / PB1 / PB2 / PF11 / PF12**(LED1~LED6) | **低电平点亮**;网名↔实板丝印为 `LED(n+4)` 倒序(V0.1 网表关注点 V3),固件 LED 序表须按丝印对齐 |
|
||||
|
||||
### 3.3 资源预算(部署红线)
|
||||
|
||||
实测(**2026-10-22 `@build` 快照**)。实时权威值恒以 `docs/内存管理/RAM预算台账.md` 与
|
||||
实测(**2026-10-22 `/keil-build` 快照**)。实时权威值恒以 `docs/内存管理/RAM预算台账.md` 与
|
||||
`docs/内存管理/RAM使用分析与优化方案.md` §2.1 受管快照为准,本节只是部署视角的摘要:
|
||||
|
||||
| 资源 | 已用 | 总量 | 利用率 | 余量 | 红线说明 |
|
||||
@@ -582,7 +582,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
|
||||
|
||||
| # | 项目 | 方法 | 通过标准 |
|
||||
|---|---|---|---|
|
||||
| 1 | 上电自启动 | 断电上电 + 串口采集 | `@test` PASS:Required `App init OK;Network Init;net init: OK;PHY: 100M FULL`,无 `FAIL/ERROR/HardFault` |
|
||||
| 1 | 上电自启动 | 断电上电 + 串口采集 | `/serial-boot-test` PASS:Required `App init OK;Network Init;net init: OK;PHY: 100M FULL`,无 `FAIL/ERROR/HardFault` |
|
||||
| 2 | 网络连通 | PC ping 192.168.1.100 | 通(PHY 100M FULL) |
|
||||
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
|
||||
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
|
||||
|
||||
@@ -132,7 +132,7 @@ STM32(U17)为 LQFP144 脚号。
|
||||
| STM32 ADC 外设 | **已生成(2026-10-09,CubeMX 6.17 + FW_F4)**:`Src/adc.c`/`Inc/adc.h` 已入库并挂进 `MDK-ARM/*.uvprojx`;`Inc/stm32f4xx_hal_conf.h` 的 `HAL_ADC_MODULE_ENABLED` 已生效;HAL `stm32f4xx_hal_adc.c/_ex.c` + `stm32f4xx_ll_adc.h` 已补入仓库(HAL 原本是裁剪子集);`Src/main.c` 里 `MX_ADC1_Init()` 排在 `MX_IWDG_Init()` 之后。生成配置核对无误:9 通道 / Rank 1~9 / 全部 144 cycles / `ScanConvMode=ENABLE` / `ContinuousConvMode=DISABLE` / `ExternalTrigConv=ADC_SOFTWARE_START` + `EDGE_NONE` / `EOCSelection=ADC_EOC_SINGLE_CONV` / `DataAlign=RIGHT` / 12 位;`HAL_ADC_MspInit()` 已把 PA0~PA7+PC4 配成 `GPIO_MODE_ANALOG` | 硬件通路已打通(`hadc1` 可用);**消费者已落地**——`App/algo/adc_input.c` 的 `stm32_take()`(「STM32 ADC1 启动与扫描」,v2.0,2026-10-10):ADC1 由 **TIM3 TRGO 硬件触发**一轮 9 通道 + **DMA2_Stream4 循环搬运**进乒乓双缓冲,任务侧只消费刚完成的半区(零忙等),9 通道一轮 ≈ 67 µs |
|
||||
| ADC1 上电自校准 | **已补(2026-10-10)**:`Src/adc.c` 的 `USER CODE BEGIN ADC1_Init 2` 调用 `adc1_calibrate()`(实现同文件 `USER CODE BEGIN 1`,按 RM0090 §13.3.6:清 ADON→等生效→置 ADON→置 CAL→等 CAL 由硬件清零)。⚠ **CubeMX 不生成这一步**,且本仓库裁剪版 HAL **没有** `HAL_ADCEx_Calibration_Start`、裁剪版 CMSIS 头**没有** `ADC_CR2_CAL`(bit3,代码里按 RM 自建 `ADC1_CAL_BIT`)→ 属 USER CODE 区手工定制,审计项 `adc1-calibration-call` / `adc1-calibration-impl` 兜底 | 实测有效:校准前后同一拍 STM32 侧 6 路**同向**变化 `UB 898→961`、`UC 978→1038`、`IB 906→922`、`IC 1061→1071`、`IN 1017→1055`、`T 790→846`(+10~+63 LSB ≈ 0.2~1.5 % FS);TPAFE 侧 `UA/UN/IA` 一字不变 → 变化确来自 ADC1 校准。缺这一步不报错,只是**读数带失调** |
|
||||
| ADC 时钟 | CubeMX 的 F4 ADC 面板**有** Clock Prescaler 一项(此前"面板没有该项、固定 DIV2"的判断有误),已选 **PCLK2/4 → ADCCLK = 21 MHz** ✓。面板里 **DIV2 是灰的、选不到**:DB 里 `FadcConstraint Max=36000000`,APB2 = 84 MHz → DIV2 = 42 MHz 超限,该候选值被 `Action="DISABLE"` 禁掉,超规值进不来 | **不设额外防护(2026-10-09 决定)**:约束已由 CubeMX 把关;`.ioc` 虽不保存 `ADC1.ClockPrescaler` 键,重生成也只会回落到约束允许的值。按 144+12 cycles 算 ≈ 7.4 µs/通道、9 通道一轮 ≈ 67 µs |
|
||||
| `adc_task` | **已实现(B1,2026-10-10;语义取数与有效性层 v2.3;P1-5 事件驱动消费,2026-10-10)**:`App/task/adc_task.c` 每拍调 `adc_input_poll()`(`App/algo/adc_input.c`)= TPAFE `tpafe5160_snapshot()` 原子快照(8 通道)+ 消费一帧 STM32 ADC1 DMA 乒乓缓冲,**经 `adc_input_raw()` 按语义取数**(不再直接索引驱动缓冲);**消费唤醒由 TIM3 ISR 置位 `ADC_WAKE_FLAG`**(不再 1 ms 轮询);任务在 `Src/freertos.c` 的 `USER CODE` 区创建为 `adcTask`(512 words / **AboveNormal**) | 数据路径已通(台架 `@test`:`frames` 严格 1000 Hz 递增、**`idle=0`**、`adc1_rc=0` / `adc1_err=0`) |
|
||||
| `adc_task` | **已实现(B1,2026-10-10;语义取数与有效性层 v2.3;P1-5 事件驱动消费,2026-10-10)**:`App/task/adc_task.c` 每拍调 `adc_input_poll()`(`App/algo/adc_input.c`)= TPAFE `tpafe5160_snapshot()` 原子快照(8 通道)+ 消费一帧 STM32 ADC1 DMA 乒乓缓冲,**经 `adc_input_raw()` 按语义取数**(不再直接索引驱动缓冲);**消费唤醒由 TIM3 ISR 置位 `ADC_WAKE_FLAG`**(不再 1 ms 轮询);任务在 `Src/freertos.c` 的 `USER CODE` 区创建为 `adcTask`(512 words / **AboveNormal**) | 数据路径已通(台架 `/serial-boot-test`:`frames` 严格 1000 Hz 递增、**`idle=0`**、`adc1_rc=0` / `adc1_err=0`) |
|
||||
| TPAFE 采样触发 | **周期 CONVST 已实现(2026-10-10)**:`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback` 在 **TIM3 更新事件(1 kHz)** 内调 `tpafe5160_start_conv_irq()`;`app_main_init()` 用 `HAL_TIM_Base_Start_IT(&htim3)` 启动节拍(在 `tpafe5160_init()` 之后)。同一 TIM3 更新事件还经 **TRGO** 硬件触发 ADC1 一轮 9 通道(DMA 搬运),并调 `adc_tick_irq_notify()` **唤醒消费者**(P1-5 方案 a,2026-10-10;内含"消费者就绪"门)→ 两片转换器由同一硬件事件同源对齐 | 连续波形(1 kHz);详见 `docs/子系统设计/ADC采样节拍设计说明.md` §4(DMA 乒乓搬运)与 §6(事件驱动唤醒) |
|
||||
| 前端偏置 | 每路 1k 串联 + 滤波 C 跨接差分输入;差分负端(`*_GND` 网)经 1k 拴 AGND。**不是对地分压**(2026-10-27 网表追线定死,拓扑图见 §7.1),**无中点偏置** | TPAFE 输入阻抗 **1 MΩ 恒定**(`../芯片手册/TPAFE5160.md` "Analog Input Impedance" 节),故端子对 AGND 的直流通路 = 1k + 1 MΩ + 1k ≈ **1.002 MΩ**,不是 1 kΩ。两侧各 1 kΩ 是手册要求的**源阻抗匹配**(AIN_nP / AIN_nGND 外接源阻抗相等以抵消失调),**不是分压**。STM32 侧只能测 **0~3.3 V 单极性**,交流量负半周被削顶;主板侧只做监视可接受,若要算 RMS/功率需硬件补偏置或明确算法口径 |
|
||||
| TPAFE 量程(**2026-10-25 量程口径冻结定死**) | 板上 RANGE 接 GND → **±5 V 双极性量程**(芯片手册输入量程表:RANGE = 0 → −5 ~ +5 V),16 位二进制补码,**±5 V 满量程 = 10 V 跨度**,LSB = 10 V / 65536 ≈ **152.59 µV/码(@ 转换器引脚)**。“±10 V” 是芯片的另一可选档(RANGE = HIGH,跨度 20 V、LSB ≈ 305.18 µV),**本板未用**——历史版本曾把本板误标为“±10 V 双极性”,与 §7 本行矛盾、且会让 TPAFE 侧系数差一倍,更正记录见 §9 | 用 `tpafe5160_to_voltage()` 换算;端到端口径见 §7.1 |
|
||||
|
||||
+11
-11
@@ -4,7 +4,7 @@
|
||||
> **参考来源**:`D:\Code\DTU\参考资料\huaiyuan\huaiyuan\Scatter_P_HV03.01_SV01.01\项目分析文档\04-算法与采样专题\ADC采集至数据使用全流程详解`(同专题另有《项目详细分析-交流采样与有效值计算》《傅氏算法详解》《项目详细分析-阶段6-继电器保护单元分析》)。
|
||||
> **阅读前提**:先读 `docs/算法/傅氏算法详解.md`(算法原理)与 `docs/硬件平台/ADC通道映射说明.md`(采样通道权威口径)。
|
||||
> **相关文档**:`docs/产品需求/产品需求设计文档.md`、`docs/任务规划/README.md`、`docs/内存管理/RAM使用分析与优化方案.md`。
|
||||
> **状态**:**P0 采样与相量底座已台架闭环(2026-10-11)**——语义取数与有效性采集链路(B1,2026-10-10)、 采样有效性校验与 全周傅氏相量层(`sample_ring` + `phasor`,57 项主机单测 + 台架 `@test` 含 `phasor(first)/phasor(hb)` 标记闭环)均已落地;**P1/P2 保护元件与定值模型软件侧完成(2026-10-11,`prot_set` + `prot_elm`,57 项主机单测)**;**B5 板端接线完成(2026-10-12)**——`protectTask`(20 ms 判据节拍 + 「自检失败闭锁」采样故障闭锁 + 定值校验和巡检,IWDG `TASK_WD_PROTECT` bit8,栈 512 words 静态链 872 B / 余 57%),判据结果经 B4 IEC104 遥信(YX15~23/31)上送;**B7 定值持久化 + 全局软压板 + 竞态收口完成(2026-10-12,含台架腿验证)**;**出口记录(出口与记录)与标准源注入验收为下一步**(见 §7)。
|
||||
> **状态**:**P0 采样与相量底座已台架闭环(2026-10-11)**——语义取数与有效性采集链路(B1,2026-10-10)、 采样有效性校验与 全周傅氏相量层(`sample_ring` + `phasor`,57 项主机单测 + 台架 `/serial-boot-test` 含 `phasor(first)/phasor(hb)` 标记闭环)均已落地;**P1/P2 保护元件与定值模型软件侧完成(2026-10-11,`prot_set` + `prot_elm`,57 项主机单测)**;**B5 板端接线完成(2026-10-12)**——`protectTask`(20 ms 判据节拍 + 「自检失败闭锁」采样故障闭锁 + 定值校验和巡检,IWDG `TASK_WD_PROTECT` bit8,栈 512 words 静态链 872 B / 余 57%),判据结果经 B4 IEC104 遥信(YX15~23/31)上送;**B7 定值持久化 + 全局软压板 + 竞态收口完成(2026-10-12,含台架腿验证)**;**出口记录(出口与记录)与标准源注入验收为下一步**(见 §7)。
|
||||
|
||||
---
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
| 已实现子系统 | 网络(CH395F)、存储(GD5F2GQ5UE SPI NAND + dhara FTL + FatFs)、时间(SD2506 RTC + `sys_clock`)、配置(cfg_file)、日志、UI(远端显示)、看门狗(IWDG)、FTP |
|
||||
| 采样硬件 | TPAFE5160(16 位、8 通道**同步**、±5 V 双极性、并行 `GPIOG[15:0]`)+ STM32 内部 ADC1(12 位、9 通道**顺序**扫描、0~3.3 V 单极性) |
|
||||
| 采样软件 | `Drivers/BSP/TPAFE5160/tpafe5160.h` 驱动就绪(原子快照 API);`App/task/adc_task.c` **已实现真实消费者(B1,2026-10-10)**——`tpafe5160_snapshot()` 取数 + 帧统计;**采样节拍由 TIM3 更新事件(1 kHz)硬件触发**(2026-10-10;同一事件经 TRGO 触发 ADC1 + DMA);STM32 侧 **`App/algo/adc_input.c` 已实现语义映射 + ADC1 DMA 乒乓帧消费(「STM32 ADC1 启动与扫描」,v2.0)**,`HAL_ADC_Start_DMA` 已出现 |
|
||||
| 保护算法层 | **全周傅氏相量层已实现并台架闭环**(`App/algo/sample_ring.c` + `phasor.c`:全周傅氏 5 实例、双区交换、每 1 ms 刷新,2026-10-11 `@test`);**保护判据元件框架 + 过流/过负荷/过压/欠压/零序过压 + 定值模型软件侧完成**(`prot_elm.c` + `prot_set.c`,2026-10-11,57 项主机单测);**出口 / SOE 记录仍缺失**(P4) |
|
||||
| 保护算法层 | **全周傅氏相量层已实现并台架闭环**(`App/algo/sample_ring.c` + `phasor.c`:全周傅氏 5 实例、双区交换、每 1 ms 刷新,2026-10-11 `/serial-boot-test`);**保护判据元件框架 + 过流/过负荷/过压/欠压/零序过压 + 定值模型软件侧完成**(`prot_elm.c` + `prot_set.c`,2026-10-11,57 项主机单测);**出口 / SOE 记录仍缺失**(P4) |
|
||||
| 已知硬件缺陷 | 登记簿条目「双转换器无共同采样触发」:8 路电气量分属两片转换器 → V0.1 上禁止跨转换器做相位/功率/矢量运算 |
|
||||
|
||||
### 1.3 复现范围界定
|
||||
@@ -65,9 +65,9 @@
|
||||
| 依赖 | 说明 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| **B0 产品定义** | 采集是否持续 / 采样率与通道 / 转发目标 / 保护元件清单——决定 B1 与保护层的**规模** | ✅ 已确认(2026-10-10,见 §1.5) |
|
||||
| **B1 ADC 采集链路** | 周期 CONVST + `adc_task` 消费 `tpafe5160_snapshot()` + 建任务 + 栈预算 | ✅ 已实现并 `@test` 实机闭环(2026-10-10;`adcTask` 512 words,台架 1000 Hz) |
|
||||
| **B1 ADC 采集链路** | 周期 CONVST + `adc_task` 消费 `tpafe5160_snapshot()` + 建任务 + 栈预算 | ✅ 已实现并 `/serial-boot-test` 实机闭环(2026-10-10;`adcTask` 512 words,台架 1000 Hz) |
|
||||
| **双转换器无共同采样触发(硬件缺陷)** | V0.1 跨转换器无同一时刻性 → 限定 V0.1 可实现的保护范围 | ⚠️ 硬件改版项(V0.2) |
|
||||
| **栈预算** | 保护任务栈需入 `Src/freertos.c` 并经 `scripts/stack_report.py` 核算 | ✅ 两任务均已核算:`adcTask`(**「采样有效性校验」后:栈抬至 640 words,静态 1080 B / 台架实测峰值 952/2560 / 余 57%**);`protectTask`(B5,2026-10-12:**512 words,静态链 872 B / 余 1176 B / 57%**,台架水位待 `@test`) |
|
||||
| **栈预算** | 保护任务栈需入 `Src/freertos.c` 并经 `scripts/stack_report.py` 核算 | ✅ 两任务均已核算:`adcTask`(**「采样有效性校验」后:栈抬至 640 words,静态 1080 B / 台架实测峰值 952/2560 / 余 57%**);`protectTask`(B5,2026-10-12:**512 words,静态链 872 B / 余 1176 B / 57%**,台架水位待 `/serial-boot-test`) |
|
||||
|
||||
### 1.5 B0 产品定义(2026-10-10 确认)
|
||||
|
||||
@@ -238,12 +238,12 @@
|
||||
|
||||
| 功能名 | 关键参数 | 完成判据 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 周期 CONVST 重触发 | 1000 Hz(与保护节拍同源) | 示波器见 U3 `BUSY` 连续脉冲;`tpafe5160_frame_seq()` 持续递增 | ✅ 2026-10-10:**TIM3 更新事件(1 kHz)硬件触发**(ISR 内 `tpafe5160_start_conv_irq()`;方案 A,零引脚/零驱动改动。⚠ 节拍源**不能**是 TIM6/TIM7——F4 上 ADC1 收不到它们的 TRGO,见 `docs/子系统设计/ADC采样节拍设计说明.md` §3.2);台架 `@test` PASS——`seq` 每 10 s 精确 +10000 = 1000.0 Hz、`frst_err=0`(早期证据 `docs/测试说明/测试数据/boot_log_data/20261010_prod_adc_tim6_boot_log.txt`,节拍源当时为 TIM6) |
|
||||
| TPAFE 快照消费 | 8 通道 / 帧 | `adc_task` 每拍调 `tpafe5160_snapshot()`,返回 1/0/-1 有处理 | ✅ B1 已实现并 `@test` 闭环(`adc_task.c`;台架 `idle=0`、`frst_err=0`) |
|
||||
| STM32 ADC1 启动与扫描 | 9 通道、144 cycles、**TIM3 TRGO 硬件触发 + DMA** | 全库出现 `HAL_ADC_Start_DMA`;9 通道码值随注入变化 | ✅ 2026-10-10(v2.0):`App/algo/adc_input.c` 的 `stm32_take()`——ADC1 由 TIM3 更新事件经 TRGO 触发一轮 9 通道,`HAL_ADC_Start_DMA(&hadc1, buf, 18)` + DMA2_Stream4 循环模式把码值自动搬进**乒乓双缓冲**,任务侧只消费刚完成的半区(**零忙等**,实测单拍 `adcp` 122 µs → **8 µs**);台架 `@test` PASS——`adc1_seq` 每拍 +1、`adc1_err=0`、`adc1_hw=0`。⚠ 「9 通道码值随注入变化」已由 2026-10-27 台架已知电压注入直接证实(六项判据全 PASS,实测记录 `docs/硬件平台/ADC通道映射说明.md` §8.1);两点标定与端子/子板变比确认仍待做 |
|
||||
| 周期 CONVST 重触发 | 1000 Hz(与保护节拍同源) | 示波器见 U3 `BUSY` 连续脉冲;`tpafe5160_frame_seq()` 持续递增 | ✅ 2026-10-10:**TIM3 更新事件(1 kHz)硬件触发**(ISR 内 `tpafe5160_start_conv_irq()`;方案 A,零引脚/零驱动改动。⚠ 节拍源**不能**是 TIM6/TIM7——F4 上 ADC1 收不到它们的 TRGO,见 `docs/子系统设计/ADC采样节拍设计说明.md` §3.2);台架 `/serial-boot-test` PASS——`seq` 每 10 s 精确 +10000 = 1000.0 Hz、`frst_err=0`(早期证据 `docs/测试说明/测试数据/boot_log_data/20261010_prod_adc_tim6_boot_log.txt`,节拍源当时为 TIM6) |
|
||||
| TPAFE 快照消费 | 8 通道 / 帧 | `adc_task` 每拍调 `tpafe5160_snapshot()`,返回 1/0/-1 有处理 | ✅ B1 已实现并 `/serial-boot-test` 闭环(`adc_task.c`;台架 `idle=0`、`frst_err=0`) |
|
||||
| STM32 ADC1 启动与扫描 | 9 通道、144 cycles、**TIM3 TRGO 硬件触发 + DMA** | 全库出现 `HAL_ADC_Start_DMA`;9 通道码值随注入变化 | ✅ 2026-10-10(v2.0):`App/algo/adc_input.c` 的 `stm32_take()`——ADC1 由 TIM3 更新事件经 TRGO 触发一轮 9 通道,`HAL_ADC_Start_DMA(&hadc1, buf, 18)` + DMA2_Stream4 循环模式把码值自动搬进**乒乓双缓冲**,任务侧只消费刚完成的半区(**零忙等**,实测单拍 `adcp` 122 µs → **8 µs**);台架 `/serial-boot-test` PASS——`adc1_seq` 每拍 +1、`adc1_err=0`、`adc1_hw=0`。⚠ 「9 通道码值随注入变化」已由 2026-10-27 台架已知电压注入直接证实(六项判据全 PASS,实测记录 `docs/硬件平台/ADC通道映射说明.md` §8.1);两点标定与端子/子板变比确认仍待做 |
|
||||
| 通道语义映射 | 见 `docs/硬件平台/ADC通道映射说明.md`(**按语义取数,不写死下标**) | `UA→buf[6]`、`UN→buf[5]`、`IA→buf[2]`;STM32 侧 `UB→ADC_CHANNEL_14` | ✅ 2026-10-10:`App/algo/adc_input.c` 的 `k_adc_map[]` 语义表(`UA/UN/IA`→TPAFE `buf[6]/[5]/[2]`,`UB/UC/IB/IC/IN/TEMP`→ADC1 `CH14/3/6/1/2/5`)+ `adc_input_raw()` 按语义取数;开机**映射自检**(`adc_input_init()` 校验 STM32 映射通道必落在扫描序列内 → 挡住"映射到不扫描的通道"静默错)通过(`ADC input layer init: OK`)。台架首帧日志 8 路 + 温度全部读出:`UA=11429 UN=11454 IA=11452`(与 TIM3 轮次一致)、`UB=1018 UC=1044 IB=943 IC=1069 IN=1052 T=850`(互异、非饱和;**两次开机读数不同** → 确为实时转换而非常量) |
|
||||
| 通道标定(一次系数 + 零漂) | 每通道 `k`、`b`;系数定点 `2^10` | 注入标准源,读数误差 ≤ 目标精度 | ⬜ |
|
||||
| 采样有效性校验 | FRSTDATA 跳变、帧序号单调、丢帧计数 | 芯片静默/丢帧被检出并置告警 | ✅ 2026-10-11:新增 `App/algo/adc_health.c/h`(纯逻辑判定层:静默 100 ms 门限 / 丢帧边沿 / 序号倒退锁存 / FRSTDATA·HAL 错误·STM32 停摆计数增量)+ `adc_task.c` 告警编排(严重事件与故障上升沿 `DBG_ERROR` + 每类每次开机一次 `SYSLOG_SELFTEST`,恢复 `DBG_INFO`;gap 不告警只计数,心跳增 `tgap`);主机单测 `test/host/adc_health/` **31 用例全绿**(`adc-health-unit-test` 钩子);台架 `@test` PASS 零 `[ERR]`(健康不误报,`docs/测试说明/测试数据/boot_log_data/20261011_prod_adc_health_boot_log.txt`)。⚠ 抬栈:判定链 `adc_health_report`→`syslog_post` 回显尾使 adcTask 静态最坏链 832→**1080 B** → 栈 512→**640 words**(余 1480 B / 57%) |
|
||||
| 采样有效性校验 | FRSTDATA 跳变、帧序号单调、丢帧计数 | 芯片静默/丢帧被检出并置告警 | ✅ 2026-10-11:新增 `App/algo/adc_health.c/h`(纯逻辑判定层:静默 100 ms 门限 / 丢帧边沿 / 序号倒退锁存 / FRSTDATA·HAL 错误·STM32 停摆计数增量)+ `adc_task.c` 告警编排(严重事件与故障上升沿 `DBG_ERROR` + 每类每次开机一次 `SYSLOG_SELFTEST`,恢复 `DBG_INFO`;gap 不告警只计数,心跳增 `tgap`);主机单测 `test/host/adc_health/` **31 用例全绿**(`adc-health-unit-test` 钩子);台架 `/serial-boot-test` PASS 零 `[ERR]`(健康不误报,`docs/测试说明/测试数据/boot_log_data/20261011_prod_adc_health_boot_log.txt`)。⚠ 抬栈:判定链 `adc_health_report`→`syslog_post` 回显尾使 adcTask 静态最坏链 832→**1080 B** → 栈 512→**640 words**(余 1480 B / 57%) |
|
||||
| 等周波采样(软件测频 + 调频闭环) | 20 点/周波;测频范围建议 **45~55 Hz**(参考 40.5~60.5) | 频率偏移 ±2 Hz 时幅值误差 ≤ 1% | ⬜ |
|
||||
| 零序通道接入(外部专用 / 自产切换) | 外部 `UN`/`IN`;自产 `3I0 = Ia+Ib+Ic`(V0.2 起) | 零序通道可切换来源,值正确 | ⬜ |
|
||||
| 二次谐波/五次谐波提取入口 | k=2(涌流)、k=5(选线) | 相量层可产出对应谐波相量 | ✅ 2026-10-11:`phasor.c` 实例表 `C04-IA-F2`(k=2)/ `C05-IA-F5`(k=5)随每拍重算产出,台架心跳日志 `phasor(hb) C04/C05` 行可见(`docs/测试说明/测试数据/boot_log_data/20261011_prod_phasor_boot_log.txt`);谐波**抑制/提取正确性**已由主机单测覆盖(`test/host/phasor/`) |
|
||||
@@ -267,7 +267,7 @@
|
||||
|
||||
> **「基波相量实例」实例数 4 → 5 的来历**:参考项目(`APP/CLS100_Dsp/InstCalCell.c`)为 **4 个实例**——C01 基波 I/U、C02 零序基波、C03 二次谐波、C04 五次谐波。本项目按 V0.1 **有效通道**(`UA`/`UN`/`IA`,见 §1.6:`UB/UC/IB/IC/IN` 无中点偏置 → 削顶不可用)展开:C01 的"I/U"拆为两行(`C01-IA-F1` 电流 / `C02-UA-F1` 电压),其余顺延为 `C03-UN-F1` 零序、`C04-IA-F2` 二次、`C05-IA-F5` 五次。实现见 `App/algo/phasor.c` 的 `k_inst[]`;判据按 **5** 个实例断言(见 `docs/测试说明/单元测试/相量层单元测试说明.md` §2.6)。
|
||||
|
||||
> **本表状态列口径**(2026-10-11 更新):「全周傅氏系数表」~「相位输出」/「每 1 ms 滑动窗刷新」/「双区指针原子交换」已**实现 + 主机单测(57 用例)+ 台架 `@test` 闭环**——闭环命令 `run_boot_test.py --required "...;phasor(first);phasor(hb)" --require-order "App init OK>phasor(first)>phasor(hb)"`,证据 `docs/测试说明/测试数据/boot_log_data/20261011_prod_phasor_boot_log.txt`(`fw build` 身份戳匹配、回读一致、锚点 ×1、零 `[ERR]`)。⚠ **幅值/相位精度判据(标准源)在无信号源台架上不可达**(同 §7.1 无示波器口径),且「通道标定(一次系数 + 零漂)」标定未实现 → 现读数为**原始码值**,只证明链路通;精度复核排在标定之后。「线/负序/零序合成」随 V0.2(双转换器无共同采样触发),「遥测谐波轨(可选)」可选项未做。
|
||||
> **本表状态列口径**(2026-10-11 更新):「全周傅氏系数表」~「相位输出」/「每 1 ms 滑动窗刷新」/「双区指针原子交换」已**实现 + 主机单测(57 用例)+ 台架 `/serial-boot-test` 闭环**——闭环命令 `run_boot_test.py --required "...;phasor(first);phasor(hb)" --require-order "App init OK>phasor(first)>phasor(hb)"`,证据 `docs/测试说明/测试数据/boot_log_data/20261011_prod_phasor_boot_log.txt`(`fw build` 身份戳匹配、回读一致、锚点 ×1、零 `[ERR]`)。⚠ **幅值/相位精度判据(标准源)在无信号源台架上不可达**(同 §7.1 无示波器口径),且「通道标定(一次系数 + 零漂)」标定未实现 → 现读数为**原始码值**,只证明链路通;精度复核排在标定之后。「线/负序/零序合成」随 V0.2(双转换器无共同采样触发),「遥测谐波轨(可选)」可选项未做。
|
||||
|
||||
### 3.3 保护判据元件
|
||||
|
||||
@@ -362,7 +362,7 @@
|
||||
| 定值区 RAM | 元件数 × 定值项 × 字宽 | 可由 cfg_file 载入 |
|
||||
| 任务栈 | `adcTask` ✅ / `protectTask` ✅(B5:512 words,静态链 872 B / 余 57%) | 建任务前后各跑 `scripts/stack_report.py` |
|
||||
|
||||
> ⚠️ **改内存相关代码后必须刷新快照**:`@build` 出 `.map` 后跑 `scripts/check_mem_numbers.py --write`,同步 `docs/内存管理/RAM使用分析与优化方案.md` §2.1(`mem-numbers` 钩子会因体积变化 FAIL)。
|
||||
> ⚠️ **改内存相关代码后必须刷新快照**:`/keil-build` 出 `.map` 后跑 `scripts/check_mem_numbers.py --write`,同步 `docs/内存管理/RAM使用分析与优化方案.md` §2.1(`mem-numbers` 钩子会因体积变化 FAIL)。
|
||||
|
||||
### 4.4 频率跟踪
|
||||
|
||||
@@ -483,7 +483,7 @@
|
||||
|
||||
| 阶段 | 交付物 | 验收判据 |
|
||||
| --- | --- | --- |
|
||||
| P0 | `adcTask`/`algo` 模块(采集触发 + 语义映射 + 标定 + 全周傅氏 + 双区交换)+ 设计文档 + 主机侧纯逻辑单测 | TPAFE 三路(`UA`/`UN`/`IA`)标准源读数误差 ≤ 目标;相量每 1 ms 刷新;`@test` 通过 |
|
||||
| P0 | `adcTask`/`algo` 模块(采集触发 + 语义映射 + 标定 + 全周傅氏 + 双区交换)+ 设计文档 + 主机侧纯逻辑单测 | TPAFE 三路(`UA`/`UN`/`IA`)标准源读数误差 ≤ 目标;相量每 1 ms 刷新;`/serial-boot-test` 通过 |
|
||||
| P1 | A 相过流 + A 相过负荷元件 + 定值模型 | 注入 1.2×/0.9× 定值,A 相正确动作/返回;返回系数正确 |
|
||||
| P2 | 过压/欠压 + 过频/低频 + 零序过压元件 | 越限动作时间符合定值延时 |
|
||||
| P3 | 自检闭锁 + 采样通道自检;三相类元件建接口 | 自检失败闭锁;未使能元件不误动 |
|
||||
|
||||
@@ -455,7 +455,7 @@ stateDiagram-v2
|
||||
|
||||
## 11. 怎么验证
|
||||
|
||||
**验证分工先说清**:台架 `@test` 只能证明「健康时不误报」(要求零 `[ERR]`),
|
||||
**验证分工先说清**:台架 `/serial-boot-test` 只能证明「健康时不误报」(要求零 `[ERR]`),
|
||||
**证不了「故障时必报」**——静默与倒退在健康台架上不可复现(复现 = 拔芯片或改驱动)。
|
||||
「必报」这一半完全由主机侧单测承担,这就是本层必须是纯逻辑的原因(§4.4)。
|
||||
|
||||
|
||||
@@ -500,7 +500,7 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分
|
||||
|
||||
### 12.2 板端闭环
|
||||
|
||||
`@build` 0 错误 0 警告 → `@test` 看 `[ADC_TASK]` 首拍与心跳:`phasor(...)` 行、`freq(...)` 行
|
||||
`/keil-build` 0 错误 0 警告 → `/serial-boot-test` 看 `[ADC_TASK]` 首拍与心跳:`phasor(...)` 行、`freq(...)` 行
|
||||
(`f=5000 valid=1`,`×0.01` 定点,MicroLIB 无 `%f`)、`ring` / `skip` / `adc1_*` / `tgap`
|
||||
计数;健康判据 = **零 `[ERR]`**(`adc_health_report()` 的告警全部以 `[ERR]` 开头 → 误报会被闭环
|
||||
直接抓住)。浮空工况应为 `valid = 0` 且读数为保持值。
|
||||
|
||||
@@ -15,7 +15,7 @@ HEAD 是 `EF BB BF` 而工作区不是 → FAIL 并列出文件与补救动作
|
||||
## 2. 为什么要它(拦下过什么真事)
|
||||
|
||||
- **立项直接来自一天内 5 次踩坑**:2026-10-25 道E 在 task-15(UI 页面批次)里连踩 4 次,
|
||||
道B 在批 1 踩过 1 次。每次的症状都一样:改完 UI 源文件、`@build` 突然报一串语法错,而改动
|
||||
道B 在批 1 踩过 1 次。每次的症状都一样:改完 UI 源文件、`/keil-build` 突然报一串语法错,而改动
|
||||
本身是加注释或改常量;`MDK-ARM/build_log.txt` 里的行号指向中文注释所在的**上一行**,
|
||||
照着报错去查会查不出任何东西。真因只是文件开头少了 3 个字节。
|
||||
- **别的门禁和编译器都不会响**:MSVC(模拟器侧)带不带 BOM 都编得过、0 警告;`no-mojibake`
|
||||
@@ -61,7 +61,7 @@ HEAD 是 `EF BB BF` 而工作区不是 → FAIL 并列出文件与补救动作
|
||||
"HEAD 无 BOM、工作区加了 BOM" **不报**。那是另一个方向的改动,可能是有意的(把某个文件纳入
|
||||
BOM 名单),也可能是无意的;报红只会逼人 `--no-verify`,而**养成绕门禁的习惯比漏检一次贵得多**。
|
||||
"新增文件没有 BOM" 同样不报:名单里的新文件(如新建 `ui_page_xxx.c`)由创建者负责加 BOM,
|
||||
漏加会在 `@build` 时以真错误暴露——本门禁不假装自己能替代那一步。
|
||||
漏加会在 `/keil-build` 时以真错误暴露——本门禁不假装自己能替代那一步。
|
||||
|
||||
### 6.2 名单三处互指(防漂移)
|
||||
AGENTS 铁律第 2 条的名单、钩子 `files` 正则、脚本内 `BOM_LIST` 必须一致;脚本每次运行都核一次,
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
`Middlewares/`、`MDK-ARM/`、`test/`(`test/` 由第二个钩子负责)。
|
||||
- UI 生成文件(`ui_font_cn_12`、`ui_menu_tab`)**纳入受检**——生成器按工程约定产出,不是第三方。
|
||||
- 本机须装了 cppcheck 才有效(远端 CI 的 runner 同样按 `DTU_CPPCHECK` → PATH 探测,缺则 job 直接失败);
|
||||
CI 不跑构建类门禁 `@build`(见 `docs/CI/README.md`)。
|
||||
CI 不跑构建类门禁 `/keil-build`(见 `docs/CI/README.md`)。
|
||||
|
||||
- 速度口径:**单文件约 1~2 秒、全库约 20 s**(2026-10-28 实测 `cppcheck` 20.5 s、`test-cppcheck` 38.1 s)——
|
||||
提交期不跑它们,全量由 CI 与 `ci_local.py` 的 manual 段承担。
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
## 3. 触发面 / 看不见什么
|
||||
|
||||
- `MDK-ARM/*.uvprojx` **不参与漂移检测**(`no_baseline=True`):该文件每次 `@build` 后都被 UV4
|
||||
- `MDK-ARM/*.uvprojx` **不参与漂移检测**(`no_baseline=True`):该文件每次 `/keil-build` 后都被 UV4
|
||||
自动改写(XML 归一化 + `PackID` 降到本机 DFP 版本),参与检测等于每次构建后提交都被拦;
|
||||
对本项只保留 `must_contain` 断言("源文件仍挂载")。
|
||||
- 只查清单里**列了的东西**:没写进清单的新定制 = 不受保护(这是清单式门禁的固有代价)。
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
## 6. 限制与盲区
|
||||
|
||||
1. **宿主编译器 ≠ ARMCC**:ARMCC 的警告(`#870-D` 多字节字符、`#1-D` 之类)、结构体对齐、
|
||||
`enum` 取型、MicroLIB 行为差异,本层一概照不到 → 必须 `@build`。
|
||||
`enum` 取型、MicroLIB 行为差异,本层一概照不到 → 必须 `/keil-build`。
|
||||
2. **桩就是第二份真相**:`stub/` 与被测代码依赖的真实头文件不同步时,会出现"单测全绿、真机
|
||||
行为不同"。桩签名改了而测试没跟着改,编译器未必报错(尤其桩返回宽类型)。
|
||||
3. `include_dirs` 与源文件清单是**手写**的:新依赖一个目录就会假失败(见 §4 第 2 类),反过来,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
配套的两个入口(**不在提交门禁里**,属于日常):
|
||||
|
||||
- `--update`:每次 `@build` 后往 `scripts/ram_baseline.tsv` 追一行趋势;
|
||||
- `--update`:每次 `/keil-build` 后往 `scripts/ram_baseline.tsv` 追一行趋势;
|
||||
- `--trend`:打印趋势并**高亮 `RW+ZI` 较上一行增长 > 1 KB 的对象**——把"ZI 增量超 1 KB 必须解释" 这条纪律机械化;
|
||||
- `--write`:同步台账受管区(区域水位 + 子系统配额对照)。
|
||||
|
||||
@@ -48,7 +48,7 @@ RAM 预算门禁 FAIL:RW_CCM 占用 92.3% > 92%(阈值 92%)
|
||||
`--trend -n 12` 看趋势。**`--gate` 不修改任何东西**,要同步台账用 `--write`。
|
||||
- 报 FAIL 的正确动作是**回去减**(缩缓冲/挪区段/减日志调用点),不是抬阈值。
|
||||
|
||||
- 两类**软提醒不拦提交**:`tsv` 末行落后于 `.map`(忘了跑 `--update`)、`.map` 早于最新源码(忘了 `@build`)。
|
||||
- 两类**软提醒不拦提交**:`tsv` 末行落后于 `.map`(忘了跑 `--update`)、`.map` 早于最新源码(忘了 `/keil-build`)。
|
||||
- 退出码:0 = 通过或跳过 / 1 = 超阈值或台账漂移 / 2 = **解析失败**(`.map` 读不出结构,不等于"没问题")。
|
||||
- `.map` 解析口径复用 `scripts/ram_report.py`,两个脚本不会各说各话。
|
||||
- 为什么需要(背景):2026-09 体系评审提出的"超阈值即失败 + Top-10 附表"此前**没有任何机械检查**
|
||||
@@ -78,7 +78,7 @@ RAM 预算门禁 FAIL:RW_CCM 占用 92.3% > 92%(阈值 92%)
|
||||
**git 索引里的 blob**,所以「工作区有别人在飞、尚未入索引的改动」时指纹一定对不上,报「这份源码没有被
|
||||
真实链接测量过」——**这不是预算问题,也不拦提交**(该脚本只挂在 `scripts/ci_local.py` 的体积门禁,不在 51 条
|
||||
pre-commit 钩子内)。判据:报错里若同时列出「未入索引的文件」且那些文件正被别的任务改,就等它们入库后
|
||||
统一 `@build` → `ram_baseline.py --update` 补凭证,**别为了把它调绿去 `git add` 别人在飞的改动**。本条写作时
|
||||
统一 `/keil-build` → `ram_baseline.py --update` 补凭证,**别为了把它调绿去 `git add` 别人在飞的改动**。本条写作时
|
||||
(2026-10-27 09:4x)凭证**仍是红的**:`App/protocol/iec104/iec104_comm.{c,h}` 正被通讯道改、未入索引;
|
||||
回填动作发生在该卡入库之后,届时以 `--update` 的实际输出为准,本节不预告结果。
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
## 3. 触发面与"看不见"的地方
|
||||
|
||||
- 事实源必须是**最新一次 `@build` 产出的 `.map`**;`.map` 不存在(这台机器没做 Keil 构建)
|
||||
- 事实源必须是**最新一次 `/keil-build` 产出的 `.map`**;`.map` 不存在(这台机器没做 Keil 构建)
|
||||
→ **自适应跳过**(与 `ram_baseline.py --gate` 同口径)。CI 明确不跑构建,这一门在远端曾长期
|
||||
是盲区——**2026-10-25 已由同码凭证关闭为强制路径**(`scripts/check_size_gate.py`,口径正文见其
|
||||
docstring;CI 复核同码性 + 按凭证数字复跑红线,不重算绝对值)。
|
||||
@@ -51,11 +51,11 @@
|
||||
2. **散句只报不改**,且 `--scan` 靠正则认数字——写成"约 75 KB""七万五千字节"或换单位的句子
|
||||
不在核对范围内(假阴性)。
|
||||
3. `.map` 缺失即跳过 → **在没有 Keil 构建的机器上它是绿的**(自适应跳过 = 无信息,不是通过)。
|
||||
4. 依赖最新构建:`@build` 之后没跑 `--write` 与文档同步,就会红;反过来,**先 `--write` 再改代码**
|
||||
4. 依赖最新构建:`/keil-build` 之后没跑 `--write` 与文档同步,就会红;反过来,**先 `--write` 再改代码**
|
||||
会写出与将要提交的代码不一致的漂亮数字。
|
||||
5. 只覆盖已登记的文档:新开一篇写满体积数字的文档,不在名单里就没人管。
|
||||
6. 同码凭证(2026-10-25)不重算绝对数字,只复核"代码 == 测量时的代码"与红线复算;编译器选项
|
||||
(`.uvprojx`)不在指纹范围内——换优化等级不改指纹,靠本地 `@build` 纪律兜。
|
||||
(`.uvprojx`)不在指纹范围内——换优化等级不改指纹,靠本地 `/keil-build` 纪律兜。
|
||||
7. 数字口径 = `.map` 的静态视图,**不含运行时水位**(栈峰值、堆低水位、碎片)——那些要另跑
|
||||
`scripts/stack_report.py` 与串口 `[STACK]` 读数。
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
- **按包含者数量刷屏**:一处嵌套写在被广泛 include 的头里,每个包含者各打一行。
|
||||
本仓实测一例扩散到 **16 条警告**,把同批 10 条**真**警告挤到看不见——
|
||||
等于把「能不能看清报错」这件事本身弄坏。
|
||||
- 本闸**不需要构建**:提交时就报,不必等 @build,也不必去猜是谁的编译单元。
|
||||
- 本闸**不需要构建**:提交时就报,不必等 /keil-build,也不必去猜是谁的编译单元。
|
||||
|
||||
## 2.1 接线当日实测(都可复现)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
> ..\App\algo\run_par.c(267): warning: #9-D: nested comment is not allowed
|
||||
> ```
|
||||
>
|
||||
> ⚠ 该日志每次 @build 都被覆盖、且不入库(02:37 已被下一次构建清掉,我写完这段再去查就没了),
|
||||
> ⚠ 该日志每次 /keil-build 都被覆盖、且不入库(02:37 已被下一次构建清掉,我写完这段再去查就没了),
|
||||
> 所以把原文抄在这里存档;当日那三处同类**没有一处在已提交历史里留下字节**(上表最后一行),
|
||||
> 本闸真正第一次拦住,会是「下一次有人在注释里写星号形态」。
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
形态会算新发(这条有专门的自测项)。
|
||||
4. 只覆盖 .c/.h。Python 生成器模板将来若写出 /**,本闸照得到**产物**(产物是 .c/.h),
|
||||
但不会指出模板——那段链由 `generated-fresh` 保证「脚本与产物同一笔提交」。
|
||||
5. 编译器侧的真实警告仍以 `@build` 为准:本闸只拦「会让 ARMCC 报 9-D 的那一类」,
|
||||
5. 编译器侧的真实警告仍以 `/keil-build` 为准:本闸只拦「会让 ARMCC 报 9-D 的那一类」,
|
||||
编译日志里其他诊断一概不管。
|
||||
6. **仓库根探测必须兼容 linked worktree**:脚本原先用「`.git` 是不是目录」定位仓库根,而 `git worktree`
|
||||
出来的任务工作树里 `.git` 是**一个文件**(写着 `gitdir: …`)不是目录 → 本闸在任何任务树里恒
|
||||
|
||||
@@ -155,7 +155,7 @@ if (ok) {
|
||||
1. 探针文件名改 `/fschk.tmp`(严格 8.3:名≤8、扩展≤3、不以点开头);
|
||||
2. `FR_INVALID_NAME` 移出重格式化触发集合(名字语法错误与卷健康无关)。
|
||||
|
||||
**验证**(台架日志代次 45~48,@test 连续复位):
|
||||
**验证**(台架日志代次 45~48,/serial-boot-test 连续复位):
|
||||
`Map resumed: 197→198→199→200 sectors used`(卷随 boot 正常增量持久)、
|
||||
无 reformat 行、`[CFG] load: OK (523B)`(config.ini 跨复位存活)、VERDICT PASS。
|
||||
|
||||
@@ -271,7 +271,7 @@ if (ok) {
|
||||
|
||||
**验证**:
|
||||
|
||||
- `@build` 0 错 0 警;`@test` PASS,且**台架 journal 恢复点与修复前同轨**——连续四次启动
|
||||
- `/keil-build` 0 错 0 警;`/serial-boot-test` PASS,且**台架 journal 恢复点与修复前同轨**——连续四次启动
|
||||
`sectors used` 恒 14,094、`Map head` 每轮 +128 单调推进(37,472 → 37,600 → 37,728 → 37,856)、
|
||||
`epoch` 稳定 3 → 整页 `is_free` 未改变恢复语义、无双编程、无回退。
|
||||
- 主机侧移植层单测 `test/host/nand_port/`(RAM 假芯片 + "旧算法只看页首 64 B"**对照用例**,
|
||||
|
||||
Reference in New Issue
Block a user