chore(全仓): @build/@test 旧文档别名清退为技能调用名
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名, 不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体 run_boot_test.py 的真实调用。 - AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改 「编译(/keil-build)」「串口回归(/serial-boot-test)」 - keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree 方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围) - 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入 等 41 行历史证据保留旧名 - gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑 - 顺手补并行方案同行漏网计数 51→52 钩子 验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52 钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
This commit is contained in:
@@ -14,7 +14,7 @@ whenToUse: 要跑板级测试套件(CH395F/STORAGE/GD5F/RTC)回归、要开/
|
|||||||
实测 GD5F 整轮破坏性跑一次即多记一个 NAND 坏块(容量 95248→95200),
|
实测 GD5F 整轮破坏性跑一次即多记一个 NAND 坏块(容量 95248→95200),
|
||||||
计数持久化在 dhara journal checkpoint 头里,普通格式化恢复不回来。
|
计数持久化在 dhara journal checkpoint 头里,普通格式化恢复不回来。
|
||||||
- 提交门禁:pre-commit 钩子 `test-suite-switch` 默认拦截测试构建
|
- 提交门禁:pre-commit 钩子 `test-suite-switch` 默认拦截测试构建
|
||||||
(确需提交用 `ALLOW_TEST_BUILD=1` 显式放行)——**跑完务必改回全注释并 `@build`**。
|
(确需提交用 `ALLOW_TEST_BUILD=1` 显式放行)——**跑完务必改回全注释并 `/keil-build`**。
|
||||||
|
|
||||||
## 一键回归(推荐路径,勿手工循环)
|
## 一键回归(推荐路径,勿手工循环)
|
||||||
|
|
||||||
@@ -28,13 +28,13 @@ whenToUse: 要跑板级测试套件(CH395F/STORAGE/GD5F/RTC)回归、要开/
|
|||||||
- 脚本 try/finally **无条件还原 `test_config.h`**(含 Ctrl-C 路径)——这是它存在的核心
|
- 脚本 try/finally **无条件还原 `test_config.h`**(含 Ctrl-C 路径)——这是它存在的核心
|
||||||
理由:跑完忘还原 = 测试固件进库。`--keep-config` 仅调试用。
|
理由:跑完忘还原 = 测试固件进库。`--keep-config` 仅调试用。
|
||||||
- 退出码 0=全 PASS / 1=有 FAIL / 2=环境错误;4 套件真机一轮数分钟起,**仅回归窗口用,绝不挂 pre-commit**。
|
- 退出码 0=全 PASS / 1=有 FAIL / 2=环境错误;4 套件真机一轮数分钟起,**仅回归窗口用,绝不挂 pre-commit**。
|
||||||
- 手工循环时的顺序(每轮):改宏(只开一个)→ `@build` 0错0警 → 烧录 →
|
- 手工循环时的顺序(每轮):改宏(只开一个)→ `/keil-build` 0错0警 → 烧录 →
|
||||||
`serial-assistant` 采集(open→clear→烧录→wait-idle)→ 判读套件汇总行。
|
`serial-assistant` 采集(open→clear→烧录→wait-idle)→ 判读套件汇总行。
|
||||||
|
|
||||||
## 跑后检查清单(必做)
|
## 跑后检查清单(必做)
|
||||||
|
|
||||||
1. `test_config.h` 恢复全注释(用 run_all_suites.py 则已自动);
|
1. `test_config.h` 恢复全注释(用 run_all_suites.py 则已自动);
|
||||||
2. `@build` 确认产品构建 0 错 0 警;
|
2. `/keil-build` 确认产品构建 0 错 0 警;
|
||||||
3. 若开过破坏性开关:核对开机日志容量数字(健康满值 96064 pages / 187 MB),
|
3. 若开过破坏性开关:核对开机日志容量数字(健康满值 96064 pages / 187 MB),
|
||||||
掉了 → 按 `docs/测试说明/专项验证/存储维护与容量恢复.md` 走恢复动作;
|
掉了 → 按 `docs/测试说明/专项验证/存储维护与容量恢复.md` 走恢复动作;
|
||||||
4. 提交前 `test-suite-switch` 门禁会再兜一道。
|
4. 提交前 `test-suite-switch` 门禁会再兜一道。
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ whenToUse: 要用 CubeMX 重新生成工程(改 .ioc / 外设配置)、改
|
|||||||
- 启用 FreeRTOS 后模板会生成 `USER CODE BEGIN 4 / 5` 区(默认空桩)——**钩子实现必须
|
- 启用 FreeRTOS 后模板会生成 `USER CODE BEGIN 4 / 5` 区(默认空桩)——**钩子实现必须
|
||||||
整体写进这两个区**,且 helper 函数放更早的区(同名两处定义 = 编译错)。
|
整体写进这两个区**,且 helper 函数放更早的区(同名两处定义 = 编译错)。
|
||||||
- 生成文件行尾/末尾空行按工具自身风格,**勿用改写型钩子去修剪**(已豁免,勿"顺手格式化")。
|
- 生成文件行尾/末尾空行按工具自身风格,**勿用改写型钩子去修剪**(已豁免,勿"顺手格式化")。
|
||||||
- 重生成后除审计脚本外,照常走 `@build`(0 错 0 警)+ 必要时 `@test` 闭环。
|
- 重生成后除审计脚本外,照常走 `/keil-build`(0 错 0 警)+ 必要时 `/serial-boot-test` 闭环。
|
||||||
|
|
||||||
## 其它 CubeMX 项目怎么套
|
## 其它 CubeMX 项目怎么套
|
||||||
|
|
||||||
|
|||||||
@@ -110,10 +110,10 @@ version: 1.1.0
|
|||||||
2. 编号字面量与**不可判定引用绝对数**只降不升、不超 `undecidable_total_ceiling`;可判定比例不低于 `id_registry.json` 基线(86.0%)。
|
2. 编号字面量与**不可判定引用绝对数**只降不升、不超 `undecidable_total_ceiling`;可判定比例不低于 `id_registry.json` 基线(86.0%)。
|
||||||
3. 本族剩余站点复扫归零:有识别面族从 `ck.refs` 按族正则过滤字面量逐条打印;除冻结历史/原始证据行外无残留。
|
3. 本族剩余站点复扫归零:有识别面族从 `ck.refs` 按族正则过滤字面量逐条打印;除冻结历史/原始证据行外无残留。
|
||||||
4. `pre-commit run generated-fresh --all-files` PASS + 目检 svg diff 只有文案与框宽自适应(只在动过生成器时适用);`check_doc_links.py --all` 通过。
|
4. `pre-commit run generated-fresh --all-files` PASS + 目检 svg diff 只有文案与框宽自适应(只在动过生成器时适用);`check_doc_links.py --all` 通过。
|
||||||
5. `@build`:`BUILD=0`、0 错 0 警、`Program Size` 与改前同数(零行为改动证据,**本回合真跑**);相关主机组(如 `net_socket 457/457`)全绿。
|
5. `/keil-build`:`BUILD=0`、0 错 0 警、`Program Size` 与改前同数(零行为改动证据,**本回合真跑**);相关主机组(如 `net_socket 457/457`)全绿。
|
||||||
6. 真实 pre-commit 钩子全绿(如 `passed=33 failed=0`);提交笔内不含他道文件。
|
6. 真实 pre-commit 钩子全绿(如 `passed=33 failed=0`);提交笔内不含他道文件。
|
||||||
7. 规范与台账已回写并复跑核数:自己先前写进规范的清点数字要么按最终实测改对,要么标明为动手前基线;「等你裁定」残留 = 0;「不做」裁定已在 §4.1.1 + §9.1 + 批次结论段落三处落地(原「变更记录」那一处已随 2026-10-28 清流水退场)。
|
7. 规范与台账已回写并复跑核数:自己先前写进规范的清点数字要么按最终实测改对,要么标明为动手前基线;「等你裁定」残留 = 0;「不做」裁定已在 §4.1.1 + §9.1 + 批次结论段落三处落地(原「变更记录」那一处已随 2026-10-28 清流水退场)。
|
||||||
8. **无识别面族**:`git grep -nE '卡[ ]*[0-9]' -- .` 无输出(裸跑读 rc);自建扫描器的站点/文件数与提交正文一致;交付里明写「本族不在 check_ids 语料内、门禁判不到」与「刻意不进 `id_aliases.json`」的理由;§4.1.1 停发行 + §9.3 换算表 + 批次结论段落三处齐全,换算表行数 = 该族字面量数(实测 14),且写明旧号永久作废、不得重新取走。
|
8. **无识别面族**:`git grep -nE '卡[ ]*[0-9]' -- .` 无输出(裸跑读 rc);自建扫描器的站点/文件数与提交正文一致;交付里明写「本族不在 check_ids 语料内、门禁判不到」与「刻意不进 `id_aliases.json`」的理由;§4.1.1 停发行 + §9.3 换算表 + 批次结论段落三处齐全,换算表行数 = 该族字面量数(实测 14),且写明旧号永久作废、不得重新取走。
|
||||||
9. **二次清洗三道各有实测数**:同句重复短语 0 行、各空格形态先计数后归一(复扫 0)、固定正则残留 0;被代码头注释引用的设计约束章节仍在(`git diff` 里没被动过)。
|
9. **二次清洗三道各有实测数**:同句重复短语 0 行、各空格形态先计数后归一(复扫 0)、固定正则残留 0;被代码头注释引用的设计约束章节仍在(`git diff` 里没被动过)。
|
||||||
10. **叙述同步**:`AGENTS.md` / `docs/README.md` / `docs/任务规划/README.md` / `docs/任务规划/登记簿.md` 里旧口径(「三项」「卡 N」类)实测归零;`ci-artifacts/` 里本次 spec 与中间产物已删。
|
10. **叙述同步**:`AGENTS.md` / `docs/README.md` / `docs/任务规划/README.md` / `docs/任务规划/登记簿.md` 里旧口径(「三项」「卡 N」类)实测归零;`ci-artifacts/` 里本次 spec 与中间产物已删。
|
||||||
11. 门禁复跑清单齐:`check_ids.py` plain + `--strict`、`check_doc_links.py --all`、`bom-guard`、`no-mojibake`、`git diff --check`、`@build` 或受影响单测——**每个数字标明是本回合实测**。
|
11. 门禁复跑清单齐:`check_ids.py` plain + `--strict`、`check_doc_links.py --all`、`bom-guard`、`no-mojibake`、`git diff --check`、`/keil-build` 或受影响单测——**每个数字标明是本回合实测**。
|
||||||
|
|||||||
@@ -25,6 +25,6 @@
|
|||||||
- **游标整型比较静默不生效**(字符串形态);抬完用 `--strict` 与 plain 双验。
|
- **游标整型比较静默不生效**(字符串形态);抬完用 `--strict` 与 plain 双验。
|
||||||
- **`git add` 目录路径带进别道文件**、`git add -A` 卷进在飞改动;撤销自己的改动只按文件点名 `git restore scripts/archive/id_gate/id_registry.json`,不碰同树其它道文件。
|
- **`git add` 目录路径带进别道文件**、`git add -A` 卷进在飞改动;撤销自己的改动只按文件点名 `git restore scripts/archive/id_gate/id_registry.json`,不碰同树其它道文件。
|
||||||
- **amend 被 gitlint B1 拦**:正文行 >80,别把 ASCII 双引号写成正文首字符。
|
- **amend 被 gitlint B1 拦**:正文行 >80,别把 ASCII 双引号写成正文首字符。
|
||||||
- **改「只动注释」要拿机器证据**:`@build` 0 错 0 警且 `Program Size` 与改前**逐字节同数**(本回合真跑,引用历史提交正文自报的数字不算证据),别口头承诺。
|
- **改「只动注释」要拿机器证据**:`/keil-build` 0 错 0 警且 `Program Size` 与改前**逐字节同数**(本回合真跑,引用历史提交正文自报的数字不算证据),别口头承诺。
|
||||||
- **冻结历史与原始证据不改**:boot 串口日志等采集现场是冻结证据,改了就是篡改;历史「修改记录」行按裁定保持改名后形态(号已全仓退役,留一个查无此号的引用比留改过名的条目更坏)。
|
- **冻结历史与原始证据不改**:boot 串口日志等采集现场是冻结证据,改了就是篡改;历史「修改记录」行按裁定保持改名后形态(号已全仓退役,留一个查无此号的引用比留改过名的条目更坏)。
|
||||||
- **`*.uvprojx` 抖动不入库**;`ci-artifacts/` 里的 spec 与扫描中间产物收尾删掉,别当交付物留下。
|
- **`*.uvprojx` 抖动不入库**;`ci-artifacts/` 里的 spec 与扫描中间产物收尾删掉,别当交付物留下。
|
||||||
|
|||||||
@@ -62,4 +62,4 @@
|
|||||||
- **同句重复短语**:编号位置补上的语义短语与原句已有短语撞车 → 扫「同一行里同一短语出现 ≥2 次」(实测 12 行,如 `.pre-commit-config.yaml:401`「运行参数段(「数据可信」运行参数段交付)」、`App/algo/README.md:110`「遥测快照发布(L2 发布侧,「数据可信」遥测快照发布)」)→ 改法是**把括号里的引用降级**成不含该短语的写法(「道D 交付项」「波次」「该点 DoD 原句」),逐处断言命中 = 1,别删信息;
|
- **同句重复短语**:编号位置补上的语义短语与原句已有短语撞车 → 扫「同一行里同一短语出现 ≥2 次」(实测 12 行,如 `.pre-commit-config.yaml:401`「运行参数段(「数据可信」运行参数段交付)」、`App/algo/README.md:110`「遥测快照发布(L2 发布侧,「数据可信」遥测快照发布)」)→ 改法是**把括号里的引用降级**成不含该短语的写法(「道D 交付项」「波次」「该点 DoD 原句」),逐处断言命中 = 1,别删信息;
|
||||||
- **空格与引号形态归一**:同一语义短语可能有 `端子 / 子板变比确认` 与 `端子/子板变比确认` 两种形态 → **先 `git grep -c -F` 分别数**(实测带空格 8 处),再整文件归一,归一后复扫到 0(残留常藏在表格单元格里,逐行打印定位);引号形态同理;
|
- **空格与引号形态归一**:同一语义短语可能有 `端子 / 子板变比确认` 与 `端子/子板变比确认` 两种形态 → **先 `git grep -c -F` 分别数**(实测带空格 8 处),再整文件归一,归一后复扫到 0(残留常藏在表格单元格里,逐行打印定位);引号形态同理;
|
||||||
- **固定正则复扫**:`卡[ ]*[0-9]` 全仓无输出。⚠ 判 rc 时**不要把命令接在 `| head` 之后再读 `$?`**(那拿到的是 `head` 的 0,会把「还有残留」读成「已归零」)。
|
- **固定正则复扫**:`卡[ ]*[0-9]` 全仓无输出。⚠ 判 rc 时**不要把命令接在 `| head` 之后再读 `$?`**(那拿到的是 `head` 的 0,会把「还有残留」读成「已归零」)。
|
||||||
9. **收尾复跑 + 清中间产物**:`check_ids.py`(plain 与 `--strict`)、`check_doc_links.py --all`、bom 与乱码两道(`pre-commit run bom-guard --files <改过的文件>`、`pre-commit run no-mojibake --files <改过的文件>`)、`git diff --check`;**纯注释/纯文档改动不算免编译**——仍跑一次 `@build` 取「0 错误 0 警告」且 `Program Size` 与改前同数(**本回合真跑**,引用历史提交正文自报的四个数不算),受影响的主机组单测照跑。最后**删掉 spec 与全部扫描中间产物**,别往 `ci-artifacts/` 再堆 log/txt。
|
9. **收尾复跑 + 清中间产物**:`check_ids.py`(plain 与 `--strict`)、`check_doc_links.py --all`、bom 与乱码两道(`pre-commit run bom-guard --files <改过的文件>`、`pre-commit run no-mojibake --files <改过的文件>`)、`git diff --check`;**纯注释/纯文档改动不算免编译**——仍跑一次 `/keil-build` 取「0 错误 0 警告」且 `Program Size` 与改前同数(**本回合真跑**,引用历史提交正文自报的四个数不算),受影响的主机组单测照跑。最后**删掉 spec 与全部扫描中间产物**,别往 `ci-artifacts/` 再堆 log/txt。
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: keil-build
|
name: keil-build
|
||||||
description: "Keil MDK-ARM 命令行编译闭环(自带独立脚本 keil_build.py,纯标准库,拷到任何 Keil 项目即用),覆盖 UV4 -b 构建、「0 错误 0 警告」判定、build_log 定位报错、PowerShell 与 bash 两种调用形态及各自的 exit 2 归属、退出码与 build 锁持有者名判读(含插件树的 pw: 前缀)、exit 2 时 UV4.exe 的候选路径探测与单次调用 env 注入闭环、UV4 改写工程文件的入库陷阱、构建副产物「%SystemDrive% 字面量垃圾目录」的取证、清理与入库排除。"
|
description: "Keil MDK-ARM 命令行编译闭环(自带独立脚本 keil_build.py,纯标准库,拷到任何 Keil 项目即用),覆盖 UV4 -b 构建、「0 错误 0 警告」判定、build_log 定位报错、PowerShell 与 bash 两种调用形态及各自的 exit 2 归属、退出码与 build 锁持有者名判读(含插件树的 pw: 前缀)、exit 2 时 UV4.exe 的候选路径探测与单次调用 env 注入闭环、UV4 改写工程文件的入库陷阱、构建副产物「%SystemDrive% 字面量垃圾目录」的取证、清理与入库排除。"
|
||||||
whenToUse: "需要编译 Keil MDK 工程、跑 @build 验证代码改动(只改 .c 注释也算改动)是否「0 错误 0 警告」、@build 报 BUILD_ENV_ERROR 与 UV4.exe not found(退出码 2;DSH 会话看不见用户级 DTU_KEIL_UV4)、解读 UV4 构建日志、退出码 2/4 或 build 锁持有者名(含 pw: 前缀),在 bash / ops_bash 里执行 @build 报 syntax error near unexpected token,或构建后 git status 冒出 %SystemDrive% 目录时。"
|
whenToUse: "需要编译 Keil MDK 工程、跑 /keil-build 验证代码改动(只改 .c 注释也算改动)是否「0 错误 0 警告」、/keil-build 报 BUILD_ENV_ERROR 与 UV4.exe not found(退出码 2;DSH 会话看不见用户级 DTU_KEIL_UV4)、解读 UV4 构建日志、退出码 2/4 或 build 锁持有者名(含 pw: 前缀),在 bash / ops_bash 里执行 PowerShell 形态的编译命令行报 syntax error near unexpected token,或构建后 git status 冒出 %SystemDrive% 目录时。"
|
||||||
---
|
---
|
||||||
|
|
||||||
# keil-build — Keil 命令行编译(独立可运行 skill)
|
# keil-build — Keil 命令行编译(独立可运行 skill)
|
||||||
@@ -10,7 +10,7 @@ whenToUse: "需要编译 Keil MDK 工程、跑 @build 验证代码改动(只
|
|||||||
(纯标准库,任意 python3;拷整个 skill 目录到别的 Keil 项目即可用,不依赖仓库脚本)。
|
(纯标准库,任意 python3;拷整个 skill 目录到别的 Keil 项目即可用,不依赖仓库脚本)。
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# cwd = 项目根目录;PowerShell 形态(AGENTS.md 里的 `@build` 写法)
|
# cwd = 项目根目录;PowerShell 形态(AGENTS「构建与验证」的编译入口)
|
||||||
& $PY ".dsh\skills\keil-build\scripts\keil_build.py"
|
& $PY ".dsh\skills\keil-build\scripts\keil_build.py"
|
||||||
# 覆盖:--project X.uvprojx --uv4 UV4.exe --target 名字 --log 路径 --timeout 600
|
# 覆盖:--project X.uvprojx --uv4 UV4.exe --target 名字 --log 路径 --timeout 600
|
||||||
```
|
```
|
||||||
@@ -25,7 +25,7 @@ column 14` → **技能整份不加载**(`skill()` 读不到、也不进技能
|
|||||||
|
|
||||||
## 调用形态:先认执行器是哪个 shell(2026-10-26 实测)
|
## 调用形态:先认执行器是哪个 shell(2026-10-26 实测)
|
||||||
|
|
||||||
- 同一个 @build,PowerShell 与 bash 是两种写法:bash 侧要去掉前导 & 并把路径改正斜杠(照抄 AGENTS 的 PowerShell 形态进 ops_bash 会语法错秒退 exit 2)
|
- 同一条编译命令,PowerShell 与 bash 是两种写法:bash 侧要去掉前导 & 并把路径改正斜杠(照抄 AGENTS 的 PowerShell 形态进 ops_bash 会语法错秒退 exit 2)
|
||||||
- ⚠ 那个 exit 2 不是脚本的环境错误 2:stderr 含 syntax error near unexpected token = 写法问题,换 bash 形态重跑,别去查 DTU_KEIL_UV4、别当环境错误排障
|
- ⚠ 那个 exit 2 不是脚本的环境错误 2:stderr 含 syntax error near unexpected token = 写法问题,换 bash 形态重跑,别去查 DTU_KEIL_UV4、别当环境错误排障
|
||||||
- 后台跑(run_in_background: true)要自己收码,且别把脚本接进管道:实测 $? 取到的是 tail 的码(恒 0);要看报错行就单独 grep MDK-ARM/build_log.txt
|
- 后台跑(run_in_background: true)要自己收码,且别把脚本接进管道:实测 $? 取到的是 tail 的码(恒 0);要看报错行就单独 grep MDK-ARM/build_log.txt
|
||||||
- 解析顺序:命令行 > 项目档案 .serialtest/config.json > 环境 > 探测;UV4 显式设了但路径无效 = 立即报错,不静默回退
|
- 解析顺序:命令行 > 项目档案 .serialtest/config.json > 环境 > 探测;UV4 显式设了但路径无效 = 立即报错,不静默回退
|
||||||
@@ -84,7 +84,7 @@ column 14` → **技能整份不加载**(`skill()` 读不到、也不进技能
|
|||||||
但 stderr 是 `syntax error near unexpected token`(见「调用形态」)。**先看 stderr 再决定排障方向**,
|
但 stderr 是 `syntax error near unexpected token`(见「调用形态」)。**先看 stderr 再决定排障方向**,
|
||||||
两种 2 的处置完全不同。
|
两种 2 的处置完全不同。
|
||||||
- **改 `.c` 注释(哪怕只改一行中文注释)也必须跑完这道构建验证**:2026-10-30 的实测就是
|
- **改 `.c` 注释(哪怕只改一行中文注释)也必须跑完这道构建验证**:2026-10-30 的实测就是
|
||||||
「改注释 → `@build` → 吃 rc=2 → 探测+注入 → 10 s 通过」。注释类坑(BOM 被剥 → `#870-D`、
|
「改注释 → `/keil-build` → 吃 rc=2 → 探测+注入 → 10 s 通过」。注释类坑(BOM 被剥 → `#870-D`、
|
||||||
注释里写 `**` 路径 → 9-D)只在**重编译那个 TU 时**才暴露,"以前 0 警告"不代表安全。
|
注释里写 `**` 路径 → 9-D)只在**重编译那个 TU 时**才暴露,"以前 0 警告"不代表安全。
|
||||||
|
|
||||||
## build 锁:脚本自持自释(2026-10-12)
|
## build 锁:脚本自持自释(2026-10-12)
|
||||||
@@ -107,21 +107,21 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
|||||||
- 锁工具 = 兄弟 skill `task-worktree/scripts/task_worktree.py`;**找不到则降级为不加锁
|
- 锁工具 = 兄弟 skill `task-worktree/scripts/task_worktree.py`;**找不到则降级为不加锁
|
||||||
并告警**(保持本 skill 可独立搬运)。
|
并告警**(保持本 skill 可独立搬运)。
|
||||||
- ⚠ 从 `_wt/<ID>-<slug>/` 反推持有者名,意味着"手工 `lock acquire build --task <ID>`
|
- ⚠ 从 `_wt/<ID>-<slug>/` 反推持有者名,意味着"手工 `lock acquire build --task <ID>`
|
||||||
后再跑 `@build`"仍**幂等**(同名即视为自己持有),不会自我死锁。
|
后再跑 `/keil-build`"仍**幂等**(同名即视为自己持有),不会自我死锁。
|
||||||
- **退出码 4 的持有者名带 `pw:`(如 `pw:refactor-logging`)= 插件在 `.dsh-worktrees/worktree/<name>/` 里跑的构建占着 UV4,不是某个 `@task` 任务 ID**:
|
- **退出码 4 的持有者名带 `pw:`(如 `pw:refactor-logging`)= 插件在 `.dsh-worktrees/worktree/<name>/` 里跑的构建占着 UV4,不是某个 `@task` 任务 ID**:
|
||||||
插件树**没有台账**,别拿它去 `lock release <ID>`(释放不掉别人的锁)、更别去杀 UV4 进程。正解 =
|
插件树**没有台账**,别拿它去 `lock release <ID>`(释放不掉别人的锁)、更别去杀 UV4 进程。正解 =
|
||||||
等那次构建自己 `finally` 释放(自持自释;崩溃则按自报 TTL 兜底),或本次加 `--lock-wait <N>` 排队;
|
等那次构建自己 `finally` 释放(自持自释;崩溃则按自报 TTL 兜底),或本次加 `--lock-wait <N>` 排队;
|
||||||
想知道是哪棵树的谁:`"$PY" "$TW" plugin list`(缺联接再 `plugin adopt <name>`)→ 细节归
|
想知道是哪棵树的谁:`"$PY" "$TW" plugin list`(缺联接再 `plugin adopt <name>`)→ 细节归
|
||||||
`task-worktree` skill F 节(插件桥)。
|
`task-worktree` skill F 节(插件桥)。
|
||||||
- **只 build 锁自动;`bench` 锁仍须 Agent 手工持**(它是**会话作用域**,跨
|
- **只 build 锁自动;`bench` 锁仍须 Agent 手工持**(它是**会话作用域**,跨
|
||||||
烧录→`@test`→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。
|
烧录→`/serial-boot-test`→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。
|
||||||
|
|
||||||
## 构建副产物:`%SystemDrive%` 字面量垃圾目录(2026-10-26 两次复发 → 2026-10-28 定性并加守卫)
|
## 构建副产物:`%SystemDrive%` 字面量垃圾目录(2026-10-26 两次复发 → 2026-10-28 定性并加守卫)
|
||||||
|
|
||||||
- 症状:@build(或 check_font_charset.py 这类 fontTools 工具)跑完,仓库根冒出未跟踪的 %SystemDrive%/ProgramData/…/Caches/(几个 .db,约 1 MB)= 本机缓存被按字面量原地复刻
|
- 症状:/keil-build(或 check_font_charset.py 这类 fontTools 工具)跑完,仓库根冒出未跟踪的 %SystemDrive%/ProgramData/…/Caches/(几个 .db,约 1 MB)= 本机缓存被按字面量原地复刻
|
||||||
- 根因(2026-10-28 实测):run_code 的 Node 进程 process.env 是空的 → known-folder 可展开串展不开变成相对路径 → 按当前目录建在仓库根;同族还会冒出 %USERPROFILE%/AppData/Local/Temp
|
- 根因(2026-10-28 实测):run_code 的 Node 进程 process.env 是空的 → known-folder 可展开串展不开变成相对路径 → 按当前目录建在仓库根;同族还会冒出 %USERPROFILE%/AppData/Local/Temp
|
||||||
- 清理有安全阀:⚠ 绝不用 cmd / PowerShell 直接删(rd /s /q %SystemDrive% 会被展开成 C:);只用 bash 引号形态,先 find '%SystemDrive%' -type l 预检再 rm -rf --。它是工具副产物,直接删、别 stash、更别提交
|
- 清理有安全阀:⚠ 绝不用 cmd / PowerShell 直接删(rd /s /q %SystemDrive% 会被展开成 C:);只用 bash 引号形态,先 find '%SystemDrive%' -type l 预检再 rm -rf --。它是工具副产物,直接删、别 stash、更别提交
|
||||||
- 两层防线均已落地可核验:.gitignore:167 挡入库 + 脚本 harden_windows_env() 回填环境变量、check_percent_literal_junk() 只告警不自动删;守卫是兜底不是许可,@build 仍要走 ops_bash / PowerShell
|
- 两层防线均已落地可核验:.gitignore:167 挡入库 + 脚本 harden_windows_env() 回填环境变量、check_percent_literal_junk() 只告警不自动删;守卫是兜底不是许可,/keil-build 仍要走 ops_bash / PowerShell
|
||||||
- **取证在前,处置在后**(2026-10-28 实测,别一上来 `rm -rf`):`ls -laR -- "%SystemDrive%"` + `du -sh "%SystemDrive%"` 枚举内容与体积,再 `find "%SystemDrive%" -printf "%T@ %p\n" | sort -rn | head -3` 看**最新 mtime** —— 最近几分钟还在被重建 = 环境仍残缺(脚本会打 `WARN: ENV_SPARSE:`),先断源头(换 `ops_bash`/PowerShell 跑,或按「UV4.exe not found」一节把 `env` 传全)再删,否则删完立刻长回来;顺带确认仓库里只有这一个该名字的东西(`find . -maxdepth 1 -name "*SystemDrive*"`)
|
- **取证在前,处置在后**(2026-10-28 实测,别一上来 `rm -rf`):`ls -laR -- "%SystemDrive%"` + `du -sh "%SystemDrive%"` 枚举内容与体积,再 `find "%SystemDrive%" -printf "%T@ %p\n" | sort -rn | head -3` 看**最新 mtime** —— 最近几分钟还在被重建 = 环境仍残缺(脚本会打 `WARN: ENV_SPARSE:`),先断源头(换 `ops_bash`/PowerShell 跑,或按「UV4.exe not found」一节把 `env` 传全)再删,否则删完立刻长回来;顺带确认仓库里只有这一个该名字的东西(`find . -maxdepth 1 -name "*SystemDrive*"`)
|
||||||
- **要留证据就搬走、别就地删** —— ⚠ **shell 的 `mv` 搬不动它**:`mv "%SystemDrive%/..."` 报 `cannot stat`(含 `%` 的目录名被当变量展开 / 吃掉,路径根本对不上)。改用 Python 遍历搬到**仓库外**的临时目录 —— `os.walk` + `shutil.move`(或 `glob.glob("%SystemDrive%/*")` 逐个 `shutil.move` 到 `os.environ["TEMP"]` 下);确认是纯工具副产物(几个 `.db` 缓存)才 `rm -rf --`
|
- **要留证据就搬走、别就地删** —— ⚠ **shell 的 `mv` 搬不动它**:`mv "%SystemDrive%/..."` 报 `cannot stat`(含 `%` 的目录名被当变量展开 / 吃掉,路径根本对不上)。改用 Python 遍历搬到**仓库外**的临时目录 —— `os.walk` + `shutil.move`(或 `glob.glob("%SystemDrive%/*")` 逐个 `shutil.move` 到 `os.environ["TEMP"]` 下);确认是纯工具副产物(几个 `.db` 缓存)才 `rm -rf --`
|
||||||
- **入库排除要落成条目并核验**:补 `.gitignore` 前先 `grep -n "SystemDrive" .gitignore` 定位精确原文行再 Edit(凭记忆写 `old_string` 必不匹配,2026-10-28 踩过)。本仓条目 = `.gitignore:167` 的 `%SystemDrive%/`,上面那段注释就是复发证据(新增条目也照此**写明第几次复发、谁造的**)。收尾核验 —— `git status --porcelain` 里该目录零命中(且别的未跟踪产物一个没少)、`git check-ignore -v` 落在那一行、`git add -A --dry-run` 零命中;**绝不提交**(把本机缓存塞进历史)
|
- **入库排除要落成条目并核验**:补 `.gitignore` 前先 `grep -n "SystemDrive" .gitignore` 定位精确原文行再 Edit(凭记忆写 `old_string` 必不匹配,2026-10-28 踩过)。本仓条目 = `.gitignore:167` 的 `%SystemDrive%/`,上面那段注释就是复发证据(新增条目也照此**写明第几次复发、谁造的**)。收尾核验 —— `git status --porcelain` 里该目录零命中(且别的未跟踪产物一个没少)、`git check-ignore -v` 落在那一行、`git add -A --dry-run` 零命中;**绝不提交**(把本机缓存塞进历史)
|
||||||
@@ -129,11 +129,11 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
|||||||
|
|
||||||
## 本仓库
|
## 本仓库
|
||||||
|
|
||||||
**`@build` = 本 skill**(2026-10-10 起为仓库文档口径;**`@build` 是文档别名、不是 shell 命令**——原样敲进终端是 command not found / syntax error,可执行形态永远是下面这条命令行;DSH 会话显式调用 `/keil-build`):在仓库根目录跑
|
**本 skill 的调用名 = `/keil-build`**(**技能调用名不是 shell 命令**——原样敲进终端是 command not found / syntax error,可执行形态永远是下面这条命令行):在仓库根目录跑
|
||||||
`& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`——
|
`& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`——
|
||||||
**bash / `ops_bash` 下要写成 `'.venv/Scripts/python.exe' .dsh/skills/keil-build/scripts/keil_build.py`**
|
**bash / `ops_bash` 下要写成 `'.venv/Scripts/python.exe' .dsh/skills/keil-build/scripts/keil_build.py`**
|
||||||
(原因见「调用形态」)。
|
(原因见「调用形态」)。
|
||||||
判定口径(与原 `MDK-ARM/build.bat` 同款;该文件已于 2026-10-10 删除,本 skill 即 `@build`):
|
判定口径(与原 `MDK-ARM/build.bat` 同款;该文件已于 2026-10-10 删除,本 skill 即编译入口):
|
||||||
先删旧日志 → `-b -j0 -o` → 无 `error:` 且含 `0 Error(s), 0 Warning(s)`。
|
先删旧日志 → `-b -j0 -o` → 无 `error:` 且含 `0 Error(s), 0 Warning(s)`。
|
||||||
本 skill 另有超时保护、UV4 多级解析链(含 PATH 兜底)、失败只打报错行。
|
本 skill 另有超时保护、UV4 多级解析链(含 PATH 兜底)、失败只打报错行。
|
||||||
环境变量 `DTU_KEIL_UV4`(用户级,指向 UV4.exe)——注意**它设了不等于本会话能看见**,仍报退出码 2 时走「exit 2 实战闭环」那一节,别先去找用户。
|
环境变量 `DTU_KEIL_UV4`(用户级,指向 UV4.exe)——注意**它设了不等于本会话能看见**,仍报退出码 2 时走「exit 2 实战闭环」那一节,别先去找用户。
|
||||||
@@ -142,7 +142,7 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
|||||||
|
|
||||||
- **任何代码改动后必须先编译验证(0 错误 0 警告)再谈烧录/提交**——编译不过就进
|
- **任何代码改动后必须先编译验证(0 错误 0 警告)再谈烧录/提交**——编译不过就进
|
||||||
硬件闭环只会把编译错误伪装成运行问题。**只改 `.c` 里一行中文注释也算改动**(2026-10-30 实测:
|
硬件闭环只会把编译错误伪装成运行问题。**只改 `.c` 里一行中文注释也算改动**(2026-10-30 实测:
|
||||||
改注释 → `@build` → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。
|
改注释 → `/keil-build` → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。
|
||||||
- **警告也算失败**:判 PASS 只认退出码 0,别只看"有没有 error:"(取退出码的管道陷阱见「调用形态」)。
|
- **警告也算失败**:判 PASS 只认退出码 0,别只看"有没有 error:"(取退出码的管道陷阱见「调用形态」)。
|
||||||
- **`MDK-ARM/*.uvprojx` 每次构建后被 UV4 自动改写,改动一律不入库**:UV4 会把工程
|
- **`MDK-ARM/*.uvprojx` 每次构建后被 UV4 自动改写,改动一律不入库**:UV4 会把工程
|
||||||
XML 归一化成本机风格(`<Tag />` → `<Tag></Tag>`、XML 声明加 `standalone="no"`)并把
|
XML 归一化成本机风格(`<Tag />` → `<Tag></Tag>`、XML 声明加 `standalone="no"`)并把
|
||||||
@@ -169,6 +169,6 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
|||||||
- **build 锁的对面是谁**(`BUILD_LOCK_BUSY`、退出码 4、`pw:<name>` 插件树、`@task plugin list` / `plugin adopt`、`_wt/` 台账与合并回收)归 `task-worktree` skill(F 节 = DSH worktree 插件桥);换机/新克隆时 `DTU_*` 工具路径变量的设立与「会话不继承」坑 = `new-machine-setup` skill。
|
- **build 锁的对面是谁**(`BUILD_LOCK_BUSY`、退出码 4、`pw:<name>` 插件树、`@task plugin list` / `plugin adopt`、`_wt/` 台账与合并回收)归 `task-worktree` skill(F 节 = DSH worktree 插件桥);换机/新克隆时 `DTU_*` 工具路径变量的设立与「会话不继承」坑 = `new-machine-setup` skill。
|
||||||
- 烧录/回读 = `stlink-flash` skill;串口闭环 = `serial-boot-test` skill
|
- 烧录/回读 = `stlink-flash` skill;串口闭环 = `serial-boot-test` skill
|
||||||
(其档案 `flash_cmd`/`readback_cmd` 即 stlink-flash 的入口命令)。
|
(其档案 `flash_cmd`/`readback_cmd` 即 stlink-flash 的入口命令)。
|
||||||
- 收尾固定链(2026-10-26 实证):`@build` → `scripts/ram_baseline.py --update` →
|
- 收尾固定链(2026-10-26 实证):`/keil-build` → `scripts/ram_baseline.py --update` →
|
||||||
体积门禁盖戳 → 内存数字回填与 `--scan` → 复跑 `ci_local` 静态段,
|
体积门禁盖戳 → 内存数字回填与 `--scan` → 复跑 `ci_local` 静态段,
|
||||||
直到钩子全绿且 0 错误 0 警告(RAM/栈口径见 `mem-budget` skill)。
|
直到钩子全绿且 0 错误 0 警告(RAM/栈口径见 `mem-budget` skill)。
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> 本文由 keil-build 的 SKILL.md「调用形态:先认执行器是哪个 shell(2026-10-26 实测)」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。
|
> 本文由 keil-build 的 SKILL.md「调用形态:先认执行器是哪个 shell(2026-10-26 实测)」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。
|
||||||
|
|
||||||
同一个 `@build`,**PowerShell 与 bash 是两种写法**;`ops_bash` / `run_code` 起的是
|
同一条编译命令,**PowerShell 与 bash 是两种写法**;`ops_bash` / `run_code` 起的是
|
||||||
`/usr/bin/bash -c`,**不认 PowerShell 形态**,照抄仓库 `AGENTS.md` 会直接语法错退出。
|
`/usr/bin/bash -c`,**不认 PowerShell 形态**,照抄仓库 `AGENTS.md` 会直接语法错退出。
|
||||||
|
|
||||||
| 执行器 | 正确写法(cwd = 仓库根) |
|
| 执行器 | 正确写法(cwd = 仓库根) |
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> 本文由 keil-build 的 SKILL.md「构建副产物:`%SystemDrive%` 字面量垃圾目录(2026-10-26 两次复发 → 2026-10-28 定性并加守卫)」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。
|
> 本文由 keil-build 的 SKILL.md「构建副产物:`%SystemDrive%` 字面量垃圾目录(2026-10-26 两次复发 → 2026-10-28 定性并加守卫)」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。
|
||||||
|
|
||||||
**症状**:`@build`(或 `scripts/check_font_charset.py` 这类 fontTools 字体工具)跑完,仓库根
|
**症状**:`/keil-build`(或 `scripts/check_font_charset.py` 这类 fontTools 字体工具)跑完,仓库根
|
||||||
冒出未跟踪目录 `%SystemDrive%/ProgramData/Microsoft/Windows/Caches/`,里面是几个 `.db`
|
冒出未跟踪目录 `%SystemDrive%/ProgramData/Microsoft/Windows/Caches/`,里面是几个 `.db`
|
||||||
(`cversions.2.db` + `{6AF0698E-…}` 图标缓存 + `{DDF571F2-…}` 字体缓存,合计约 1 MB)——
|
(`cversions.2.db` + `{6AF0698E-…}` 图标缓存 + `{DDF571F2-…}` 字体缓存,合计约 1 MB)——
|
||||||
就是本机 `C:\ProgramData\Microsoft\Windows\Caches` 那份缓存被**按字面量原地复刻**了一份。
|
就是本机 `C:\ProgramData\Microsoft\Windows\Caches` 那份缓存被**按字面量原地复刻**了一份。
|
||||||
@@ -41,4 +41,4 @@ node -e "console.log(Object.keys(process.env).length)" # run_code 里 = 0(
|
|||||||
并把 `TMP`/`TEMP` 指到真实目录;命中即打 `WARN: ENV_SPARSE:`。构建后再跑
|
并把 `TMP`/`TEMP` 指到真实目录;命中即打 `WARN: ENV_SPARSE:`。构建后再跑
|
||||||
`check_percent_literal_junk()`,看到 `%SystemDrive%`/`%USERPROFILE%` 一类字面量目录就打
|
`check_percent_literal_junk()`,看到 `%SystemDrive%`/`%USERPROFILE%` 一类字面量目录就打
|
||||||
`WARN: JUNK_DIR:`(**只告警不自动删**——删目录不该由构建脚本代劳)。守卫是兜底不是许可:
|
`WARN: JUNK_DIR:`(**只告警不自动删**——删目录不该由构建脚本代劳)。守卫是兜底不是许可:
|
||||||
**`@build` 仍要走 `ops_bash` / PowerShell(继承完整环境)**,非在 Node 里 spawn 就把 `env` 传全。
|
**`/keil-build` 仍要走 `ops_bash` / PowerShell(继承完整环境)**,非在 Node 里 spawn 就把 `env` 传全。
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
(环境变量**显式设了但路径无效 = 立即报错,不静默回退**)
|
(环境变量**显式设了但路径无效 = 立即报错,不静默回退**)
|
||||||
日志 --log > 档案 "build_log" > <工程目录>\\build_log.txt
|
日志 --log > 档案 "build_log" > <工程目录>\\build_log.txt
|
||||||
|
|
||||||
判定口径(实测教训沉淀;原仓库 MDK-ARM/build.bat 已删除,本脚本即 @build):
|
判定口径(实测教训沉淀;原仓库 MDK-ARM/build.bat 已删除,本脚本即仓库编译入口):
|
||||||
1. 先删旧日志——UV4 -o 只在真跑了构建才写日志,残留旧日志会被当成本次结果
|
1. 先删旧日志——UV4 -o 只在真跑了构建才写日志,残留旧日志会被当成本次结果
|
||||||
2. UV4 单实例:IDE 开着时 -b 可能被转发给 IDE(退出码丢失)→ 只 WARN 不杀进程
|
2. UV4 单实例:IDE 开着时 -b 可能被转发给 IDE(退出码丢失)→ 只 WARN 不杀进程
|
||||||
3. PASS = 日志存在 + 无 "error:"(大小写不敏感)+ 含 "0 Error(s), 0 Warning(s)"
|
3. PASS = 日志存在 + 无 "error:"(大小写不敏感)+ 含 "0 Error(s), 0 Warning(s)"
|
||||||
@@ -157,7 +157,7 @@ def harden_windows_env():
|
|||||||
if filled or expanded:
|
if filled or expanded:
|
||||||
print("WARN: ENV_SPARSE: 子进程环境残缺/含未展开字面量 —— 已回填[{}] 已展开[{}]".format(
|
print("WARN: ENV_SPARSE: 子进程环境残缺/含未展开字面量 —— 已回填[{}] 已展开[{}]".format(
|
||||||
", ".join(sorted(filled)) or "-", ", ".join(sorted(expanded)) or "-"))
|
", ".join(sorted(filled)) or "-", ", ".join(sorted(expanded)) or "-"))
|
||||||
print(" 正解:用继承**完整环境**的 shell 跑 @build(ops_bash / PowerShell),"
|
print(" 正解:用继承**完整环境**的 shell 跑编译(ops_bash / PowerShell),"
|
||||||
"别从 Node spawn(空 process.env)直接起 UV4 或字体门禁脚本")
|
"别从 Node spawn(空 process.env)直接起 UV4 或字体门禁脚本")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ PC 上编译运行与 MCU **同一份** UI 源码(CMake + MSVC + SDL2,x64,
|
|||||||
|
|
||||||
## UI 改动回归 → 用 `ui-verify` skill(勿在此零散手跑)
|
## UI 改动回归 → 用 `ui-verify` skill(勿在此零散手跑)
|
||||||
|
|
||||||
5 步门禁 = 构建 → `check_menu_rows.py`(菜单 8 状态像素 + 池基线)→ `check_content_pages.py`(文本断言 + 66 张黄金参考 + 往返池)→ `@build` → `coding-standard`。
|
5 步门禁 = 构建 → `check_menu_rows.py`(菜单 8 状态像素 + 池基线)→ `check_content_pages.py`(文本断言 + 66 张黄金参考 + 往返池)→ `/keil-build` → `coding-standard`。
|
||||||
RemoDispBus 推流回归单独跑:`.venv\Scripts\python.exe simulator/tests/check_remo_sink.py`。
|
RemoDispBus 推流回归单独跑:`.venv\Scripts\python.exe simulator/tests/check_remo_sink.py`。
|
||||||
黄金参考 `tests/ref/*.bin` **随仓库入库**(`.gitignore` 在全仓 *.bin 兜底之后又显式反选,2026-10-22 起;缺基准 = 新克隆跑 `ui-golden` 必 FAIL):不入库的只是派生目检图 `ref/*.png`,换机后先 `check_content_pages.py --update` 生成,再 `regen_ref_png.py` 出目检 PNG。
|
黄金参考 `tests/ref/*.bin` **随仓库入库**(`.gitignore` 在全仓 *.bin 兜底之后又显式反选,2026-10-22 起;缺基准 = 新克隆跑 `ui-golden` 必 FAIL):不入库的只是派生目检图 `ref/*.png`,换机后先 `check_content_pages.py --update` 生成,再 `regen_ref_png.py` 出目检 PNG。
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: mem-budget
|
name: mem-budget
|
||||||
description: 嵌入式 RAM/栈预算核算工作流:Keil .map/.htm 静态报告(ram_report/stack_report)+ 串口运行时水位叠加 + 权威构建后的台账回填(ram_baseline.py --update 只往 ram_baseline.tsv 追加趋势行、--write 才同步 RAM预算台账 受管区、check_mem_numbers.py --write 同步文档快照区)+ DBG 日志成本模型(每条约 90B Flash/216B 栈)+ 跨树(worktree)测量的身份口径——脚本按 __file__ 定位产物、什么才算一次新测量、多条链同涨同一数值的共享帧/幻影环归因法。"改任务/缓冲/栈之前必须核算"、"这个数属于哪棵树、哪次构建"与"内存/Flash 数字钩子红了、提交前台账怎么回填"的固定动作和判读口径。
|
description: 嵌入式 RAM/栈预算核算工作流:Keil .map/.htm 静态报告(ram_report/stack_report)+ 串口运行时水位叠加 + 权威构建后的台账回填(ram_baseline.py --update 只往 ram_baseline.tsv 追加趋势行、--write 才同步 RAM预算台账 受管区、check_mem_numbers.py --write 同步文档快照区)+ DBG 日志成本模型(每条约 90B Flash/216B 栈)+ 跨树(worktree)测量的身份口径——脚本按 __file__ 定位产物、什么才算一次新测量、多条链同涨同一数值的共享帧/幻影环归因法。"改任务/缓冲/栈之前必须核算"、"这个数属于哪棵树、哪次构建"与"内存/Flash 数字钩子红了、提交前台账怎么回填"的固定动作和判读口径。
|
||||||
whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 L6406E 或 RAM 溢出、串口 [STACK] 水位余量低于底线、要回答"这个改动花多少 RAM/Flash";以及要复算或比对另一个 worktree 的栈数字、独立验证某个栈回归该记在哪一笔提交上、判断多次启动的栈峰值抖动算不算劣化时;还有 @build 之后要把内存数字回填进台账/文档、纠结该跑 ram_baseline.py --write 还是 --update、提交时 mem-budget / mem-numbers 两条钩子报"与 .map 不一致"时。
|
whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 L6406E 或 RAM 溢出、串口 [STACK] 水位余量低于底线、要回答"这个改动花多少 RAM/Flash";以及要复算或比对另一个 worktree 的栈数字、独立验证某个栈回归该记在哪一笔提交上、判断多次启动的栈峰值抖动算不算劣化时;还有 /keil-build 之后要把内存数字回填进台账/文档、纠结该跑 ram_baseline.py --write 还是 --update、提交时 mem-budget / mem-numbers 两条钩子报"与 .map 不一致"时。
|
||||||
---
|
---
|
||||||
|
|
||||||
# mem-budget — RAM/栈预算核算
|
# mem-budget — RAM/栈预算核算
|
||||||
@@ -9,7 +9,7 @@ whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报
|
|||||||
## 固定动作(改前、改后各跑一次)
|
## 固定动作(改前、改后各跑一次)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# cwd = 项目根;先 @build 保证 .htm/.map 是最新的(产物按脚本自身位置定位,见下节)
|
# cwd = 项目根;先 /keil-build 保证 .htm/.map 是最新的(产物按脚本自身位置定位,见下节)
|
||||||
& '.venv\Scripts\python.exe' ".dsh\skills\mem-budget\scripts\mem_check.py"
|
& '.venv\Scripts\python.exe' ".dsh\skills\mem-budget\scripts\mem_check.py"
|
||||||
# 等价于分别跑:
|
# 等价于分别跑:
|
||||||
# scripts/ram_report.py (.map → RAM/Flash 分区与按文件占用表)
|
# scripts/ram_report.py (.map → RAM/Flash 分区与按文件占用表)
|
||||||
@@ -67,7 +67,7 @@ whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报
|
|||||||
| `docs/内存管理/RAM预算台账.md` 的 `<!-- MEM_BUDGET:BEGIN/END -->` 受管区 | `ram_baseline.py --write` | 从 .map 重渲染区域水位 + 子系统配额对照(打印「已同步台账受管区。」或「台账受管区已是最新。」) |
|
| `docs/内存管理/RAM预算台账.md` 的 `<!-- MEM_BUDGET:BEGIN/END -->` 受管区 | `ram_baseline.py --write` | 从 .map 重渲染区域水位 + 子系统配额对照(打印「已同步台账受管区。」或「台账受管区已是最新。」) |
|
||||||
| `docs/内存管理/RAM使用分析与优化方案.md` 的 `<!-- MEM_SNAPSHOT:BEGIN/END -->` 快照区 | `check_mem_numbers.py --write` | 从**同一份** .map 重渲染内存/Flash 快照表(打印「已更新内存快照:…」) |
|
| `docs/内存管理/RAM使用分析与优化方案.md` 的 `<!-- MEM_SNAPSHOT:BEGIN/END -->` 快照区 | `check_mem_numbers.py --write` | 从**同一份** .map 重渲染内存/Flash 快照表(打印「已更新内存快照:…」) |
|
||||||
|
|
||||||
**AGENTS.md 与台账 §4 那句「每次 `@build` 后跑 `scripts/ram_baseline.py --update`」喂的是趋势行,不等于回填受管区。**
|
**AGENTS.md 与台账 §4 那句「每次 `/keil-build` 后跑 `scripts/ram_baseline.py --update`」喂的是趋势行,不等于回填受管区。**
|
||||||
实测代价:照那句跑完 `--update` 就提交 → `mem-budget`、`mem-numbers` 两条钩子当场红。
|
实测代价:照那句跑完 `--update` 就提交 → `mem-budget`、`mem-numbers` 两条钩子当场红。
|
||||||
`mem-budget` 的 entry 是 `python scripts/ram_baseline.py --gate`(`.pre-commit-config.yaml:571`)——它在 92% 阈值之外
|
`mem-budget` 的 entry 是 `python scripts/ram_baseline.py --gate`(`.pre-commit-config.yaml:571`)——它在 92% 阈值之外
|
||||||
还核**台账受管区与 .map 的一致性**,漂移即 `[FAIL] 台账受管区与 .map 不一致(区域水位或子系统配额表漂移)`
|
还核**台账受管区与 .map 的一致性**,漂移即 `[FAIL] 台账受管区与 .map 不一致(区域水位或子系统配额表漂移)`
|
||||||
@@ -144,7 +144,7 @@ git add docs/内存管理/RAM预算台账.md docs/内存管理/RAM使用分析
|
|||||||
3. 树身份已按 `tree/commit + 时刻 + 产物来源` 写进提交正文/台账,而不是只留在终端输出里;
|
3. 树身份已按 `tree/commit + 时刻 + 产物来源` 写进提交正文/台账,而不是只留在终端输出里;
|
||||||
4. 结论里互比的数字必须来自**同一棵树同一次链接**;跨树比较要各自给出树与时刻;
|
4. 结论里互比的数字必须来自**同一棵树同一次链接**;跨树比较要各自给出树与时刻;
|
||||||
5. 涉及劣化判定时注明依据的是静态最坏链(硬线)还是运行时峰值(趋势),并已计入跨启动抖动。
|
5. 涉及劣化判定时注明依据的是静态最坏链(硬线)还是运行时峰值(趋势),并已计入跨启动抖动。
|
||||||
6. 要入库的数字:本轮已按「`@build` → `ram_baseline.py --update` → `ram_baseline.py --write` →
|
6. 要入库的数字:本轮已按「`/keil-build` → `ram_baseline.py --update` → `ram_baseline.py --write` →
|
||||||
`check_mem_numbers.py --write`」回填,并逐字自跑 `ram_baseline.py --gate` 与 `check_mem_numbers.py --scan` 各返 0;
|
`check_mem_numbers.py --write`」回填,并逐字自跑 `ram_baseline.py --gate` 与 `check_mem_numbers.py --scan` 各返 0;
|
||||||
台账 / 文档 / tsv 的改动与代码同一笔入库,**未手改数字、未用 `--no-verify`**。
|
台账 / 文档 / tsv 的改动与代码同一笔入库,**未手改数字、未用 `--no-verify`**。
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
(`ram_baseline.py:232`),**拦下来的是受管区漂移**。所以"钩子绿"绝不等于"趋势行也补了",反之亦然,各查各的。
|
(`ram_baseline.py:232`),**拦下来的是受管区漂移**。所以"钩子绿"绝不等于"趋势行也补了",反之亦然,各查各的。
|
||||||
- **`--gate` 无 .map → SKIP,而脚本自己写明「SKIP 不等于 PASS」**(`ram_baseline.py:177`):
|
- **`--gate` 无 .map → SKIP,而脚本自己写明「SKIP 不等于 PASS」**(`ram_baseline.py:177`):
|
||||||
新开的 task-worktree 从没在这棵树上构建过时必然走到这里;同一状态下 `--write` 直接
|
新开的 task-worktree 从没在这棵树上构建过时必然走到这里;同一状态下 `--write` 直接
|
||||||
`[FAIL] 找不到 .map 事实源,先 @build`(`ram_baseline.py:250`)——不是代码缺陷,是这棵树还没测过体积。
|
`[FAIL] 找不到 .map 事实源,先 /keil-build`(`ram_baseline.py:250`)——不是代码缺陷,是这棵树还没测过体积。
|
||||||
无需 Keil 的强制兜底是 `scripts/check_size_gate.py --local`(= `scripts/ci_local.py` 的体积门禁步)与 CI 的 `--ci`。
|
无需 Keil 的强制兜底是 `scripts/check_size_gate.py --local`(= `scripts/ci_local.py` 的体积门禁步)与 CI 的 `--ci`。
|
||||||
- **`--map <path>` 只换读、不换写**:`LEDGER`(`ram_baseline.py:45`)与 `TARGETS`(`check_mem_numbers.py:53-54`)
|
- **`--map <path>` 只换读、不换写**:`LEDGER`(`ram_baseline.py:45`)与 `TARGETS`(`check_mem_numbers.py:53-54`)
|
||||||
都由 `REPO_ROOT = Path(__file__).resolve().parent.parent` 算出。拿主仓脚本配 `--map <另一棵树>/…map` 去 `--write`,
|
都由 `REPO_ROOT = Path(__file__).resolve().parent.parent` 算出。拿主仓脚本配 `--map <另一棵树>/…map` 去 `--write`,
|
||||||
|
|||||||
@@ -36,5 +36,5 @@ cmake --build simulator/build --config Debug # 0 警告
|
|||||||
- **禁止**把源文件复制进 `simulator/`——两端共用 `Drivers/BSP/` 原件(UI_界面制作流程.md C7)
|
- **禁止**把源文件复制进 `simulator/`——两端共用 `Drivers/BSP/` 原件(UI_界面制作流程.md C7)
|
||||||
- 只注册 `.c`(含 `.s`);`.h` 靠 include 路径找到,不加工程
|
- 只注册 `.c`(含 `.s`);`.h` 靠 include 路径找到,不加工程
|
||||||
- 漏注册的典型症状:一端编译失败(undefined reference)或一端行为悄悄停留在旧逻辑
|
- 漏注册的典型症状:一端编译失败(undefined reference)或一端行为悄悄停留在旧逻辑
|
||||||
- 注册完成后跑回归:UI 文件 → `ui-verify` skill;其他模块 → `@test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`)
|
- 注册完成后跑回归:UI 文件 → `ui-verify` skill;其他模块 → `/serial-boot-test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`)
|
||||||
- 改过 `CMakeLists.txt` 后需重新 `cmake -B build` 配置
|
- 改过 `CMakeLists.txt` 后需重新 `cmake -B build` 配置
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
status 全状态(单条 JSON)
|
status 全状态(单条 JSON)
|
||||||
log --tail N --grep P 读日志(先过滤后截尾;-i 忽略大小写 / --regex 正则)
|
log --tail N --grep P 读日志(先过滤后截尾;-i 忽略大小写 / --regex 正则)
|
||||||
clear 清空日志(原子截断 + 代际递增)
|
clear 清空日志(原子截断 + 代际递增)
|
||||||
wait-idle 等 RX 静默(@test 用;--settle-ms / --max-ms / --first-timeout-ms)
|
wait-idle 等 RX 静默(/serial-boot-test 用;--settle-ms / --max-ms / --first-timeout-ms)
|
||||||
start / stop / restart daemon 生命周期(其余命令会自动拉起 daemon)
|
start / stop / restart daemon 生命周期(其余命令会自动拉起 daemon)
|
||||||
selftest 无头自检(行拆分/解码回退/截头算法,无需硬件)
|
selftest 无头自检(行拆分/解码回退/截头算法,无需硬件)
|
||||||
|
|
||||||
@@ -615,10 +615,10 @@ class Daemon:
|
|||||||
def wait_idle(self, settle_s, max_s, first_timeout_s=None):
|
def wait_idle(self, settle_s, max_s, first_timeout_s=None):
|
||||||
"""等 RX 静默,返回 reason ∈ {idle, no_data, timeout}。
|
"""等 RX 静默,返回 reason ∈ {idle, no_data, timeout}。
|
||||||
|
|
||||||
2026-10-09 修三处(旧实现会让 @test 在"板子全程没输出"时误判收尾):
|
2026-10-09 修三处(旧实现会让 /serial-boot-test 在"板子全程没输出"时误判收尾):
|
||||||
1. no_data 判据:旧实现比的是"进入函数时 daemon 累计 rx_bytes 是否为 0"。
|
1. no_data 判据:旧实现比的是"进入函数时 daemon 累计 rx_bytes 是否为 0"。
|
||||||
daemon 常驻(本机实测 rx_bytes=1.2MB)→ 永远 >0 → 板子一个字节都不发
|
daemon 常驻(本机实测 rx_bytes=1.2MB)→ 永远 >0 → 板子一个字节都不发
|
||||||
也返回 idle:true,@test 的 no_data 拦截形同虚设。现按**本次增量**判。
|
也返回 idle:true,/serial-boot-test 的 no_data 拦截形同虚设。现按**本次增量**判。
|
||||||
2. 首字节门:旧实现从进入即开始计静默时间,板子第一行比 settle_ms 来得
|
2. 首字节门:旧实现从进入即开始计静默时间,板子第一行比 settle_ms 来得
|
||||||
晚就提前收尾("开机日志缺尾巴"的另一半成因)。现首字节到达前不启动
|
晚就提前收尾("开机日志缺尾巴"的另一半成因)。现首字节到达前不启动
|
||||||
settle 计时,改由 first_timeout_s 兜底(默认 = max_s)。
|
settle 计时,改由 first_timeout_s 兜底(默认 = max_s)。
|
||||||
@@ -1353,7 +1353,7 @@ def build_parser():
|
|||||||
|
|
||||||
sub.add_parser("clear", parents=[common], help="清空日志(代际 +1)")
|
sub.add_parser("clear", parents=[common], help="清空日志(代际 +1)")
|
||||||
|
|
||||||
p = sub.add_parser("wait-idle", parents=[common], help="等 RX 静默(@test 用)")
|
p = sub.add_parser("wait-idle", parents=[common], help="等 RX 静默(/serial-boot-test 用)")
|
||||||
p.add_argument("--settle-ms", type=int, default=8000,
|
p.add_argument("--settle-ms", type=int, default=8000,
|
||||||
help="静默判据毫秒(默认 8000:boot 有 3~4s 自然静默间隙)")
|
help="静默判据毫秒(默认 8000:boot 有 3~4s 自然静默间隙)")
|
||||||
p.add_argument("--max-ms", type=int, default=30000, help="最长等待毫秒(默认 30000)")
|
p.add_argument("--max-ms", type=int, default=30000, help="最长等待毫秒(默认 30000)")
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ whenToUse: 需要向目标板烧录或复位后,用串口日志自动判定运
|
|||||||
| 脚本 | 作用 |
|
| 脚本 | 作用 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | 一键闭环:open+clear → 烧录 → wait-idle → 提取 boot log → 烧录回读校验 → 标记校验。串口助手按 `$DSH_SERIAL_TOOL` > 同目录 > `../serial-assistant/scripts/serial_tool.py` 解析 |
|
| `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | 一键闭环:open+clear → 烧录 → wait-idle → 提取 boot log → 烧录回读校验 → 标记校验。串口助手按 `$DSH_SERIAL_TOOL` > 同目录 > `../serial-assistant/scripts/serial_tool.py` 解析 |
|
||||||
| `.dsh/skills/serial-boot-test/scripts/verify_log.py` | 对任意日志文件做标记校验(Required/Forbidden/锚点计数/顺序/耗时),可单独用。`--exact` 做**整行 `^…$` 锚定**(子串口径照不出"行还在但行首被吃掉/行内被插字节"那一类损坏)、`--pair` 做跨复位配对、`--allow-residual/--head-char` 服务崩溃注入腿、`--selftest` 跑内置判别用例(**证明这套判据不是空跑**)。四个行级开关(`--exact` / `--pair` / `--allow-residual` / `--head-char`)同时接进了 `run_boot_test.py`,@test 闭环直接可用 |
|
| `.dsh/skills/serial-boot-test/scripts/verify_log.py` | 对任意日志文件做标记校验(Required/Forbidden/锚点计数/顺序/耗时),可单独用。`--exact` 做**整行 `^…$` 锚定**(子串口径照不出"行还在但行首被吃掉/行内被插字节"那一类损坏)、`--pair` 做跨复位配对、`--allow-residual/--head-char` 服务崩溃注入腿、`--selftest` 跑内置判别用例(**证明这套判据不是空跑**)。四个行级开关(`--exact` / `--pair` / `--allow-residual` / `--head-char`)同时接进了 `run_boot_test.py`,/serial-boot-test 闭环直接可用 |
|
||||||
|
|
||||||
> **`@test` 是 AGENTS.md 的文档别名,不是 shell 命令**(同 `@build` / `@task`,见 AGENTS「构建与验证」总口径):可执行形态 = 上表 `run_boot_test.py` 命令行(仓库根跑),DSH 会话显式调用 `/serial-boot-test`;项目配置与判据见本仓 `docs/测试说明/流程与规范/自动化测试流程.md`。
|
> **`/serial-boot-test` 是 DSH 会话的技能调用名、不是 shell 命令**(同 `/keil-build`,见 AGENTS「构建与验证」总口径):可执行形态 = 上表 `run_boot_test.py` 命令行(仓库根跑);项目配置与判据见本仓 `docs/测试说明/流程与规范/自动化测试流程.md`。
|
||||||
|
|
||||||
> **判据取向(重要,会复发)**:`--exact` 默认**只锚文本、不钉长度**——字节长度属于某一份构建,钉死它会在无辜改动上假红(`@@LEN=<n>` 是显式逃生口,不是默认)。子串 `--required` 不能用于"可能丢行首/被插字节"的行。来龙去脉与固定动作见本仓 `docs/测试说明/专项验证/启动日志原子性复验流程.md`。
|
> **判据取向(重要,会复发)**:`--exact` 默认**只锚文本、不钉长度**——字节长度属于某一份构建,钉死它会在无辜改动上假红(`@@LEN=<n>` 是显式逃生口,不是默认)。子串 `--required` 不能用于"可能丢行首/被插字节"的行。来龙去脉与固定动作见本仓 `docs/测试说明/专项验证/启动日志原子性复验流程.md`。
|
||||||
|
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ def _ftp_quit(ftp):
|
|||||||
def _prune_out_dir(out_dir, keep):
|
def _prune_out_dir(out_dir, keep):
|
||||||
"""只保留最近 N 份产物(`boot_log_*.txt` / `selftest_log_*.log`)。
|
"""只保留最近 N 份产物(`boot_log_*.txt` / `selftest_log_*.log`)。
|
||||||
|
|
||||||
这些文件按 `%Y%m%d_%H%M%S` 命名、每次 @test 各留一份,旧实现从不清理——
|
这些文件按 `%Y%m%d_%H%M%S` 命名、每次 /serial-boot-test 各留一份,旧实现从不清理——
|
||||||
本机实测 out 目录已堆到 31 份 boot_log + 7 份 selftest_log(2026-10-10)。
|
本机实测 out 目录已堆到 31 份 boot_log + 7 份 selftest_log(2026-10-10)。
|
||||||
文件名含时间戳 → 按名字排序即按时间排序。
|
文件名含时间戳 → 按名字排序即按时间排序。
|
||||||
"""
|
"""
|
||||||
@@ -294,7 +294,7 @@ def _fetch_selftest_log(device_ip, out_dir, stamp, ftp_user="admin", ftp_pass="a
|
|||||||
2026-10-02 台架修复:原实现只 `except OSError`,而 ftplib 的 error_temp /
|
2026-10-02 台架修复:原实现只 `except OSError`,而 ftplib 的 error_temp /
|
||||||
error_perm 继承自 Exception 而非 OSError —— 台账"仅故障才写",干净设备上
|
error_perm 继承自 Exception 而非 OSError —— 台账"仅故障才写",干净设备上
|
||||||
/log/selftest.log 根本不存在,RETR 返回 450 直接抛出未捕获异常,令整个
|
/log/selftest.log 根本不存在,RETR 返回 450 直接抛出未捕获异常,令整个
|
||||||
`@test --require-selftest-pass` 在最后一步崩掉(而它本该是 PASS 路径)。
|
`run_boot_test.py --require-selftest-pass` 在最后一步崩掉(而它本该是 PASS 路径)。
|
||||||
现把"设备侧无此文件"单列为正常路径(INFO),其余 FTP 错误保留 WARN。
|
现把"设备侧无此文件"单列为正常路径(INFO),其余 FTP 错误保留 WARN。
|
||||||
"""
|
"""
|
||||||
local = Path(out_dir) / "selftest_log_{}.log".format(stamp)
|
local = Path(out_dir) / "selftest_log_{}.log".format(stamp)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""verify_log.py - 对抓取的 MCU 调试日志做标记校验(纯标准库,2026-09-20 替代 verify_log.ps1)。
|
"""verify_log.py - 对抓取的 MCU 调试日志做标记校验(纯标准库,2026-09-20 替代 verify_log.ps1)。
|
||||||
|
|
||||||
用于 @test 闭环:烧录后从串口助手日志提取的开机日志交给本脚本 grep 标记,
|
用于 /serial-boot-test 闭环:烧录后从串口助手日志提取的开机日志交给本脚本 grep 标记,
|
||||||
输出结构化判定(格式与旧 PowerShell 版逐字一致,下游文档/习惯不受影响)。
|
输出结构化判定(格式与旧 PowerShell 版逐字一致,下游文档/习惯不受影响)。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
[--require-order 'App init OK>Network Init>net init: OK']
|
[--require-order 'App init OK>Network Init>net init: OK']
|
||||||
[--done-marker 'net init: OK'] [--max-boot-ms 8000] [--json]
|
[--done-marker 'net init: OK'] [--max-boot-ms 8000] [--json]
|
||||||
|
|
||||||
退出码(2026-10-27 task-32 起把 2 讲清楚;@test 那层的码不变):
|
退出码(2026-10-27 task-32 起把 2 讲清楚;/serial-boot-test 那层的码不变):
|
||||||
0 = PASS(所有 Required 标记找到,Forbidden 零命中,汇总行要求满足)
|
0 = PASS(所有 Required 标记找到,Forbidden 零命中,汇总行要求满足)
|
||||||
1 = FAIL(**设备侧真有坏消息**:缺 Required / 命中 Forbidden / 汇总行不满足 / 顺序或耗时超限)
|
1 = FAIL(**设备侧真有坏消息**:缺 Required / 命中 Forbidden / 汇总行不满足 / 顺序或耗时超限)
|
||||||
2 = **测量没成立**(日志不存在或不可读;或开了 --max-boot-count 却零启动锚点 = 采集器没开、
|
2 = **测量没成立**(日志不存在或不可读;或开了 --max-boot-count 却零启动锚点 = 采集器没开、
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
"$PY" "$TW" new N21 dhara-isfree --needs-bench # 把新会话工作目录切到打印出来的 _wt/N21-dhara-isfree/
|
"$PY" "$TW" new N21 dhara-isfree --needs-bench # 把新会话工作目录切到打印出来的 _wt/N21-dhara-isfree/
|
||||||
"$PY" "$TW" lock acquire bench --task N21 # 烧录 / @test / 板级套件
|
"$PY" "$TW" lock acquire bench --task N21 # 烧录 / /serial-boot-test / 板级套件
|
||||||
"$PY" "$TW" lock release bench --task N21 --bench-pass
|
"$PY" "$TW" lock release bench --task N21 --bench-pass
|
||||||
"$PY" "$TW" done N21 # 回主工作树(铁律 1);先 --dry-run 读全量
|
"$PY" "$TW" done N21 # 回主工作树(铁律 1);先 --dry-run 读全量
|
||||||
```
|
```
|
||||||
@@ -110,7 +110,7 @@ git add <上面刷出来的基线/快照文件> # 显式列文件,别 -A
|
|||||||
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md 边界)
|
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md 边界)
|
||||||
```
|
```
|
||||||
|
|
||||||
为什么必然要补 → 陷阱「`done` 的门禁跑在 `@build` 之前」。合并解过冲突时,**合并提交期 pre-commit 只查冲突文件**(→ G 节第 3 条)→ 落主干后必须另跑全量。
|
为什么必然要补 → 陷阱「`done` 的门禁跑在 `/keil-build` 之前」。合并解过冲突时,**合并提交期 pre-commit 只查冲突文件**(→ G 节第 3 条)→ 落主干后必须另跑全量。
|
||||||
**注释/文档-only 但碰了 .c/.h 的短链**:先比 `Program Size` 与合并前是否逐项相同——相同就只 `ram_baseline.py --update` 重写体积凭证(`scripts/size_attestation.json` 跟着补提交笔走),**别 `--write` 动受管区**(等于给在飞分支造冲突面);数字变了才走上面全链。**已命令化**:`"$PY" "$TW" postmerge`——新鲜直接跳过;红则先短链,再用只读 `check_mem_numbers` 复检裁定是否升全链;末尾跑 `--all` 并列出待提交文件(不代提交)。上面的手工全链仍是权威兜底。
|
**注释/文档-only 但碰了 .c/.h 的短链**:先比 `Program Size` 与合并前是否逐项相同——相同就只 `ram_baseline.py --update` 重写体积凭证(`scripts/size_attestation.json` 跟着补提交笔走),**别 `--write` 动受管区**(等于给在飞分支造冲突面);数字变了才走上面全链。**已命令化**:`"$PY" "$TW" postmerge`——新鲜直接跳过;红则先短链,再用只读 `check_mem_numbers` 复检裁定是否升全链;末尾跑 `--all` 并列出待提交文件(不代提交)。上面的手工全链仍是权威兜底。
|
||||||
6. **重跑 `done` 前主树必须干净**,否则 `失败:主工作树不干净(…)——合并会把未提交改动卷进来` → 第 5 步产物先提交。分支已并时重跑打印 `[i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场`——**该路径在门禁之前就返回,不会替你复绿**:上一次门禁没跑到的尾巴(主树 `ci_local.py` + `check_doc_links.py --all`;体积/文档那半可 `postmerge` 一把跑)必须自己补。主树脏且那份脏不是你的 → **C2**。
|
6. **重跑 `done` 前主树必须干净**,否则 `失败:主工作树不干净(…)——合并会把未提交改动卷进来` → 第 5 步产物先提交。分支已并时重跑打印 `[i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场`——**该路径在门禁之前就返回,不会替你复绿**:上一次门禁没跑到的尾巴(主树 `ci_local.py` + `check_doc_links.py --all`;体积/文档那半可 `postmerge` 一把跑)必须自己补。主树脏且那份脏不是你的 → **C2**。
|
||||||
7. 清场后再敲一次 `done <ID>` 得 `前置/环境错误:任务 <ID> 不存在(缺台账 …json)`(exit 2)——**不是失败**:清场最后一步 unlink 了台账。**判完成以 `git log` 为准**。
|
7. 清场后再敲一次 `done <ID>` 得 `前置/环境错误:任务 <ID> 不存在(缺台账 …json)`(exit 2)——**不是失败**:清场最后一步 unlink 了台账。**判完成以 `git log` 为准**。
|
||||||
@@ -153,7 +153,7 @@ git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md
|
|||||||
|
|
||||||
1. **`done` 只能在主工作树执行**——主干分支检出在主树,git 不允许同一分支两处检出。
|
1. **`done` 只能在主工作树执行**——主干分支检出在主树,git 不允许同一分支两处检出。
|
||||||
2. **`done` 不 push**:它结束于本地合入 + 门禁全绿;推送需用户明确许可。
|
2. **`done` 不 push**:它结束于本地合入 + 门禁全绿;推送需用户明确许可。
|
||||||
3. **`build` 与 `bench` 都必须串行**(UV4 不能两个实例同时 `-b`;COM6、ST-LINK、设备 IP `192.168.1.100` 各一份)。`build` 锁由 **@build 自持自释**(`keil_build.py`)→ 调用方**不要**手工 acquire/release;`bench` 锁**必须手工** acquire → 烧录 / `@test` → release `--bench-pass`。非登记持有者怎么放 → B 节;插件树 → F 节。
|
3. **`build` 与 `bench` 都必须串行**(UV4 不能两个实例同时 `-b`;COM6、ST-LINK、设备 IP `192.168.1.100` 各一份)。`build` 锁由 **/keil-build 自持自释**(`keil_build.py`)→ 调用方**不要**手工 acquire/release;`bench` 锁**必须手工** acquire → 烧录 / `/serial-boot-test` → release `--bench-pass`。非登记持有者怎么放 → B 节;插件树 → F 节。
|
||||||
4. **单写者文档**:`docs/任务规划/`(整个文件夹)、`AGENTS.md` 任务表块、`MDK-ARM/*.uvprojx`、`.pre-commit-config.yaml`、内存快照 —— 任务期**不在任务树里改**,留到 `done` 统一处理;用户显式豁免时(在任务树写、由 done 合回)要在交付报告里点名这条例外。
|
4. **单写者文档**:`docs/任务规划/`(整个文件夹)、`AGENTS.md` 任务表块、`MDK-ARM/*.uvprojx`、`.pre-commit-config.yaml`、内存快照 —— 任务期**不在任务树里改**,留到 `done` 统一处理;用户显式豁免时(在任务树写、由 done 合回)要在交付报告里点名这条例外。
|
||||||
5. **清场必须先摘联接**:脚本先 `os.rmdir` 掉 `.venv` / `simulator/SDL2` 联接再 `git worktree remove`。手工拆树前必跑 **E 第 3 步**预扫拿到 `REPARSE: none`——否则 git 可能递归进联接把主树 `.venv` 一起删掉。
|
5. **清场必须先摘联接**:脚本先 `os.rmdir` 掉 `.venv` / `simulator/SDL2` 联接再 `git worktree remove`。手工拆树前必跑 **E 第 3 步**预扫拿到 `REPARSE: none`——否则 git 可能递归进联接把主树 `.venv` 一起删掉。
|
||||||
6. **活跃任务硬上限 8 = 脚本常量 `MAX_CONCURRENT`(`task_worktree.py:30`)——唯一事实源**:报错文案与 `new` 的插件树提示都插值它,AGENTS.md / 方案文档 / frontmatter / A 节的数字全是引用同步。改这个数字 → 全形态扫描走 `stale-doc-count-sweep` skill + 隔离小仓验证上限真生效;每棵磁盘代价按实测口径重述、别照抄旧数(细节 → 验证清单末条)。满了 exit 2 → A;悬挂台账 → E 第 6 步。
|
6. **活跃任务硬上限 8 = 脚本常量 `MAX_CONCURRENT`(`task_worktree.py:30`)——唯一事实源**:报错文案与 `new` 的插件树提示都插值它,AGENTS.md / 方案文档 / frontmatter / A 节的数字全是引用同步。改这个数字 → 全形态扫描走 `stale-doc-count-sweep` skill + 隔离小仓验证上限真生效;每棵磁盘代价按实测口径重述、别照抄旧数(细节 → 验证清单末条)。满了 exit 2 → A;悬挂台账 → E 第 6 步。
|
||||||
@@ -163,11 +163,11 @@ git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md
|
|||||||
|
|
||||||
## 陷阱与验证(收工前各扫一遍)
|
## 陷阱与验证(收工前各扫一遍)
|
||||||
|
|
||||||
- [reference/pitfalls.md](reference/pitfalls.md):全部实测坑逐条(含 `--dry-run` 不保护清场段、门禁跑在 `@build` 之前、未跟踪文件在 `git log` 与合并里都不留痕、并集合并残留、新树从未构建 → 体积类门禁必然红、注释-only 也刷红体积凭证、「已合入」快路径在门禁前返回不替你复绿、`--force` 只覆盖四类检查、联接不是 symlink(`core.symlinks=false`)、锁不绑进程存活、文件工具先读后改按绝对路径记账、中文门禁回显乱码但 rc 可信)。收工前扫一眼。
|
- [reference/pitfalls.md](reference/pitfalls.md):全部实测坑逐条(含 `--dry-run` 不保护清场段、门禁跑在 `/keil-build` 之前、未跟踪文件在 `git log` 与合并里都不留痕、并集合并残留、新树从未构建 → 体积类门禁必然红、注释-only 也刷红体积凭证、「已合入」快路径在门禁前返回不替你复绿、`--force` 只覆盖四类检查、联接不是 symlink(`core.symlinks=false`)、锁不绑进程存活、文件工具先读后改按绝对路径记账、中文门禁回显乱码但 rc 可信)。收工前扫一眼。
|
||||||
- [reference/verification.md](reference/verification.md):每节要留的证据(预演 sha + `MT_RC=0`、blob id 对账、两把锁空闲、禁词计数 0、`REPARSE: none`、改上限数字后的全形态扫描)。
|
- [reference/verification.md](reference/verification.md):每节要留的证据(预演 sha + `MT_RC=0`、blob id 对账、两把锁空闲、禁词计数 0、`REPARSE: none`、改上限数字后的全形态扫描)。
|
||||||
|
|
||||||
## 与其它 skill 的衔接
|
## 与其它 skill 的衔接
|
||||||
|
|
||||||
- 构建 = `keil-build`(`@build`)· 烧录/回读 = `stlink-flash` · 串口闭环 = `serial-boot-test`(真实 hex 路径以 `.serialtest/config.json` 的 hex 键为准——实测 `MDK-ARM/Objects/STM32F407-Demo.hex` 不存在,拷错 = 烧旧镜像)
|
- 构建 = `keil-build`· 烧录/回读 = `stlink-flash` · 串口闭环 = `serial-boot-test`(真实 hex 路径以 `.serialtest/config.json` 的 hex 键为准——实测 `MDK-ARM/Objects/STM32F407-Demo.hex` 不存在,拷错 = 烧旧镜像)
|
||||||
- 新 `.c` 双端注册 = `new-source-file` · RAM/.map 台账 = `mem-budget` · UI 回归 = `ui-verify` · 计数同步 = `stale-doc-count-sweep` · 卡死的技能台账行 = `mnemon-skill-ledger-stuck-repair`
|
- 新 `.c` 双端注册 = `new-source-file` · RAM/.map 台账 = `mem-budget` · UI 回归 = `ui-verify` · 计数同步 = `stale-doc-count-sweep` · 卡死的技能台账行 = `mnemon-skill-ledger-stuck-repair`
|
||||||
- DSH worktree 插件(`/worktree` / `worktree_create`,仓内 `.dsh-worktrees/`)与本 skill 的分工、桥接命令、锁归属 → F 节 · 完整机制与目录分配 → `docs/环境与部署/并行任务工作树方案.md`
|
- DSH worktree 插件(`/worktree` / `worktree_create`,仓内 `.dsh-worktrees/`)与本 skill 的分工、桥接命令、锁归属 → F 节 · 完整机制与目录分配 → `docs/环境与部署/并行任务工作树方案.md`
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
它按缺的那一样分别给后果,`adopt` 没补齐也只报「还不能算就绪」,不许说「现在可以跑」。
|
它按缺的那一样分别给后果,`adopt` 没补齐也只报「还不能算就绪」,不许说「现在可以跑」。
|
||||||
- **adopt 只碰联接**:已联的跳过;`simulator/SDL2` 若已是实体目录只告警不动;`.dsh-worktrees/` 之外的路径
|
- **adopt 只碰联接**:已联的跳过;`simulator/SDL2` 若已是实体目录只告警不动;`.dsh-worktrees/` 之外的路径
|
||||||
一律 exit 2(实测 `plugin adopt ".."` 被拒)——这道闸防的是把主树或 `_wt` 目录当插件树遮蔽掉真实内容。
|
一律 exit 2(实测 `plugin adopt ".."` 被拒)——这道闸防的是把主树或 `_wt` 目录当插件树遮蔽掉真实内容。
|
||||||
- **锁归属现在认得出来**:`@build` 在插件树里自持自释时,持有者名从 cwd 反推成 `pw:<name>`(多段名压平成
|
- **锁归属现在认得出来**:`/keil-build` 在插件树里自持自释时,持有者名从 cwd 反推成 `pw:<name>`(多段名压平成
|
||||||
`-`),与 `plugin adopt` 打印的那个名字逐字相同(`keil_build.py: plugin_tree_owner()` ↔
|
`-`),与 `plugin adopt` 打印的那个名字逐字相同(`keil_build.py: plugin_tree_owner()` ↔
|
||||||
`task_worktree.py: plugin_lock_owner()`)→ `lock status` 不再一律落成 `build-cli`。
|
`task_worktree.py: plugin_lock_owner()`)→ `lock status` 不再一律落成 `build-cli`。
|
||||||
- 上台架仍须手工占锁、用同一个名:`lock acquire bench --task pw:<name> --note <说明>` →
|
- 上台架仍须手工占锁、用同一个名:`lock acquire bench --task pw:<name> --note <说明>` →
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
subprocess,含空格的路径(本项目 `DTU Code`)由 subprocess 自动加引号。
|
subprocess,含空格的路径(本项目 `DTU Code`)由 subprocess 自动加引号。
|
||||||
但**读属性**用 ctypes 没问题:`GetFileAttributesW` + `FILE_ATTRIBUTE_REPARSE_POINT` 就是 E 第 3 步的预扫手段。
|
但**读属性**用 ctypes 没问题:`GetFileAttributesW` + `FILE_ATTRIBUTE_REPARSE_POINT` 就是 E 第 3 步的预扫手段。
|
||||||
- **联接建失败不中止 `new`**:只告警并打印手工补建命令,避免留下半成品任务空间;
|
- **联接建失败不中止 `new`**:只告警并打印手工补建命令,避免留下半成品任务空间;
|
||||||
但该任务里跑不了 `.venv\Scripts\python.exe`,`@build` / `ci_local.py` 会找不到解释器。
|
但该任务里跑不了 `.venv\Scripts\python.exe`,`/keil-build` / `ci_local.py` 会找不到解释器。
|
||||||
- **`done` 前主工作树必须干净**:否则未提交改动会被卷进合并(补完基线再重跑 `done` 时最容易忘,见 C 第 6 步)。
|
- **`done` 前主工作树必须干净**:否则未提交改动会被卷进合并(补完基线再重跑 `done` 时最容易忘,见 C 第 6 步)。
|
||||||
- **⚠ 任务树里 `check_doc_links.py --all` 的红多半是 `simulator/SDL2` 目录联接的假红**:全库扫描会吞下那份
|
- **⚠ 任务树里 `check_doc_links.py --all` 的红多半是 `simulator/SDL2` 目录联接的假红**:全库扫描会吞下那份
|
||||||
「仓库里的第二份仓库副本」(实测任务树 222 个文档 / 35 处问题全在联接下、RC=1,而主树同命令基线全绿)。
|
「仓库里的第二份仓库副本」(实测任务树 222 个文档 / 35 处问题全在联接下、RC=1,而主树同命令基线全绿)。
|
||||||
@@ -39,11 +39,11 @@
|
|||||||
`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):**不许用 stash / 提交 / 删除去清场**(项目级 Never,
|
`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):**不许用 stash / 提交 / 删除去清场**(项目级 Never,
|
||||||
你也无从判断它写没写完)→ 走 **C2**:取证(mtime + diff + `hash-object` 存进 ci-artifacts)→ `merge-tree` 预演
|
你也无从判断它写没写完)→ 走 **C2**:取证(mtime + diff + `hash-object` 存进 ci-artifacts)→ `merge-tree` 预演
|
||||||
→ 主树 `merge --no-ff -F` → 合并前后 blob id 与 `diff --stat` 对账。**「它看着像废弃的」不是丢弃判据**。
|
→ 主树 `merge --no-ff -F` → 合并前后 blob id 与 `diff --stat` 对账。**「它看着像废弃的」不是丢弃判据**。
|
||||||
- **⚠ `done` 的门禁跑在 `@build` 之前 → 依赖 `.map` 的钩子会误报**(2026-10-12 实测):
|
- **⚠ `done` 的门禁跑在 `/keil-build` 之前 → 依赖 `.map` 的钩子会误报**(2026-10-12 实测):
|
||||||
`done` 的顺序是「合并 → 重生成 → **门禁(ci_local)** → `@build`」,而
|
`done` 的顺序是「合并 → 重生成 → **门禁(ci_local)** → `/keil-build`」,而
|
||||||
`mem-budget`(登记簿条目「缩堆迁 CCM 后半(暂缓)」)/ `mem-numbers` 是拿**文档快照 vs `.map`** 比对 —— 门禁跑时
|
`mem-budget`(登记簿条目「缩堆迁 CCM 后半(暂缓)」)/ `mem-numbers` 是拿**文档快照 vs `.map`** 比对 —— 门禁跑时
|
||||||
`.map` 还是**主工作树合并前**的旧产物,与刚合并进来的新台账**必然不一致** →
|
`.map` 还是**主工作树合并前**的旧产物,与刚合并进来的新台账**必然不一致** →
|
||||||
误报 `[FAIL] 台账受管区与 .map 不一致`。`@build` 跑完后 `.map` 才是新的。
|
误报 `[FAIL] 台账受管区与 .map 不一致`。`/keil-build` 跑完后 `.map` 才是新的。
|
||||||
**处置(2026-10-25 补全)**:按 C 节第 5 步的完整链补——`check_mem_numbers.py --write` →
|
**处置(2026-10-25 补全)**:按 C 节第 5 步的完整链补——`check_mem_numbers.py --write` →
|
||||||
`ram_baseline.py --write` → `ram_baseline.py --update` → `check_test_coverage.py --update-baseline` →
|
`ram_baseline.py --write` → `ram_baseline.py --update` → `check_test_coverage.py --update-baseline` →
|
||||||
`check_doc_links.py --all` → 提交 → 重跑 `done`(它会检测到「已合入」直接清场)。
|
`check_doc_links.py --all` → 提交 → 重跑 `done`(它会检测到「已合入」直接清场)。
|
||||||
@@ -57,15 +57,15 @@
|
|||||||
- **⚠ 新建工作树从未构建过 → 体积类门禁必然红**(2026-10-25 实测):`.map` 是每棵树各自的
|
- **⚠ 新建工作树从未构建过 → 体积类门禁必然红**(2026-10-25 实测):`.map` 是每棵树各自的
|
||||||
构建产物、不入版本库,所以 `new` 出来的空间里 `mem-numbers` / `mem-budget` / `ci_local.py`
|
构建产物、不入版本库,所以 `new` 出来的空间里 `mem-numbers` / `mem-budget` / `ci_local.py`
|
||||||
的体积门禁步都拿不到事实源,报 `[FAIL] 找不到 .map 事实源`(脚本提示已写明原因,不是代码缺陷)。
|
的体积门禁步都拿不到事实源,报 `[FAIL] 找不到 .map 事实源`(脚本提示已写明原因,不是代码缺陷)。
|
||||||
同理 `done` 的重生成步骤会 `[skip] 未找到 .map,跳过内存快照同步(需先 @build)`。
|
同理 `done` 的重生成步骤会 `[skip] 未找到 .map,跳过内存快照同步(需先 /keil-build)`。
|
||||||
**处置**:新空间第一次提交前先 `@build` 一次(`build` 锁自动排队,与别的树串行)。与上一条
|
**处置**:新空间第一次提交前先 `/keil-build` 一次(`build` 锁自动排队,与别的树串行)。与上一条
|
||||||
的区别要分清:上一条是「有 `.map` 但是旧的」,本条是「根本没有 `.map`」,处置动作不同。
|
的区别要分清:上一条是「有 `.map` 但是旧的」,本条是「根本没有 `.map`」,处置动作不同。
|
||||||
- **⚠ 只改注释里的文档路径,也会把「体积同码凭证」刷红**(2026-10-30 实测,`done testdoc`):
|
- **⚠ 只改注释里的文档路径,也会把「体积同码凭证」刷红**(2026-10-30 实测,`done testdoc`):
|
||||||
`scripts/check_size_gate.py` 的判据是**索引 blob 指纹**(实测 1466 个文件),**不是体积数字**——
|
`scripts/check_size_gate.py` 的判据是**索引 blob 指纹**(实测 1466 个文件),**不是体积数字**——
|
||||||
那笔全库路径同步顺带改了 20 个 `.c/.h` 的注释,`Program Size` 逐项没变
|
那笔全库路径同步顺带改了 20 个 `.c/.h` 的注释,`Program Size` 逐项没变
|
||||||
(Code=246160 / RO-data=29816 / RW-data=2036 / ZI-data=149700),凭证照旧红:
|
(Code=246160 / RO-data=29816 / RW-data=2036 / ZI-data=149700),凭证照旧红:
|
||||||
`本次代码(指纹 172c6c78b8fb…,1466 文件)与体积测量(指纹 1ed4267c77f6…,2026-10-30T01:15:01)不同码`。
|
`本次代码(指纹 172c6c78b8fb…,1466 文件)与体积测量(指纹 1ed4267c77f6…,2026-10-30T01:15:01)不同码`。
|
||||||
**处置比上一条那条补绿链短**:合并后 `done` 自己那次 `@build` 已 0 错 0 警,此时只需
|
**处置比上一条那条补绿链短**:合并后 `done` 自己那次 `/keil-build` 已 0 错 0 警,此时只需
|
||||||
`ram_baseline.py --update` 重写凭证(改的是 `scripts/size_attestation.json`;同日同数字时
|
`ram_baseline.py --update` 重写凭证(改的是 `scripts/size_attestation.json`;同日同数字时
|
||||||
`ram_baseline.tsv` 连趋势行都不会新增),**别顺手 `--write` 去动内存受管快照**——数字没变就改受管区,
|
`ram_baseline.tsv` 连趋势行都不会新增),**别顺手 `--write` 去动内存受管快照**——数字没变就改受管区,
|
||||||
等于给别的在飞分支造冲突面(台账归属口径见 `mem-budget` skill)。
|
等于给别的在飞分支造冲突面(台账归属口径见 `mem-budget` skill)。
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
落主干后的全量证据得自己跑:`scripts/ci_local.py`(实测 47 runner / 3896 断言全绿)
|
落主干后的全量证据得自己跑:`scripts/ci_local.py`(实测 47 runner / 3896 断言全绿)
|
||||||
+ 主树 `check_doc_links.py --all`(实测 215 个文档 / 2993 处章节引用 rc=0)。
|
+ 主树 `check_doc_links.py --all`(实测 215 个文档 / 2993 处章节引用 rc=0)。
|
||||||
- **⚠ 增量构建可能不重链 → `.map` 数字瞬态不一致**(2026-10-12 实测):
|
- **⚠ 增量构建可能不重链 → `.map` 数字瞬态不一致**(2026-10-12 实测):
|
||||||
`@build` 若判定无需重链,`.map` 保持旧值,此时 `--write` 生成的快照是**瞬态值**
|
`/keil-build` 若判定无需重链,`.map` 保持旧值,此时 `--write` 生成的快照是**瞬态值**
|
||||||
(实测同一份源码两次读到 223,560 与 223,724,差 164 B)。**以主工作树(集成点)
|
(实测同一份源码两次读到 223,560 与 223,724,差 164 B)。**以主工作树(集成点)
|
||||||
的 `.map` 为准**;改完 RAM 相关代码后确认重链再刷新快照。
|
的 `.map` 为准**;改完 RAM 相关代码后确认重链再刷新快照。
|
||||||
- **`ram_baseline.py` 的 help 串里有未转义的 `%`**(2026-10-25 实测:某些直调姿势会渲染 argparse help 并崩)——
|
- **`ram_baseline.py` 的 help 串里有未转义的 `%`**(2026-10-25 实测:某些直调姿势会渲染 argparse help 并崩)——
|
||||||
@@ -120,15 +120,15 @@
|
|||||||
同任务重复 acquire 是幂等的(不会把自己锁死)。
|
同任务重复 acquire 是幂等的(不会把自己锁死)。
|
||||||
- **TTL 按锁分设**(2026-10-12):`build` = **1h**、`bench` = **6h**(`LOCK_TTL_HOURS`)。
|
- **TTL 按锁分设**(2026-10-12):`build` = **1h**、`bench` = **6h**(`LOCK_TTL_HOURS`)。
|
||||||
口径是「兜底窗口应与操作真实时长同量级」——编译是秒~分钟级,6h 的兜底太钝;
|
口径是「兜底窗口应与操作真实时长同量级」——编译是秒~分钟级,6h 的兜底太钝;
|
||||||
bench 是会话级(flash→`@test`→探针),才配得上小时级。持有者还可用
|
bench 是会话级(flash→`/serial-boot-test`→探针),才配得上小时级。持有者还可用
|
||||||
`lock acquire ... --ttl <小时>` **自报**更精确的窗口(`keil_build.py` 即按其 `--timeout` 自报)。
|
`lock acquire ... --ttl <小时>` **自报**更精确的窗口(`keil_build.py` 即按其 `--timeout` 自报)。
|
||||||
- **陈旧锁自动接管**:超过该锁 TTL 未释放即视为陈旧,下次 `acquire` 会接管并提示;
|
- **陈旧锁自动接管**:超过该锁 TTL 未释放即视为陈旧,下次 `acquire` 会接管并提示;
|
||||||
未到 TTL 的活锁需 `--force`。`done` 的收口前置也会**自动释放陈旧锁**(不再硬失败),但
|
未到 TTL 的活锁需 `--force`。`done` 的收口前置也会**自动释放陈旧锁**(不再硬失败),但
|
||||||
**未到 TTL 的活锁会把 `done` 挡在前置检查**(B 节第 8 步)。
|
**未到 TTL 的活锁会把 `done` 挡在前置检查**(B 节第 8 步)。
|
||||||
- **`build` 锁不必手工持**(2026-10-12 起):`@build`(`keil_build.py`)**自持自释**——
|
- **`build` 锁不必手工持**(2026-10-12 起):`/keil-build`(`keil_build.py`)**自持自释**——
|
||||||
进入 acquire、`finally` 无条件 release,所以"Agent 忘了 release、锁一直挂到 TTL"这条在构建路径上已消除。
|
进入 acquire、`finally` 无条件 release,所以"Agent 忘了 release、锁一直挂到 TTL"这条在构建路径上已消除。
|
||||||
`done` 的门禁自己持锁并给子进程设 `DTU_LOCK_HELD=build` 告知跳过(新旧脚本都兼容)。
|
`done` 的门禁自己持锁并给子进程设 `DTU_LOCK_HELD=build` 告知跳过(新旧脚本都兼容)。
|
||||||
**`bench` 锁仍须 Agent 手工持**(会话作用域:跨烧录→`@test`→探针,见 `keil-build` SKILL.md)。
|
**`bench` 锁仍须 Agent 手工持**(会话作用域:跨烧录→`/serial-boot-test`→探针,见 `keil-build` SKILL.md)。
|
||||||
- **⚠ `done --dry-run` 只保护预演段、不保护清场段**(2026-10-29 实测):`done <ID> --dry-run --skip-build --skip-gates` 在「分支已合入主干」这条路上**照样执行清场**——摘 `.venv` / `simulator/SDL2` 联接、删分支、清台账,然后打「完成(分支此前已合入)」。想真·零改动就只跑不带 `--skip-gates` 的 `--dry-run` 并停在预演输出;用完一律 `git worktree list` 复验(判完成以 `git log` 为准,不是以有没有再敲一次 `done` 为准)。另:`--dry-run` 的前置检查同样要抢 `build` 锁——被别的任务占着时它会直接报「独占资源 build 仍被 X 占用,先 release」,此时加 `--skip-build` 或先 release,**别为了预演去抢别人的锁**。
|
- **⚠ `done --dry-run` 只保护预演段、不保护清场段**(2026-10-29 实测):`done <ID> --dry-run --skip-build --skip-gates` 在「分支已合入主干」这条路上**照样执行清场**——摘 `.venv` / `simulator/SDL2` 联接、删分支、清台账,然后打「完成(分支此前已合入)」。想真·零改动就只跑不带 `--skip-gates` 的 `--dry-run` 并停在预演输出;用完一律 `git worktree list` 复验(判完成以 `git log` 为准,不是以有没有再敲一次 `done` 为准)。另:`--dry-run` 的前置检查同样要抢 `build` 锁——被别的任务占着时它会直接报「独占资源 build 仍被 X 占用,先 release」,此时加 `--skip-build` 或先 release,**别为了预演去抢别人的锁**。
|
||||||
- **⚠ 文件工具「先读后改」按解析后的绝对路径记账,切树后要重新读**(2026-10-30 实测,屏板链路任务树):开新树后先用**相对路径**读过一批文件(读历史记到当时解析到的主树文件上),再进任务树直接 `edit` → 6 条并行编辑全被 `file has not been read — read the file, then retry` 拒。判据:**树内改动一律用树内绝对路径,先 read 再改**——读历史不跨路径复用,工具不做「同名相对路径」宽容。
|
- **⚠ 文件工具「先读后改」按解析后的绝对路径记账,切树后要重新读**(2026-10-30 实测,屏板链路任务树):开新树后先用**相对路径**读过一批文件(读历史记到当时解析到的主树文件上),再进任务树直接 `edit` → 6 条并行编辑全被 `file has not been read — read the file, then retry` 拒。判据:**树内改动一律用树内绝对路径,先 read 再改**——读历史不跨路径复用,工具不做「同名相对路径」宽容。
|
||||||
- **⚠ 中文门禁输出回显乱码 ≠ 路径坏**(2026-10-30 实测):对含中文文件名的文档跑 `check_doc_links.py`,终端回显整段乱码,但 **rc 与判定计数全部正确**。只认 `[SUMMARY]` 行与退出码,别误诊成「文档丢失」;脚本化批量调用走 python subprocess + 显式 `encoding`,别经命令行中转中文名。
|
- **⚠ 中文门禁输出回显乱码 ≠ 路径坏**(2026-10-30 实测):对含中文文件名的文档跑 `check_doc_links.py`,终端回显整段乱码,但 **rc 与判定计数全部正确**。只认 `[SUMMARY]` 行与退出码,别误诊成「文档丢失」;脚本化批量调用走 python subprocess + 显式 `encoding`,别经命令行中转中文名。
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ LOCK_NAMES = ("build", "bench")
|
|||||||
# 按锁分设陈旧窗口(小时)——兜底窗口应与「操作真实时长」同量级:
|
# 按锁分设陈旧窗口(小时)——兜底窗口应与「操作真实时长」同量级:
|
||||||
# build:编译是秒~分钟级(keil_build.py 自持自释,持有窗口 ≈ UV4 运行时长,
|
# build:编译是秒~分钟级(keil_build.py 自持自释,持有窗口 ≈ UV4 运行时长,
|
||||||
# 上限 = --timeout,默认 600s);崩溃泄漏时应在数十分钟内即可接管 → 1h。
|
# 上限 = --timeout,默认 600s);崩溃泄漏时应在数十分钟内即可接管 → 1h。
|
||||||
# bench:会话级(flash → @test → 探针,天然几十分钟~数小时)→ 保留 6h。
|
# bench:会话级(flash → /serial-boot-test → 探针,天然几十分钟~数小时)→ 保留 6h。
|
||||||
# 另:锁 payload 里持有者可用 `ttl` 字段自报窗口(见 lock_acquire(ttl=...)),
|
# 另:锁 payload 里持有者可用 `ttl` 字段自报窗口(见 lock_acquire(ttl=...)),
|
||||||
# 优先于这里的默认值——这样 build 锁的窗口能自动跟随其 --timeout。
|
# 优先于这里的默认值——这样 build 锁的窗口能自动跟随其 --timeout。
|
||||||
LOCK_TTL_HOURS = {"build": 1.0, "bench": 6.0}
|
LOCK_TTL_HOURS = {"build": 1.0, "bench": 6.0}
|
||||||
@@ -404,7 +404,7 @@ def bootstrap(wt: Path):
|
|||||||
# 插件建的树(.dsh-worktrees/worktree/<name>)缺三样东西,下面的命令逐条补。
|
# 插件建的树(.dsh-worktrees/worktree/<name>)缺三样东西,下面的命令逐条补。
|
||||||
# 回收**不在这里**:插件树由「/worktree remove|prune」收,done/abort 也不该拿去处理它。
|
# 回收**不在这里**:插件树由「/worktree remove|prune」收,done/abort 也不该拿去处理它。
|
||||||
# 1. 共享联接——没有 .venv 与 simulator 的 SDL2 SDK,插件树里「.venv\Scripts\python.exe」
|
# 1. 共享联接——没有 .venv 与 simulator 的 SDL2 SDK,插件树里「.venv\Scripts\python.exe」
|
||||||
# 直接找不到解释器:@build、ci_local.py、所有 scripts/check_*.py 全部起跑失败;
|
# 直接找不到解释器:/keil-build、ci_local.py、所有 scripts/check_*.py 全部起跑失败;
|
||||||
# 2. 可见性——插件树不在台账里,「list」看不见它,8 个活跃名额也只数登记任务;
|
# 2. 可见性——插件树不在台账里,「list」看不见它,8 个活跃名额也只数登记任务;
|
||||||
# 3. 独占资源归属——build 锁由 keil_build.py 自持自释(按 cwd 反推持有者名,
|
# 3. 独占资源归属——build 锁由 keil_build.py 自持自释(按 cwd 反推持有者名,
|
||||||
# 插件树里算成 pw:<name>),bench 锁仍要人占,占锁名得和「lock status」对得上。
|
# 插件树里算成 pw:<name>),bench 锁仍要人占,占锁名得和「lock status」对得上。
|
||||||
@@ -519,7 +519,7 @@ def print_plugin_trees() -> int:
|
|||||||
def miss(rel):
|
def miss(rel):
|
||||||
return any(t["jn"].get(rel) == "absent" for t in trees)
|
return any(t["jn"].get(rel) == "absent" for t in trees)
|
||||||
if miss(".venv"):
|
if miss(".venv"):
|
||||||
print(" 缺 .venv:进去跑 @build / ci_local.py 会**静默**落到机器级解释器(无 "
|
print(" 缺 .venv:进去跑 /keil-build / ci_local.py 会**静默**落到机器级解释器(无 "
|
||||||
"fontTools,generated-fresh 假红的老根因)")
|
"fontTools,generated-fresh 假红的老根因)")
|
||||||
if miss("simulator/SDL2"):
|
if miss("simulator/SDL2"):
|
||||||
print(" 缺 simulator/SDL2:模拟器 build_sim.bat 直接报错——CMakeLists.txt 的 "
|
print(" 缺 simulator/SDL2:模拟器 build_sim.bat 直接报错——CMakeLists.txt 的 "
|
||||||
@@ -562,7 +562,7 @@ def cmd_plugin(a) -> int:
|
|||||||
print("({} 下没有插件工作树)".format(PLUGIN_SUBDIR))
|
print("({} 下没有插件工作树)".format(PLUGIN_SUBDIR))
|
||||||
print(" 建法:新会话输入框选「Worktree模式」/ 让模型调 worktree_create / "
|
print(" 建法:新会话输入框选「Worktree模式」/ 让模型调 worktree_create / "
|
||||||
"/worktree create <name>")
|
"/worktree create <name>")
|
||||||
print(" 建完回来「plugin adopt <name>」补共享联接,才能在里面跑 @build 与门禁。")
|
print(" 建完回来「plugin adopt <name>」补共享联接,才能在里面跑 /keil-build 与门禁。")
|
||||||
return EXIT_OK
|
return EXIT_OK
|
||||||
|
|
||||||
wt = plugin_dir_for(a.name)
|
wt = plugin_dir_for(a.name)
|
||||||
@@ -584,7 +584,7 @@ def cmd_plugin(a) -> int:
|
|||||||
print("[warn] 仍缺 {}:主线里该路径不存在或联接失败——先补主线再复跑 adopt"
|
print("[warn] 仍缺 {}:主线里该路径不存在或联接失败——先补主线再复跑 adopt"
|
||||||
"(这棵树还不能算就绪)".format("、".join(still)))
|
"(这棵树还不能算就绪)".format("、".join(still)))
|
||||||
else:
|
else:
|
||||||
print("该目录现在可以跑 @build / ci_local.py / @test")
|
print("该目录现在可以跑 /keil-build / ci_local.py / /serial-boot-test")
|
||||||
print("build 锁由 keil_build.py 自持自释,持有者名就是 {}".format(owner))
|
print("build 锁由 keil_build.py 自持自释,持有者名就是 {}".format(owner))
|
||||||
print("上台架前:$TW lock acquire bench --task {} --note <说明>".format(owner))
|
print("上台架前:$TW lock acquire bench --task {} --note <说明>".format(owner))
|
||||||
print("下台架后:$TW lock release bench --task {}".format(owner))
|
print("下台架后:$TW lock release bench --task {}".format(owner))
|
||||||
@@ -606,25 +606,25 @@ def regen(cwd: Path) -> None:
|
|||||||
if list((Path(cwd) / "MDK-ARM").glob("**/*.map")):
|
if list((Path(cwd) / "MDK-ARM").glob("**/*.map")):
|
||||||
run_script("scripts/check_mem_numbers.py", ["--write"], cwd, "同步内存/Flash 快照")
|
run_script("scripts/check_mem_numbers.py", ["--write"], cwd, "同步内存/Flash 快照")
|
||||||
else:
|
else:
|
||||||
print("[skip] 未找到 .map,跳过内存快照同步(需先 @build)")
|
print("[skip] 未找到 .map,跳过内存快照同步(需先 /keil-build)")
|
||||||
|
|
||||||
|
|
||||||
def run_gates(cwd: Path, skip_build: bool) -> bool:
|
def run_gates(cwd: Path, skip_build: bool) -> bool:
|
||||||
ok = run_script("scripts/ci_local.py", [], cwd, "静态门禁 + 主机侧纯逻辑单测 本地等价 CI")
|
ok = run_script("scripts/ci_local.py", [], cwd, "静态门禁 + 主机侧纯逻辑单测 本地等价 CI")
|
||||||
if skip_build:
|
if skip_build:
|
||||||
print("[skip] 按 --skip-build 跳过 @build")
|
print("[skip] 按 --skip-build 跳过 /keil-build")
|
||||||
return ok
|
return ok
|
||||||
got, info = lock_acquire("build", "__done__", wait=900)
|
got, info = lock_acquire("build", "__done__", wait=900)
|
||||||
if not got:
|
if not got:
|
||||||
print("!! build 锁被 {} 占用(UV4 不能并发构建),跳过 @build".format(info.get("task")))
|
print("!! build 锁被 {} 占用(UV4 不能并发构建),跳过 /keil-build".format(info.get("task")))
|
||||||
return False
|
return False
|
||||||
# 告知 @build「调用方已持锁」:新版 keil_build.py 自持自释(见其 docstring),
|
# 告知 /keil-build「调用方已持锁」:新版 keil_build.py 自持自释(见其 docstring),
|
||||||
# 读到本变量即跳过,避免自我死锁;**旧版根本不看此变量、也不会自锁**,
|
# 读到本变量即跳过,避免自我死锁;**旧版根本不看此变量、也不会自锁**,
|
||||||
# 故此处对「工作树里还是旧脚本」的在途任务同样安全(向后兼容)。
|
# 故此处对「工作树里还是旧脚本」的在途任务同样安全(向后兼容)。
|
||||||
os.environ["DTU_LOCK_HELD"] = "build"
|
os.environ["DTU_LOCK_HELD"] = "build"
|
||||||
try:
|
try:
|
||||||
if not run_script(".dsh/skills/keil-build/scripts/keil_build.py", [], cwd,
|
if not run_script(".dsh/skills/keil-build/scripts/keil_build.py", [], cwd,
|
||||||
"@build(0 错 0 警)"):
|
"/keil-build(0 错 0 警)"):
|
||||||
ok = False
|
ok = False
|
||||||
finally:
|
finally:
|
||||||
os.environ.pop("DTU_LOCK_HELD", None)
|
os.environ.pop("DTU_LOCK_HELD", None)
|
||||||
@@ -789,7 +789,7 @@ def cmd_new(a) -> int:
|
|||||||
print()
|
print()
|
||||||
print("下一步:把新会话的工作目录切到")
|
print("下一步:把新会话的工作目录切到")
|
||||||
print(" " + str(wt))
|
print(" " + str(wt))
|
||||||
print("在该目录里干活、跑 @build 与 ci_local.py(并行安全);")
|
print("在该目录里干活、跑 /keil-build 与 ci_local.py(并行安全);")
|
||||||
print("要动台架先 `lock acquire bench --task {}`,完事 `lock release bench --task {} --bench-pass`。".format(
|
print("要动台架先 `lock acquire bench --task {}`,完事 `lock release bench --task {} --bench-pass`。".format(
|
||||||
a.task_id, a.task_id))
|
a.task_id, a.task_id))
|
||||||
print("回收用 `done {}`(**必须在主工作树执行**)。".format(a.task_id))
|
print("回收用 `done {}`(**必须在主工作树执行**)。".format(a.task_id))
|
||||||
@@ -978,7 +978,7 @@ def cmd_done(a) -> int:
|
|||||||
return EXIT_OK
|
return EXIT_OK
|
||||||
|
|
||||||
if st.get("needs_bench") and not st.get("bench_pass"):
|
if st.get("needs_bench") and not st.get("bench_pass"):
|
||||||
raise Fail("任务标记 --needs-bench 但无台架 PASS 记录;跑完 @test 后执行 "
|
raise Fail("任务标记 --needs-bench 但无台架 PASS 记录;跑完 /serial-boot-test 后执行 "
|
||||||
"`lock release bench --task {} --bench-pass`".format(st["id"]))
|
"`lock release bench --task {} --bench-pass`".format(st["id"]))
|
||||||
print("[ok] 前置检查通过(领先 {} 共 {} 个提交)".format(base, ahead))
|
print("[ok] 前置检查通过(领先 {} 共 {} 个提交)".format(base, ahead))
|
||||||
|
|
||||||
@@ -1125,7 +1125,7 @@ def cmd_postmerge(a) -> int:
|
|||||||
if Path.cwd().resolve() != main.resolve():
|
if Path.cwd().resolve() != main.resolve():
|
||||||
raise EnvError("postmerge 必须在主工作树跑:{}".format(main))
|
raise EnvError("postmerge 必须在主工作树跑:{}".format(main))
|
||||||
if not list((main / "MDK-ARM").glob("**/*.map")):
|
if not list((main / "MDK-ARM").glob("**/*.map")):
|
||||||
raise EnvError("主树没有 .map——先 @build 0/0 再回来(C.5 的前提)")
|
raise EnvError("主树没有 .map——先 /keil-build 0/0 再回来(C.5 的前提)")
|
||||||
gate = "scripts/check_size_gate.py"
|
gate = "scripts/check_size_gate.py"
|
||||||
if not a.force and run_script(gate, ["--local"], main, "体积凭证检查(同码)", tail=8):
|
if not a.force and run_script(gate, ["--local"], main, "体积凭证检查(同码)", tail=8):
|
||||||
print("[ok] 凭证同码新鲜,无需补链")
|
print("[ok] 凭证同码新鲜,无需补链")
|
||||||
@@ -1234,7 +1234,7 @@ def build_parser():
|
|||||||
p.add_argument("task_id")
|
p.add_argument("task_id")
|
||||||
p.add_argument("--base", default=None)
|
p.add_argument("--base", default=None)
|
||||||
p.add_argument("--dry-run", action="store_true", help="只预演,不做任何改动")
|
p.add_argument("--dry-run", action="store_true", help="只预演,不做任何改动")
|
||||||
p.add_argument("--skip-build", action="store_true", help="跳过 @build")
|
p.add_argument("--skip-build", action="store_true", help="跳过 /keil-build")
|
||||||
p.add_argument("--skip-gates", action="store_true", help="跳过全部门禁")
|
p.add_argument("--skip-gates", action="store_true", help="跳过全部门禁")
|
||||||
p.add_argument("--force", action="store_true", help="清场时强制移除工作树")
|
p.add_argument("--force", action="store_true", help="清场时强制移除工作树")
|
||||||
p.set_defaults(func=cmd_done)
|
p.set_defaults(func=cmd_done)
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
# 它们在 .pre-commit-config.yaml 里带 stages: [manual],提交期不跑,由本 job 强制)
|
# 它们在 .pre-commit-config.yaml 里带 stages: [manual],提交期不跑,由本 job 强制)
|
||||||
# 主机侧纯逻辑单测 host-unit = test/host/**(MSVC 离线编译,零硬件)
|
# 主机侧纯逻辑单测 host-unit = test/host/**(MSVC 离线编译,零硬件)
|
||||||
#
|
#
|
||||||
# ⛔ 板端测试套件(test/board/,需真机 + ST-LINK)/ 主机侧回归(@test 闭环)/ 人工目检
|
# ⛔ 板端测试套件(test/board/,需真机 + ST-LINK)/ 主机侧回归(/serial-boot-test 闭环)/ 人工目检
|
||||||
# **不上 CI** —— 需要开发板与烧录器,只能在本地跑。
|
# **不上 CI** —— 需要开发板与烧录器,只能在本地跑。
|
||||||
#
|
#
|
||||||
# 明确不做的两件事(都是刻意为之):
|
# 明确不做的两件事(都是刻意为之):
|
||||||
# 1. `@build`(Keil UV4)—— 需本机 Keil 授权,runner 上不具备;且静态门禁的 cppcheck +
|
# 1. `/keil-build`(Keil UV4)—— 需本机 Keil 授权,runner 上不具备;且静态门禁的 cppcheck +
|
||||||
# 代码规范已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(AGENTS.md)。
|
# 代码规范已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(AGENTS.md)。
|
||||||
# 2. `mem-numbers` / `mem-budget` 两根钩子 —— 事实源都是 `MDK-ARM/**/*.map`(Keil
|
# 2. `mem-numbers` / `mem-budget` 两根钩子 —— 事实源都是 `MDK-ARM/**/*.map`(Keil
|
||||||
# 构建产物、已 gitignore),无 `@build` 即无 `.map`。故本 workflow 在**检测不到
|
# 构建产物、已 gitignore),无 `/keil-build` 即无 `.map`。故本 workflow 在**检测不到
|
||||||
# .map 时自动跳过它们**(见下方 "决定跳过哪些钩子" 步骤),有 .map 的 runner 照常跑。
|
# .map 时自动跳过它们**(见下方 "决定跳过哪些钩子" 步骤),有 .map 的 runner 照常跑。
|
||||||
#
|
#
|
||||||
# 🔴 体积门禁:盲区已关闭为**强制路径**(2026-10-25 起;原先只做到"声明")
|
# 🔴 体积门禁:盲区已关闭为**强制路径**(2026-10-25 起;原先只做到"声明")
|
||||||
|
|||||||
@@ -818,8 +818,8 @@ repos:
|
|||||||
# /* 就是第二次注释的起点(C 的块注释不嵌套)。
|
# /* 就是第二次注释的起点(C 的块注释不嵌套)。
|
||||||
# 为什么要闸:它**只在重编译那个 TU 时才暴露**,历史「0 警告」不代表安全;而且按包含者
|
# 为什么要闸:它**只在重编译那个 TU 时才暴露**,历史「0 警告」不代表安全;而且按包含者
|
||||||
# 数量刷屏——一份被广泛 include 的头里一处嵌套 = 每个包含者一行警告(实测一例扩散到 16
|
# 数量刷屏——一份被广泛 include 的头里一处嵌套 = 每个包含者一行警告(实测一例扩散到 16
|
||||||
# 条,把整批真警告挤到看不见)。有了这根,提交时就报,不用等 @build。
|
# 条,把整批真警告挤到看不见)。有了这根,提交时就报,不用等 /keil-build。
|
||||||
# 接线当日实证(冻结引用,别去翻构建日志——它每次 @build 就被覆盖、也不入库):当日 02:22
|
# 接线当日实证(冻结引用,别去翻构建日志——它每次 /keil-build 就被覆盖、也不入库):当日 02:22
|
||||||
# 那次构建日志报过 ..\App\algo\run_par.c(267): warning: #9-D: nested comment is not
|
# 那次构建日志报过 ..\App\algo\run_par.c(267): warning: #9-D: nested comment is not
|
||||||
# allowed ——那是别人**未提交**的在飞状态。原文抄在 docs/门禁说明/nested-comment.md §2.1;
|
# allowed ——那是别人**未提交**的在飞状态。原文抄在 docs/门禁说明/nested-comment.md §2.1;
|
||||||
# 已提交历史回放 281 份旧字节 0 命中 ⇒ 这类问题一直靠人工在编译后清,从没被机器拦过。
|
# 已提交历史回放 281 份旧字节 0 命中 ⇒ 这类问题一直靠人工在编译后清,从没被机器拦过。
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
**仅 Keil MDK-ARM v5 (ARMCC, C99) 工具链**;FreeRTOS V10.3.1 / CMSIS-RTOS2;192KB SRAM / 1MB Flash。
|
**仅 Keil MDK-ARM v5 (ARMCC, C99) 工具链**;FreeRTOS V10.3.1 / CMSIS-RTOS2;192KB SRAM / 1MB Flash。
|
||||||
|
|
||||||
**任何代码修改后必须先 `@build` 验证(0 错误 0 警告)再提交;硬件闭环用 `@test`。**
|
**任何代码修改后必须先 `/keil-build` 验证(0 错误 0 警告)再提交;硬件闭环用 `/serial-boot-test`。**
|
||||||
|
|
||||||
> 本文件 = agent 入口:宏观概况 + 每次都在场的铁律在这里,细节在 `docs/` 与 skills(文末索引)。同一事实只在一处详述(`docs/文档规范.md` 同此要求)。
|
> 本文件 = agent 入口:宏观概况 + 每次都在场的铁律在这里,细节在 `docs/` 与 skills(文末索引)。同一事实只在一处详述(`docs/文档规范.md` 同此要求)。
|
||||||
|
|
||||||
@@ -78,10 +78,10 @@
|
|||||||
|
|
||||||
## 构建与验证
|
## 构建与验证
|
||||||
|
|
||||||
- **门禁时机(用户裁定 2026-10-13)**:`@build` / `scripts/ci_local.py` / `check_doc_links.py` **只在准备提交时跑**(用户让 commit 后统一跑一遍),**不是每次中间改动后都跑**——多步改动期间直接改即可,验证集中在提交门禁;`@test` 硬件闭环仍按任务需要跑
|
- **门禁时机(用户裁定 2026-10-13)**:`/keil-build` / `scripts/ci_local.py` / `check_doc_links.py` **只在准备提交时跑**(用户让 commit 后统一跑一遍),**不是每次中间改动后都跑**——多步改动期间直接改即可,验证集中在提交门禁;`/serial-boot-test` 硬件闭环仍按任务需要跑
|
||||||
- **`@build` / `@test` / `@task` 都是本仓库的文档别名,不是 shell 命令**(2026-10-30 实测:bash 里直接敲 `@task` / `@build` 是 command not found,`dsh task` 会被解析成 profile 名报错)——下面 `=` 后的命令行才是可执行形态(PowerShell 形态,bash 下路径正斜杠);DSH 会话显式调用 skill 用 `/keil-build`、`/serial-boot-test`、`/task-worktree`
|
- **`/keil-build` / `/serial-boot-test` / `/task-worktree` 是 DSH 会话的技能调用名、不是 shell 命令**(bash 里直接敲是 command not found / syntax error)——下面 `=` 后的命令行才是可执行形态(PowerShell 形态,bash 下路径正斜杠)
|
||||||
- `@build` = `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`(仓库根执行)。退出码 **0=PASS / 1=构建失败 / 2=环境错误(UV4/工程/档案)/ 3=用法 / 4=build 锁被占用**;日志 `MDK-ARM/build_log.txt`;换机必设 `DTU_KEIL_UV4` → 细节 **`keil-build` skill** + `docs/环境与部署/本地工具路径环境变量方案.md`
|
- **编译**(`/keil-build`)= `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`(仓库根执行)。退出码 **0=PASS / 1=构建失败 / 2=环境错误(UV4/工程/档案)/ 3=用法 / 4=build 锁被占用**;日志 `MDK-ARM/build_log.txt`;换机必设 `DTU_KEIL_UV4` → 细节 **`keil-build` skill** + `docs/环境与部署/本地工具路径环境变量方案.md`
|
||||||
- `@test` = `& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\run_boot_test.py`:开串口+清日志 → 烧录(**固件身份戳**保证烧进去的就是这份构建、**回读校验**)→ wait-idle → 采日志 → 标记校验(含启动锚点计数)。退出码 **0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法**;戳提取规则、锚点计数判据与按模块定制标记见 `docs/测试说明/流程与规范/自动化测试流程.md`
|
- **串口回归**(`/serial-boot-test`)= `& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\run_boot_test.py`:开串口+清日志 → 烧录(**固件身份戳**保证烧进去的就是这份构建、**回读校验**)→ wait-idle → 采日志 → 标记校验(含启动锚点计数)。退出码 **0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法**;戳提取规则、锚点计数判据与按模块定制标记见 `docs/测试说明/流程与规范/自动化测试流程.md`
|
||||||
- Forbidden 标记含 `[ERR]`:**四个 Fault 的崩溃日志都以 `[ERR]` 开头 → 出现 Fault 即本次不通过**(刻意)
|
- Forbidden 标记含 `[ERR]`:**四个 Fault 的崩溃日志都以 `[ERR]` 开头 → 出现 Fault 即本次不通过**(刻意)
|
||||||
- **烧录必须 `-Rst -Run`**(`stlink-flash` skill 的 `flash`):裸 `-P` 会报 `Programming Complete`,但目标核处于死循环/异常时写入并不落地("反复重下载、芯片一直是旧镜像、二分法失效"的根因)。**烧后必须回读校验**(`stlink_flash.py cmp` = `-CmpFile`,`No difference found` 才算真正写入);⚠ `-CmpFile` 连接时会**复位目标** → 必须排在**采日志之后** → `docs/测试说明/测试工具/ST-LINK_CLI_复位使用说明.md`
|
- **烧录必须 `-Rst -Run`**(`stlink-flash` skill 的 `flash`):裸 `-P` 会报 `Programming Complete`,但目标核处于死循环/异常时写入并不落地("反复重下载、芯片一直是旧镜像、二分法失效"的根因)。**烧后必须回读校验**(`stlink_flash.py cmp` = `-CmpFile`,`No difference found` 才算真正写入);⚠ `-CmpFile` 连接时会**复位目标** → 必须排在**采日志之后** → `docs/测试说明/测试工具/ST-LINK_CLI_复位使用说明.md`
|
||||||
- ⚠️ 旧绝对路径 `D:\Code\DTU 程序\...` 出现即失效(仓库已搬迁),一律改用仓库相对路径
|
- ⚠️ 旧绝对路径 `D:\Code\DTU 程序\...` 出现即失效(仓库已搬迁),一律改用仓库相对路径
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
- ⚠ **pre-commit 4.x 已移除 `always: true`**(实测对 `local` 钩子只告警 `Unexpected key(s)` 并跳过)→ 想"总是跑"的钩子**必须让 `files:` 覆盖到所有会触发它的文件**,没有兜底开关
|
- ⚠ **pre-commit 4.x 已移除 `always: true`**(实测对 `local` 钩子只告警 `Unexpected key(s)` 并跳过)→ 想"总是跑"的钩子**必须让 `files:` 覆盖到所有会触发它的文件**,没有兜底开关
|
||||||
- **改写型钩子(`trailing-whitespace` + `end-of-file-fixer`)豁免 CubeMX/Keil 生成文件**(配置里 YAML 锚点 `&cubemx-generated`);⚠ **只读检查不受影响**:`cppcheck` / 乱码 / `cubemx-survival` / `task-table` / `mem-numbers` 仍覆盖 `Src/`、`Inc/`
|
- **改写型钩子(`trailing-whitespace` + `end-of-file-fixer`)豁免 CubeMX/Keil 生成文件**(配置里 YAML 锚点 `&cubemx-generated`);⚠ **只读检查不受影响**:`cppcheck` / 乱码 / `cubemx-survival` / `task-table` / `mem-numbers` 仍覆盖 `Src/`、`Inc/`
|
||||||
- 提交信息:标题 ≤100 字符、**正文每行 ≤80**(`.gitlint` + gitlint 默认)→ 长说明一律用**短标题 + `git commit -F <UTF-8 无 BOM 文件>` 多行正文**(中文长标题极易超限,且 `-m` 会被终端代码页转码破坏)
|
- 提交信息:标题 ≤100 字符、**正文每行 ≤80**(`.gitlint` + gitlint 默认)→ 长说明一律用**短标题 + `git commit -F <UTF-8 无 BOM 文件>` 多行正文**(中文长标题极易超限,且 `-m` 会被终端代码页转码破坏)
|
||||||
- **CI**(`.gitea/workflows/ci.yml`)只跑静态门禁 + 主机侧纯逻辑单测,**刻意不跑 `@build`**(需 Keil 授权)、不含采集链分层(需真机)。**远程 CI 的全部资料(服务端开关 / runner 部署与保活 / workflow / 排障 / 实测坑)集中在 [`docs/CI/README.md`](docs/CI/README.md),不放本节**——它**只跑在本机一台 runner `win-stm32-ci` 上**,其他机器与项目都不涉及。日常改代码的等价自查是 `& '.venv\Scripts\python.exe' scripts/ci_local.py`(静态门禁 + 提交信息门禁 + 主机侧纯逻辑单测)——**runner 不在线也不影响本地门禁,不能因"已配 CI"放宽本地要求**
|
- **CI**(`.gitea/workflows/ci.yml`)只跑静态门禁 + 主机侧纯逻辑单测,**刻意不跑 `/keil-build`**(需 Keil 授权)、不含采集链分层(需真机)。**远程 CI 的全部资料(服务端开关 / runner 部署与保活 / workflow / 排障 / 实测坑)集中在 [`docs/CI/README.md`](docs/CI/README.md),不放本节**——它**只跑在本机一台 runner `win-stm32-ci` 上**,其他机器与项目都不涉及。日常改代码的等价自查是 `& '.venv\Scripts\python.exe' scripts/ci_local.py`(静态门禁 + 提交信息门禁 + 主机侧纯逻辑单测)——**runner 不在线也不影响本地门禁,不能因"已配 CI"放宽本地要求**
|
||||||
|
|
||||||
## 平台约束
|
## 平台约束
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
- **`osKernelStart` 之前禁止任何 FreeRTOS/CMSIS-RTOS 创建类 API**(`osMessageQueueNew` 等会杀死启动,2026-09-20 实证根因)→ 内核前标准模式(纯寄存器/HAL 轮询 + **无锁静态缓冲** + 内核启动后**惰性创建**真队列)见 `docs/架构设计/项目启动顺序管理.md`
|
- **`osKernelStart` 之前禁止任何 FreeRTOS/CMSIS-RTOS 创建类 API**(`osMessageQueueNew` 等会杀死启动,2026-09-20 实证根因)→ 内核前标准模式(纯寄存器/HAL 轮询 + **无锁静态缓冲** + 内核启动后**惰性创建**真队列)见 `docs/架构设计/项目启动顺序管理.md`
|
||||||
- **就绪屏障位必须 `osFlagsNoClear`**:`osEventFlagsWait` **默认消费**所等位,就绪位被先消费掉 → `ftpTask` 的 `WaitAll` 永不成立 → **FTP 21 端口静默不监听**(无日志无报错)。命令位(`SYS_EVT_CFG_SAVE`/`NET_RECONFIG`)保持消费型
|
- **就绪屏障位必须 `osFlagsNoClear`**:`osEventFlagsWait` **默认消费**所等位,就绪位被先消费掉 → `ftpTask` 的 `WaitAll` 永不成立 → **FTP 21 端口静默不监听**(无日志无报错)。命令位(`SYS_EVT_CFG_SAVE`/`NET_RECONFIG`)保持消费型
|
||||||
- **自研代码禁用 C 库堆分配**(`malloc/calloc/realloc/strdup/free`):**2026-10-23 起由提交门禁机器拦截**(`coding-standard` 的 CODE-RULE-20,唯一豁免 = 平台分配后端的宿主侧实现,见 `docs/门禁说明/coding-standard.md` §7):MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 限定为 **512 B**,KB 级分配必然返回 NULL,而**模拟器(宿主 CRT 堆)完全正常、照不出来** → 只用 `pvPortMalloc/vPortFree`,或把分配收进平台后端 → `docs/内存管理/RAM使用分析与优化方案.md`
|
- **自研代码禁用 C 库堆分配**(`malloc/calloc/realloc/strdup/free`):**2026-10-23 起由提交门禁机器拦截**(`coding-standard` 的 CODE-RULE-20,唯一豁免 = 平台分配后端的宿主侧实现,见 `docs/门禁说明/coding-standard.md` §7):MicroLIB 的 C 堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 限定为 **512 B**,KB 级分配必然返回 NULL,而**模拟器(宿主 CRT 堆)完全正常、照不出来** → 只用 `pvPortMalloc/vPortFree`,或把分配收进平台后端 → `docs/内存管理/RAM使用分析与优化方案.md`
|
||||||
- **内存预算紧张**:新增任何任务/缓冲/栈前先核算 RAM 余量;改栈或加大局部变量**前后各跑一次** `scripts/stack_report.py`(静态最坏链 + 串口 `[STACK]` 水位)与 `scripts/ram_report.py`(.map 静态占用);**总量红线已机械化**:`mem-budget` 钩子拦 RW_IRAM1/RW_CCM >92%,子系统配额 / CCM 台账 / 趋势见 `docs/内存管理/RAM预算台账.md`(**每次 `@build` 后跑 `scripts/ram_baseline.py --update`**)→ `docs/内存管理/`
|
- **内存预算紧张**:新增任何任务/缓冲/栈前先核算 RAM 余量;改栈或加大局部变量**前后各跑一次** `scripts/stack_report.py`(静态最坏链 + 串口 `[STACK]` 水位)与 `scripts/ram_report.py`(.map 静态占用);**总量红线已机械化**:`mem-budget` 钩子拦 RW_IRAM1/RW_CCM >92%,子系统配额 / CCM 台账 / 趋势见 `docs/内存管理/RAM预算台账.md`(**每次 `/keil-build` 后跑 `scripts/ram_baseline.py --update`**)→ `docs/内存管理/`
|
||||||
- FreeRTOS V10.3.1(CMSIS-RTOS V2);HAL 时基 = **TIM7**(非 SysTick),tick = 1ms;NVIC 4 位优先级分组,**外设中断优先级 ≥ 5**
|
- FreeRTOS V10.3.1(CMSIS-RTOS V2);HAL 时基 = **TIM7**(非 SysTick),tick = 1ms;NVIC 4 位优先级分组,**外设中断优先级 ≥ 5**
|
||||||
|
|
||||||
**采集链(ADC / RS-485)**
|
**采集链(ADC / RS-485)**
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
**日志与工具链**
|
**日志与工具链**
|
||||||
|
|
||||||
- **`DBG_*` 每条日志约 90 B Flash + 216 B 栈**(每个调用点内联行缓冲 + 时间戳缓冲)→ 日志不是免费的,已覆盖的步骤别加重复锚点,只在原先静默返回的黑洞处补;**深调用链模块在 `#include "dbg_log.h"` 之前 `#define DBG_LINE_BUF_MAX 128U`**(取值必须 ≥ 该模块最长日志行,否则该行被截断)
|
- **`DBG_*` 每条日志约 90 B Flash + 216 B 栈**(每个调用点内联行缓冲 + 时间戳缓冲)→ 日志不是免费的,已覆盖的步骤别加重复锚点,只在原先静默返回的黑洞处补;**深调用链模块在 `#include "dbg_log.h"` 之前 `#define DBG_LINE_BUF_MAX 128U`**(取值必须 ≥ 该模块最长日志行,否则该行被截断)
|
||||||
- **`MDK-ARM/*.uvprojx` 每次 `@build` 后被 UV4 自动改写,改动一律不入库**(XML 归一化 + `PackID` 降到本机 DFP 版本);判据与例外(真的增删源文件才提交)→ **`keil-build` skill**
|
- **`MDK-ARM/*.uvprojx` 每次 `/keil-build` 后被 UV4 自动改写,改动一律不入库**(XML 归一化 + `PackID` 降到本机 DFP 版本);判据与例外(真的增删源文件才提交)→ **`keil-build` skill**
|
||||||
|
|
||||||
## 硬件引脚
|
## 硬件引脚
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -223,7 +223,7 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
|||||||
路径——**别拿旧副本当本次产物**(踩过:拷过一次之后没刷,别人读到的是上一轮的字节)。
|
路径——**别拿旧副本当本次产物**(踩过:拷过一次之后没刷,别人读到的是上一轮的字节)。
|
||||||
|
|
||||||
### 4.3 板端
|
### 4.3 板端
|
||||||
`@build` 0 错误 0 警告 → `@test` 看 `[ADC_TASK]` 首拍 / 心跳(`phasor(...)`、`freq(...)`、
|
`/keil-build` 0 错误 0 警告 → `/serial-boot-test` 看 `[ADC_TASK]` 首拍 / 心跳(`phasor(...)`、`freq(...)`、
|
||||||
`cal: ua_mv=… vl=0x00 cl=0xE6 rail=…`、`ring` / `skip` / `adc1_*` / `tgap`);
|
`cal: ua_mv=… vl=0x00 cl=0xE6 rail=…`、`ring` / `skip` / `adc1_*` / `tgap`);
|
||||||
健康判据 = **零 `[ERR]`**(健康告警与标定哨兵全以 `[ERR]` 开头,误报会被闭环直接抓住;
|
健康判据 = **零 `[ERR]`**(健康告警与标定哨兵全以 `[ERR]` 开头,误报会被闭环直接抓住;
|
||||||
`vl` 在未标定默认构建必须为 0x00,未标定即闭锁落地后 `cal:` 行就是台架期望的直接证据)。
|
`vl` 在未标定默认构建必须为 0x00,未标定即闭锁落地后 `cal:` 行就是台架期望的直接证据)。
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#include "wave_ring.h"
|
#include "wave_ring.h"
|
||||||
|
|
||||||
/* ARMCC 的 stdint.h 不提供 NULL,而主机侧 gcc / MSVC 会传递性把它拉进来 ——
|
/* ARMCC 的 stdint.h 不提供 NULL,而主机侧 gcc / MSVC 会传递性把它拉进来 ——
|
||||||
* 这个差别主机单测照不出来(2026-10-29 台基线实跑 @build 才暴露:本层
|
* 这个差别主机单测照不出来(2026-10-29 台基线实跑 /keil-build 才暴露:本层
|
||||||
* wave_ring.c 报 22 条 identifier "NULL" 未定义)。与 App/algo/sample_ring.c、App/algo/freq.c
|
* wave_ring.c 报 22 条 identifier "NULL" 未定义)。与 App/algo/sample_ring.c、App/algo/freq.c
|
||||||
* 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */
|
* 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include "wave_store.h"
|
#include "wave_store.h"
|
||||||
|
|
||||||
/* ARMCC 的 stdint.h 不提供 NULL,而主机侧 gcc / MSVC 会传递性把它拉进来 ——
|
/* ARMCC 的 stdint.h 不提供 NULL,而主机侧 gcc / MSVC 会传递性把它拉进来 ——
|
||||||
* 这个差别主机单测照不出来(2026-10-29 台基线实跑 @build 才暴露:本层
|
* 这个差别主机单测照不出来(2026-10-29 台基线实跑 /keil-build 才暴露:本层
|
||||||
* wave_store.c 报 22 条 identifier "NULL" 未定义)。与 App/algo/sample_ring.c、App/algo/freq.c
|
* wave_store.c 报 22 条 identifier "NULL" 未定义)。与 App/algo/sample_ring.c、App/algo/freq.c
|
||||||
* 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */
|
* 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|||||||
+1
-1
@@ -130,7 +130,7 @@ int app_main_init(void) {
|
|||||||
|
|
||||||
/* 固件身份戳(2026-10-10):__DATE__/__TIME__ 由编译器展开,与前面的字面量做
|
/* 固件身份戳(2026-10-10):__DATE__/__TIME__ 由编译器展开,与前面的字面量做
|
||||||
* C 字符串拼接 → .rodata 里是一段连续 ASCII,可直接在 .hex 里原样搜到。
|
* C 字符串拼接 → .rodata 里是一段连续 ASCII,可直接在 .hex 里原样搜到。
|
||||||
* 用途:@test 把"芯片里正在跑的镜像"和"刚编出来的镜像"对上号。此前开机标记
|
* 用途:/serial-boot-test 把"芯片里正在跑的镜像"和"刚编出来的镜像"对上号。此前开机标记
|
||||||
* 全是固定串(App init OK / Network Init / ...),旧镜像也照样全中,于是
|
* 全是固定串(App init OK / Network Init / ...),旧镜像也照样全中,于是
|
||||||
* "反复重下载、芯片一直是旧镜像"这类事故在自动闭环里完全不可见
|
* "反复重下载、芯片一直是旧镜像"这类事故在自动闭环里完全不可见
|
||||||
* (AGENTS.md 烧录铁律记的正是它)。代价:1 条 DBG ≈ 90 B Flash + 30 B 常量。
|
* (AGENTS.md 烧录铁律记的正是它)。代价:1 条 DBG ≈ 90 B Flash + 30 B 常量。
|
||||||
|
|||||||
@@ -427,7 +427,7 @@ void dbg_console_tx_end(void) {
|
|||||||
* · 拿到所有权 -> 整行一次发完,中途不会有别人的字节插进来;
|
* · 拿到所有权 -> 整行一次发完,中途不会有别人的字节插进来;
|
||||||
* · 拿不到 -> **本行不丢**,改投既有环形缓冲延后发。竞争失败不该变成丢行,更不该
|
* · 拿不到 -> **本行不丢**,改投既有环形缓冲延后发。竞争失败不该变成丢行,更不该
|
||||||
* 变成 [ERR]:台架那个未注册窗口每 3 次启动命中 2 次,按"丢弃 + [ERR]"处理只会
|
* 变成 [ERR]:台架那个未注册窗口每 3 次启动命中 2 次,按"丢弃 + [ERR]"处理只会
|
||||||
* 把静默缺陷换成常红的门禁([ERR] 是 @test 的 Forbidden 标记)。
|
* 把静默缺陷换成常红的门禁([ERR] 是 /serial-boot-test 的 Forbidden 标记)。
|
||||||
* 入环的是**同一批字节原文**,本模块与 drain 都不补前缀、不重打时间戳(DBG_* 的
|
* 入环的是**同一批字节原文**,本模块与 drain 都不补前缀、不重打时间戳(DBG_* 的
|
||||||
* 前缀在调用点就已 snprintf 进调用方缓冲)-> 延后发出去的行与它当场直发时
|
* 前缀在调用点就已 snprintf 进调用方缓冲)-> 延后发出去的行与它当场直发时
|
||||||
* 逐字节相同(判据⑦),取证时不会出现"证据说谎";
|
* 逐字节相同(判据⑦),取证时不会出现"证据说谎";
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ extern "C" {
|
|||||||
* (道G task-23)。诊断;[ERR] CONSOLE dropped 行报的就是它。
|
* (道G task-23)。诊断;[ERR] CONSOLE dropped 行报的就是它。
|
||||||
* ⚠ 抢不到所有权但成功让路入环的那条行**不入本账**(它没丢,
|
* ⚠ 抢不到所有权但成功让路入环的那条行**不入本账**(它没丢,
|
||||||
* 只是晚一点发)——这本账必须保持"红得有意义",因为
|
* 只是晚一点发)——这本账必须保持"红得有意义",因为
|
||||||
* [ERR] 是 @test 的 Forbidden 标记)
|
* [ERR] 是 /serial-boot-test 的 Forbidden 标记)
|
||||||
* dbg_console_tx_fault - 因僵尸通道软复位自愈的次数(诊断,语义不变)
|
* dbg_console_tx_fault - 因僵尸通道软复位自愈的次数(诊断,语义不变)
|
||||||
* dbg_console_tx_timeout - 写者排空时观察到的块超时次数(TXE / TC 两条硬上界都算)。
|
* dbg_console_tx_timeout - 写者排空时观察到的块超时次数(TXE / TC 两条硬上界都算)。
|
||||||
* 三本账分开:看日志有没有在丢 -> dropped;看外设自愈了几次
|
* 三本账分开:看日志有没有在丢 -> dropped;看外设自愈了几次
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ static uint32_t s_quiet_reported; /* drain 侧已上报水位(唯一
|
|||||||
* 的那一支),一旦成立,后面每一次取锁都零互斥;若"计数每前进一次报一条"
|
* 的那一支),一旦成立,后面每一次取锁都零互斥;若"计数每前进一次报一条"
|
||||||
* 就等于每轮 drain 一条记录 + 一次 NAND 写——诊断自己变成负载,正是上面 s_quiet_drop
|
* 就等于每轮 drain 一条记录 + 一次 NAND 写——诊断自己变成负载,正是上面 s_quiet_drop
|
||||||
* 那条纪律要避免的事(同款先例另有 App/fs/vol_idle_sync.c 的 s_vol_sync_fail_latch)。
|
* 那条纪律要避免的事(同款先例另有 App/fs/vol_idle_sync.c 的 s_vol_sync_fail_latch)。
|
||||||
* ⚠ **不打 DBG 错级、不打 [ERR]**:[ERR] 在本仓是"设备出故障"的唯一信号通道(@test 的
|
* ⚠ **不打 DBG 错级、不打 [ERR]**:[ERR] 在本仓是"设备出故障"的唯一信号通道(/serial-boot-test 的
|
||||||
* Forbidden 标记也依赖它,四个 Fault 的崩溃日志都以 [ERR] 开头)。把一个可观测性事件
|
* Forbidden 标记也依赖它,四个 Fault 的崩溃日志都以 [ERR] 开头)。把一个可观测性事件
|
||||||
* 塞进 [ERR],它就同时不再是故障、也不再是禁令,两边一起失去意义——与 task-23 里
|
* 塞进 [ERR],它就同时不再是故障、也不再是禁令,两边一起失去意义——与 task-23 里
|
||||||
* "日志让路不计 [ERR]"是同一根判据。机器判据 = TC-SYSLOG-137 里的 g_sys_err_count == 0。
|
* "日志让路不计 [ERR]"是同一根判据。机器判据 = TC-SYSLOG-137 里的 g_sys_err_count == 0。
|
||||||
|
|||||||
+2
-2
@@ -576,7 +576,7 @@ void adc_task_func(void *arg) {
|
|||||||
s_wave_bench_on = 1u;
|
s_wave_bench_on = 1u;
|
||||||
s_wave_bench_t0 = now;
|
s_wave_bench_t0 = now;
|
||||||
s_wave_bench_next = now + WAVE_BENCH_PERIOD_MS;
|
s_wave_bench_next = now + WAVE_BENCH_PERIOD_MS;
|
||||||
/* [WARN] 进正文(本仓无 WARN 级;用 ERR 级会带 [ERR] 误触 @test 禁用标记) */
|
/* [WARN] 进正文(本仓无 WARN 级;用 ERR 级会带 [ERR] 误触 /serial-boot-test 禁用标记) */
|
||||||
DBG_INFO("[WARN] wave bench autocap ON: 1 seg / %u ms, auto-stop %u s (BENCH BUILD ONLY)",
|
DBG_INFO("[WARN] wave bench autocap ON: 1 seg / %u ms, auto-stop %u s (BENCH BUILD ONLY)",
|
||||||
(unsigned int)WAVE_BENCH_PERIOD_MS,
|
(unsigned int)WAVE_BENCH_PERIOD_MS,
|
||||||
(unsigned int)(WAVE_BENCH_MAX_MS / 1000u));
|
(unsigned int)(WAVE_BENCH_MAX_MS / 1000u));
|
||||||
@@ -621,7 +621,7 @@ void adc_task_func(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 4) 首拍相量上报:攒满一周波(20 ms)后立即落一次,作为 全周傅氏相量链路的开机证据。
|
/* 4) 首拍相量上报:攒满一周波(20 ms)后立即落一次,作为 全周傅氏相量链路的开机证据。
|
||||||
* 不依赖 10 s 心跳 → 台架 @test 的 settle 窗口内即可见(否则会被静默判据
|
* 不依赖 10 s 心跳 → 台架 /serial-boot-test 的 settle 窗口内即可见(否则会被静默判据
|
||||||
* 截断,见 docs/测试说明/流程与规范/自动化测试流程.md 的 settle 口径)。 */
|
* 截断,见 docs/测试说明/流程与规范/自动化测试流程.md 的 settle 口径)。 */
|
||||||
if ((first_phasor_logged == 0u) && (dbg_console_writer_ready() != 0u)) {
|
if ((first_phasor_logged == 0u) && (dbg_console_writer_ready() != 0u)) {
|
||||||
phasor_t p;
|
phasor_t p;
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ void default_task_func(void *argument) {
|
|||||||
* 节奏:首次 STACK_PROBE_FIRST_MS 后报告(bring-up 深链已跑过),此后每
|
* 节奏:首次 STACK_PROBE_FIRST_MS 后报告(bring-up 深链已跑过),此后每
|
||||||
* STACK_PROBE_PERIOD_MS 采样一次,只在出现更深峰值时打印——既不改串口静默
|
* STACK_PROBE_PERIOD_MS 采样一次,只在出现更深峰值时打印——既不改串口静默
|
||||||
* 窗口,也不漏掉之后才走到的更深分支(如卷修复/配置保存路径)。
|
* 窗口,也不漏掉之后才走到的更深分支(如卷修复/配置保存路径)。
|
||||||
* 输出(纯 ASCII,便于 @test 采集):
|
* 输出(纯 ASCII,便于 /serial-boot-test 采集):
|
||||||
* [STACK] <task> peak_used=<B>/<cfg> free_min=<B> (<pct>%)
|
* [STACK] <task> peak_used=<B>/<cfg> free_min=<B> (<pct>%)
|
||||||
* [STACK] heap free=<B> min_ever=<B>
|
* [STACK] heap free=<B> min_ever=<B>
|
||||||
* 未覆盖 idle/timer 任务(内核静态小栈、纯循环,未取句柄)。
|
* 未覆盖 idle/timer 任务(内核静态小栈、纯循环,未取句柄)。
|
||||||
@@ -826,7 +826,7 @@ static void wave_store_drain(void) {
|
|||||||
s_wheld_ms_max = held_ms;
|
s_wheld_ms_max = held_ms;
|
||||||
}
|
}
|
||||||
if ((skip_ack == 0u) && (held_ms > (uint32_t)WAVE_DRAIN_HELD_WARN_MS)) {
|
if ((skip_ack == 0u) && (held_ms > (uint32_t)WAVE_DRAIN_HELD_WARN_MS)) {
|
||||||
/* 本仓 dbg_log 无 WARN 级(宏只有 ERROR/INFO/DEBUG,ERR 级自动带 [ERR] = @test
|
/* 本仓 dbg_log 无 WARN 级(宏只有 ERROR/INFO/DEBUG,ERR 级自动带 [ERR] = /serial-boot-test
|
||||||
* 禁用标记):[WARN] 按 console_log.c 先例进正文,慢不是错(真失败另有 seg drop) */
|
* 禁用标记):[WARN] 按 console_log.c 先例进正文,慢不是错(真失败另有 seg drop) */
|
||||||
DBG_INFO("[WAVE][WARN] seg held %lu ms (idx=%u)",
|
DBG_INFO("[WAVE][WARN] seg held %lu ms (idx=%u)",
|
||||||
(unsigned long)held_ms, (unsigned)s_wnext_idx);
|
(unsigned long)held_ms, (unsigned)s_wnext_idx);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#ifndef IEC104_SERV_H
|
#ifndef IEC104_SERV_H
|
||||||
#define IEC104_SERV_H
|
#define IEC104_SERV_H
|
||||||
|
|
||||||
#include <stdint.h> /* uint32_t(观测计数声明):头文件自含——2026-10-27 @build 实测
|
#include <stdint.h> /* uint32_t(观测计数声明):头文件自含——2026-10-27 /keil-build 实测
|
||||||
* 本头此前全靠包含者先带 stdint,feed 换 include 即断链报 #20 */
|
* 本头此前全靠包含者先带 stdint,feed 换 include 即断链报 #20 */
|
||||||
/* 监听端口的宏出口已删除(道G task-9 收口 task-8 交接痕):运行期真源 =
|
/* 监听端口的宏出口已删除(道G task-9 收口 task-8 交接痕):运行期真源 =
|
||||||
* [comm] 段 getter(App/protocol/iec104/iec104_comm.h 的 iec104_comm_port(),
|
* [comm] 段 getter(App/protocol/iec104/iec104_comm.h 的 iec104_comm_port(),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* (eng=0),拿去比二次值定值必然错判 → 复位元件 + 本拍不 eval。
|
* (eng=0),拿去比二次值定值必然错判 → 复位元件 + 本拍不 eval。
|
||||||
* 默认构建(未标定)恒闭锁是**期望行为**:边沿用 DBG_INFO 而非
|
* 默认构建(未标定)恒闭锁是**期望行为**:边沿用 DBG_INFO 而非
|
||||||
* [ERR](未标定是出厂常态、不是故障;每次开机都走这条沿,
|
* [ERR](未标定是出厂常态、不是故障;每次开机都走这条沿,
|
||||||
* [ERR] 还会误触 @test 禁用标记);台架旁路只走编译期宏
|
* [ERR] 还会误触 /serial-boot-test 禁用标记);台架旁路只走编译期宏
|
||||||
* DTU_BENCH(「数据可信」§4.3:不进 cfg、不进 104,本仓任何
|
* DTU_BENCH(「数据可信」§4.3:不进 cfg、不进 104,本仓任何
|
||||||
* 提交目标都不定义它),且该宏**只作用于判定本体**
|
* 提交目标都不定义它),且该宏**只作用于判定本体**
|
||||||
* adc_cal_prot_blocked()(App/algo/adc_cal.c,全仓唯一一处 #ifndef;
|
* adc_cal_prot_blocked()(App/algo/adc_cal.c,全仓唯一一处 #ifndef;
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
* VALID 口径,docs/任务规划/数据可信.md §4.3):
|
* VALID 口径,docs/任务规划/数据可信.md §4.3):
|
||||||
* 采样恢复分支内、巡检/压板之前判;边沿 INFO 对
|
* 采样恢复分支内、巡检/压板之前判;边沿 INFO 对
|
||||||
* + SOE_T_CAL_BLOCK/RESUME(不用 [ERR]:未标定是
|
* + SOE_T_CAL_BLOCK/RESUME(不用 [ERR]:未标定是
|
||||||
* 常态非故障,且 @test 禁用 [ERR]);DTU_BENCH
|
* 常态非故障,且 /serial-boot-test 禁用 [ERR]);DTU_BENCH
|
||||||
* 编译期旁路(本仓目标不定义 = 产品恒强制闭锁)
|
* 编译期旁路(本仓目标不定义 = 产品恒强制闭锁)
|
||||||
* v1.3 2026-10-26 DTU 项目组 道F task-16:本文件的三路判定 s_cal_locked()
|
* v1.3 2026-10-26 DTU 项目组 道F task-16:本文件的三路判定 s_cal_locked()
|
||||||
* 删除,改调 App/algo/adc_cal.c 的 adc_cal_prot_blocked()
|
* 删除,改调 App/algo/adc_cal.c 的 adc_cal_prot_blocked()
|
||||||
@@ -234,7 +234,7 @@ void protect_task(void *argument) {
|
|||||||
/* 启动锚点行:控制台写者注册后首拍补打(2026-10-10 门控纪律,adcTask
|
/* 启动锚点行:控制台写者注册后首拍补打(2026-10-10 门控纪律,adcTask
|
||||||
* 首帧同款)。写者注册前 con_append 走无锁裸发,与 defaultTask 启动日志
|
* 首帧同款)。写者注册前 con_append 走无锁裸发,与 defaultTask 启动日志
|
||||||
* 逐字节交错——B6 台架实证:本行曾损坏为 "st[art: cycle=220ms elm=70"
|
* 逐字节交错——B6 台架实证:本行曾损坏为 "st[art: cycle=220ms elm=70"
|
||||||
* 致 @test 锚点 MISS(控制台单写者日志设计说明.md §3.4)。 */
|
* 致 /serial-boot-test 锚点 MISS(控制台单写者日志设计说明.md §3.4)。 */
|
||||||
if ((start_logged == 0u) && (dbg_console_writer_ready() != 0u)) {
|
if ((start_logged == 0u) && (dbg_console_writer_ready() != 0u)) {
|
||||||
DBG_INFO("protect task start: cycle=%ums elm=%u",
|
DBG_INFO("protect task start: cycle=%ums elm=%u",
|
||||||
(unsigned)PROTECT_TASK_CYCLE_MS, (unsigned)PE_COUNT);
|
(unsigned)PROTECT_TASK_CYCLE_MS, (unsigned)PE_COUNT);
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ const char *boot_stats_cause_str(uint8_t cause);
|
|||||||
* 本函数做 1 次 I2C 读 + 至多 1 次 I2C 写:I2C1 与 RTC 同总线,严禁再把它
|
* 本函数做 1 次 I2C 读 + 至多 1 次 I2C 写:I2C1 与 RTC 同总线,严禁再把它
|
||||||
* 搬进 iec104/adc 任何关键链或 ISR。重复调用直接返回,不重复计数。
|
* 搬进 iec104/adc 任何关键链或 ISR。重复调用直接返回,不重复计数。
|
||||||
* 函数说明:① 全程**不打日志**:I2C 失败/电池换过都是可观测性事件,不是装置故障;
|
* 函数说明:① 全程**不打日志**:I2C 失败/电池换过都是可观测性事件,不是装置故障;
|
||||||
* `[ERR]` 在本仓是"设备出故障"的唯一信号通道(@test 的 Forbidden 依赖它),
|
* `[ERR]` 在本仓是"设备出故障"的唯一信号通道(/serial-boot-test 的 Forbidden 依赖它),
|
||||||
* 把可观测性事件塞进去就变成第二个信号源——与"日志让路不计 [ERR]"、
|
* 把可观测性事件塞进去就变成第二个信号源——与"日志让路不计 [ERR]"、
|
||||||
* 卷锁 fail-open 只投自检记录是同一条口径。状态一律留在结构体里。
|
* 卷锁 fail-open 只投自检记录是同一条口径。状态一律留在结构体里。
|
||||||
* ② 落盘只写 8 字节到 RTC 用户 SRAM:无 FTL 写放大、不取卷锁、不进 /log;
|
* ② 落盘只写 8 字节到 RTC 用户 SRAM:无 FTL 写放大、不取卷锁、不进 /log;
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ static int cmd_pass(lftpd_client_t *client, const char *arg)
|
|||||||
send_simple_response(client->socket, 230, STATUS_230);
|
send_simple_response(client->socket, 230, STATUS_230);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
/* 预期事件(爆破/配错口令):用 INFO 而非 ERROR——[ERR] 是 @test Forbidden 标记 */
|
/* 预期事件(爆破/配错口令):用 INFO 而非 ERROR——[ERR] 是 /serial-boot-test Forbidden 标记 */
|
||||||
if (res == FTP_AUTH_LOCKED) {
|
if (res == FTP_AUTH_LOCKED) {
|
||||||
lftpd_log_info("auth locked after %u failures", (unsigned)FTP_AUTH_FAIL_MAX);
|
lftpd_log_info("auth locked after %u failures", (unsigned)FTP_AUTH_FAIL_MAX);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
* 对端行为级别(既不是缺陷,也不该长得像缺陷)——
|
* 对端行为级别(既不是缺陷,也不该长得像缺陷)——
|
||||||
* 控制连接被对端关闭 / 客户端连上就走 / PASV 之后不来数据连接 / 网络重建导致
|
* 控制连接被对端关闭 / 客户端连上就走 / PASV 之后不来数据连接 / 网络重建导致
|
||||||
* 监听重建,这些都是**外部输入决定的正常收尾**。它们原先记 error 级,代价实测:
|
* 监听重建,这些都是**外部输入决定的正常收尾**。它们原先记 error 级,代价实测:
|
||||||
* ① 本仓 @test 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀),
|
* ① 本仓 /serial-boot-test 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀),
|
||||||
* 于是"FileZilla 关了个窗口"就能把一次启动测试判 FAIL;② 任何人对着 21 端口
|
* 于是"FileZilla 关了个窗口"就能把一次启动测试判 FAIL;② 任何人对着 21 端口
|
||||||
* 连一下就走,就能无限制造固件级 ERROR 日志(日志面 + 门禁面都能被外部驱动)。
|
* 连一下就走,就能无限制造固件级 ERROR 日志(日志面 + 门禁面都能被外部驱动)。
|
||||||
* 所以这一类降为 info,并单独起一个名字,让"谁是真故障"在读代码时就一目了然。
|
* 所以这一类降为 info,并单独起一个名字,让"谁是真故障"在读代码时就一目了然。
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ void sb_link_on_error(UART_HandleTypeDef *p_huart)
|
|||||||
* 2. 上报每 SB_LINK_REPORT_MS 一次,**只有计数有变化才打**;RX 与 TX 各一行,
|
* 2. 上报每 SB_LINK_REPORT_MS 一次,**只有计数有变化才打**;RX 与 TX 各一行,
|
||||||
* 分开是因为控制台单行上限 192 字符,塞一行会被截断(截断的行比不打印更误导)。
|
* 分开是因为控制台单行上限 192 字符,塞一行会被截断(截断的行比不打印更误导)。
|
||||||
* 3. 第一帧成功收到时立刻单独打一行,台架刚上电就能确认"链路通了、长度对、CRC 过"。
|
* 3. 第一帧成功收到时立刻单独打一行,台架刚上电就能确认"链路通了、长度对、CRC 过"。
|
||||||
* 4. ⚠ TX 行的字段名**不能含 fail**:`@test` 的 forbidden 模式是
|
* 4. ⚠ TX 行的字段名**不能含 fail**:`/serial-boot-test` 的 forbidden 模式是
|
||||||
* `FAIL;ERROR;HardFault;[ERR]` 且**大小写不敏感**,写 `fail=0` 会让每次启动
|
* `FAIL;ERROR;HardFault;[ERR]` 且**大小写不敏感**,写 `fail=0` 会让每次启动
|
||||||
* 自检都判红(2026-10-29 实测,被自己的日志字段坑了一次)。故用 kerr / abor。
|
* 自检都判红(2026-10-29 实测,被自己的日志字段坑了一次)。故用 kerr / abor。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
* 自洽假绿(见 test/host/net_socket/run_unit_test.py 的注释)。
|
* 自洽假绿(见 test/host/net_socket/run_unit_test.py 的注释)。
|
||||||
* ⚠ 本注释块内禁止出现"斜杠紧跟星号"的连写(Markdown 粗体收尾在路径末尾时最容易
|
* ⚠ 本注释块内禁止出现"斜杠紧跟星号"的连写(Markdown 粗体收尾在路径末尾时最容易
|
||||||
* 撞上,例如目录通配那种写法):ARMCC 报 #9-D nested comment is not allowed,
|
* 撞上,例如目录通配那种写法):ARMCC 报 #9-D nested comment is not allowed,
|
||||||
* 而 @build 的验收口径是 0 错误 **0 警告**(2026-10-25 首编即中招,已改)。
|
* 而 /keil-build 的验收口径是 0 错误 **0 警告**(2026-10-25 首编即中招,已改)。
|
||||||
* 适用平台:STM32F407ZGTx(ARMCC v5,C99);PC 模拟器与主机侧单测(MSVC)同标准
|
* 适用平台:STM32F407ZGTx(ARMCC v5,C99);PC 模拟器与主机侧单测(MSVC)同标准
|
||||||
* 作者:王建锋
|
* 作者:王建锋
|
||||||
* 创建日期:2026-10-25
|
* 创建日期:2026-10-25
|
||||||
@@ -43,7 +43,7 @@ extern "C" {
|
|||||||
* 开关:**默认关闭 = 产品构建**。
|
* 开关:**默认关闭 = 产品构建**。
|
||||||
* 打开方式 = 取消下一行注释。策略与 test/board/test_config.h 的
|
* 打开方式 = 取消下一行注释。策略与 test/board/test_config.h 的
|
||||||
* ENABLE_DESTRUCTIVE_TESTS 同级:默认必须关闭,严禁以打开态提交。
|
* ENABLE_DESTRUCTIVE_TESTS 同级:默认必须关闭,严禁以打开态提交。
|
||||||
* ⚠ 关闭态是 @build 的验收口径:"0 错误 0 警告" 且 .map 无新增符号
|
* ⚠ 关闭态是 /keil-build 的验收口径:"0 错误 0 警告" 且 .map 无新增符号
|
||||||
* (规格 §2.1 完成判据)。本宏不新增任何 .c,故不涉及双端注册。
|
* (规格 §2.1 完成判据)。本宏不新增任何 .c,故不涉及双端注册。
|
||||||
*/
|
*/
|
||||||
//#define APP_ASSERT_ENABLE
|
//#define APP_ASSERT_ENABLE
|
||||||
@@ -119,7 +119,7 @@ extern void vApplicationAssertFailed(const char *file, long line);
|
|||||||
* 里打开,**跑完必须还原成注释态**。
|
* 里打开,**跑完必须还原成注释态**。
|
||||||
* 4. 断言条件**不得带副作用**:关闭态不求值、打开态才求值,带副作用等于让行为
|
* 4. 断言条件**不得带副作用**:关闭态不求值、打开态才求值,带副作用等于让行为
|
||||||
* 随开关漂移——这是本宏唯一真正危险的误用形态。
|
* 随开关漂移——这是本宏唯一真正危险的误用形态。
|
||||||
* 5. 台架口径:MCU 钩子打印以 [ERR] 开头 → 真机上断言一旦触发,@test 必然判本次
|
* 5. 台架口径:MCU 钩子打印以 [ERR] 开头 → 真机上断言一旦触发,/serial-boot-test 必然判本次
|
||||||
* 失败,这是刻意的(AGENTS「Forbidden 标记含 [ERR]」/ 出现 Fault 即不通过)。
|
* 失败,这是刻意的(AGENTS「Forbidden 标记含 [ERR]」/ 出现 Fault 即不通过)。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
## 构建与烧录
|
## 构建与烧录
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 编译(@build = keil-build skill;相对路径,勿用绝对路径)
|
# 编译(keil-build skill 的 keil_build.py;相对路径,勿用绝对路径)
|
||||||
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 退出码 0 = 0 errors, 0 warnings
|
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 退出码 0 = 0 errors, 0 warnings
|
||||||
|
|
||||||
# 烧录 + 复位运行(@download = stlink-flash skill)
|
# 烧录 + 复位运行(@download = stlink-flash skill)
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ STM32F4-Base/
|
|||||||
│ │ ├── storage_test_task.c/h # 存储性能测试
|
│ │ ├── storage_test_task.c/h # 存储性能测试
|
||||||
│ │ ├── net_test_task.c/h # 网络层测试
|
│ │ ├── net_test_task.c/h # 网络层测试
|
||||||
│ │ └── net_latency_test.c/h # 链路时延探针
|
│ │ └── net_latency_test.c/h # 链路时延探针
|
||||||
│ └── host/ # 主机端脚本(PC 侧;@test 入口已迁至 .dsh/skills/serial-boot-test/)
|
│ └── host/ # 主机端脚本(PC 侧;串口回归入口在 .dsh/skills/serial-boot-test/)
|
||||||
│ ├── ch395f_socket_test.py # Socket 通信测试
|
│ ├── ch395f_socket_test.py # Socket 通信测试
|
||||||
│ ├── ftp_test.py / ftp_up_dl_test.py / ftp_regression.py / ftp_integrity_test.py / ftp_capture.py
|
│ ├── ftp_test.py / ftp_up_dl_test.py / ftp_regression.py / ftp_integrity_test.py / ftp_capture.py
|
||||||
│ ├── e02_probe.py # 队头阻塞专项探针
|
│ ├── e02_probe.py # 队头阻塞专项探针
|
||||||
@@ -395,7 +395,7 @@ pre-commit run --all-files
|
|||||||
| `Drivers/BSP/NET/net_events.c` | 芯片边沿事件处理 + 周期对账 |
|
| `Drivers/BSP/NET/net_events.c` | 芯片边沿事件处理 + 周期对账 |
|
||||||
| `Drivers/BSP/NET/net_listener.c` | 监听生命周期(单一重建所有者) |
|
| `Drivers/BSP/NET/net_listener.c` | 监听生命周期(单一重建所有者) |
|
||||||
| `App/algo/prot_elm.h` | 保护判据元件框架(判据消费入口) |
|
| `App/algo/prot_elm.h` | 保护判据元件框架(判据消费入口) |
|
||||||
| `.dsh/skills/keil-build/scripts/keil_build.py` | 编译脚本(`@build`) |
|
| `.dsh/skills/keil-build/scripts/keil_build.py` | 编译脚本(`/keil-build`) |
|
||||||
| `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | 自动化测试脚本 |
|
| `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | 自动化测试脚本 |
|
||||||
|
|
||||||
## 已知问题
|
## 已知问题
|
||||||
|
|||||||
+6
-6
@@ -40,8 +40,8 @@
|
|||||||
|
|
||||||
> **曾经的盲区已关闭为强制路径(2026-10-25,「体积门禁 CI 强制路径」残余销项,`task/A5-size-gate`→564715c)。**
|
> **曾经的盲区已关闭为强制路径(2026-10-25,「体积门禁 CI 强制路径」残余销项,`task/A5-size-gate`→564715c)。**
|
||||||
> `mem-numbers` 钩子的事实源是 Keil 构建产物 `MDK-ARM/**/*.map`(已 gitignore),CI 刻意不跑
|
> `mem-numbers` 钩子的事实源是 Keil 构建产物 `MDK-ARM/**/*.map`(已 gitignore),CI 刻意不跑
|
||||||
> `@build` → 该钩子在远端只会被跳过(现以 `SKIP` 名单诚实显示 Skipped,不再伪装 Passed)。
|
> `/keil-build` → 该钩子在远端只会被跳过(现以 `SKIP` 名单诚实显示 Skipped,不再伪装 Passed)。
|
||||||
> 补上的强制路径 = **同码凭证**:本地 `@build` 0/0 后 `ram_baseline.py --update` 顺手盖
|
> 补上的强制路径 = **同码凭证**:本地 `/keil-build` 0/0 后 `ram_baseline.py --update` 顺手盖
|
||||||
> `scripts/size_attestation.json`(三道前置:`.map` 可解析 / `.map` 不早于指纹内源码 mtime /
|
> `scripts/size_attestation.json`(三道前置:`.map` 可解析 / `.map` 不早于指纹内源码 mtime /
|
||||||
> 受管区 ≤92%);CI 无需 Keil,静态门禁作业末尾的「体积门禁」步用 `scripts/check_size_gate.py --ci`
|
> 受管区 ≤92%);CI 无需 Keil,静态门禁作业末尾的「体积门禁」步用 `scripts/check_size_gate.py --ci`
|
||||||
> 复核两件事——**工作树指纹 == 凭证指纹**(改了代码从没测过 = 拦)+ **按凭证数字复跑 92% 红线**。
|
> 复核两件事——**工作树指纹 == 凭证指纹**(改了代码从没测过 = 拦)+ **按凭证数字复跑 92% 红线**。
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
> (只复核同码性与红线复算);编译器选项(`.uvprojx`)不在指纹内;凭证由本地真实构建产生,
|
> (只复核同码性与红线复算);编译器选项(`.uvprojx`)不在指纹内;凭证由本地真实构建产生,
|
||||||
> 链路本身不经 Keil 授权就无法伪造,但"盖戳后换机器环境"的差异只能靠 mtime 前置兜。
|
> 链路本身不经 Keil 授权就无法伪造,但"盖戳后换机器环境"的差异只能靠 mtime 前置兜。
|
||||||
>
|
>
|
||||||
> 本地兜底动作不变(每次 `@build` 后):`ram_baseline.py --update`(含盖戳)+
|
> 本地兜底动作不变(每次 `/keil-build` 后):`ram_baseline.py --update`(含盖戳)+
|
||||||
> `check_mem_numbers.py --scan`。若 runner 以后装了 Keil,`mem-numbers` 会在有 `.map` 时照常执行。
|
> `check_mem_numbers.py --scan`。若 runner 以后装了 Keil,`mem-numbers` 会在有 `.map` 时照常执行。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -657,7 +657,7 @@ runner 是一台干净的 Windows,**下面这些必须装**,否则 job 会
|
|||||||
| **cppcheck** | 🔴 必需 | 同上(`cppcheck --version` 探测不过) | `winget install Cppcheck.Cppcheck` |
|
| **cppcheck** | 🔴 必需 | 同上(`cppcheck --version` 探测不过) | `winget install Cppcheck.Cppcheck` |
|
||||||
| **pre-commit** | 🔴 必需 | 同上(`python -m pre_commit --version` 探测) | `pip install pre-commit` 或 `uv tool install pre-commit` |
|
| **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++ 的桌面开发" |
|
| **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 | — |
|
| 开发板 / 串口 | ⛔ 不需要 | 板端测试套件/主机侧回归/人工目检不上 CI | — |
|
||||||
|
|
||||||
Git 另算:`git --version` 任意版本即可(你已在用)。
|
Git 另算:`git --version` 任意版本即可(你已在用)。
|
||||||
@@ -1066,7 +1066,7 @@ date +%s # 对比 last_online
|
|||||||
| `static-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) |
|
| `static-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) |
|
||||||
| `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §5.3 |
|
| `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §5.3 |
|
||||||
| `l05` 日志出现 `::warning::runner 未安装 MSVC` | VS 装在非默认路径或未装 | 见 §4.2(默认路径 / 同步改两处根目录) |
|
| `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` |
|
| 主机侧纯逻辑单测显示 `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` 是否一致 |
|
| 主机侧纯逻辑单测报 `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 |
|
| `Tee-Object` 步骤报 `TerminatorExpectedAtEndOfString` / 字符串缺少终止符 | `run:` 块混入了非 ASCII(GBK 解码错位) | 见文件头 🔴🔴;`run:` 内必须纯 ASCII |
|
||||||
@@ -1211,7 +1211,7 @@ runner 的日志走 **stderr**,于是上面两个日志文件都栽在这两
|
|||||||
带 `stages: [manual]` 的三根(`cppcheck` / `test-cppcheck` / `test-coverage`)也不在其中,
|
带 `stages: [manual]` 的三根(`cppcheck` / `test-cppcheck` / `test-coverage`)也不在其中,
|
||||||
靠 static-gates 的「手动阶段门禁」一步逐根 `--hook-stage manual` 跑(新增 manual 钩子须同步加进
|
靠 static-gates 的「手动阶段门禁」一步逐根 `--hook-stage manual` 跑(新增 manual 钩子须同步加进
|
||||||
该步的 `$hooks` 列表,否则只声明不执行);
|
该步的 `$hooks` 列表,否则只声明不执行);
|
||||||
- **`@build` / 板端测试套件 / 主机侧回归**:永远留在本地,不要往 CI 加(需 Keil 授权与真机);
|
- **`/keil-build` / 板端测试套件 / 主机侧回归**:永远留在本地,不要往 CI 加(需 Keil 授权与真机);
|
||||||
- **runner 长时间不可用**时:CI 只是"多一道网",**提交前的本地 `ci_local.py` 仍是 静态门禁的唯一保障**,
|
- **runner 长时间不可用**时:CI 只是"多一道网",**提交前的本地 `ci_local.py` 仍是 静态门禁的唯一保障**,
|
||||||
不能因"已经配了 CI"放宽(见 [docs/任务规划/登记簿.md](../任务规划/登记簿.md) 的条目「CI 远端闭环未验证」)。
|
不能因"已经配了 CI"放宽(见 [docs/任务规划/登记簿.md](../任务规划/登记簿.md) 的条目「CI 远端闭环未验证」)。
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -45,7 +45,7 @@ docs/
|
|||||||
| 2 | [`项目架构总览.md`](架构设计/项目架构总览.md) | 六层分层、启动时序、存储/网络/UI 三栈、目录组织 |
|
| 2 | [`项目架构总览.md`](架构设计/项目架构总览.md) | 六层分层、启动时序、存储/网络/UI 三栈、目录组织 |
|
||||||
| 3 | [`项目启动顺序管理.md`](架构设计/项目启动顺序管理.md) | Event Flags 就绪屏障(取代魔法延时)的任务启动模型 |
|
| 3 | [`项目启动顺序管理.md`](架构设计/项目启动顺序管理.md) | Event Flags 就绪屏障(取代魔法延时)的任务启动模型 |
|
||||||
| 4 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) | 开发机/目标板/现场部署的完整环境核对清单 |
|
| 4 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) | 开发机/目标板/现场部署的完整环境核对清单 |
|
||||||
| 5 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) → 构建 | 跑通 `@build` / `@test`(见根 `AGENTS.md`) |
|
| 5 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) → 构建 | 跑通 `/keil-build` / `/serial-boot-test`(见根 `AGENTS.md`) |
|
||||||
| 6 | [`测试开发标准流程.md`](测试说明/流程与规范/测试开发标准流程.md) | 测试分层 静态门禁~保护判据与门禁决策表 |
|
| 6 | [`测试开发标准流程.md`](测试说明/流程与规范/测试开发标准流程.md) | 测试分层 静态门禁~保护判据与门禁决策表 |
|
||||||
| 7 | [`嵌入式C语言代码规范_V1.0.md`](代码规范/嵌入式C语言代码规范_V1.0.md) | 命名/格式/注释规范(提交门禁强制) |
|
| 7 | [`嵌入式C语言代码规范_V1.0.md`](代码规范/嵌入式C语言代码规范_V1.0.md) | 命名/格式/注释规范(提交门禁强制) |
|
||||||
| 8 | [`项目架构总览.md`](架构设计/项目架构总览.md) → 按需 | 进入具体子系统设计文档 |
|
| 8 | [`项目架构总览.md`](架构设计/项目架构总览.md) → 按需 | 进入具体子系统设计文档 |
|
||||||
@@ -124,7 +124,7 @@ docs/
|
|||||||
| 文档 | 内容 |
|
| 文档 | 内容 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [`测试开发标准流程.md`](测试说明/流程与规范/测试开发标准流程.md) | 测试开发总流程:规划→板端套件→主机工具→门禁→沉淀;门禁决策表 |
|
| [`测试开发标准流程.md`](测试说明/流程与规范/测试开发标准流程.md) | 测试开发总流程:规划→板端套件→主机工具→门禁→沉淀;门禁决策表 |
|
||||||
| [`自动化测试流程.md`](测试说明/流程与规范/自动化测试流程.md) | `@test` 闭环细节:命令 / 串口标记 / 失败模式 |
|
| [`自动化测试流程.md`](测试说明/流程与规范/自动化测试流程.md) | `/serial-boot-test` 闭环细节:命令 / 串口标记 / 失败模式 |
|
||||||
| [`测试文档模板规范.md`](测试说明/流程与规范/测试文档模板规范.md) | 测试文档模板与命名规则(TM / TC / DP) |
|
| [`测试文档模板规范.md`](测试说明/流程与规范/测试文档模板规范.md) | 测试文档模板与命名规则(TM / TC / DP) |
|
||||||
|
|
||||||
**单元测试/**(主机侧纯逻辑单测说明与覆盖率口径)
|
**单元测试/**(主机侧纯逻辑单测说明与覆盖率口径)
|
||||||
@@ -149,7 +149,7 @@ docs/
|
|||||||
| [`GD5F2GQ5UE_Test_Guide.md`](测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md) | GD5F2GQ5UE SPI NAND 存储测试 |
|
| [`GD5F2GQ5UE_Test_Guide.md`](测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md) | GD5F2GQ5UE SPI NAND 存储测试 |
|
||||||
| [`SD2506_Test_Guide.md`](测试说明/板级测试/SD2506_Test_Guide.md) | SD2506 RTC 测试 |
|
| [`SD2506_Test_Guide.md`](测试说明/板级测试/SD2506_Test_Guide.md) | SD2506 RTC 测试 |
|
||||||
| [`FTP_Test_Guide.md`](测试说明/板级测试/FTP_Test_Guide.md) | FTP 服务测试 |
|
| [`FTP_Test_Guide.md`](测试说明/板级测试/FTP_Test_Guide.md) | FTP 服务测试 |
|
||||||
| [`实机测试实施指南.md`](测试说明/板级测试/实机测试实施指南.md) | **上板(真机)测试执行流程**(按顺序:环境预检 → `@test`+自检判定 → IWDG 注入/长跑 → 堆告警 → GD5F 错误通道 → 网络基线 → 工程卫生;每步含判据/命令/状态);已完成事项证据去向见 §10 |
|
| [`实机测试实施指南.md`](测试说明/板级测试/实机测试实施指南.md) | **上板(真机)测试执行流程**(按顺序:环境预检 → `/serial-boot-test`+自检判定 → IWDG 注入/长跑 → 堆告警 → GD5F 错误通道 → 网络基线 → 工程卫生;每步含判据/命令/状态);已完成事项证据去向见 §10 |
|
||||||
|
|
||||||
**专项验证/**(针对一个缺陷或一次复验的台架专项流程;做完即定稿)
|
**专项验证/**(针对一个缺陷或一次复验的台架专项流程;做完即定稿)
|
||||||
|
|
||||||
@@ -173,7 +173,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]` 片段,含逐帧尾批明细与方法学约束) |
|
| [`m2_data/README.md`](测试说明/测试数据/m2_data/README.md) | 链路时延 M2 安静档采集数据(JSON + 串口 `[LAT]` 片段,含逐帧尾批明细与方法学约束) |
|
||||||
| `m3_data/` | 链路时延 M3 各档原始 JSON(quiet / stress / pending / 尾批实验;无独立 README,口径见 [`链路延迟测试说明.md`](测试说明/专项验证/链路延迟测试说明.md) 附表) |
|
| `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. 编译验证
|
## 6. 编译验证
|
||||||
|
|
||||||
```bash
|
```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` 正常编入。
|
要求:`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` 文件头;口径出处只在
|
以及「禁写终身次数」「跨开机累计不做」这两条口径,落在 `Drivers/BSP/UI/ui_page_runreport.c` 文件头;口径出处只在
|
||||||
`App/util/boot_stats.h` 四数台账与本节各述一次(同一事实只在一处详述,`docs/文档规范.md` 同此要求)。
|
`App/util/boot_stats.h` 四数台账与本节各述一次(同一事实只在一处详述,`docs/文档规范.md` 同此要求)。
|
||||||
- 代价(本批
|
- 代价(本批
|
||||||
`@build` 实测,0 错 0 警):
|
`/keil-build` 实测,0 错 0 警):
|
||||||
`Program Size` 由本批前
|
`Program Size` 由本批前
|
||||||
Code=235536 RO=26392 RW=1572 ZI=136924 变为 Code=236456 RO=28124 RW=1876 ZI=137084(ROM 合计 +2,652 B)
|
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
|
→ **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%),
|
`RW_IRAM1` 62.2% / `RW_CCM` 87.9%(阈值 92%),
|
||||||
`mem-budget` PASS,
|
`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 行)。两条带的元素同形(数值 + 品质),页面行表按需选一条。
|
> 另一处落地细化:`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 层的数据可及性与一条
|
**结论**:代价 = 约 1.5 KB 静态 + 每 200 ms 一次 memcpy,换来整个 UI 层的数据可及性与一条
|
||||||
可测的写路径。区域水位(RW_IRAM1 / RW_CCM / Flash)**不在本文复制**——那是受管快照,
|
可测的写路径。区域水位(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` §2.1 与
|
||||||
`docs/内存管理/RAM预算台账.md`)。本次新增约 1.5 KB 静态 + 一条 LVGL 池定时器,量级无压力。
|
`docs/内存管理/RAM预算台账.md`)。本次新增约 1.5 KB 静态 + 一条 LVGL 池定时器,量级无压力。
|
||||||
|
|
||||||
@@ -617,7 +617,7 @@ typedef struct {
|
|||||||
| 步 | 内容 | 验证 | 依赖 |
|
| 步 | 内容 | 验证 | 依赖 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **步骤1** | `ui_data.h/.c` + 主机单测(无任何消费者) | `test/host/ui_data` 全绿;`layer-deps` 无新增 | 无 |
|
| **步骤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 |
|
| **步骤3** | 交流量 / 直流量回炉(接快照 + 行表加 `src/dot/unit`) | 模拟器固定帧 golden 更新;台架看真实码值变化 | 步骤2 |
|
||||||
| **步骤4** | 遥信量 + 定值/控制字/软压板查看(4 页) | 主站改定值 → 页面 200 ms 内变 | 步骤3 |
|
| **步骤4** | 遥信量 + 定值/控制字/软压板查看(4 页) | 主站改定值 → 页面 200 ms 内变 | 步骤3 |
|
||||||
| **步骤5** | 写路径(请求/状态)+ 整定 4 页 | 就地改定值 → 保存 → 掉电重启 → 值保持 | 步骤4。**已落**(道F task-1 落本层队列与状态机、task-2 落整定 4 页与模拟器侧,2026-10-26);「掉电重启值保持」只能上板收口,见 §12 判据 6 |
|
| **步骤5** | 写路径(请求/状态)+ 整定 4 页 | 就地改定值 → 保存 → 掉电重启 → 值保持 | 步骤4。**已落**(道F task-1 落本层队列与状态机、task-2 落整定 4 页与模拟器侧,2026-10-26);「掉电重启值保持」只能上板收口,见 §12 判据 6 |
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ flowchart TD
|
|||||||
P2 --> C2{"输出图 ↔ 参考图<br/>同构?"}
|
P2 --> C2{"输出图 ↔ 参考图<br/>同构?"}
|
||||||
C2 -- "否: 按根因修正(修代码 / 回 P0 修规格)→ 再出图" --> P2
|
C2 -- "否: 按根因修正(修代码 / 回 P0 修规格)→ 再出图" --> P2
|
||||||
C2 -- "是(Gate: 导航符合按键语义 + 自动化回归 PASS)" --> P3["P3 MCU 验证<br/>→ 日志 + 上机目检结论"]
|
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`(参数表/字符集/问题/映射/翻译要点) |
|
| P0 需求录入 + 设计规格 | 提供图片(每批 1~3 屏);说明各屏作用与菜单位置;回答问题;确认布局参数表 | 图片分析、界面登记、字体度量核对、字符集核对、布局计算(验证等式)、规格制作 | 台账 + `_说明.md`(参数表/字符集/问题/映射/翻译要点) |
|
||||||
| P1 规格→代码 | (可选)抽查代码 | 逐参数翻译、字体重生成、内存预算、双端注册 | 代码 + 字体 |
|
| P1 规格→代码 | (可选)抽查代码 | 逐参数翻译、字体重生成、内存预算、双端注册 | 代码 + 字体 |
|
||||||
| P2 模拟器 | **视觉确认**(窗口模式/出图),指出差异 | 构建、离线出图、出图与原图逐项比对、依规格改码 | 比对结论 |
|
| P2 模拟器 | **视觉确认**(窗口模式/出图),指出差异 | 构建、离线出图、出图与原图逐项比对、依规格改码 | 比对结论 |
|
||||||
| P3 MCU | **真机目检**(DTU-RemoteLCD) | @build / @test 执行、日志解析 | 日志 + 结论 |
|
| P3 MCU | **真机目检**(DTU-RemoteLCD) | /keil-build / /serial-boot-test 执行、日志解析 | 日志 + 结论 |
|
||||||
| P4 归档 | — | 把受影响章节改成改动后的现状 + 更新台账 | 文档 |
|
| P4 归档 | — | 把受影响章节改成改动后的现状 + 更新台账 | 文档 |
|
||||||
|
|
||||||
> 用户视觉确认点由旧"浏览器打开 HTML 样机"改为 **P2 模拟器窗口/出图**:P0 确认**参数表**(数值层面),P2 确认**渲染结果**(视觉层面);渲染结果与意图不符时按 §5.1 根因修正(代码错 → 修代码;参数表错 → 回 P0 修规格再修代码)。
|
> 用户视觉确认点由旧"浏览器打开 HTML 样机"改为 **P2 模拟器窗口/出图**:P0 确认**参数表**(数值层面),P2 确认**渲染结果**(视觉层面);渲染结果与意图不符时按 §5.1 根因修正(代码错 → 修代码;参数表错 → 回 P0 修规格再修代码)。
|
||||||
@@ -248,8 +248,8 @@ flowchart TD
|
|||||||
|
|
||||||
## 六、P3:MCU 验证
|
## 六、P3:MCU 验证
|
||||||
|
|
||||||
1. `@build` → **0 错误 0 警告**(AGENTS.md 强制;有警告看 `MDK-ARM/build_log.txt`)
|
1. `/keil-build` → **0 错误 0 警告**(AGENTS.md 强制;有警告看 `MDK-ARM/build_log.txt`)
|
||||||
2. `@test`(一键 `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):
|
2. `/serial-boot-test`(一键 `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):
|
||||||
- 采集窗口**先于烧录武装**(脚本 `--flash-cmd` 已保证顺序)
|
- 采集窗口**先于烧录武装**(脚本 `--flash-cmd` 已保证顺序)
|
||||||
- Required:`UI port init: OK;UI menu init: OK` + 本界面定制标记(按屏增补,如页头标题字样)
|
- Required:`UI port init: OK;UI menu init: OK` + 本界面定制标记(按屏增补,如页头标题字样)
|
||||||
- Forbidden:`FAIL;ERROR;HardFault`
|
- Forbidden:`FAIL;ERROR;HardFault`
|
||||||
@@ -257,8 +257,8 @@ flowchart TD
|
|||||||
4. 串口日志:池水位、任务栈高水位(`uxTaskGetStackHighWaterMark`,uiTask 预算 16384 words)
|
4. 串口日志:池水位、任务栈高水位(`uxTaskGetStackHighWaterMark`,uiTask 预算 16384 words)
|
||||||
|
|
||||||
### Gate(进入 P4)
|
### Gate(进入 P4)
|
||||||
- [ ] @build 0错0警
|
- [ ] /keil-build 0错0警
|
||||||
- [ ] @test PASS
|
- [ ] /serial-boot-test PASS
|
||||||
- [ ] 真机目检通过(用户确认)
|
- [ ] 真机目检通过(用户确认)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -278,7 +278,7 @@ flowchart TD
|
|||||||
2. **按界面族批量**:
|
2. **按界面族批量**:
|
||||||
- 列表页(复用现有级联框自绘架构)→ 数据显示页(数值行框架)→ 参数编辑页(编辑/确认交互)→ 曲线/状态页
|
- 列表页(复用现有级联框自绘架构)→ 数据显示页(数值行框架)→ 参数编辑页(编辑/确认交互)→ 曲线/状态页
|
||||||
- 每族先做**族内第一屏**产出可复用的页面框架,再批量铺同族界面
|
- 每族先做**族内第一屏**产出可复用的页面框架,再批量铺同族界面
|
||||||
3. **每批收尾**:字体统一重生成(各屏字符集**并集**,一次生成)→ 全量回归(已完成界面重跑 @test)
|
3. **每批收尾**:字体统一重生成(各屏字符集**并集**,一次生成)→ 全量回归(已完成界面重跑 /serial-boot-test)
|
||||||
4. 字符集只增不减;字体文件每次**全量重生成**,禁止手工拼合
|
4. 字符集只增不减;字体文件每次**全量重生成**,禁止手工拼合
|
||||||
|
|
||||||
### 8.1 规格投入分流(按屏型)
|
### 8.1 规格投入分流(按屏型)
|
||||||
@@ -384,8 +384,8 @@ P2 [ ] 模拟器构建 0 错误
|
|||||||
[ ] shot.png 与参考图/规格参数表逐项比对(几何/文本/状态);不符按根因(修码/修规格)再出图(§5.1)
|
[ ] shot.png 与参考图/规格参数表逐项比对(几何/文本/状态);不符按根因(修码/修规格)再出图(§5.1)
|
||||||
[ ] 导航行为验证(窗口模式按键)
|
[ ] 导航行为验证(窗口模式按键)
|
||||||
[ ] 自动化回归:check_menu_rows.py + check_content_pages.py PASS
|
[ ] 自动化回归:check_menu_rows.py + check_content_pages.py PASS
|
||||||
P3 [ ] @build 0 错 0 警
|
P3 [ ] /keil-build 0 错 0 警
|
||||||
[ ] @test PASS(Required:____ / Forbidden:FAIL;ERROR;HardFault)
|
[ ] /serial-boot-test PASS(Required:____ / Forbidden:FAIL;ERROR;HardFault)
|
||||||
[ ] 真机目检通过
|
[ ] 真机目检通过
|
||||||
P4 [ ] UI_设计架构.md 受影响章节已改成现状(不记流水)
|
P4 [ ] UI_设计架构.md 受影响章节已改成现状(不记流水)
|
||||||
[ ] 台账状态 → 已完成(代码+字体+字符集+规格+文档 一并提交)
|
[ ] 台账状态 → 已完成(代码+字体+字符集+规格+文档 一并提交)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓
|
|||||||
3. 文本一律用静态字符串(`lv_label_set_text_static`),不 strdup
|
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)
|
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(双端注册)
|
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)
|
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]`——
|
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。
|
`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(成功)
|
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 个子框体),远小于池余量。
|
- LVGL 池实测(模拟器 16 KB 池):根框建完 `used=5368 B`(旧行对象实现为 13312 B,自绘 + 容器收敛省 8.3 KB);最深四级级联 `used=6040 B`(增量 1256 B = 3 个子框体),远小于池余量。
|
||||||
|
|
||||||
### 1.6 层间不变量(每层"绝不做")
|
### 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)目检 |
|
| 模拟器无头 | `lvgl_sim.exe --screenshot shot.bin` / `--keys <seq> shot.bin`;I1 原始像素(160×160 = 3200 B)转 PNG(PIL)目检 |
|
||||||
| 对照参考图 | 7 状态(根页 + 6 个一级子框)逐一对照参考图 01~07;反色行/框几何用像素统计核验(偏差见 §8.2 注 5) |
|
| 对照参考图 | 7 状态(根页 + 6 个一级子框)逐一对照参考图 01~07;反色行/框几何用像素统计核验(偏差见 §8.2 注 5) |
|
||||||
| 编译 | `@build`(ARMCC,0 错 0 警,强制) |
|
| 编译 | `/keil-build`(ARMCC,0 错 0 警,强制) |
|
||||||
| 真机 | `@test`(Required 含 `UI port init: OK;UI menu init: OK`;屏幕内容仍需人工目检) |
|
| 真机 | `/serial-boot-test`(Required 含 `UI port init: OK;UI menu init: OK`;屏幕内容仍需人工目检) |
|
||||||
|
|
||||||
### 8.2 陷阱与保真度说明
|
### 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-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-3 | `ui_w_editor`(数值编辑器) | 模拟器可改值 |
|
||||||
| 0-4 | `ui_page_action`(确认操作页) | 模拟器确认/取消 |
|
| 0-4 | `ui_page_action`(确认操作页) | 模拟器确认/取消 |
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</defs>
|
</defs>
|
||||||
<text x="440.0" y="22" text-anchor="middle" class="title">FreeRTOS 任务栈预算(9 任务;栈全部从 56KB FreeRTOS 堆分配,堆位于 CCM)</text>
|
<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="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>
|
<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"/>
|
<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; }
|
.bar-seg { stroke:#fff; stroke-width:0.5; }
|
||||||
</style>
|
</style>
|
||||||
</defs>
|
</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"/>
|
<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>
|
<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"/>
|
<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; }
|
.bar-seg { stroke:#fff; stroke-width:0.5; }
|
||||||
</style>
|
</style>
|
||||||
</defs>
|
</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>
|
<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"/>
|
<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>
|
<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
@@ -53,7 +53,7 @@
|
|||||||
| **IWDG** | Independent Watchdog,独立看门狗(LSI 时钟源) |
|
| **IWDG** | Independent Watchdog,独立看门狗(LSI 时钟源) |
|
||||||
| **就绪屏障** | 用 CMSIS-RTOS2 Event Flags 表达"子系统就绪"的启动顺序机制 |
|
| **就绪屏障** | 用 CMSIS-RTOS2 Event Flags 表达"子系统就绪"的启动顺序机制 |
|
||||||
| **双端** | MCU(Keil/ARMCC)与 PC 模拟器(CMake/MSVC)共用同一份 UI/配置源码 |
|
| **双端** | MCU(Keil/ARMCC)与 PC 模拟器(CMake/MSVC)共用同一份 UI/配置源码 |
|
||||||
| **@build / @test** | 仓库约定的构建 / 自动化测试命令别名 |
|
| **/keil-build / /serial-boot-test** | 仓库约定的构建 / 自动化测试命令别名 |
|
||||||
| **语义取数与有效性 / 标定折算 / 全周傅氏相量 / 测频与遥测快照 / 保护判据** | 采集链路分层:语义取数与有效性(语义取数与有效性)→ 标定折算(标定折算)→ 全周傅氏相量(全周傅氏相量)→ 测频与遥测快照(测频与遥测快照)→ 保护判据(保护判据) |
|
| **语义取数与有效性 / 标定折算 / 全周傅氏相量 / 测频与遥测快照 / 保护判据** | 采集链路分层:语义取数与有效性(语义取数与有效性)→ 标定折算(标定折算)→ 全周傅氏相量(全周傅氏相量)→ 测频与遥测快照(测频与遥测快照)→ 保护判据(保护判据) |
|
||||||
| **全周傅氏** | 单频 DFT 取基波相量,窗长 = 整周波(20 点);幅值即基波有效值 |
|
| **全周傅氏** | 单频 DFT 取基波相量,窗长 = 整周波(20 点);幅值即基波有效值 |
|
||||||
| **相量** | `{fRe, fIm, fAm, fAmSQ}`,实部 / 虚部 / 幅值 / 有效值 |
|
| **相量** | `{fRe, fIm, fAm, fAmSQ}`,实部 / 虚部 / 幅值 / 有效值 |
|
||||||
@@ -168,7 +168,7 @@ flowchart LR
|
|||||||
| G2 | **可维护**:分层解耦、单一事实源、双端复用、门禁强制 | 六层架构、就绪屏障、生成器(菜单/字体/任务表)、pre-commit 钩子族 |
|
| G2 | **可维护**:分层解耦、单一事实源、双端复用、门禁强制 | 六层架构、就绪屏障、生成器(菜单/字体/任务表)、pre-commit 钩子族 |
|
||||||
| G3 | **可移植**:UI/配置源码 MCU 与 PC 模拟器共用 | 平台后端抽象(`*_mcu.c` / `*_sim.c`),核心零平台依赖 |
|
| G3 | **可移植**:UI/配置源码 MCU 与 PC 模拟器共用 | 平台后端抽象(`*_mcu.c` / `*_sim.c`),核心零平台依赖 |
|
||||||
| G4 | **可扩展**:新子系统/新配置段/新页面按模板接入 | 事件位扩展、段注册三步、页面插件描述符、`App/protocol/` 预留 |
|
| G4 | **可扩展**:新子系统/新配置段/新页面按模板接入 | 事件位扩展、段注册三步、页面插件描述符、`App/protocol/` 预留 |
|
||||||
| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `@build` / `@test` / 静态门禁 / 主机侧纯逻辑单测 / 台架验证 |
|
| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `/keil-build` / `/serial-boot-test` / 静态门禁 / 主机侧纯逻辑单测 / 台架验证 |
|
||||||
|
|
||||||
**设计原则**(贯穿全项目):
|
**设计原则**(贯穿全项目):
|
||||||
|
|
||||||
@@ -1294,11 +1294,11 @@ mac=00:00:00:00:00:00
|
|||||||
| 项 | 值 |
|
| 项 | 值 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 端口 | UART5(RS232#1:PC12 TX / PD2 RX,经 U9 隔离 → U13 → CN1 端子),115200 8N1;台架端子接法见 `docs/子系统设计/屏板链路设计说明.md` §3.2 |
|
| 端口 | 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`(单一配置源) |
|
| 配置源 | `.serialtest/config.json`(单一配置源) |
|
||||||
| 工具 | 仓库内置 Python 串口助手(`.dsh/skills/serial-assistant/`,常驻 daemon + CLI) |
|
| 工具 | 仓库内置 Python 串口助手(`.dsh/skills/serial-assistant/`,常驻 daemon + CLI) |
|
||||||
|
|
||||||
**启动标记(`@test` 判据)**:
|
**启动标记(`/serial-boot-test` 判据)**:
|
||||||
- Required:`App init OK` / `Network Init` / `net init: OK` / `PHY: 100M FULL`
|
- Required:`App init OK` / `Network Init` / `net init: OK` / `PHY: 100M FULL`
|
||||||
- Forbidden:`FAIL` / `ERROR` / `HardFault` / `[ERR]`
|
- Forbidden:`FAIL` / `ERROR` / `HardFault` / `[ERR]`
|
||||||
|
|
||||||
@@ -1407,7 +1407,7 @@ mac=00:00:00:00:00:00
|
|||||||
| 网络重建生效 | 2~5 s(含等链路 ≤3 s) | 配置设计 |
|
| 网络重建生效 | 2~5 s(含等链路 ≤3 s) | 配置设计 |
|
||||||
| UI 刷新 | 10 ms 循环 | `ui_task_func` |
|
| UI 刷新 | 10 ms 循环 | `ui_task_func` |
|
||||||
| IWDG 停喂到复位 | ≤2.5 s | IWDG 设计 |
|
| 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` 实测 |
|
| **单拍采集消费** | ≤10 µs(DMA 搬运后实测 8 µs) | `adc_task` 实测 |
|
||||||
| **相量刷新** | 1 ms(每拍整窗重算 5 实例) | `phasor_run` |
|
| **相量刷新** | 1 ms(每拍整窗重算 5 实例) | `phasor_run` |
|
||||||
| **遥测快照周期** | 200 ms(经过时间分频) | `meas_tick` |
|
| **遥测快照周期** | 200 ms(经过时间分频) | `meas_tick` |
|
||||||
@@ -1459,14 +1459,14 @@ mac=00:00:00:00:00:00
|
|||||||
| 静态门禁 | pre-commit 钩子族:格式 / 编码 / 静态分析 / 文档链接 / 体积 / 生成物新鲜度 / 编号 | 本地 / CI |
|
| 静态门禁 | pre-commit 钩子族:格式 / 编码 / 静态分析 / 文档链接 / 体积 / 生成物新鲜度 / 编号 | 本地 / CI |
|
||||||
| 主机侧纯逻辑单测 | MSVC 离线编译真实源码 | 本地 / CI |
|
| 主机侧纯逻辑单测 | MSVC 离线编译真实源码 | 本地 / CI |
|
||||||
| 板端测试套件 | 板端套件(testTask 串口 `[PASS]/[FAIL]` 汇总) | 本地(需真机 + 串口) |
|
| 板端测试套件 | 板端套件(testTask 串口 `[PASS]/[FAIL]` 汇总) | 本地(需真机 + 串口) |
|
||||||
| 主机侧回归 | 模拟器像素 / 池水位回归 + 真机 `@test` 闭环(含探针) | 本地(模拟器免硬件;`@test` 需真机 + ST-LINK) |
|
| 主机侧回归 | 模拟器像素 / 池水位回归 + 真机 `/serial-boot-test` 闭环(含探针) | 本地(模拟器免硬件;`/serial-boot-test` 需真机 + ST-LINK) |
|
||||||
|
|
||||||
**构建 / 测试命令**:
|
**构建 / 测试命令**:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# 构建(@build):0 错 0 警
|
# 构建(/keil-build):0 错 0 警
|
||||||
& '.venv\Scripts\python.exe' .dsh/skills/keil-build/scripts/keil_build.py # 退出码 0=PASS / 1=构建失败 / 2=环境错误
|
& '.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=用法错误
|
& '.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
|
pre-commit run --all-files
|
||||||
@@ -1497,7 +1497,7 @@ pre-commit run --all-files
|
|||||||
| 构建 | Keil MDK-ARM v5(ARMCC) | CMake + SDL2 + MSVC |
|
| 构建 | Keil MDK-ARM v5(ARMCC) | CMake + SDL2 + MSVC |
|
||||||
| 共用源码 | `Drivers/BSP/UI/*`、`Drivers/BSP/NETCFG/net_cfg.c`、`App/cfg/cfg_file.c` | 同左 |
|
| 共用源码 | `Drivers/BSP/UI/*`、`Drivers/BSP/NETCFG/net_cfg.c`、`App/cfg/cfg_file.c` | 同左 |
|
||||||
| 平台后端 | `*_mcu.c`(CH395F/FatFS) | `*_sim.c`(Winsock/宿主文件) |
|
| 平台后端 | `*_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`),否则不参与编译。
|
> **新源文件必须双端注册**(Keil `.uvprojx` + `simulator/CMakeLists.txt`),否则不参与编译。
|
||||||
|
|
||||||
@@ -1716,7 +1716,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) |
|
| 静态门禁 | `pre-commit run --all-files` 全部通过(钩子族见 §7.4) |
|
||||||
| 主机侧单测 | **全部套件全绿**(看门狗、时钟、配置、相量、采集层、标定、遥测、测频、定值、元件、运行参数、104、SOE 等;套件与断言数随代码演进,以 `.pre-commit-config.yaml` 与 `test/host/` 为准) |
|
| 主机侧单测 | **全部套件全绿**(看门狗、时钟、配置、相量、采集层、标定、遥测、测频、定值、元件、运行参数、104、SOE 等;套件与断言数随代码演进,以 `.pre-commit-config.yaml` 与 `test/host/` 为准) |
|
||||||
| 栈门禁 | `scripts/stack_report.py` 9/9 PASS(余量 ≥ max(1024 B, 30%)) |
|
| 栈门禁 | `scripts/stack_report.py` 9/9 PASS(余量 ≥ max(1024 B, 30%)) |
|
||||||
@@ -1724,7 +1724,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
|||||||
| 双端构建 | Keil 与 CMake 均 0 错 0 警 |
|
| 双端构建 | Keil 与 CMake 均 0 错 0 警 |
|
||||||
| UI 回归 | 菜单行 + 内容页像素黄金 + 导航自检全 PASS |
|
| UI 回归 | 菜单行 + 内容页像素黄金 + 导航自检全 PASS |
|
||||||
|
|
||||||
### 10.2 上电自检(`@test`)
|
### 10.2 上电自检(`/serial-boot-test`)
|
||||||
|
|
||||||
| 层 | 判据 |
|
| 层 | 判据 |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -1735,13 +1735,13 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
|||||||
| Forbidden 标记 | 无 `FAIL` / `ERROR` / `HardFault` / `[ERR]`(四个 Fault 的崩溃日志都以 `[ERR]` 开头,出现即不通过) |
|
| Forbidden 标记 | 无 `FAIL` / `ERROR` / `HardFault` / `[ERR]`(四个 Fault 的崩溃日志都以 `[ERR]` 开头,出现即不通过) |
|
||||||
| 自检记录 | `selftest: no fault record` |
|
| 自检记录 | `selftest: no fault record` |
|
||||||
| 固件身份 | `fw build` 身份戳匹配 + 烧录后回读 `No difference found` |
|
| 固件身份 | `fw build` 身份戳匹配 + 烧录后回读 `No difference found` |
|
||||||
| 退出码 | `@test` → 0(PASS) |
|
| 退出码 | `/serial-boot-test` → 0(PASS) |
|
||||||
|
|
||||||
### 10.3 现场验收清单
|
### 10.3 现场验收清单
|
||||||
|
|
||||||
| # | 项目 | 方法 | 通过标准 |
|
| # | 项目 | 方法 | 通过标准 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1 | 上电自启动 | 断电上电 + 串口采集 | `@test` PASS,无 Forbidden 标记 |
|
| 1 | 上电自启动 | 断电上电 + 串口采集 | `/serial-boot-test` PASS,无 Forbidden 标记 |
|
||||||
| 2 | 网络连通 | PC ping 设备 IP | 通(PHY 100M FULL) |
|
| 2 | 网络连通 | PC ping 设备 IP | 通(PHY 100M FULL) |
|
||||||
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
|
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
|
||||||
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
|
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
|
||||||
|
|||||||
@@ -792,7 +792,7 @@ tTimer_NoUI 无压无流判据(CN_
|
|||||||
> **一条物理前置(已完成 2026-10-29)+ 一条实现纪律**:
|
> **一条物理前置(已完成 2026-10-29)+ 一条实现纪律**:
|
||||||
> ① **台架串口线已挪位**——日志口迁到 RS-232 侧后,台架那根线改接 CN1 的 RS232#1 端子
|
> ① **台架串口线已挪位**——日志口迁到 RS-232 侧后,台架那根线改接 CN1 的 RS232#1 端子
|
||||||
> (`CN1.10` 接适配器 TXD、`CN1.12` 接适配器 RXD、`CN1.7` 或 `CN1.8` 接 GND),台架日志口
|
> (`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 直连不了,
|
> `serial-assistant` 不必再手工指端口。这些端子是 **RS-232 电平**,TTL 的 CH340 直连不了,
|
||||||
> 用的是 USB 转 RS-232 适配器;接法与端子出处见 `docs/子系统设计/屏板链路设计说明.md` §3.2。
|
> 用的是 USB 转 RS-232 适配器;接法与端子出处见 `docs/子系统设计/屏板链路设计说明.md` §3.2。
|
||||||
> **这条要在第 16 条动代码之前完成。**
|
> **这条要在第 16 条动代码之前完成。**
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ git commit
|
|||||||
| 一把梭(上面多数静态项 + 主机单测) | `& '.venv\Scripts\python.exe' scripts/ci_local.py` | 非 0 就别抢跑(但它**不含** `stack_report` / `ram_baseline`,数字与安全边界要另跑) |
|
| 一把梭(上面多数静态项 + 主机单测) | `& '.venv\Scripts\python.exe' scripts/ci_local.py` | 非 0 就别抢跑(但它**不含** `stack_report` / `ram_baseline`,数字与安全边界要另跑) |
|
||||||
|
|
||||||
> ⚠ **这张表为什么必须存在**:`stack_report` 与 `ram_baseline --gate` 这类**根本不在钩子清单里**(它们要 Keil 构建产物,
|
> ⚠ **这张表为什么必须存在**:`stack_report` 与 `ram_baseline --gate` 这类**根本不在钩子清单里**(它们要 Keil 构建产物,
|
||||||
> 只能靠人在 `@build` 后跑),所以「人漏跑一次」直接等于「这道检查这一次不存在」。想机械化,先要解决「磁盘上的
|
> 只能靠人在 `/keil-build` 后跑),所以「人漏跑一次」直接等于「这道检查这一次不存在」。想机械化,先要解决「磁盘上的
|
||||||
> `.htm` / `.map` 属于最后一次构建的人」这个归因问题——与 §2.2「stash 覆盖面」那条同源,不是加一根钩子就完事
|
> `.htm` / `.map` 属于最后一次构建的人」这个归因问题——与 §2.2「stash 覆盖面」那条同源,不是加一根钩子就完事
|
||||||
> (`mem-budget` 今天红在「台账与 `.map` 不同码」正是同一个形状)。
|
> (`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) |
|
| 跑 `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` 都是显式文件列表 |
|
| 新 .c 双端注册 | `new-source-file`:`MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 都是显式文件列表 |
|
||||||
| `@build` 0 错误 0 警告 | 任何代码改动后的固定动作 |
|
| `/keil-build` 0 错误 0 警告 | 任何代码改动后的固定动作 |
|
||||||
| 主机侧单测新增三套并挂总闸 | 环、段格式、触发状态机都是纯逻辑,能被 `test-coverage` 真跑;别把该测的部分留给台架 |
|
| 主机侧单测新增三套并挂总闸 | 环、段格式、触发状态机都是纯逻辑,能被 `test-coverage` 真跑;别把该测的部分留给台架 |
|
||||||
| 菜单与字库生成物重跑 | 新增叶子改叶数 → 同步 `docs/UI设计/页面补齐规划.md` 与 `docs/任务规划/UI补齐.md` 的口径闭合,重跑 `tools/menu_gen/gen_menu.py` 与中文字库链,过 `generated-fresh` 与 `font-charset` |
|
| 菜单与字库生成物重跑 | 新增叶子改叶数 → 同步 `docs/UI设计/页面补齐规划.md` 与 `docs/任务规划/UI补齐.md` 的口径闭合,重跑 `tools/menu_gen/gen_menu.py` 与中文字库链,过 `generated-fresh` 与 `font-charset` |
|
||||||
| 台账回填与文档现状化 | `docs/任务规划/README.md` 登记簿销项时同步清理引用;`docs/子系统设计/故障录波设计说明.md` §11 的三处口径要回写 `docs/任务规划/数据可信.md` |
|
| 台账回填与文档现状化 | `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)。
|
`adc_cal_prot_blocked()`,`App/task/protect_task.c:245` 消费并打 INFO + 记 SOE)。
|
||||||
|
|
||||||
- 与"自检失败 / 采样故障闭锁"同构(`PROT_HF_LOCK_MASK` 那一套),**不新增机制**。
|
- 与"自检失败 / 采样故障闭锁"同构(`PROT_HF_LOCK_MASK` 那一套),**不新增机制**。
|
||||||
- 现有台架 `@test` 的期望会因此变化——**这是有意的**,任务卡里同步更新判据。
|
- 现有台架 `/serial-boot-test` 的期望会因此变化——**这是有意的**,任务卡里同步更新判据。
|
||||||
- 台架调试要旁路只能走编译期开关 `DTU_BENCH`,不进 cfg、不进 104。
|
- 台架调试要旁路只能走编译期开关 `DTU_BENCH`,不进 cfg、不进 104。
|
||||||
⚠ **`DTU_BENCH` 只旁路这一处**:它**不**旁路 §2.3 #1 的测频幅值门,也不产生任何读数出口。
|
⚠ **`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 % |
|
| **波形轮** | 读数是否只跟基波走 | 同幅值改方波、三角 | 同上 | 期望值 = **基波分量有效值**(同峰值下的基波 / 峰值比:方波 ≈ **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 拍乒乓滞后 |
|
| **相位轮** | 相位 | 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 风险行 |
|
| **测频轮** | 测频 | 同幅值轮的 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 的谐波轨立项 |
|
| **谐波轮** | 谐波轨 | 基波 + 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 的裁定。每条的两档跑法(周期行标量档 /
|
**先做幅值 / 波形 / 削顶三轮**(纯读数、无跨通道约定),相位 / 谐波次之,测频轮视 §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);发生器只当"波形干净、频率可锁"的信号源,它自己的幅值误差正是要被校的量 |
|
| **拿发生器的精度当标准用** | 用发生器自报 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(且负半周本就钉轨,只作削顶轮用) |
|
| **峰值当成有效值,顶到量程外** | 只看 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:` |
|
| **非整 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)。
|
> 已按合并后的 `.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/`)
|
> 变更: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)
|
> 适用工程: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`(本文只引用、不重复)。
|
> **本文档是内存管理主文档**(2026-09-21 合并):原《STM32F4-Base 内存使用分析报告》(现状审计/快照)与《内存使用分析规划》(执行规划,已全部闭环)已并入本文;**区域放置与 DMA 可达性规范**单列于 `内存内容分配策略.md`(本文只引用、不重复)。
|
||||||
> 快照产物:`data/ram_report_<日期>.md`(静态台账)、`data/stack_report_<日期>.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)
|
- 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 底线 → **抬栈**
|
> timeSyncTask 链 **1264 B**(同一控制台回显尾),512 words 余 784 B < ISR 底线 → **抬栈**
|
||||||
> defaultTask 1024→**1280**、timeSyncTask 512→**640**(堆 +1536 B,台账 §6 已登记);
|
> defaultTask 1024→**1280**、timeSyncTask 512→**640**(堆 +1536 B,台账 §6 已登记);
|
||||||
> 台架实测 `heap free=16400 / min_ever=8464`(与预算 −1536 吻合,>3072 告警线),
|
> 台架实测 `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 实测峰值 / 静态最坏链 |
|
| 任务 | 配置栈 | 字节 | 实际职责 | 2026-10-10 实测峰值 / 静态最坏链 |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
@@ -126,7 +126,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
|||||||
| testTask | `2048*4` | 8KB | 测试套件(产品构建不创建;存储套件深链需要) | 未创建 / 静态 496 B |
|
| testTask | `2048*4` | 8KB | 测试套件(产品构建不创建;存储套件深链需要) | 未创建 / 静态 496 B |
|
||||||
| defaultTask | `1280*4` | 5KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2152 B(余 41%)/ 静态 **3016 B**(栈余量收口时抬栈 1024→1280,2026-10-12) |
|
| 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) |
|
| 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%) |
|
| 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%) |
|
| 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 | 空闲 | 未观测(未取句柄) |
|
| 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` |
|
| 菜单根框建完 | 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` |
|
| 开页-关页往返(一次性残留 +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` |
|
| **最重页面峰值**(网口设置保存态) | **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` |
|
| 真机开机(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。
|
⚠ 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%**。
|
> **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,差额正是这些"锚点消失后连带消失的分支"。
|
> 对象级对照(`.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 时若上游已修可去掉。
|
> ⚠ **配套本地补丁**:`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`(含四类判定、端专属白名单与首次落地实测)。
|
> **长效防复发**:已建门禁 `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**,
|
> ⚠ **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
|
行为:挂在 defaultTask 主循环——首次 5s 后报告一次(bring-up 深链 mount/mkfs/探针/cfg/syslog
|
||||||
均已跑过),此后每 2s 采样,**只在出现更深峰值时打印**:既不破坏串口静默窗口,也不漏掉
|
均已跑过),此后每 2s 采样,**只在出现更深峰值时打印**:既不破坏串口静默窗口,也不漏掉
|
||||||
之后才走到的更深分支(卷修复、配置保存等)。输出纯 ASCII,便于 `@test` 采集:
|
之后才走到的更深分支(卷修复、配置保存等)。输出纯 ASCII,便于 `/serial-boot-test` 采集:
|
||||||
|
|
||||||
```
|
```
|
||||||
[STACK] <task> peak_used=<B>/<cfg> free_min=<B> (<pct>%)
|
[STACK] <task> peak_used=<B>/<cfg> free_min=<B> (<pct>%)
|
||||||
@@ -335,7 +335,7 @@ void vApplicationMallocFailedHook(void)
|
|||||||
要点:
|
要点:
|
||||||
|
|
||||||
- `uxTaskGetStackHighWaterMark` 返回**任务创建以来**的最小剩余栈(单位 word=4B);`used = 配置 − 剩余` 即该任务历史峰值栈占用。
|
- `uxTaskGetStackHighWaterMark` 返回**任务创建以来**的最小剩余栈(单位 word=4B);`used = 配置 − 剩余` 即该任务历史峰值栈占用。
|
||||||
- **测量必须覆盖全功能路径**(高水位是历史最小值,只反映跑过的路径):全菜单遍历、FTP 上传/下载大文件、CH395F 断链重连、NAND 格式化、时间同步。建议 `@test` 全回归 + 1 小时浸泡。
|
- **测量必须覆盖全功能路径**(高水位是历史最小值,只反映跑过的路径):全菜单遍历、FTP 上传/下载大文件、CH395F 断链重连、NAND 格式化、时间同步。建议 `/serial-boot-test` 全回归 + 1 小时浸泡。
|
||||||
- 未覆盖 idle/timer(内核静态小栈、纯循环,未取句柄);构建期静态侧见 §4.0.6。
|
- 未覆盖 idle/timer(内核静态小栈、纯循环,未取句柄);构建期静态侧见 §4.0.6。
|
||||||
|
|
||||||
#### 4.0.3 map 静态占用报告脚本
|
#### 4.0.3 map 静态占用报告脚本
|
||||||
@@ -360,7 +360,7 @@ void vApplicationMallocFailedHook(void)
|
|||||||
#### 4.0.6 静态栈分析 + 汇总工具(构建期,零目标依赖;2026-09-21 落地)
|
#### 4.0.6 静态栈分析 + 汇总工具(构建期,零目标依赖;2026-09-21 落地)
|
||||||
|
|
||||||
**UV4 每次构建已自动生成静态调用图** `MDK-ARM/STM32F407-Demo/STM32F407-Demo.htm`
|
**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`(其子树最坏累计深度)
|
条目自带 `Stack size N bytes`(该函数自身帧)与 `Max Depth = N`(其子树最坏累计深度)
|
||||||
以及完整调用链——即"不烧板子也能看到最坏链有多深、多宽"。
|
以及完整调用链——即"不烧板子也能看到最坏链有多深、多宽"。
|
||||||
|
|
||||||
@@ -459,7 +459,7 @@ void vApplicationMallocFailedHook(void)
|
|||||||
`name[49]/num[16]/rev[16]` 改 static + 原地反转、去掉 `HAL_Delay`(断言现场 tick 未必可信)。
|
`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;
|
代价与复核:静态 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**。
|
→ `stack_report.py` **6/6 PASS**。
|
||||||
|
|
||||||
顺带揪出并修掉的**两个静态虚高源**:① `dbg_console_drain` 故障路径调 `syslog_post` →
|
顺带揪出并修掉的**两个静态虚高源**:① `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(待评估)
|
#### 4.1.1 LVGL 内存池 40KB → 16KB(✅ 已执行 2026-10-15,省 24,576 B)→ 8KB(待评估)
|
||||||
|
|
||||||
- 依据:屏幕仅 160×160×1bpp,菜单为轻量控件树;LVGL 内存池只装对象/样式/字符串缓冲,与像素缓冲无关。40KB 来自模板默认值,无适配依据。
|
- 依据:屏幕仅 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=` 日志。
|
- 验证口径(本次即用它):`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,不勉强。
|
- **再降 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 不受影响)。
|
缩减后任务栈合计 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)
|
#### 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。
|
- **结果**: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 不变)。
|
- **代价(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)。
|
- **结论:原 ① 方案(ftpTask 涨到 1600 words)不再需要**,栈保持 1024 words(4096 B)。
|
||||||
|
|
||||||
> lftpd 属第三方代码(pre-commit 排除范围),只做最小侵入改动(每处仅加 `static` + 单行注释),改动点在本台账登记。
|
> lftpd 属第三方代码(pre-commit 排除范围),只做最小侵入改动(每处仅加 `static` + 单行注释),改动点在本台账登记。
|
||||||
@@ -645,8 +645,8 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
|
|||||||
1. **RAM 预算台账**(✅ **2026-10-12 落地** `docs/内存管理/RAM预算台账.md`,模板见 §8.2):记录每个 Region/缓冲/栈的计划值、实测值、变更历史;每次改内存相关代码必须更新
|
1. **RAM 预算台账**(✅ **2026-10-12 落地** `docs/内存管理/RAM预算台账.md`,模板见 §8.2):记录每个 Region/缓冲/栈的计划值、实测值、变更历史;每次改内存相关代码必须更新
|
||||||
2. **AGENTS.md 增补**(✅ 2026-09-21 已完成:"内存预算紧张"条目已含 `ram_report.py`/`stack_report.py` 数据源与"改栈前后各跑一次"要求)
|
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 对象表
|
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"`,把堆余量下限纳入自动化回归
|
4. **/serial-boot-test 扩展**:`verify_log.py` 增加可选 Required 标记 `"[MEM] heap free"`,把堆余量下限纳入自动化回归
|
||||||
5. **map diff 常态化**:每次 `@build` 后跑 `ram_report.py`,ZI 增量 >1KB 的改动必须在提交说明中解释
|
5. **map diff 常态化**:每次 `/keil-build` 后跑 `ram_report.py`,ZI 增量 >1KB 的改动必须在提交说明中解释
|
||||||
|
|
||||||
## 5. 预期效果
|
## 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`) |
|
| 步骤 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 浸泡待补 |
|
| 步骤 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 完成) |
|
| 步骤 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 迁移与门禁宏不再排期 |
|
| 步骤 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 实测修正,重开时可直接用) |
|
| 步骤 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` |
|
| 步骤 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. 风险与注意事项
|
## 7. 风险与注意事项
|
||||||
|
|
||||||
@@ -758,7 +758,7 @@ V1 链接视图(map:Grand Totals / Image component sizes / Symbol Table)
|
|||||||
|
|
||||||
V4 逐项相加须与 V1 的 `Image component sizes` 逐 .o 吻合;不等则以 V1 为准并记录差异原因。
|
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 图表与台账重生成
|
### 8.5 图表与台账重生成
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
无 `.map` 时自适应跳过(CI 侧跳过 `mem-numbers` 的边界 = `scripts/check_size_gate.py` 的 docstring,唯一正文)。
|
无 `.map` 时自适应跳过(CI 侧跳过 `mem-numbers` 的边界 = `scripts/check_size_gate.py` 的 docstring,唯一正文)。
|
||||||
- **阈值依据**:92% 由 2026-09 体系评审提出、2026-10-12 落地为门禁(提出时的报告条目已随销项删除,闭环说明见 `RAM使用分析与优化方案.md` §4 阶段 3「长效机制」与本文)——留 ~8% 给突发缓冲与栈抬升,
|
- **阈值依据**:92% 由 2026-09 体系评审提出、2026-10-12 落地为门禁(提出时的报告条目已随销项删除,闭环说明见 `RAM使用分析与优化方案.md` §4 阶段 3「长效机制」与本文)——留 ~8% 给突发缓冲与栈抬升,
|
||||||
触线即触发排期,而不是静默逼近溢出。
|
触线即触发排期,而不是静默逼近溢出。
|
||||||
- **软提醒**(不拦提交):tsv 末行落后于 `.map`(忘跑 `--update`)、`.map` 早于最新源码(忘 `@build`)。
|
- **软提醒**(不拦提交):tsv 末行落后于 `.map`(忘跑 `--update`)、`.map` 早于最新源码(忘 `/keil-build`)。
|
||||||
- **子系统归集为参考值**:按对象名前缀归集(口径见 §2),合计与区域总量可差几十字节
|
- **子系统归集为参考值**:按对象名前缀归集(口径见 §2),合计与区域总量可差几十字节
|
||||||
(链接器填充行与 `UNINIT_CCM` 等小区分段所致);区域行才是门禁事实源。
|
(链接器填充行与 `UNINIT_CCM` 等小区分段所致);区域行才是门禁事实源。
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名
|
|||||||
|
|
||||||
## 4. 趋势基线(`scripts/ram_baseline.tsv`)
|
## 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 对象;同日重跑覆盖当日行)。
|
追加一行(Code/RO/RW/ZI + 三区水位 + Top-10 RW+ZI 对象;同日重跑覆盖当日行)。
|
||||||
- **看趋势 / 找增量**:`scripts/ram_baseline.py --trend`——高亮较上一行 RW+ZI 增长 >1 KB 的对象,
|
- **看趋势 / 找增量**:`scripts/ram_baseline.py --trend`——高亮较上一行 RW+ZI 增长 >1 KB 的对象,
|
||||||
即 `docs/内存管理/RAM使用分析与优化方案.md` §4.0.3 "ZI 增量 >1KB 必须在提交说明中解释"的机械化;提交说明直接抄 `--trend` 输出行。
|
即 `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 / 堆大小变更后,依次通过以下全部关卡方可认定生效:
|
任何 scatter / CCM / 堆大小变更后,依次通过以下全部关卡方可认定生效:
|
||||||
|
|
||||||
1. `@build`(0 错误 0 警告)
|
1. `/keil-build`(0 错误 0 警告)
|
||||||
2. 产品构建 `@test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):标记校验 PASS,且**零 `[ERR]`**(默认 Forbidden 已含 `[ERR]`)
|
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)
|
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"`)
|
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**
|
5. 恢复跑(`ENABLE_STORAGE_RECOVERY_TESTS`):期望 **3/3 PASSED**
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
| # | 方案 | 省 RAM | 区域 | 风险 | 验证 |
|
| # | 方案 | 省 RAM | 区域 | 风险 | 验证 |
|
||||||
|---|---|---:|---|---|---|
|
|---|---|---:|---|---|---|
|
||||||
| `s_copy_buf` 分块 | `s_copy_buf` 2048→512:GC 拷贝改分段 read→program_load(`program_load`/`read_from_cache` 支持列地址);纯 RAM 优化(GC 拷贝暂存减半) | **−1,536B(可选,不计入当前预期)** | SRAM1 | 低(仅动拷贝机制) | 待 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 降档) |
|
| `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 下的语义须台架实证) | 仅评估,暂缓 |
|
| 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 仍紧时才做 |
|
| lftpd 数据块减半 | lftpd 数据块 2×1024→512 | −1,024B | SRAM1 | 吞吐 −~30% | RAM 仍紧时才做 |
|
||||||
| 共享静态 FIL | syslog/cfg 共享一个静态 FIL(同任务串行) | 净≈0(静态+528/栈−528) | — | 不推荐:`FF_FS_TINY` 那条已覆盖同一收益面 | — |
|
| 共享静态 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)|
|
| 主机侧纯逻辑单测 | `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 |
|
| 静态门禁 | `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 限制与待办(如实登记)
|
## 8. V0.1 限制与待办(如实登记)
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ flowchart TB
|
|||||||
|
|
||||||
- 台架须用 **USB 转 RS-232 适配器**(TTL 的 CH340 直连不了,且 RS-232 的 ±电平可能打坏 `U13` TPT3232E 或适配器):
|
- 台架须用 **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`)。
|
适配器 `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 端子;
|
- RS232#1 **没有 TTL 出口**(`ST_TX1`/`ST_RX1` 只连 `U9` 隔离器与 MCU),唯一外部出口是 CN1 的 RS-232 端子;
|
||||||
`U14` 是 RS232#3 的网口座,本通道用不上。
|
`U14` 是 RS232#3 的网口座,本通道用不上。
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ flowchart TB
|
|||||||
|
|
||||||
## 10. 坑账本
|
## 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=` 命名。
|
会把启动自检判红,本模块统计行用 `kerr=` / `abor=` / `off=` 命名。
|
||||||
2. **`ReceiveToIdle_IT` 是一次性的**:漏重挂 = 链路永久静默,且没有任何报错;ORE 不自己清,一次噪声打死接收。
|
2. **`ReceiveToIdle_IT` 是一次性的**:漏重挂 = 链路永久静默,且没有任何报错;ORE 不自己清,一次噪声打死接收。
|
||||||
3. **NE/FE 非阻塞、ORE 阻塞**:重挂判据用 `RxState == READY` 而不是错误位;台架"NE 每帧出现 + `rx_ok` 照涨"
|
3. **NE/FE 非阻塞、ORE 阻塞**:重挂判据用 `RxState == READY` 而不是错误位;台架"NE 每帧出现 + `rx_ok` 照涨"
|
||||||
|
|||||||
+10
-10
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> **版本**:v1.8(已实施;修订史见文末 §11「实施时间线」)
|
> **版本**: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)
|
> **日期**: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「平台约束」
|
> **关联文档**:`项目架构总览.md` · `运行与自检记录设计说明.md`(姊妹模块:持久化日志,同一写者任务) · `docs/测试说明/专项验证/链路延迟测试说明.md`(D6 日志税 / 步骤 8 非阻塞日志,需求出处) · AGENTS.md「平台约束」
|
||||||
|
|
||||||
## 1. 背景:实测故障与根因
|
## 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 逐字节读取 |
|
| F2 | HardFault(脏指针直接进入 UART) | BFAR=0x10010000(恰为 CCM 末尾+1),PC 落在 `HAL_UART_Transmit` 的缓冲读取处 | 崩溃上报路径自身再崩;被破坏的字符串指针被 UART 逐字节读取 |
|
||||||
|
|
||||||
前兆:2026-09-22 链路延迟测试实测**开机连续丢行**(多任务并发 `printf` → 非重入 `fputc`/`HAL_UART_Transmit` 丢整行,~2s 自恢复;`链路延迟测试说明.md` §9.2)——同类竞态的温和形态,当时列为待办(日志非阻塞化)。
|
前兆: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;写者发出的缓冲恒为环形缓冲或模块本地静态缓冲 |
|
| G2 | 指针可控:进入 `HAL_UART_Transmit` 的缓冲恒受本模块控制 | 调用方指针只经拷贝入环、到不了 UART;写者发出的缓冲恒为环形缓冲或模块本地静态缓冲 |
|
||||||
| G3 | 不新增任务、不用 C 库堆、不新增内核对象 | 复用既有 defaultTask 作写者;环形缓冲 = 4KB 静态 BSS;复用 `sys_evt` 事件标志位 |
|
| G3 | 不新增任务、不用 C 库堆、不新增内核对象 | 复用既有 defaultTask 作写者;环形缓冲 = 4KB 静态 BSS;复用 `sys_evt` 事件标志位 |
|
||||||
| G4 | 内核启动前 / 写者未注册 / ISR / 异常钩子路径不降级 | 回退:有界直发(`dbg_console_write_raw`,≤256B 截断、不等待、不死锁、零内核对象) |
|
| 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 约束
|
### 2.2 约束
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ flowchart LR
|
|||||||
| 写者未注册(内核启动 → attach) | 任何任务都可能记日志(2026-10-26 起这是**常态而非违规**) | **整行独占**(行级单一所有者,§4.4.1):同一时刻只有一个上下文在写 `CON_UART->DR`,抢不到独占的整行转入环形缓冲延后发 → 交错结构性消除,且不丢行 |
|
| 写者未注册(内核启动 → attach) | 任何任务都可能记日志(2026-10-26 起这是**常态而非违规**) | **整行独占**(行级单一所有者,§4.4.1):同一时刻只有一个上下文在写 `CON_UART->DR`,抢不到独占的整行转入环形缓冲延后发 → 交错结构性消除,且不丢行 |
|
||||||
| 写者已注册(稳态) | 任务上下文唯一调用者 = 写者 | 任务上下文零竞争;残余窗口 = ISR 直发撞写者发送中(丢失、不挂死,§7) |
|
| 写者已注册(稳态) | 任务上下文唯一调用者 = 写者 | 任务上下文零竞争;残余窗口 = 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-12(B6,启动锚点)与 2026-10-26(未标定闭锁边沿,见 §4.4.1)两次被同一个窗口咬到,
|
||||||
> 因为"这条行要不要留痕"是**业务边沿**决定的,调用方无法预知它是否落在未注册期。2026-10-26 起该窗口改由**机制**兜住
|
> 因为"这条行要不要留痕"是**业务边沿**决定的,调用方无法预知它是否落在未注册期。2026-10-26 起该窗口改由**机制**兜住
|
||||||
@@ -156,7 +156,7 @@ flowchart TD
|
|||||||
|
|
||||||
- **整行原子入环**:拷贝与 head 推进在同一次 `taskENTER_CRITICAL` 内完成 → 写者永不见"半行";临界区时长有界(≤192B 拷贝,微秒级)。
|
- **整行原子入环**:拷贝与 head 推进在同一次 `taskENTER_CRITICAL` 内完成 → 写者永不见"半行";临界区时长有界(≤192B 拷贝,微秒级)。
|
||||||
- **自等待规避**:写者自身记录日志时先 `drain` 腾空(否则"等写者腾出的空间"会等自己,死锁);腾空后入环必立即成功。
|
- **自等待规避**:写者自身记录日志时先 `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 超时兜底(事件位异常也不滞留日志)。
|
- **唤醒**:入环后置 `SYS_EVT_CONSOLE_TX` 命令位 → 写者立即被唤醒(零时延);写者循环另有 50ms 超时兜底(事件位异常也不滞留日志)。
|
||||||
|
|
||||||
### 4.3 写者路径(`attach` / `drain`)
|
### 4.3 写者路径(`attach` / `drain`)
|
||||||
@@ -250,7 +250,7 @@ sequenceDiagram
|
|||||||
2. 判据**只用原子性**,不碰 `[ERR]` 计数:① 崩溃行**一整行**逐字节完整(`[ERR] MEMFAULT PC=… LR=… xPSR=… R0..R3=… CFSR=… HFSR=…` 字段齐全、无邻居行字节插入);② 该行在本次启动日志里**恰好出现一次**(既不丢也不重);③ `CONSOLE dropped` = 0。首个字符应为 `[`;前面若出现 ≤1 个移位寄存器残留字节,记为已知残余,不判失败。
|
2. 判据**只用原子性**,不碰 `[ERR]` 计数:① 崩溃行**一整行**逐字节完整(`[ERR] MEMFAULT PC=… LR=… xPSR=… R0..R3=… CFSR=… HFSR=…` 字段齐全、无邻居行字节插入);② 该行在本次启动日志里**恰好出现一次**(既不丢也不重);③ `CONSOLE dropped` = 0。首个字符应为 `[`;前面若出现 ≤1 个移位寄存器残留字节,记为已知残余,不判失败。
|
||||||
3. ⚠ **不得在同一条腿要求 `[ERR]` = 0**:崩溃上报本身就以 `[ERR]` 起头,在同一路径上要求「无 `[ERR]`」等于要求这条路径别工作(本次就是这么被台架纠正的)。`[ERR]` 零命中的判据只属于腿 B 之外的**常规回归腿**(3~5 次仅复位不重烧 + 两行整行锚定)。
|
3. ⚠ **不得在同一条腿要求 `[ERR]` = 0**:崩溃上报本身就以 `[ERR]` 起头,在同一路径上要求「无 `[ERR]`」等于要求这条路径别工作(本次就是这么被台架纠正的)。`[ERR]` 零命中的判据只属于腿 B 之外的**常规回归腿**(3~5 次仅复位不重烧 + 两行整行锚定)。
|
||||||
4. **故意复位的注入腿不得并入「连续 N 次仅复位不重烧」的计数**:注入会主动 `NVIC_SystemReset()`,混进常规序列会把正常行为判成异常(栈溢出钩子同理)。两条腿分开跑、分开记台账。
|
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`)
|
### 4.5 `fputc` 重定向(MicroLIB `printf`)
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ sequenceDiagram
|
|||||||
| 单行 192B(最坏) | ≈17 ms | `DBG_CON_LINE_MAX` |
|
| 单行 192B(最坏) | ≈17 ms | `DBG_CON_LINE_MAX` |
|
||||||
| 生产者最长等待 | 200 ms(200 × 1ms tick) | ≈9 个发送块的时间,稳态下环恒被及时排空 |
|
| 生产者最长等待 | 200 ms(200 × 1ms tick) | ≈9 个发送块的时间,稳态下环恒被及时排空 |
|
||||||
| 写者兜底周期 | 50 ms | 事件位失效时日志滞留上限 |
|
| 写者兜底周期 | 50 ms | 事件位失效时日志滞留上限 |
|
||||||
| 实测日志时延 | 事件唤醒级(无丢失) | 2026-09-25 @test:67 行开机日志完整、无交错、无丢行告警 |
|
| 实测日志时延 | 事件唤醒级(无丢失) | 2026-09-25 /serial-boot-test:67 行开机日志完整、无交错、无丢行告警 |
|
||||||
|
|
||||||
## 5. API 参考
|
## 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` 钉死 |
|
| 未注册期其他任务直发 | **已消除**(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) |
|
| 崩溃上报(四个 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` —— 六层分层 / 启动时序 / 6 任务(写者任务与就绪屏障位置)
|
||||||
- `运行与自检记录设计说明.md` —— sys_log 持久化日志(姊妹模块:同写者任务、不同通道)
|
- `运行与自检记录设计说明.md` —— sys_log 持久化日志(姊妹模块:同写者任务、不同通道)
|
||||||
- `docs/测试说明/专项验证/链路延迟测试说明.md` —— D6 日志税 / 步骤 8 非阻塞日志(需求出处与 09-22 丢行实证)
|
- `docs/测试说明/专项验证/链路延迟测试说明.md` —— D6 日志税 / 步骤 8 非阻塞日志(需求出处与 09-22 丢行实证)
|
||||||
- `docs/测试说明/流程与规范/自动化测试流程.md` —— `@test` 标记体系(Required / Forbidden 模式,`[ERR]` 语义)
|
- `docs/测试说明/流程与规范/自动化测试流程.md` —— `/serial-boot-test` 标记体系(Required / Forbidden 模式,`[ERR]` 语义)
|
||||||
- AGENTS.md —— 平台约束(内核前禁内核对象 / C 堆禁令 / 事件位语义 / 内存预算)
|
- AGENTS.md —— 平台约束(内核前禁内核对象 / C 堆禁令 / 事件位语义 / 内存预算)
|
||||||
|
|
||||||
## 附录:日志静默观察项 × 步骤 8 合并评估(2026-10-25,道F 批 2 / task-18,零代码改动)
|
## 附录:日志静默观察项 × 步骤 8 合并评估(2026-10-25,道F 批 2 / task-18,零代码改动)
|
||||||
@@ -457,7 +457,7 @@ D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏
|
|||||||
- 判据②(静默 → 可观测)= **已具备**:控制台三本账 `dbg_console_dropped /
|
- 判据②(静默 → 可观测)= **已具备**:控制台三本账 `dbg_console_dropped /
|
||||||
dbg_console_tx_fault / dbg_console_tx_timeout` + syslog 的
|
dbg_console_tx_fault / dbg_console_tx_timeout` + syslog 的
|
||||||
`queue full, drop` / `preboot full, drop` / `hold-over drop`
|
`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)
|
- 判据①(复现 49s 并定位):V5 30min 未复现;实测(`docs/测试说明/板级测试/实机测试实施指南.md` §7.2)
|
||||||
运行期 >10s 静默 24 处**全部落在 e02_probe 窗口、长度 ≈28s**——其
|
运行期 >10s 静默 24 处**全部落在 e02_probe 窗口、长度 ≈28s**——其
|
||||||
同源阻塞机制(`drop_conn` 同步占用 netTask 400-600ms)已随登记式拆链改造修复
|
同源阻塞机制(`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` 同口径,别塞公共头) |
|
| 跨工具链分叉(实证,写进来是为了下一个模块别重踩) | 两份录波 .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` 留痕(不是每拍刷屏)、且段号不推进(下一段仍写同号文件) |
|
| 板级 | 信号发生器注 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` 重生成 + 字库链 |
|
| 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` 钩子 |
|
| 台账 | R6 三处同步 + `scripts/ram_baseline.py --update` + `mem-numbers` 与 `mem-budget` 钩子 |
|
||||||
| 板级(读路径耗时,2026-10-29 补) | 进回放页后**连续切段 + 连续切道**,读 `task_prof` 里 uiTask 的单轮工作耗时峰值与 800 ms 窗的余量;**不要为此新加埋点**(现成读数够用)。判读:峰值贴着窗走就是读路径该再加节流(先想缓存与分次,别去动心跳窗) |
|
| 板级(读路径耗时,2026-10-29 补) | 进回放页后**连续切段 + 连续切道**,读 `task_prof` 里 uiTask 的单轮工作耗时峰值与 800 ms 窗的余量;**不要为此新加埋点**(现成读数够用)。判读:峰值贴着窗走就是读路径该再加节流(先想缓存与分次,别去动心跳窗) |
|
||||||
| 图与生成物 | 本设计文档的配图生成器受 `generated-fresh` 逐字节幂等约束;脚本与产物同一笔提交 |
|
| 图与生成物 | 本设计文档的配图生成器受 `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")`**(只记故障,符合既有约定) |
|
| `selftest.log` | **看门狗/窗口看门狗复位 → `syslog_post(SYSLOG_SELFTEST, "watchdog reset")`**(只记故障,符合既有约定) |
|
||||||
| `run.log` | 仍是"上电 / 重启"二分类(靠末条 epoch 时间窗推断),**未接入硬件复位原因**——细粒度原因不上 `run.log`/UI(避免中文字符集重生成),见 §9 O2 |
|
| `run.log` | 仍是"上电 / 重启"二分类(靠末条 epoch 时间窗推断),**未接入硬件复位原因**——细粒度原因不上 `run.log`/UI(避免中文字符集重生成),见 §9 O2 |
|
||||||
| `/log/wdev.log` | 停喂现场证据(`sys_log_evidence()`,>8 KB 先 unlink 重建,防长跑无界增长) |
|
| `/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 命中才打,一次复位只报一次) |
|
| `PREVRUN seg=… seq=… t=…` | 开机(裸 UART) | 上一轮 defaultTask 最后进段(magic 命中才打,一次复位只报一次) |
|
||||||
| `TICKRUN uw=… n=… duw=…` | 紧随其后 | 上一轮 HAL 时基终值;`duw≈0` → TIM7 与主循环同刻停摆;`duw≈2000+` → 另有阻塞点 |
|
| `TICKRUN uw=… n=… duw=…` | 紧随其后 | 上一轮 HAL 时基终值;`duw≈0` → TIM7 与主循环同刻停摆;`duw≈2000+` → 另有阻塞点 |
|
||||||
| `[WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms` | `task_wd_start()` | IWDG 已启动(真机自检必查标记) |
|
| `[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] HARDFAULT / MEMFAULT / BUSFAULT / USAGEFAULT PC=… CFSR=… [BFAR=] [MMFAR=]` | 4 个 Fault | 崩溃现场 + 故障名归因 |
|
||||||
| `[ERR] STACK OVERFLOW task=<name>` / `[ERR] MALLOC FAILED …` | 内核钩子 | 栈溢出(复位)/ 堆耗尽(仅告警) |
|
| `[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 字符串陷阱)。
|
> 日志标签:`[APP]` = `app_main.c` 的 `DBG_TAG`;`[WD]` = `task_watchdog.c` 的 `DBG_TAG`。全部纯 ASCII(规避 ARMCC CP936 字符串陷阱)。
|
||||||
|
|
||||||
### 6.2 判读顺序(现场"设备反复重启")
|
### 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) |
|
| 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` 全内核可达);行为经编译与代码路径核对,未做定向注入用例 |
|
| V3 | 断言失败 | 🟡 未单独注入 | 宏与钩子已在用(`configASSERT` 全内核可达);行为经编译与代码路径核对,未做定向注入用例 |
|
||||||
| V4 | **关中断死循环** | ✅ **2026-10-03 闭环** | `__disable_irq()` + `while(1)` → 每 ~4.9 s 稳定复位(20+ 周期,`IWDG`)→ 证实 IWDG 独立于中断 |
|
| 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` 关键项、钩子落点三项)。
|
- **清单 21 项**覆盖 P0/P1 全部防线(iwdg 延后启动、4 个 Fault Handler 采集、故障使能、两个加固宏、断言改造 ×2、看门狗模块与工程挂载、复位原因锁存、HAL 驱动与使能宏、`.ioc` 关键项、钩子落点三项)。
|
||||||
- **漂移检测**:各项内容哈希存 `.cubemx_survival_baseline.json`;有意改动确认后 `--write` 重建。
|
- **漂移检测**:各项内容哈希存 `.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` **只能断言代码符号**(锚点注释会被剥掉,断言它等于没断言)。
|
- **注释过滤**:断言前剥掉 C 注释,故 `protected` 项的 `must_contain` **只能断言代码符号**(锚点注释会被剥掉,断言它等于没断言)。
|
||||||
- 已挂 pre-commit `cubemx-survival`(改 `.ioc`/`Src/`/`Inc/`/`task_watchdog`/`.uvprojx` 触发)。
|
- 已挂 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 的原始设计口径不变)
|
> **版本**:v1.3(§4.4 新增"卷锁 fail-open 边沿补投自检记录"一条,道G task-40;v1.2 为 §4.3 随七类记录更新,v1.1 的原始设计口径不变)
|
||||||
> **日期**:2026-09-14(v1.2 更新 2026-10-26)
|
> **日期**: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)
|
> **关联文档**:`项目架构总览.md` · `STM32F4-Base存储架构说明.md` · `系统墙钟时间维护说明.md` · `UI设计/内容页架构.md`(如存在)· `docs/UI设计/mockups/`(待机显示页面.jpg / 运行记录页.jpg / 自检记录页.jpg / 密码页面实际机器的示例图片.jpg)
|
||||||
|
|
||||||
## 1. 背景与需求
|
## 1. 背景与需求
|
||||||
@@ -186,7 +186,7 @@ defaultTask(唯一写者,挂载成功后):
|
|||||||
复位 latch 后故障复发仍会再投(否则第二次开机起永久静默)。文本形如
|
复位 latch 后故障复发仍会再投(否则第二次开机起永久静默)。文本形如
|
||||||
`vollock failopen=3`(累计值就是证据,不带态号),落 `selftest.log`。
|
`vollock failopen=3`(累计值就是证据,不带态号),落 `selftest.log`。
|
||||||
⚠ 三条约束(与 task-23「日志让路不计 `[ERR]`」同一根判据):① 不在失败调用点就地打日志;
|
⚠ 三条约束(与 task-23「日志让路不计 `[ERR]`」同一根判据):① 不在失败调用点就地打日志;
|
||||||
② 不打 DBG 错级、不打 `[ERR]`——`[ERR]` 是"设备出故障"的唯一信号通道(`@test` 的 Forbidden
|
② 不打 DBG 错级、不打 `[ERR]`——`[ERR]` 是"设备出故障"的唯一信号通道(`/serial-boot-test` 的 Forbidden
|
||||||
也依赖它),塞进可观测性事件就同时不再是故障、不再是禁令;③ 记录自带的时间戳是
|
也依赖它),塞进可观测性事件就同时不再是故障、不再是禁令;③ 记录自带的时间戳是
|
||||||
**被观测到的时刻**(粒度 = 一轮 `syslog_drain()`),不是发生时刻,所以本模块刻意不再另造
|
**被观测到的时刻**(粒度 = 一轮 `syslog_drain()`),不是发生时刻,所以本模块刻意不再另造
|
||||||
`obs_first_ms`/`obs_last_ms` 字段去"看起来更精确"。主机判据:`TC-SYSLOG-137` 至
|
`obs_first_ms`/`obs_last_ms` 字段去"看起来更精确"。主机判据:`TC-SYSLOG-137` 至
|
||||||
@@ -253,7 +253,7 @@ defaultTask 挂载成功、排空待发队列**之后**写一条:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- 阈值 `SYSLOG_REBOOT_WINDOW_S = 600`:断电 <10min 会被误判为重启——无掉电检测硬件时该歧义不可消除,取工程折中并在此声明。
|
- 阈值 `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 项表)
|
### 5.4 R4 自检记录(v1 项表)
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ defaultTask 挂载成功、排空待发队列**之后**写一条:
|
|||||||
| 记录页像素回归 | `--keys` 导航至 node 39/40 → 截图 + 文本断言(时间戳行/描述行/页脚分页),纳入 ui-verify 门禁 |
|
| 记录页像素回归 | `--keys` 导航至 node 39/40 → 截图 + 文本断言(时间戳行/描述行/页脚分页),纳入 ui-verify 门禁 |
|
||||||
| 密码错误提示 | `--keys` 输错密码序列 → 截图断言"密码输入错误"可见;再按方向键 → 提示消失 |
|
| 密码错误提示 | `--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)`**;
|
Required:`App init OK`、`Network Init`、`net init: OK`、**`runlog boot entry:`**(装置上电记录 / 装置重启记录)、**`UI standby: ready (idle=60s)`**;
|
||||||
Forbidden:`FAIL`/`ERROR`/`HardFault`。
|
Forbidden:`FAIL`/`ERROR`/`HardFault`。
|
||||||
@@ -341,5 +341,5 @@ Forbidden:`FAIL`/`ERROR`/`HardFault`。
|
|||||||
| 步骤 5 | 待机屏 `ui_standby`(空闲检测 + 覆盖层)+ 键挂钩 | ui_menu/ui_content_page 各一行通知 |
|
| 步骤 5 | 待机屏 `ui_standby`(空闲检测 + 覆盖层)+ 键挂钩 | ui_menu/ui_content_page 各一行通知 |
|
||||||
| 步骤 6 | 记录页 ×2 + 内容页框架接线(枚举/描述符表/node 映射) | ui_page_runlog/selftest、ui_content_page.h/.c、ui_menu.c |
|
| 步骤 6 | 记录页 ×2 + 内容页框架接线(枚举/描述符表/node 映射) | ui_page_runlog/selftest、ui_content_page.h/.c、ui_menu.c |
|
||||||
| 步骤 7 | 双端注册新文件(uvprojx + CMakeLists)+ 构建 0/0 | new-source-file skill |
|
| 步骤 7 | 双端注册新文件(uvprojx + CMakeLists)+ 构建 0/0 | new-source-file skill |
|
||||||
| 步骤 8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 @test | 测试通过记录 |
|
| 步骤 8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 /serial-boot-test | 测试通过记录 |
|
||||||
| 步骤 9 | 文档同步(项目架构总览、AGENTS.md、本文档状态) | docs |
|
| 步骤 9 | 文档同步(项目架构总览、AGENTS.md、本文档状态) | docs |
|
||||||
|
|||||||
@@ -495,13 +495,13 @@ void adc_cfg_init(void)
|
|||||||
| 阶段 | 内容 | 涉及文件 | 验收标准 |
|
| 阶段 | 内容 | 涉及文件 | 验收标准 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **P1** | 设计文档 + 架构图 | 本文档 + `gen_cfg_arch_svg.py` + SVG | 用户确认 §2 决策 |
|
| **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(无死锁) |
|
| **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 `@build` 0/0;模拟器生成 `sim_config.ini` 且编辑回读一致;旧档 NCFG 自动迁移三态正确 —— **已实测通过**(工厂模板 / 编辑回读 / 非法键回退 / 迁移三态 / 双端 0 警告;`cfg_file.c` 含中文注释块 → UTF-8 **带 BOM**) |
|
| **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) | `@build`;`@test` 启动新标记(`cfg file: loaded OK` 等);FTP 上传无半截可见(rename 原子) |
|
| **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` 自动迁移 + 轮询沉降重载 + 外科保存无损回环 —— **已实测通过** |
|
| **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"旧表述残留 |
|
| **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) |
|
| **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 发现的收尾问题并入最近一次提交。
|
**建议提交边界**: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: factory template written` | 首启 / 删文件 | 条件(手工) |
|
||||||
| `cfg file: legacy NCFG migrated` | 旧文件迁移 | **已作废**(2026-10-10 旧档迁移删除,该标记不再产生) |
|
| `cfg file: legacy NCFG migrated` | 旧文件迁移 | **已作废**(2026-10-10 旧档迁移删除,该标记不再产生) |
|
||||||
| `cfg file: saved OK` | UI 保存 | 条件(手工) |
|
| `cfg file: saved OK` | UI 保存 | 条件(手工) |
|
||||||
|
|||||||
+2
-2
@@ -165,7 +165,7 @@
|
|||||||
|
|
||||||
1. **昵称式引用**(「内存方案 §2.1」)与正文里的裸文件名散文 → 落盘后 `git grep -n '<旧文件名>'` 扫一遍;
|
1. **昵称式引用**(「内存方案 §2.1」)与正文里的裸文件名散文 → 落盘后 `git grep -n '<旧文件名>'` 扫一遍;
|
||||||
2. **生成物**(`docs/images/引用关系图.*`)不改写 → 重跑 `& 'tools\refgraph\gen_refgraph.bat'`;
|
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):根因是
|
4. ~~搬整目录时会改写目录内部文件的内容~~ **已修**(2026-10-30,回归见 `--selftest` 案例 A):根因是
|
||||||
「相对引用文件所在目录」那条解析规则不检验目标是否存在,于是内部文件里**不是路径的斜杠片段**被
|
「相对引用文件所在目录」那条解析规则不检验目标是否存在,于是内部文件里**不是路径的斜杠片段**被
|
||||||
当成旧目录下的子路径并插上目录名 —— 分层那次 `1/1 PASSED` 改成 `test_data/1/1 PASSED`、日志日期占位
|
当成旧目录下的子路径并插上目录名 —— 分层那次 `1/1 PASSED` 改成 `test_data/1/1 PASSED`、日志日期占位
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
> **改栈流程**:先改 `Src/freertos.c` 的 `osThreadAttr_t` → 跑 `gen_task_table.py --write` → 提交。
|
> **改栈流程**:先改 `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` 让脚本重算。
|
> 钩子报不一致时**不要手改受管区**——跑 `--write` 让脚本重算。
|
||||||
|
|
||||||
#### 陈旧数字扫描(`mem-numbers --scan`)
|
#### 陈旧数字扫描(`mem-numbers --scan`)
|
||||||
|
|||||||
@@ -610,9 +610,9 @@ FTP 根目录 = 卷根,lftpd 是卷的网络文件入口(`Drivers/BSP/NET/lf
|
|||||||
lftpd 原先把全部异常路径一律按 error 级打(串口上带 `[ERR]`),其中**九处其实由外部输入决
|
lftpd 原先把全部异常路径一律按 error 级打(串口上带 `[ERR]`),其中**九处其实由外部输入决
|
||||||
定**:客户端关窗口、连上就走、PASV 之后不建数据连接、运维改 IP 触发监听重建。代价实测两条:
|
定**:客户端关窗口、连上就走、PASV 之后不建数据连接、运维改 IP 触发监听重建。代价实测两条:
|
||||||
|
|
||||||
1. `@test` 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀)——"FileZilla 关了
|
1. `/serial-boot-test` 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀)——"FileZilla 关了
|
||||||
个窗口"就能把一次启动测试判 FAIL。实测就是这样踩到的:抓自检台账基线那次 FTP 会话结束后,
|
个窗口"就能把一次启动测试判 FAIL。实测就是这样踩到的:抓自检台账基线那次 FTP 会话结束后,
|
||||||
设备打 `[ERR] [FTP] error reading next command`,本次 `@test` 直接 FAIL。
|
设备打 `[ERR] [FTP] error reading next command`,本次 `/serial-boot-test` 直接 FAIL。
|
||||||
2. 任何对 21 端口"连一下就断开"的外部行为,都能**无限制造固件级 ERROR 日志**——日志面与门禁
|
2. 任何对 21 端口"连一下就断开"的外部行为,都能**无限制造固件级 ERROR 日志**——日志面与门禁
|
||||||
面都可被外部输入驱动,这在工业现场是过不了审的。
|
面都可被外部输入驱动,这在工业现场是过不了审的。
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ sequenceDiagram
|
|||||||
| **USART1(U5 直连 TTL)** | UART | PA9 TX / PA10 RX | **700000 8N1 → 屏板链路**(TTL 直连、无 DE,`../产品需求/产品需求设计文档.md` §5.16.2);PA10 双向均已接线;日志已迁 UART5 |
|
| **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) |
|
| **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) |
|
| **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 | **低电平点亮** |
|
| **状态 LED ×6** | GPIO | PC5 / PB0 / PB1 / PB2 / PF11 / PF12 | **低电平点亮** |
|
||||||
| SWD | — | PA13 SWDIO / PA14 SWCLK | V0.1 已修复 V0.0 的 SWCLK 引脚缺陷 |
|
| SWD | — | PA13 SWDIO / PA14 SWCLK | V0.1 已修复 V0.0 的 SWCLK 引脚缺陷 |
|
||||||
| 调试口 | — | CN2(XH-3A,1=SWDIO/2=SWCLK/3=GND) | V0.1 由 H2 排针改为 XH 端子 |
|
| 调试口 | — | CN2(XH-3A,1=SWDIO/2=SWCLK/3=GND) | V0.1 由 H2 排针改为 XH 端子 |
|
||||||
|
|||||||
+3
-3
@@ -351,15 +351,15 @@ STM32F4-Base/
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
SRC["共享源码<br/>Drivers/BSP/UI/*<br/>Drivers/BSP/NETCFG/net_cfg.c<br/>App/cfg/cfg_file.c"]
|
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"]
|
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"]
|
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`(文本断言 + 像素黄金参考) |
|
| 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` 免桩直跑(覆盖率总闸全量真跑)——主机侧就是它们的"第三端"。
|
**纯逻辑模块第三端**:录波(`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 用的 |
|
| 信号发生器 | 正弦 / 方波 / 三角,**并有 DC 档**;频率可锁 50.000 Hz、也能设到 2 Hz;幅度 0~3 Vpp | **只要求"波形干净、幅度稳、频率锁得住"**,它的刻度不当基准(`docs/任务规划/数据可信.md` §7 #9)。DC 档是给削顶轮改法 A 用的 |
|
||||||
| ⚠ 发生器输出端接设置 | 接板子时设成**高阻负载**,不要按 50 Ω 端接 | 按 50 Ω 端接而实际接的是高阻板输入 → 面板值与真实电压差 2 倍;这是台架"差一倍"最常见来源 |
|
| ⚠ 发生器输出端接设置 | 接板子时设成**高阻负载**,不要按 50 Ω 端接 | 按 50 Ω 端接而实际接的是高阻板输入 → 面板值与真实电压差 2 倍;这是台架"差一倍"最常见来源 |
|
||||||
| 万用表 AC 档 | 能读到 mV;**记明型号与响应类型**(真 RMS / 均值响应) | 基准就是它;波形轮那一步两种表的读数都与基波期望差很多,不记响应类型就说不清是谁错 |
|
| 万用表 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 口径);标定一生效就换成二次值,本节全部期望值作废 |
|
| `[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:` 之外的第二条腿 |
|
| 网口(可选) | 要读 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 轮转),**取完立刻改名归档**,否则下一次触发把它盖掉 |
|
| 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 的还原判据 |
|
| 录波触发路径 | 产品构建里唯一**免改代码**的路径 = 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
|
```text
|
||||||
日期 ____ / 操作人 ____ / 板子序列号 ____
|
日期 ____ / 操作人 ____ / 板子序列号 ____
|
||||||
固件 commit ________(@build 0 错误 0 警告:是 / 否)
|
固件 commit ________(/keil-build 0 错误 0 警告:是 / 否)
|
||||||
发生器:型号 ______ 输出端接 = 高阻 / 50 Ω 波形 ____ 频率设定 ____
|
发生器:型号 ______ 输出端接 = 高阻 / 50 Ω 波形 ____ 频率设定 ____
|
||||||
万用表:型号 ______ AC 响应类型 = 真 RMS / 均值 共地方式 = 单点 / 发生器浮地
|
万用表:型号 ______ AC 响应类型 = 真 RMS / 均值 共地方式 = 单点 / 发生器浮地
|
||||||
注入点:路名 ____(端子侧 U5 / 引脚侧外串 ____ Ω)
|
注入点:路名 ____(端子侧 U5 / 引脚侧外串 ____ Ω)
|
||||||
@@ -521,7 +521,7 @@ flowchart TD
|
|||||||
| 读数 | `freq(hb): f=.. valid=..`;UI 交流量页 `Hz`;104 `0x401D` |
|
| 读数 | `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 |
|
| 期望(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` |
|
| 判据 | **如实报不可测**,而不是报一个看起来对的 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 |
|
| 坑 | 非整 50 Hz 时 `meas:` 会拍频式跳动,属预期 → **扫频读 `freq(hb)`,别读 `meas:`**(`docs/任务规划/数据可信.md` §8 风险表同一条);测频的取源顺序是先 `UA`(相量实例 C02)再退 `IA`(C01)(`App/algo/freq.c:47`)——本卡注 `UA` 走的就是 UA 源;若哪天只注 `IA`,测频会跟着 IA 走,别以为它锁在 UA |
|
||||||
|
|
||||||
记录表(A 档):
|
记录表(A 档):
|
||||||
@@ -627,8 +627,8 @@ flowchart TD
|
|||||||
|
|
||||||
### 8.7 `[ERR]` 的判读规则(削顶轮单独跑、单独归档)
|
### 8.7 `[ERR]` 的判读规则(削顶轮单独跑、单独归档)
|
||||||
|
|
||||||
- 削顶轮会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `@test` 的 forbidden 标记含
|
- 削顶轮会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `/serial-boot-test` 的 forbidden 标记含
|
||||||
`[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `@test` 的标记校验当结论**:改人工判读,并在归档文件名与
|
`[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `/serial-boot-test` 的标记校验当结论**:改人工判读,并在归档文件名与
|
||||||
抬头里写明「预期 [ERR]:贴轨锁存负向用例」;§8.6 已把它排在最后单独一轮(`docs/任务规划/数据可信.md` §8 风险表「削顶负向用例被当成固件 bug」行同一条)。
|
抬头里写明「预期 [ERR]:贴轨锁存负向用例」;§8.6 已把它排在最后单独一轮(`docs/任务规划/数据可信.md` §8 风险表「削顶负向用例被当成固件 bug」行同一条)。
|
||||||
- 除这一条 `[ERR] rail:` 之外,**任何 `[ERR]` 都是不通过**;四个 Fault 的崩溃日志也以 `[ERR]` 开头 →
|
- 除这一条 `[ERR] rail:` 之外,**任何 `[ERR]` 都是不通过**;四个 Fault 的崩溃日志也以 `[ERR]` 开头 →
|
||||||
出现 Fault 立即停线取现场(`docs/子系统设计/看门狗设计说明.md` §10),别再继续注。
|
出现 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` 必须为空 |
|
| 相位轮改倍数:`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` 除归档目录外干净 |
|
| 只动接线与发生器、没动代码 | `git status` 除归档目录外干净 |
|
||||||
| 全程 | `test/board/test_config.h` 四个套件宏保持全注释(产品构建);烧录仍须 `-Rst -Run` + 回读校验 |
|
| 全程 | `test/board/test_config.h` 四个套件宏保持全注释(产品构建);烧录仍须 `-Rst -Run` + 回读校验 |
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
| `--selftest` | 跑内置 **22 条**判别用例后退出,无需日志与硬件(含下表三计数分类器) | "不可能失败的 PASS 就是空跑"——用例里含交错、缺行首、残留 1/2、配对错序,以及**"旧子串口径在同一条缺陷行上判 PASS"** 这一条盲区证明 |
|
| `--selftest` | 跑内置 **22 条**判别用例后退出,无需日志与硬件(含下表三计数分类器) | "不可能失败的 PASS 就是空跑"——用例里含交错、缺行首、残留 1/2、配对错序,以及**"旧子串口径在同一条缺陷行上判 PASS"** 这一条盲区证明 |
|
||||||
|
|
||||||
用法示例(一次仅复位启动的原子性判定)。四个行级开关**也已接进 `run_boot_test.py`**,
|
用法示例(一次仅复位启动的原子性判定)。四个行级开关**也已接进 `run_boot_test.py`**,
|
||||||
所以 @test 闭环里直接加 `--exact ... --pair ... --allow-residual 1 --head-char '['` 即可,无需单独跑本脚本:
|
所以 /serial-boot-test 闭环里直接加 `--exact ... --pair ... --allow-residual 1 --head-char '['` 即可,无需单独跑本脚本:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\verify_log.py `
|
& '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\verify_log.py `
|
||||||
@@ -75,9 +75,9 @@
|
|||||||
2. **出像**:像必须来自**已提交 commit + 干净工作树**。主树脏(别人在飞)时走仓外只读 detached worktree:
|
2. **出像**:像必须来自**已提交 commit + 干净工作树**。主树脏(别人在飞)时走仓外只读 detached worktree:
|
||||||
`git worktree add --detach <_wt>/bench-<ID>-<sha> <sha>` + `mklink /J <wt>\.venv <main>\.venv`;
|
`git worktree add --detach <_wt>/bench-<ID>-<sha> <sha>` + `mklink /J <wt>\.venv <main>\.venv`;
|
||||||
建树后 `git -C <wt> status --short` 必须为空,全程只读、不 commit、不跑生成器。
|
建树后 `git -C <wt> status --short` 必须为空,全程只读、不 commit、不跑生成器。
|
||||||
3. **@build**:0 错误 0 警告;随后**从 .hex 二进制里抠身份戳**(不是从 build_log 里读),与之后线上读到的
|
3. **/keil-build**:0 错误 0 警告;随后**从 .hex 二进制里抠身份戳**(不是从 build_log 里读),与之后线上读到的
|
||||||
`fw build:` 逐字对上——这是"烧进去的就是这份像"的唯一硬证据。
|
`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`,
|
5. **N 次仅复位对照**(N≥3):每次 `clear`(记下 gen,证明窗口不重叠)→ `stlink_flash.py reset`(`-c SWD -Rst -Run`,
|
||||||
**零重烧**)→ `wait-idle` → 取日志逐块存档。"保存的值重启后还在"用重烧去验是**无意义**的。
|
**零重烧**)→ `wait-idle` → 取日志逐块存档。"保存的值重启后还在"用重烧去验是**无意义**的。
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
所以"跑 5 次正常启动、[ERR] 零命中"**证不到 IPSR**——那是定义上的必然,是空跑。
|
所以"跑 5 次正常启动、[ERR] 零命中"**证不到 IPSR**——那是定义上的必然,是空跑。
|
||||||
IPSR 主张交给主机侧套件(假 USART + IPSR + 临界区深度水位);要硬件证据就得由代码道**提交触发器**,
|
IPSR 主张交给主机侧套件(假 USART + IPSR + 临界区深度水位);要硬件证据就得由代码道**提交触发器**,
|
||||||
判据写"崩溃遗言一整行、恰好一次、`dropped=0`",并按 `../测试数据/boot_log_data/README.md` §2「故障注入日志」
|
判据写"崩溃遗言一整行、恰好一次、`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 定稿:三档分权,不再是"待定")
|
## 5. 栈判据(task-32 定稿:三档分权,不再是"待定")
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ SHA 只当坐标(2026-10-26 那天 SHA 被 amend 挪了三轮)。同镜像
|
|||||||
## 7. 台架卫生清单(每一条都是实测踩过的,不是设想)
|
## 7. 台架卫生清单(每一条都是实测踩过的,不是设想)
|
||||||
|
|
||||||
1. **每个台架窗口开头、以及每一圈复位之前**:`serial_tool.py open` + `status` 断言 `port.ready=true`,并**观察 `rx_lines` 在几秒内增长**。
|
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 行**且看起来"跑成功了"。
|
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`。
|
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。
|
4. **工具输出别混流**:非零退出时 stdout(结果)与 stderr(诊断)拼接后再 `JSON.parse` 必崩;解析只吃 stdout。
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
1. `test/board/test_config.h`:取消注释 `TEST_SUITE_STORAGE`;
|
1. `test/board/test_config.h`:取消注释 `TEST_SUITE_STORAGE`;
|
||||||
2. `test/board/storage_test.h`:取消注释 `ENABLE_STORAGE_RECOVERY_TESTS` 与 `STORAGE_RECOVERY_FULL_WIPE`;
|
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);
|
4. 采集顺序 `open` → `clear` → 烧录 → `wait-idle`(擦除 2044 块约 4 秒,静默判据给 20~30s);
|
||||||
5. 确认日志:
|
5. 确认日志:
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
6. **断电重启**(`stlink_flash.py reset` 无参即可)——本次启动才会重建干净 journal;
|
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)
|
[NAND_FTL] No valid map found, creating fresh (err=3)
|
||||||
@@ -74,14 +74,14 @@
|
|||||||
- **判据:只删显式点名的文件,绝不 glob**。清理脚本里写死 27 个文件名逐个 `DELE`;保留清单
|
- **判据:只删显式点名的文件,绝不 glob**。清理脚本里写死 27 个文件名逐个 `DELE`;保留清单
|
||||||
必须含 **`/config.ini`**(网口与 FTP 鉴权配置)、**`/PROT_SET.BIN`**(保护定值 blob,
|
必须含 **`/config.ini`**(网口与 FTP 鉴权配置)、**`/PROT_SET.BIN`**(保护定值 blob,
|
||||||
`PROT_SET_BLOB_SZ` = 176 B,删了等于把定值清零)、**`/log`** 目录(运行与自检记录)。
|
`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 面正常。
|
与配置加载都在这条链上);③ 跑 `test/host/ftp_list_fill_test.py` 确认 FTP 面正常。
|
||||||
- 这属于**动作 D(清文件)**,与本文 §1 的 A(重建 BBT + 格式化)/ B(全片擦除)/ C(容量恢复)
|
- 这属于**动作 D(清文件)**,与本文 §1 的 A(重建 BBT + 格式化)/ B(全片擦除)/ C(容量恢复)
|
||||||
都不同:A/B 会丢卷内容,D 不动文件系统结构。
|
都不同:A/B 会丢卷内容,D 不动文件系统结构。
|
||||||
|
|
||||||
### 4.2 自检台账清零,重建联合判定审计基线(2026-10-22 实测)
|
### 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`,
|
`/log/selftest.log` 里躺着 29 条历史记录,`2026-10-10 09:24:12 → 2026-10-14 06:55:02`,
|
||||||
组成为 **16 × `watchdog reset` + 13 × `heap low water`**——都是看门狗引入期与堆迁移期
|
组成为 **16 × `watchdog reset` + 13 × `heap low water`**——都是看门狗引入期与堆迁移期
|
||||||
留下的,两件事后来都已结案。新判据(只看本次新增)已不受影响,但**审计口径要能用,就得
|
留下的,两件事后来都已结案。新判据(只看本次新增)已不受影响,但**审计口径要能用,就得
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
256 种字节值全部出现、前 64 字节全 `FF` → 就是一份 0..255 图案的写盘校验填充,没有任何
|
256 种字节值全部出现、前 64 字节全 `FF` → 就是一份 0..255 图案的写盘校验填充,没有任何
|
||||||
产品语义;留档后再 `DELE`(判据同 §4.1:先知道删的是什么)。
|
产品语义;留档后再 `DELE`(判据同 §4.1:先知道删的是什么)。
|
||||||
- 清理后 `/log` 只剩三条、**全是产品数据**:`run.log`(运行记录)、`wdev.log`(掉电取证)、
|
- 清理后 `/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 条`)。
|
(699 ms、`SELFTEST_BASELINE: 既有记录 0 条`)。
|
||||||
- 验收(两条判定路径都要过):`--require-selftest-pass` → `SELFTEST_BASELINE: 既有记录 0 条
|
- 验收(两条判定路径都要过):`--require-selftest-pass` → `SELFTEST_BASELINE: 既有记录 0 条
|
||||||
(status=absent)` → PASS;`--selftest-scope all` → `selftest: no fault record` → **PASS**
|
(status=absent)` → PASS;`--selftest-scope all` → `selftest: no fault record` → **PASS**
|
||||||
|
|||||||
+12
-12
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
> 模拟器覆盖的是 **「纯逻辑 + 平台抽象之上」的那一层**:UI 场景/渲染/导航、配置引擎、日志记录格式、
|
> 模拟器覆盖的是 **「纯逻辑 + 平台抽象之上」的那一层**:UI 场景/渲染/导航、配置引擎、日志记录格式、
|
||||||
> `net_*` BSD API 之上的协议状态机。它**不覆盖**「硬件时序 / 中断 / 真实复位 / CH395F 与 NAND 芯片行为」。
|
> `net_*` BSD API 之上的协议状态机。它**不覆盖**「硬件时序 / 中断 / 真实复位 / CH395F 与 NAND 芯片行为」。
|
||||||
> 因此 **模拟器回归与板端测试套件 / 主机侧回归(`@test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。
|
> 因此 **模拟器回归与板端测试套件 / 主机侧回归(`/serial-boot-test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -34,16 +34,16 @@
|
|||||||
## 2. 差距清单
|
## 2. 差距清单
|
||||||
|
|
||||||
> **列含义**:`模拟器行为` / `真机行为` 描述差异本身;`影响` 说明它会让什么 bug 逃过模拟器回归;
|
> **列含义**:`模拟器行为` / `真机行为` 描述差异本身;`影响` 说明它会让什么 bug 逃过模拟器回归;
|
||||||
> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`@test` / 板端套件 / 目检)。
|
> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`/serial-boot-test` / 板端套件 / 目检)。
|
||||||
|
|
||||||
### 2.1 网络(`net_socket_sim.c` vs `net_socket.c`)
|
### 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),不再专门建模 |
|
| **无 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 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) |
|
| **代次号恒为 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 套件 |
|
| **消息泵缺失**:~~`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 套件 + 台架并发场景 |
|
| **单连接 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 机制)仍不建模 | —— |
|
| **非阻塞语义模拟** | 创建即 `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` §变更检测 |
|
| **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_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` 静态最坏链(真机侧唯一手段) |
|
| **两条投递口在宿主端合一** | `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` 残留 |
|
| **宿主 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 |
|
| **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 断言点)+ `@test` |
|
| **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `/serial-boot-test` |
|
||||||
| **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `@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` 主机侧纯逻辑单测(已覆盖算术面) |
|
| **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) |
|
| **TPAFE5160 ADC** | 无 | SPI ADC + BUSY EXTI 中断 | 中断驱动采集链路 | 待功能实现(上游 B1) |
|
||||||
| **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) |
|
| **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) |
|
||||||
| **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6 已落地) |
|
| **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6 已落地) |
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
|
|
||||||
## 3. 什么时候必须回真机(决策规则)
|
## 3. 什么时候必须回真机(决策规则)
|
||||||
|
|
||||||
模拟器回归通过后,**满足以下任一条件即必须走真机**(`@test` / 板端套件):
|
模拟器回归通过后,**满足以下任一条件即必须走真机**(`/serial-boot-test` / 板端套件):
|
||||||
|
|
||||||
1. **改动触及硬件访问层**:`Drivers/BSP/` 下任何驱动的寄存器/时序/中断代码
|
1. **改动触及硬件访问层**:`Drivers/BSP/` 下任何驱动的寄存器/时序/中断代码
|
||||||
2. **改动触及 §2.3 硬件专属差距的任一子系统**的初始化或运行时行为
|
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`)内各数行打点,
|
产品代码(`net_socket.c`/`ui_net_sink.c`/`ui_task.c`/`ui_port.c`)内各数行打点,
|
||||||
经静态环形缓冲(总计 <1.5KB BSS)上报;**产品构建下该宏未定义,探针代码编译消失**,
|
经静态环形缓冲(总计 <1.5KB BSS)上报;**产品构建下该宏未定义,探针代码编译消失**,
|
||||||
两种构建配置都必须过 `@build`(0 错 0 警)。
|
两种构建配置都必须过 `/keil-build`(0 错 0 警)。
|
||||||
|
|
||||||
- 多打点站点必须用 `lat_probe_add_seg()` 按段名过滤(探针窗口是串行全局窗口,直接打点会跨段污染,D18 教训);
|
- 多打点站点必须用 `lat_probe_add_seg()` 按段名过滤(探针窗口是串行全局窗口,直接打点会跨段污染,D18 教训);
|
||||||
- 启用探针的构建必须在用例入口调 `lat_time_init()`(漏调 = 全 0 假绿,D19 教训;`med==0` 判无效兜底)。
|
- 启用探针的构建必须在用例入口调 `lat_time_init()`(漏调 = 全 0 假绿,D19 教训;`med==0` 判无效兜底)。
|
||||||
@@ -729,7 +729,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
|
|||||||
|
|
||||||
## 11. 约束核对(AGENTS.md)
|
## 11. 约束核对(AGENTS.md)
|
||||||
|
|
||||||
- 每个测试阶段/修复 `@build`(0 错 0 警)+ `@test` 硬件闭环;
|
- 每个测试阶段/修复 `/keil-build`(0 错 0 警)+ `/serial-boot-test` 硬件闭环;
|
||||||
- 新 `.c` 注册:`test/*.c` 仅 MCU 端编译 → **只注册 `MDK-ARM/STM32F407-Demo.uvprojx`**(Test 组;
|
- 新 `.c` 注册:`test/*.c` 仅 MCU 端编译 → **只注册 `MDK-ARM/STM32F407-Demo.uvprojx`**(Test 组;
|
||||||
模拟器 CMake 不含 test 文件,与 `ch395f_test_task.c` 等现状一致);
|
模拟器 CMake 不含 test 文件,与 `ch395f_test_task.c` 等现状一致);
|
||||||
- 新增 C 字符串字面量**纯 ASCII**(`[LAT]` 报告行均为英文),中文只写注释;
|
- 新增 C 字符串字面量**纯 ASCII**(`[LAT]` 报告行均为英文),中文只写注释;
|
||||||
@@ -737,7 +737,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复
|
|||||||
- `testTask` 栈 2048 words:用例函数只用几十字节局部变量;
|
- `testTask` 栈 2048 words:用例函数只用几十字节局部变量;
|
||||||
- 套件互斥宏体系不动:Phase 12 挂 HW 组,13/14 挂 NET 组,`#error` 自动防两层同启;
|
- 套件互斥宏体系不动:Phase 12 挂 HW 组,13/14 挂 NET 组,`#error` 自动防两层同启;
|
||||||
- 非破坏性:不改 IP/卷/BBT;HW 层阶段独占 CH395F(netTask 自挂起)→ 标注"台架专用";
|
- 非破坏性:不改 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`;
|
**验收顺序**:`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` 会按台账外新账卡提交
|
16 → 22、套件 16 → 28,实测见上方落地进度),并且**先补族再往文档里引这些号**,否则 `id-check` 会按台账外新账卡提交
|
||||||
(本会话已被 `TC-NETCFG-028` 实测拦过一次)。
|
(本会话已被 `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。
|
- 自愈触发集里混着 `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` 加回传字段,且必须配真机回归);
|
**本轮刻意没做(后续项,含理由)**:① 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`,**没有输出点可看**)。
|
④ `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 的断言数改前后相同
|
`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)。**基线本轮不需要重登**(生产码零改动)。
|
(如 `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` 这三根闸验证——**别把"编译过了"当测试面改动的证据**。
|
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` |
|
| 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` 审计 |
|
| 内核前装配 | `app_main.c`(135) | 被测对象本身就是"`osKernelStart` 之前不许调内核 API"的时序,主机侧无法忠实复现(PC 上没有那个约束) | 同上 + `cubemx-survival` 审计 |
|
||||||
|
|
||||||
**四个数字对得上**:`47 = 13(P1) + 5(P2) + 29(P3)`、`9,657 = 2,617 + 2,226 + 4,814`。
|
**四个数字对得上**:`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`)
|
> **模块**:Task Watchdog(IWDG + 任务心跳位图,`App/util/task_watchdog.c`)
|
||||||
> **测试类型**:**主机侧纯逻辑单元测试**(见 §1.5)——在 PC 上离线验证
|
> **测试类型**:**主机侧纯逻辑单元测试**(见 §1.5)——在 PC 上离线验证
|
||||||
> 判定状态机,零硬件、零烧录、秒级、不占 MCU 的 RAM。
|
> 判定状态机,零硬件、零烧录、秒级、不占 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` 变更记录——本文档不再设变更记录节)
|
> **日期**:2026-10-01(2026-10-25 v1.5 按位分账改造;改造史与设计依据见 `docs/子系统设计/看门狗设计说明.md` 变更记录——本文档不再设变更记录节)
|
||||||
> **关联文档**:
|
> **关联文档**:
|
||||||
> [`../../子系统设计/看门狗设计说明.md`](../../子系统设计/看门狗设计说明.md)(被测行为的设计依据)、
|
> [`../../子系统设计/看门狗设计说明.md`](../../子系统设计/看门狗设计说明.md)(被测行为的设计依据)、
|
||||||
@@ -55,7 +55,7 @@ test/host/watchdog/watchdog_test.c
|
|||||||
| `wd_platform_tick_ms()` | `HAL_GetTick()` | 返回单测可控的 `s_tick` |
|
| `wd_platform_tick_ms()` | `HAL_GetTick()` | 返回单测可控的 `s_tick` |
|
||||||
|
|
||||||
> **生产端零增量**:三个钩子在 MCU 构建下就是原来的寄存器写 / HAL 调用,行为逐字一致,
|
> **生产端零增量**:三个钩子在 MCU 构建下就是原来的寄存器写 / HAL 调用,行为逐字一致,
|
||||||
> 无额外分支、无额外符号(`@build` 0/0 已验)。平台默认实现由
|
> 无额外分支、无额外符号(`/keil-build` 0/0 已验)。平台默认实现由
|
||||||
> `#ifndef TASK_WATCHDOG_UNIT_TEST` 守卫;测试专用复位 `task_wd_test_reset()`
|
> `#ifndef TASK_WATCHDOG_UNIT_TEST` 守卫;测试专用复位 `task_wd_test_reset()`
|
||||||
> 仅在主机侧编译(MCU 构建零代码、零符号)。
|
> 仅在主机侧编译(MCU 构建零代码、零符号)。
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ test/host/watchdog/watchdog_test.c
|
|||||||
| **门禁层级** | **主机侧纯逻辑单测**(介于静态门禁与板端测试套件之间):主机侧、秒级、纯逻辑,比板端套件便宜、比静态检查更强 |
|
| **门禁层级** | **主机侧纯逻辑单测**(介于静态门禁与板端测试套件之间):主机侧、秒级、纯逻辑,比板端套件便宜、比静态检查更强 |
|
||||||
| **pre-commit 钩子** | `watchdog-unit-test`(本地钩子,`.pre-commit-config.yaml` §7) |
|
| **pre-commit 钩子** | `watchdog-unit-test`(本地钩子,`.pre-commit-config.yaml` §7) |
|
||||||
| **汇总行** | `=== Task Watchdog Tests: N/N PASSED (failed=0) ===` |
|
| **汇总行** | `=== 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) |
|
| 长跑(主机侧回归) | ≥30 min 无假复位(`miss_count` 稳定) | ✅ 已台架验收(V5,30 min / 120×FTP 零复位,2026-10-03) |
|
||||||
|
|
||||||
> 本单测**不能**替代板端验证:IWDG 实际溢出时间、LSI 频率漂移、`SHCSR`/复位原因
|
> 本单测**不能**替代板端验证:IWDG 实际溢出时间、LSI 频率漂移、`SHCSR`/复位原因
|
||||||
> 寄存器的真机行为,仍需 `@test` 台架闭环确认。
|
> 寄存器的真机行为,仍需 `/serial-boot-test` 台架闭环确认。
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
> **模块**:System Clock(墙钟时间维护,`App/util/sys_clock.c`)
|
> **模块**:System Clock(墙钟时间维护,`App/util/sys_clock.c`)
|
||||||
> **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证时间换算与
|
> **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证时间换算与
|
||||||
> 墙钟基准逻辑,零硬件、零烧录、秒级、不占 MCU 的 RAM。
|
> 墙钟基准逻辑,零硬件、零烧录、秒级、不占 MCU 的 RAM。
|
||||||
> **状态**:✅ 已落地(20 用例 / 100 断言点,`@build` 0/0,已挂 pre-commit 钩子)
|
> **状态**:✅ 已落地(20 用例 / 100 断言点,`/keil-build` 0/0,已挂 pre-commit 钩子)
|
||||||
> **日期**:2026-10-02
|
> **日期**:2026-10-02
|
||||||
> **关联文档**:
|
> **关联文档**:
|
||||||
> [`../../../AGENTS.md`](../../../AGENTS.md)(平台约束)、
|
> [`../../../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` 保证各项非负(无符号 `%` 安全)。
|
> 注:`5J ≡ -2J (mod 7)`,取 `5J` 保证各项非负(无符号 `%` 安全)。
|
||||||
> 修复后经 Python `datetime` 独立核对 **1970~2099 全部 47,482 天零错误**;
|
> 修复后经 Python `datetime` 独立核对 **1970~2099 全部 47,482 天零错误**;
|
||||||
> `@build` 0 错误 0 警告。
|
> `/keil-build` 0 错误 0 警告。
|
||||||
|
|
||||||
### 5.5 校验数据来源
|
### 5.5 校验数据来源
|
||||||
|
|
||||||
@@ -252,4 +252,4 @@ TC-SYSCLK-019 的期望星期由 Python `datetime.date(...).weekday()` 独立生
|
|||||||
| 板端测试套件 / 主机侧回归 | 真机 SD2506 I2C 读写、掉电保持、与 PC 对时联动 | 见 [`SD2506_Test_Guide.md`](../板级测试/SD2506_Test_Guide.md) |
|
| 板端测试套件 / 主机侧回归 | 真机 SD2506 I2C 读写、掉电保持、与 PC 对时联动 | 见 [`SD2506_Test_Guide.md`](../板级测试/SD2506_Test_Guide.md) |
|
||||||
|
|
||||||
> 本单测**不替代**板端验证:I2C 时序、BCD 编解码、掉电保持、温度读等真机行为,
|
> 本单测**不替代**板端验证:I2C 时序、BCD 编解码、掉电保持、温度读等真机行为,
|
||||||
> 仍需 `@test` 台架闭环确认。
|
> 仍需 `/serial-boot-test` 台架闭环确认。
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
> **模块**:Config File Engine(通用配置文件引擎,`App/cfg/cfg_file.c`)
|
> **模块**:Config File Engine(通用配置文件引擎,`App/cfg/cfg_file.c`)
|
||||||
> **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证 INI 解析、
|
> **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证 INI 解析、
|
||||||
> 值类型编解码、段注册校验与 load/save/poll 契约,零硬件、零烧录、毫秒级、不占 MCU 的 RAM。
|
> 值类型编解码、段注册校验与 load/save/poll 契约,零硬件、零烧录、毫秒级、不占 MCU 的 RAM。
|
||||||
> **状态**:✅ 已落地(20 用例 / 139 断言点,`@build` 0/0,已挂 pre-commit 钩子)
|
> **状态**:✅ 已落地(20 用例 / 139 断言点,`/keil-build` 0/0,已挂 pre-commit 钩子)
|
||||||
> **日期**:2026-10-02
|
> **日期**:2026-10-02
|
||||||
> **关联文档**:
|
> **关联文档**:
|
||||||
> [`../../../AGENTS.md`](../../../AGENTS.md)(平台约束 · FatFs/内存铁律)、
|
> [`../../../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 建议后续动作
|
### 5.5 建议后续动作
|
||||||
|
|
||||||
|
|||||||
@@ -1121,7 +1121,7 @@ CLOSE 才落 00。与 TRAP-NET-07 的 `0x05/0x00` 驻留签名同源;`sock_res
|
|||||||
WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 TRAP-NET-09 open 重试吸收。
|
WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 TRAP-NET-09 open 重试吸收。
|
||||||
|
|
||||||
**运行方法**:`ch395f_test.h` 启用 `ENABLE_PHASE15_TESTS`(自动进入硬件层模式,
|
**运行方法**:`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. 编译与运行(固件侧)
|
## 3. 编译与运行(固件侧)
|
||||||
|
|
||||||
- **默认构建即可提供 FTP Server**,无需切换测试套件。
|
- **默认构建即可提供 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`(文件传输阶段),重新编译烧录。
|
- 若需跑**自动化 FTP 文件传输用例**:在 `test/board/test_config.h` 选择 `TEST_SUITE_CH395F`,并在 `test/board/ch395f_test.h` 打开 `ENABLE_PHASE8_TESTS`(文件传输阶段),重新编译烧录。
|
||||||
- 烧录后打开串口(115200bps)观察:
|
- 烧录后打开串口(115200bps)观察:
|
||||||
- `net_init` 完成、PHY link up;
|
- `net_init` 完成、PHY link up;
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
|
|
||||||
- **串口日志 + `DBG_*`**:在 `lftpd.c` / `net_socket` 关键路径加 `DBG_ERROR/INFO` 打印(注意编码安全铁律——只用 Edit 改中文源文件,禁止用 `Get/Set-Content` 整文件重写)。
|
- **串口日志 + `DBG_*`**:在 `lftpd.c` / `net_socket` 关键路径加 `DBG_ERROR/INFO` 打印(注意编码安全铁律——只用 Edit 改中文源文件,禁止用 `Get/Set-Content` 整文件重写)。
|
||||||
- **抓包**:`ftp_capture.py` 或 Wireshark 过滤 `tcp.port==21`。
|
- **抓包**:`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`(三选一,互斥)。
|
- **套件切换**:`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`),
|
1. 在 `test/board/test_config.h` 取消注释所需的 `TEST_SUITE_*` 宏(如 `TEST_SUITE_GD5F`),
|
||||||
需要写/擦类用例时再打开 `ENABLE_DESTRUCTIVE_TESTS`;
|
需要写/擦类用例时再打开 `ENABLE_DESTRUCTIVE_TESTS`;
|
||||||
2. 编译(`@build`)确认 0 错误 0 警告;
|
2. 编译(`/keil-build`)确认 0 错误 0 警告;
|
||||||
3. 烧录后串口观察 `mode: ...` 横幅、`[NAND-TEST]` 日志与各 TC 的 `PASS/FAIL`,结尾打印
|
3. 烧录后串口观察 `mode: ...` 横幅、`[NAND-TEST]` 日志与各 TC 的 `PASS/FAIL`,结尾打印
|
||||||
`=== GD5F2GQ5UE Tests: X/Y PASSED ===`;
|
`=== GD5F2GQ5UE Tests: X/Y PASSED ===`;
|
||||||
4. 不选任何 `TEST_SUITE_*` 时为正常产品构建(`ch395fTestTask` 空闲,不跑测试)。
|
4. 不选任何 `TEST_SUITE_*` 时为正常产品构建(`ch395fTestTask` 空闲,不跑测试)。
|
||||||
|
|||||||
@@ -22,14 +22,14 @@
|
|||||||
| 步骤 | 事项 | 状态 | 章节 |
|
| 步骤 | 事项 | 状态 | 章节 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **步骤 1** | 环境预检(P0-A,每次上台架必跑) | 常设 | §2 |
|
| **步骤 1** | 环境预检(P0-A,每次上台架必跑) | 常设 | §2 |
|
||||||
| **步骤 2** | 产品 `@test` 闭环 + 自检结果判定(P0-B) | ✅ 已闭环 | §3 |
|
| **步骤 2** | 产品 `/serial-boot-test` 闭环 + 自检结果判定(P0-B) | ✅ 已闭环 | §3 |
|
||||||
| **步骤 3** | IWDG 故障注入 V2/V4 + 长跑 V5(P1-A) | ✅ 已闭环 | §4 |
|
| **步骤 3** | IWDG 故障注入 V2/V4 + 长跑 V5(P1-A) | ✅ 已闭环 | §4 |
|
||||||
| **步骤 4** | 堆低水位告警实测(P1-B) | ✅ 已闭环 | §5 |
|
| **步骤 4** | 堆低水位告警实测(P1-B) | ✅ 已闭环 | §5 |
|
||||||
| **步骤 5** | GD5F 错误通道回归(P2-A) | ✅ 已闭环 | §6 |
|
| **步骤 5** | GD5F 错误通道回归(P2-A) | ✅ 已闭环 | §6 |
|
||||||
| **步骤 6** | 网络稳定性与基线重锁(P2-B / 日志静默观察项 + 队列往返基线待重验) | ◐ 部分闭环(7.1/7.2 ✅;7.3 待 PC peer) | §7 |
|
| **步骤 6** | 网络稳定性与基线重锁(P2-B / 日志静默观察项 + 队列往返基线待重验) | ◐ 部分闭环(7.1/7.2 ✅;7.3 待 PC peer) | §7 |
|
||||||
| **步骤 7** | 工程卫生(P3 + 待核实项) | ✅ 已闭环 | §8 |
|
| **步骤 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 引用闭环。
|
> **参考**(非执行步骤):§9 执行注意(实测陷阱)· §10 已完成事项与证据去向 · §11 引用闭环。
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
| A7 | 串口端口正确 | 检查 `.serialtest/config.json` 的 `portName` | 端口单一配置源 = 此文件(当前 COM6,2026-09-21 台架重插后由 COM7 变更);变更后先杀旧 daemon 再 `open` |
|
| 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` 可读 |
|
| 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` 不够,**必须再编一次产品固件**
|
> 套件后 `.map` 会停在**测试套件体积**;仅还原 `test_config.h` 不够,**必须再编一次产品固件**
|
||||||
> (`run_all_suites.py` 已在 `finally` 内自动补这一步)。
|
> (`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 状态与去向**,本节只给复跑命令与判据。
|
> S7 判定已落地并投用;A1~A4 已闭环。**实施细节与真机验证证据见 §10 状态与去向**,本节只给复跑命令与判据。
|
||||||
|
|
||||||
### 3.1 执行
|
### 3.1 执行
|
||||||
|
|
||||||
`@test` 闭环除串口标记外,还判定设备侧自检结论(v1 只记故障、不记通过):
|
`/serial-boot-test` 闭环除串口标记外,还判定设备侧自检结论(v1 只记故障、不记通过):
|
||||||
`selftest.log` **缺失 / 0 字节 → 无故障 → PASS**;`filesize/64 > 0`(任一 64B 定长记录)
|
`selftest.log` **缺失 / 0 字节 → 无故障 → PASS**;`filesize/64 > 0`(任一 64B 定长记录)
|
||||||
= 有故障 → FAIL,并打印故障条目(时间戳 + 描述)便于定位。
|
= 有故障 → FAIL,并打印故障条目(时间戳 + 描述)便于定位。
|
||||||
|
|
||||||
@@ -121,11 +121,11 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# --- V2/V4(故障注入,需临时改码)---
|
# --- V2/V4(故障注入,需临时改码)---
|
||||||
# 1) 基线:跑一轮产品 @test 确认无复位
|
# 1) 基线:跑一轮产品 /serial-boot-test 确认无复位
|
||||||
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py
|
& '.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);)
|
# 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\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
|
& '.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
|
& $cli -c SWD -CmpFile MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex 0x08000000
|
||||||
# 判据:No difference found
|
# 判据:No difference found
|
||||||
|
|
||||||
# 5) 撤销注入,@build 回产品态,再跑一轮 @test 确认正常
|
# 5) 撤销注入,/keil-build 回产品态,再跑一轮 /serial-boot-test 确认正常
|
||||||
|
|
||||||
# --- V5(长跑,需真实负载)---
|
# --- V5(长跑,需真实负载)---
|
||||||
# 1) 用 test/host/ftp_speed_test.py / ftp_up_dl_test.py 持续传输大文件
|
# 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
|
```bash
|
||||||
# 1) 代码改动(约 15 行 + 1 个编译期常量)在 defaultTask 低频循环内
|
# 1) 代码改动(约 15 行 + 1 个编译期常量)在 defaultTask 低频循环内
|
||||||
# 2) @build 0/0 后,与 §4.2 V5 长跑合并执行
|
# 2) /keil-build 0/0 后,与 §4.2 V5 长跑合并执行
|
||||||
# 3) 长跑结束读取 min_ever:
|
# 3) 长跑结束读取 min_ever:
|
||||||
# - 串口 [HEAP] 告警行(若有)
|
# - 串口 [HEAP] 告警行(若有)
|
||||||
# - 或 FTP 取 /log/selftest.log
|
# - 或 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` | 测试文档模板、命名规则(TM / TC / DP) |
|
||||||
| `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试 / 采集工具与陷阱(Python 串口助手) |
|
| `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试 / 采集工具与陷阱(Python 串口助手) |
|
||||||
| `docs/测试说明/测试工具/串口助手使用指南.md` | 串口助手命令用法 / 工作流 / 排障 |
|
| `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` 纳入)+ 平台桩 | **纯逻辑模块的边界穷举**(节流窗口、计数阈值、整数回绕)——板端无法覆盖、无需硬件 |
|
| **主机侧纯逻辑单测** | `test/host/<模块>/run_unit_test.py` + MSVC 离线编译真实源码(`#include` 纳入)+ 平台桩 | **纯逻辑模块的边界穷举**(节流窗口、计数阈值、整数回绕)——板端无法覆盖、无需硬件 |
|
||||||
| 板端测试套件 | testTask + `test/board/test_config.h` 套件 | 驱动 / 协议功能(串口 `[PASS]/[FAIL]` + `=== ... PASSED` 汇总) |
|
| 板端测试套件 | testTask + `test/board/test_config.h` 套件 | 驱动 / 协议功能(串口 `[PASS]/[FAIL]` + `=== ... PASSED` 汇总) |
|
||||||
| 主机侧回归 | `@test` 串口日志标记 + LVGL 模拟器像素 / 池水位回归 | 启动序列、UI 渲染、LVGL 内存池水位 |
|
| 主机侧回归 | `/serial-boot-test` 串口日志标记 + LVGL 模拟器像素 / 池水位回归 | 启动序列、UI 渲染、LVGL 内存池水位 |
|
||||||
| 人工目检 | 上机屏幕、PC 端交互工具 | 视觉内容、自动化无法覆盖的场景 |
|
| 人工目检 | 上机屏幕、PC 端交互工具 | 视觉内容、自动化无法覆盖的场景 |
|
||||||
|
|
||||||
> **主机侧纯逻辑单测与主机侧回归的区别**:主机侧回归的模拟器回归依赖 SDL / LVGL 与图形输出;主机侧纯逻辑单测是**纯计算**单测
|
> **主机侧纯逻辑单测与主机侧回归的区别**:主机侧回归的模拟器回归依赖 SDL / LVGL 与图形输出;主机侧纯逻辑单测是**纯计算**单测
|
||||||
@@ -43,9 +43,9 @@
|
|||||||
|
|
||||||
| 改动类型 | 必跑门禁 |
|
| 改动类型 | 必跑门禁 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 `@build` |
|
| 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 `/keil-build` |
|
||||||
| App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 `@test`(模块标记) |
|
| App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 `/serial-boot-test`(模块标记) |
|
||||||
| **有主机侧纯逻辑单测的模块(如看门狗)** | G0(含该模块单测钩子,自动)+ G1 `@build`(+ 台架项另按需 G2) |
|
| **有主机侧纯逻辑单测的模块(如看门狗)** | G0(含该模块单测钩子,自动)+ G1 `/keil-build`(+ 台架项另按需 G2) |
|
||||||
| UI(菜单 / 内容页 / 字体 / 样式 / 几何) | G0 + G1 + G2 + G3 ui-verify 五步 |
|
| UI(菜单 / 内容页 / 字体 / 样式 / 几何) | G0 + G1 + G2 + G3 ui-verify 五步 |
|
||||||
| UI 新增中文文案 | 另需 font-regen(字体重生成 + 回归),**先于提交** |
|
| UI 新增中文文案 | 另需 font-regen(字体重生成 + 回归),**先于提交** |
|
||||||
| `test/` 套件或 PC 端工具代码 | G1 + G2(套件标题标记 / 工具自测) |
|
| `test/` 套件或 PC 端工具代码 | G1 + G2(套件标题标记 / 工具自测) |
|
||||||
@@ -74,7 +74,7 @@ G0~G3 命令详见 §6 / §7;ui-verify 完整命令见 `UI_自动化测试流
|
|||||||
### 2.3 三条铁律
|
### 2.3 三条铁律
|
||||||
|
|
||||||
1. **先写判定标准,再写测试代码** —— 文档里先写"什么算过 / 什么算失败"(精确到返回值 / 日志字符串 / 像素参考),再实现,避免"测了但不知道算不算过"。
|
1. **先写判定标准,再写测试代码** —— 文档里先写"什么算过 / 什么算失败"(精确到返回值 / 日志字符串 / 像素参考),再实现,避免"测了但不知道算不算过"。
|
||||||
2. **判定标准必须可机器判定** —— 必须能落到:串口日志标记(→ `@test -Required`)、返回值 / 寄存器值(→ `TEST_CHECK`)、像素参考(→ 模拟器回归)、池水位(→ 基线常量)之一。无法机器判定的标准只能放 人工目检人工层。
|
2. **判定标准必须可机器判定** —— 必须能落到:串口日志标记(→ `run_boot_test.py --required`)、返回值 / 寄存器值(→ `TEST_CHECK`)、像素参考(→ 模拟器回归)、池水位(→ 基线常量)之一。无法机器判定的标准只能放 人工目检人工层。
|
||||||
3. **每条设计原则必须有对应用例** —— 覆盖表中每个 DP-xx 至少一个 TC;新功能设计时先定 DP,再设计 TC。
|
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 编码约定
|
### 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`);
|
2. `test/board/test_config.h`:补 `TEST_SUITE_XXX` 注释开关 + 互斥 `#error` 加项 + 分发分支(include + `TEST_SUITE_NAME`);
|
||||||
3. `test/board/test_config.c`:`start_test_task()` 加分发分支;
|
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 共用代码才需双端);
|
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 模板);
|
6. 立 `docs/测试说明/<模块>_Test_Guide.md`(§2 模板);
|
||||||
7. `@test` 以套件标题行为 `-Required` 标记跑通回归。
|
7. `/serial-boot-test` 以套件标题行为 `-Required` 标记跑通回归。
|
||||||
|
|
||||||
### 3.6 多套件回归编排(`test/run_all_suites.py`,2026-10-01 加)
|
### 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 加)
|
#### 3.6.1 `--check-build`:只编译不烧录(2026-10-02 加)
|
||||||
|
|
||||||
**解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `@build` 天天跑,
|
**解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `/keil-build` 天天跑,
|
||||||
但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件
|
但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件
|
||||||
7 个配置实例)。
|
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 的机器上跑) |
|
| **不需要** | MCU / 串口 / ST-LINK(**纯离机**,可在 CI 或任何有 Keil 的机器上跑) |
|
||||||
| 判定 | `@build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL |
|
| 判定 | `/keil-build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL |
|
||||||
| RAM 溢出 | "编译过但链接失败"这类问题会**自然暴露**为 `L6406E`——这正是想要的 |
|
| RAM 溢出 | "编译过但链接失败"这类问题会**自然暴露**为 `L6406E`——这正是想要的 |
|
||||||
| 退出码 | 同完整模式:0=全 PASS / 1=有套件编不过 / 2=用法缺文件 |
|
| 退出码 | 同完整模式: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 构建不编入(零符号);
|
- 测试专用钩子(如静态态复位)用 `#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 代码页解码);
|
- **test/ 自动化脚本为纯 Python 标准库**(2026-09-20 由 PowerShell 迁移:绕开控制台代码页/执行策略陷阱,全部 I/O 显式 UTF-8/字节级处理;`requirements.txt` 仍须纯 ASCII——pip 按 GBK 代码页解码);
|
||||||
- **Python 统一用仓库 `.venv`**;第三方依赖必须进 `requirements.txt`(纯 ASCII);
|
- **Python 统一用仓库 `.venv`**;第三方依赖必须进 `requirements.txt`(纯 ASCII);
|
||||||
- 落位:板端交互工具 → `test/`;模拟器回归 → `simulator/tests/`;
|
- 落位:板端交互工具 → `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 流程
|
### 6.1 流程
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A[代码改完] --> B["G1 @build(0 错 0 警,否则修复重编)"]
|
A[代码改完] --> B["G1 /keil-build(0 错 0 警,否则修复重编)"]
|
||||||
B --> C["run_boot_test.py:烧录前武装采集(daemon 全量日志兜底)"]
|
B --> C["run_boot_test.py:烧录前武装采集(daemon 全量日志兜底)"]
|
||||||
C --> D["@download 烧录(Reset & Run 触发重启)"]
|
C --> D["@download 烧录(Reset & Run 触发重启)"]
|
||||||
D --> E[提取日志 + verify_log 标记校验]
|
D --> E[提取日志 + verify_log 标记校验]
|
||||||
@@ -477,7 +477,7 @@ flowchart TD
|
|||||||
| 代码变更影响判定标准 | 立即把模块 Test_Guide **正文**改到与代码一致(现状说了算,不再记流水) |
|
| 代码变更影响判定标准 | 立即把模块 Test_Guide **正文**改到与代码一致(现状说了算,不再记流水) |
|
||||||
| 有意改视觉 | 刷黄金参考 + 重生成目检 PNG + 把新口径写进 `UI_自动化测试流程.md` 正文对应小节 |
|
| 有意改视觉 | 刷黄金参考 + 重生成目检 PNG + 把新口径写进 `UI_自动化测试流程.md` 正文对应小节 |
|
||||||
| 新增测试工具 / 新标记 | `自动化测试流程.md` 命令清单登记(§4.2) |
|
| 新增测试工具 / 新标记 | `自动化测试流程.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
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A["文档:规划 + 判定标准<br/>(Test_Guide)"] --> B["代码:套件 / 工具实现<br/>+ 双端注册核查"]
|
A["文档:规划 + 判定标准<br/>(Test_Guide)"] --> B["代码:套件 / 工具实现<br/>+ 双端注册核查"]
|
||||||
B --> C["G1 @build<br/>0 错 0 警"]
|
B --> C["G1 /keil-build<br/>0 错 0 警"]
|
||||||
C --> D["G2 @test<br/>采集→烧录→校验"]
|
C --> D["G2 /serial-boot-test<br/>采集→烧录→校验"]
|
||||||
D -->|PASS| E["G3 ui-verify<br/>(UI 改动时)"]
|
D -->|PASS| E["G3 ui-verify<br/>(UI 改动时)"]
|
||||||
E --> F["人工目检上机目检"]
|
E --> F["人工目检上机目检"]
|
||||||
F --> G["提交<br/>G0 门禁 + 逻辑提交"]
|
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,零硬件 |
|
| **静态门禁** | ✅ | `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/host/**/run_unit_test.py`(MSVC 离线编译) | 纯标准库 + `cl.exe`,零硬件 |
|
||||||
| 板端测试套件 | ⛔ | `test/board/` | 需真机 + 串口 + ST-LINK |
|
| 板端测试套件 | ⛔ | `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`)。
|
已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(`AGENTS.md`)。
|
||||||
2. **`mem-numbers` 钩子按 `.map` 有无自适应跳过**——它的事实源是 `MDK-ARM/**/*.map`
|
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` 时自动跳过该钩子**(`SKIP=mem-numbers`,钩子显示 `Skipped` 而非 `Passed`——
|
||||||
是"不适用",不是"通过");有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。
|
是"不适用",不是"通过");有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。
|
||||||
|
|
||||||
> 🔴 **门禁盲区(2026-10-02 记录)**:跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**——
|
> 🔴 **门禁盲区(2026-10-02 记录)**:跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**——
|
||||||
> 新增任务/栈/缓冲/代码导致的体积增长**不会在 CI 被拦**。故该跳过以 `::warning::` 明示
|
> 新增任务/栈/缓冲/代码导致的体积增长**不会在 CI 被拦**。故该跳过以 `::warning::` 明示
|
||||||
> (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `@build`(0/0)
|
> (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `/keil-build`(0/0)
|
||||||
> → `python scripts/check_mem_numbers.py --scan`。详见
|
> → `python scripts/check_mem_numbers.py --scan`。详见
|
||||||
> [`../../CI/远程CI完整指南.md`](../../CI/远程CI完整指南.md) §0.0。
|
> [`../../CI/远程CI完整指南.md`](../../CI/远程CI完整指南.md) §0.0。
|
||||||
|
|
||||||
@@ -578,7 +578,7 @@ CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runn
|
|||||||
- **`.map` 自适应与 CI 完全一致**:本地有 `.map` 就照常跑 `mem-numbers`,没有就跳过。
|
- **`.map` 自适应与 CI 完全一致**:本地有 `.map` 就照常跑 `mem-numbers`,没有就跳过。
|
||||||
- 退出码:`0` 通过 / `1` 门禁或单测失败 / `2` 环境错误。
|
- 退出码:`0` 通过 / `1` 门禁或单测失败 / `2` 环境错误。
|
||||||
|
|
||||||
> ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`@build`、板端测试套件/主机侧回归台架项都只存在于本地。
|
> ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`/keil-build`、板端测试套件/主机侧回归台架项都只存在于本地。
|
||||||
> 提交前跑一遍 `ci_local.py` 仍是**强制**动作,见 §7.1(`pre-commit run --all-files` 只覆盖提交期 47 根,
|
> 提交前跑一遍 `ci_local.py` 仍是**强制**动作,见 §7.1(`pre-commit run --all-files` 只覆盖提交期 47 根,
|
||||||
> 走不到 `commit-msg` 与 manual 阶段)。
|
> 走不到 `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/测试说明/板级测试/SD2506_Test_Guide.md` | RTC(TC-RTC-xxx) |
|
||||||
| `docs/测试说明/板级测试/FTP_Test_Guide.md` | FTP 服务(手动 + 阶段 8 自动化) |
|
| `docs/测试说明/板级测试/FTP_Test_Guide.md` | FTP 服务(手动 + 阶段 8 自动化) |
|
||||||
| `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试工具(Python 串口助手) |
|
| `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试工具(Python 串口助手) |
|
||||||
| `docs/测试说明/流程与规范/自动化测试流程.md` | @test 闭环细节 |
|
| `docs/测试说明/流程与规范/自动化测试流程.md` | /serial-boot-test 闭环细节 |
|
||||||
| `docs/测试说明/流程与规范/测试文档模板规范.md` | 测试文档模板与命名 |
|
| `docs/测试说明/流程与规范/测试文档模板规范.md` | 测试文档模板与命名 |
|
||||||
| `docs/测试说明/单元测试/看门狗单元测试说明.md` | 看门狗判定状态机(主机侧纯逻辑单测) |
|
| `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` | 测试开发总流程(规划 → 实现 → 门禁 → 沉淀);本文是其"测试规划(文档先行)"环节的可执行模板 |
|
||||||
| `自动化测试流程.md` | `@test` 闭环命令、参数与失败模式;本文"回归接入"章节引用它 |
|
| `自动化测试流程.md` | `/serial-boot-test` 闭环命令、参数与失败模式;本文"回归接入"章节引用它 |
|
||||||
| `docs/UI设计/UI_自动化测试流程.md` | UI 类改动的模拟器回归门禁 |
|
| `docs/UI设计/UI_自动化测试流程.md` | UI 类改动的模拟器回归门禁 |
|
||||||
| `docs/问题记录/<模块>_Trap_Records.md` | 陷阱记录;测试文档"已知陷阱"章节引用它 |
|
| `docs/问题记录/<模块>_Trap_Records.md` | 陷阱记录;测试文档"已知陷阱"章节引用它 |
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
### 1.2 设计原则覆盖表(原则短语 → 描述 → 验证用例)
|
### 1.2 设计原则覆盖表(原则短语 → 描述 → 验证用例)
|
||||||
### 1.3 测试环境(拓扑、工具链、硬件版本、PC 端依赖)
|
### 1.3 测试环境(拓扑、工具链、硬件版本、PC 端依赖)
|
||||||
### 1.4 启用方法(编译开关 + 运行命令)
|
### 1.4 启用方法(编译开关 + 运行命令)
|
||||||
### 1.5 回归接入(@test 标记 / PC 端脚本 / 门禁层级)
|
### 1.5 回归接入(/serial-boot-test 标记 / PC 端脚本 / 门禁层级)
|
||||||
|
|
||||||
## 2~N. 阶段 x:[阶段名]
|
## 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`,可按模块增补 |
|
| 禁止模式 | 默认 `FAIL;ERROR;HardFault;assert`,可按模块增补 |
|
||||||
| PC 端脚本 | `test/*.py` 工具清单与调用方式 |
|
| PC 端脚本 | `test/*.py` 工具清单与调用方式 |
|
||||||
| 门禁层级 | 板端测试套件 / 主机侧回归(串口标记 + 模拟器回归)/ 人工目检,见 `测试开发标准流程.md` §1 |
|
| 门禁层级 | 板端测试套件 / 主机侧回归(串口标记 + 模拟器回归)/ 人工目检,见 `测试开发标准流程.md` §1 |
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
| **类型** | 独立运行 / 需 PC 配合 / 需物理操作 / 在 xxTask 中运行 |
|
| **类型** | 独立运行 / 需 PC 配合 / 需物理操作 / 在 xxTask 中运行 |
|
||||||
| **耗时** | 预估执行时间 |
|
| **耗时** | 预估执行时间 |
|
||||||
| **入口** | 代码入口函数或宏定义 |
|
| **入口** | 代码入口函数或宏定义 |
|
||||||
| **出口** | 阶段结束标志(如串口输出内容,即 `@test` 可校验的标记) |
|
| **出口** | 阶段结束标志(如串口输出内容,即 `/serial-boot-test` 可校验的标记) |
|
||||||
| **阶段宏** | 对应使能宏(如 `ENABLE_PHASE1_TESTS`) |
|
| **阶段宏** | 对应使能宏(如 `ENABLE_PHASE1_TESTS`) |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -195,7 +195,7 @@ TC-{MOD}-{NNN}
|
|||||||
|
|
||||||
| 通道 | 判定方 | 适用 |
|
| 通道 | 判定方 | 适用 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 串口日志标记 | `@test` / `verify_log.py` 子串匹配 | 启动序列、阶段标题行、关键事件 |
|
| 串口日志标记 | `/serial-boot-test` / `verify_log.py` 子串匹配 | 启动序列、阶段标题行、关键事件 |
|
||||||
| 返回值 / 寄存器 / 数据 | 板端 `XXX_TEST_CHECK` 断言 | 驱动命令、协议交互、数据完整性 |
|
| 返回值 / 寄存器 / 数据 | 板端 `XXX_TEST_CHECK` 断言 | 驱动命令、协议交互、数据完整性 |
|
||||||
| 像素参考 | LVGL 模拟器回归(`simulator/tests/`) | UI 渲染、字体、样式 |
|
| 像素参考 | LVGL 模拟器回归(`simulator/tests/`) | UI 渲染、字体、样式 |
|
||||||
| 内存池水位 | 模拟器池基线 / 往返常量 | LVGL 对象泄漏、结构漂移 |
|
| 内存池水位 | 模拟器池基线 / 往返常量 | LVGL 对象泄漏、结构漂移 |
|
||||||
@@ -256,7 +256,7 @@ TC-{MOD}-{NNN}
|
|||||||
|
|
||||||
| 项 | 值 |
|
| 项 | 值 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `@test` 标记 | `=== XXX Tests:`(套件结尾汇总行的确切字符串) |
|
| `/serial-boot-test` 标记 | `=== XXX Tests:`(套件结尾汇总行的确切字符串) |
|
||||||
| PC 端脚本 | `test/xxx_test.py` |
|
| PC 端脚本 | `test/xxx_test.py` |
|
||||||
| 门禁层级 | 板端测试套件 + 主机侧回归 |
|
| 门禁层级 | 板端测试套件 + 主机侧回归 |
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
## 总体流程
|
## 总体流程
|
||||||
|
|
||||||
```
|
```
|
||||||
@test 命令触发
|
/serial-boot-test 触发
|
||||||
│
|
│
|
||||||
├─ Step 0 预检 .venv + serial_tool(selftest/ports 确认 COM)
|
├─ Step 0 预检 .venv + serial_tool(selftest/ports 确认 COM)
|
||||||
├─ Step 1 构建验证 keil-build skill(keil_build.py) (0 错误 0 警告才继续)
|
├─ 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) |
|
| `@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/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/stlink-flash/scripts/stlink_flash.py` | `stlink-flash` skill | ST-LINK CLI 封装:`flash [hex]`(烧 + `-Rst -Run`)/ **`cmp [hex]`(回读比对,2026-10-10 加)**/ `reset`(只复位)/ `hard`(nRST 脉冲,CLI 拒 `RM=` 自动降级)/ `halt`/`run`/`list`;原 `MDK-ARM/reset.bat` 已于 2026-10-10 删除并全并入本 skill |
|
||||||
| `.dsh/skills/serial-assistant/scripts/serial_tool.py` | `.dsh/skills/serial-assistant/` | Python 串口助手(常驻 daemon + 薄 CLI):ports/open/send/status/log/clear/wait-idle/selftest;全量日志 `state/serial.log`(20MB 自动截头) |
|
| `.dsh/skills/serial-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` 内部调用)。
|
- **烧录**:`@download` 退出码 0(`run_boot_test.py` 内部调用)。
|
||||||
- **日志**:`verify_log.py` 退出码
|
- **日志**:`verify_log.py` 退出码
|
||||||
- `0` = PASS:所有 `-Required` 标记均出现,且无 `-Forbidden` 模式命中
|
- `0` = PASS:所有 `-Required` 标记均出现,且无 `-Forbidden` 模式命中
|
||||||
- `1` = FAIL:存在缺失标记(`[MISS]`)或违规模式(`[HIT]`)
|
- `1` = FAIL:存在缺失标记(`[MISS]`)或违规模式(`[HIT]`)
|
||||||
- `2` = 日志文件不可读
|
- `2` = 日志文件不可读
|
||||||
- **固件身份戳(2026-10-10 加)**:`App/app/app_main.c` 开机打印 `fw build: <__DATE__> <__TIME__>`(编译期展开成一段**连续 ASCII**,`.hex` 里可原样搜到)。`run_boot_test.py` 从**待烧的 hex** 里抠出该串并追加成 Required 标记 → 芯片里跑的必须就是这份构建。反例实测:把 hex 里的戳改成 `Oct 9 2026 01:00:00` 再跑,其余标记全中但 `[MISS] fw build: Oct 9 2026 01:00:00` → FAIL——这正是过去"反复重下载、芯片一直是旧镜像"在闭环里**完全隐形**的场景。`--no-fw-check` 关闭;hex 里没有该串(旧构建)只 WARN 跳过、不假判。
|
- **固件身份戳(2026-10-10 加)**:`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 加)**:采集结束后自动跑 `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 加)**:`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 第三批)**:
|
- **判定可读性 + 时序(2026-10-10 第三批)**:
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
| daemon 无响应 / 自动拉起失败 | 查 `.dsh/skills/serial-assistant/scripts/state/daemon.log`;`serial_tool.py restart` 重建 daemon;检查 COM 是否被其他程序占用 |
|
| 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),首行晚到不再被提前收尾 |
|
| 抓到日志缺 `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 异常行 |
|
| 烧录后无数据(`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` 重烧后闭环会自动复核 |
|
| `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` 放宽 |
|
| `boot banner x2 > allowed 1` | 采集窗口内板子自己复位过(IWDG 咬狗 / Fault 后 `NVIC_SystemReset` / 掉电重启)。处置:看判定里逐条打印的 `reset cause:` 定位;跑**故意复位**的套件用 `--max-boot-count N` 放宽 |
|
||||||
| `marker order violated` | 标记全中但**先后顺序颠倒** = 某步在另一步之后补做(就绪屏障失序 / 失败重试补跑)。处置:看判定打印的各标记首次行号定位;`--require-order` 只在需要强约束时启用 |
|
| `marker order violated` | 标记全中但**先后顺序颠倒** = 某步在另一步之后补做(就绪屏障失序 / 失败重试补跑)。处置:看判定打印的各标记首次行号定位;`--require-order` 只在需要强约束时启用 |
|
||||||
@@ -118,13 +118,13 @@
|
|||||||
## UI 屏幕自动化
|
## UI 屏幕自动化
|
||||||
|
|
||||||
- **初始化回归**:`App/task/ui_task.c` 已有 `UI port init: OK` / `UI menu init: OK` / `UI net sink: started`
|
- **初始化回归**:`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 张黄金参考 + 池水位门禁,
|
- **屏幕内容回归**:由 LVGL 模拟器像素回归覆盖(文本级断言 + 66 张黄金参考 + 池水位门禁,
|
||||||
见 `docs/UI设计/UI_自动化测试流程.md`);上机屏幕仍需人工目检(见 `测试开发标准流程.md` §1)。
|
见 `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"`。
|
`-Required "App init OK;Network Init;net init: OK;PHY: 100M FULL"`。
|
||||||
- **测试套件构建**:`test/board/test_config.h` 开启某个 `TEST_SUITE_*` 后,把套件打印的
|
- **测试套件构建**:`test/board/test_config.h` 开启某个 `TEST_SUITE_*` 后,把套件打印的
|
||||||
`=== ... Test:` 标题作为 Required 标记,配合 `test/` 下各用例输出判断。
|
`=== ... Test:` 标题作为 Required 标记,配合 `test/` 下各用例输出判断。
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Python 串口助手(serial_tool)使用指南
|
# 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)
|
> **版本**:v1.1(与 `.dsh/skills/serial-assistant/scripts/serial_tool.py` 当前实现一致,2026-09-15)
|
||||||
|
|
||||||
## 1. 它是什么(60 秒版)
|
## 1. 它是什么(60 秒版)
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
| `status` | 全状态一条 JSON(含 `state`、`rx_age_ms` = 距最后一个 RX 字节的毫秒数,`null` = 这个 daemon 还没收过字节) | 是 |
|
| `status` | 全状态一条 JSON(含 `state`、`rx_age_ms` = 距最后一个 RX 字节的毫秒数,`null` = 这个 daemon 还没收过字节) | 是 |
|
||||||
| `log` | 读日志文件(`--tail N` / `--grep P`) | **否**(直接读文件,daemon 死了也能用) |
|
| `log` | 读日志文件(`--tail N` / `--grep P`) | **否**(直接读文件,daemon 死了也能用) |
|
||||||
| `clear` | 清空日志 + 代际 +1 | 是 |
|
| `clear` | 清空日志 + 代际 +1 | 是 |
|
||||||
| `wait-idle` | 等 RX 静默(@test 的收尾判据) | 是 |
|
| `wait-idle` | 等 RX 静默(/serial-boot-test 的收尾判据) | 是 |
|
||||||
| `start` / `stop` / `restart` | daemon 生命周期 | — |
|
| `start` / `stop` / `restart` | daemon 生命周期 | — |
|
||||||
| `selftest` | 无头自检(行拆分/解码/截头算法,无需硬件) | 否 |
|
| `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` 快速失败。
|
- **首字节门(2026-10-10 加)**:首字节到达**之前**不启动静默计时——旧实现从进入即计时,板子第一行比 `--settle-ms` 来得晚就提前收尾("开机日志缺尾巴"的另一半成因)。`--first-timeout-ms`(0 = 同 `--max-ms`)到点仍零字节 → `no_data` 快速失败。
|
||||||
- **`rx_bytes` 是本次增量**(旧实现回的是 daemon 累计值,常驻 daemon 下动辄百万,无判读价值)。
|
- **`rx_bytes` 是本次增量**(旧实现回的是 daemon 累计值,常驻 daemon 下动辄百万,无判读价值)。
|
||||||
- **CLI 超时自动推导**:未显式带 `--timeout` 时按 `--max-ms/1000 + 5` 取值,不再需要手工加(旧版固定 5s 会在 daemon 还在等时掐断连接 → CLI exit 2 + daemon.log 刷 `ConnectionAbortedError`)。
|
- **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 生命周期
|
### 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
|
... serial_tool.py close
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.2 抓一次开机日志(手动版 @test)
|
### 5.2 抓一次开机日志(手动版 /serial-boot-test)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
... serial_tool.py open --port COM6
|
... serial_tool.py open --port COM6
|
||||||
@@ -322,7 +322,7 @@ Get-Content '.dsh\skills\serial-assistant\scripts\state\daemon.log' -Tail 30 #
|
|||||||
| `baudRate` | 默认波特率 | 115200 |
|
| `baudRate` | 默认波特率 | 115200 |
|
||||||
| `httpPort` | daemon HTTP 端口(一般不动) | 18765 |
|
| `httpPort` | daemon HTTP 端口(一般不动) | 18765 |
|
||||||
|
|
||||||
**换端口只改这个文件**(`@test` 与手动调试都读它)。`httpPort` 冲突时才需要改。
|
**换端口只改这个文件**(`/serial-boot-test` 与手动调试都读它)。`httpPort` 冲突时才需要改。
|
||||||
|
|
||||||
## 7. 故障排查
|
## 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 释放后重开陷阱特判) |
|
| F2 | **无历史缓冲**:`read` 只能读调用**之后**到达的字节 | 抓开机日志被迫走"采集窗口 + first-byte 触发 + EOF 重试"仪式(6 个参数 + JSON 解析 + CH340 释放后重开陷阱特判) |
|
||||||
|
|
||||||
Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机器/用户可复现);常驻 daemon 把**全量** RX/TX 持续写入日志文件,历史 = 日志文件本身,采集窗口仪式整体消失。
|
Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机器/用户可复现);常驻 daemon 把**全量** RX/TX 持续写入日志文件,历史 = 日志文件本身,采集窗口仪式整体消失。
|
||||||
@@ -28,7 +28,7 @@ Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机
|
|||||||
| G3 | Agent 一切操作 = 带硬超时(5s)的短命令;串口所有权归常驻 daemon |
|
| G3 | Agent 一切操作 = 带硬超时(5s)的短命令;串口所有权归常驻 daemon |
|
||||||
| G4 | 一条 `status` 命令看全状态(daemon/端口/计数/最后 RX/日志文件/错误) |
|
| G4 | 一条 `status` 命令看全状态(daemon/端口/计数/最后 RX/日志文件/错误) |
|
||||||
| G5 | 随时清空日志(原子 + 代际号,新旧内容可区分) |
|
| G5 | 随时清空日志(原子 + 代际号,新旧内容可区分) |
|
||||||
| G6 | @test 一键流程切换新工具:open → clear → 烧录 → 读文件 → verify,无采集窗口 |
|
| G6 | /serial-boot-test 一键流程切换新工具:open → clear → 烧录 → 读文件 → verify,无采集窗口 |
|
||||||
| G7 | 全流程**不关端口**(ST-Link 烧录不占 COM),从根上规避 CH340 释放/重开陷阱 |
|
| G7 | 全流程**不关端口**(ST-Link 烧录不占 COM),从根上规避 CH340 释放/重开陷阱 |
|
||||||
|
|
||||||
## 2. 需求清单
|
## 2. 需求清单
|
||||||
@@ -45,7 +45,7 @@ Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机
|
|||||||
| R8 | 日志 20MB 上限,超限**原地截头**保留尾部 10MB(不重开端口/不 rename) | P0 | 补充(用户确认) |
|
| R8 | 日志 20MB 上限,超限**原地截头**保留尾部 10MB(不重开端口/不 rename) | P0 | 补充(用户确认) |
|
||||||
| R9 | 开端口不翻转 DTR/RTS(防 STM32 自动复位电路误复位),可配置 | P0 | 补充 |
|
| R9 | 开端口不翻转 DTR/RTS(防 STM32 自动复位电路误复位),可配置 | P0 | 补充 |
|
||||||
| R10 | 串口读错/拔线 → daemon 存活,state=error,可重开;daemon 被杀可自恢复 | 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 | 补充 |
|
| R12 | `send`(TX 进日志)、`log --tail/--grep`、`start/stop/restart`、`selftest` 无头自检 | P1 | 补充 |
|
||||||
|
|
||||||
## 3. 总体方案
|
## 3. 总体方案
|
||||||
@@ -84,7 +84,7 @@ flowchart LR
|
|||||||
| `send` | 发 TX 字节(进日志);默认**精确字节**不加换行 | 位置参数文本(UTF-8)或 `--hex 0D0A`;`--nl` 追加 CRLF | `{"ok":true,"bytes":12}` |
|
| `send` | 发 TX 字节(进日志);默认**精确字节**不加换行 | 位置参数文本(UTF-8)或 `--hex 0D0A`;`--nl` 追加 CRLF | `{"ok":true,"bytes":12}` |
|
||||||
| `status` | 全状态(见 §4.2) | — | 见 §4.2 |
|
| `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}` |
|
| `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}` |
|
| `clear` | 清空日志:原地截断 + 写 `[SYS] log cleared (gen=N+1)` | — | `{"ok":true,"gen":8}` |
|
||||||
| `start` / `stop` / `restart` | daemon 生命周期 | — | `{"ok":true,"pid":…}` / stop 后 `{"ok":true,"stopped":true}` |
|
| `start` / `stop` / `restart` | daemon 生命周期 | — | `{"ok":true,"pid":…}` / stop 后 `{"ok":true,"stopped":true}` |
|
||||||
| `selftest` | 无头自检(行拆分器/解码回退/截头算法 单测回环,无需硬件) | — | 0=PASS |
|
| `selftest` | 无头自检(行拆分器/解码回退/截头算法 单测回环,无需硬件) | — | 0=PASS |
|
||||||
@@ -175,13 +175,13 @@ flowchart LR
|
|||||||
| 重开退避 | 错误恢复路径重开端口前 ≥1.5s 退避(CH340 特性);正常流程不重开 |
|
| 重开退避 | 错误恢复路径重开端口前 ≥1.5s 退避(CH340 特性);正常流程不重开 |
|
||||||
| `stop` | 关端口(flush 尾行)→ 写 `[SYS] port closed` → 退出 |
|
| `stop` | 关端口(flush 尾行)→ 写 `[SYS] port closed` → 退出 |
|
||||||
| 进程被强杀 | Windows 进程退出自动释放 COM 端口;下次命令自动拉起新实例(`open` 幂等路径) |
|
| 进程被强杀 | 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 |
|
| 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` 显式回显 |
|
| 配置读取(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
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
@@ -193,7 +193,7 @@ flowchart LR
|
|||||||
```
|
```
|
||||||
|
|
||||||
- 对比旧流程:6 个采集窗口参数 + JSON 解析 + EOF 重试 **全部删除**;采集 = 端口常开 + 读文件
|
- 对比旧流程: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)
|
- 参数保留:`-Port` `-BaudRate` `-FlashCmd` `-Required` `-Forbidden` `-OutDir`;删除 `-DurationMs` 等窗口参数,新增 `-SettleMs`(静默判据,**默认 8000**——boot 任务间有 3~4s 自然静默间隙,2000 会截断)`-MaxWaitMs`(默认 30000)
|
||||||
|
|
||||||
### 7.2 配套变更清单
|
### 7.2 配套变更清单
|
||||||
@@ -230,7 +230,7 @@ flowchart LR
|
|||||||
| A4 | `stop` 后强杀 daemon(taskkill)→ 再发任意命令 | 自动拉起成功,`open` 幂等路径无 CH340 EOF 错误 |
|
| A4 | `stop` 后强杀 daemon(taskkill)→ 再发任意命令 | 自动拉起成功,`open` 幂等路径无 CH340 EOF 错误 |
|
||||||
| A5 | 日志灌至 >20MB | 自动截头至 ~10MB,写入连续、无重开端口,`[SYS] truncated` 行存在 |
|
| A5 | 日志灌至 >20MB | 自动截头至 ~10MB,写入连续、无重开端口,`[SYS] truncated` 行存在 |
|
||||||
| A6 | 运行中拔 CH340 → `status` | daemon 存活,`state=error`;重插 + `open` 恢复 |
|
| 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 |
|
| A8 | `selftest` | 0=PASS |
|
||||||
|
|
||||||
## 10. 实施步骤(按序执行)
|
## 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` 手动走通 ✅ |
|
| 步骤 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 + 命令走查 ✅ |
|
| 步骤 3 | CLI 全子命令 + 自动拉起 + 超时契约 + `selftest` | serial_tool.py(CLI 部分) | `selftest` 0=PASS + 命令走查 ✅ |
|
||||||
| 步骤 4 | 验收 A1–A6、A8 | 测试通过记录 | A1–A5/A8 PASS ✅(本机无 CH340,A6 待硬件) |
|
| 步骤 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 工具历史除外) ✅ |
|
| 步骤 6 | 文档/配置迁移(§7.2 全清单) | docs + 配置 | 全库 grep 无 serial-mcp-server 残留引用(本文 §12 工具历史除外) ✅ |
|
||||||
|
|
||||||
提交:全部完成后单提交(pre-commit 门禁;纯宿主侧 Python,不涉及 Keil 构建)。
|
提交:全部完成后单提交(pre-commit 门禁;纯宿主侧 Python,不涉及 Keil 构建)。
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# 启动日志(boot_log)存档
|
# 启动日志(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]` 行)。
|
用于**跨构建的栈 / 堆水位对照**(解析其中的 `[STACK]` 行)。
|
||||||
|
|
||||||
## 1. 为什么需要存档
|
## 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 后才报告);
|
- 台架长跑的 20MB 串口日志**截头会丢掉早期 `[STACK]` 水位行**(开机首次约 5s 后才报告);
|
||||||
- 两者叠加导致**无法事后回溯**"改代码前后"的栈水位变化。
|
- 两者叠加导致**无法事后回溯**"改代码前后"的栈水位变化。
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
| 命名 | `<日期>_<构建标识>_boot_log.txt`,例:`20261005_prod_none_boot_log.txt` |
|
| 命名 | `<日期>_<构建标识>_boot_log.txt`,例:`20261005_prod_none_boot_log.txt` |
|
||||||
| 构建标识 | 建议含:产品 / 测试套件(`prod_none` / `suite_ch395f` …)+ 负载(`idle` / `v5_30min` / `ftp_stress`) |
|
| 构建标识 | 建议含:产品 / 测试套件(`prod_none` / `suite_ch395f` …)+ 负载(`idle` / `v5_30min` / `ftp_stress`) |
|
||||||
| 随文件登记 | 在下方 §3 清单登记日期、构建、负载、关键 `[STACK]` 水位(如 defaultTask 峰值) |
|
| 随文件登记 | 在下方 §3 清单登记日期、构建、负载、关键 `[STACK]` 水位(如 defaultTask 峰值) |
|
||||||
| 入库门槛 | **只存有对照价值的快照**——常规 `@test` 通过日志不必入库 |
|
| 入库门槛 | **只存有对照价值的快照**——常规 `/serial-boot-test` 通过日志不必入库 |
|
||||||
| 故障注入日志 | 含**刻意产生**的 `[ERR]` 行(如兜底门闭锁)的日志用自定义标记集采集:`--required` 加闭锁/复归两行、`--forbidden` 去掉 `[ERR]` 保留 `FAIL;ERROR;HardFault`。此类日志的 PASS 只代表注入行为符合预期,不代表标记集等价于日常门禁 |
|
| 故障注入日志 | 含**刻意产生**的 `[ERR]` 行(如兜底门闭锁)的日志用自定义标记集采集:`--required` 加闭锁/复归两行、`--forbidden` 去掉 `[ERR]` 保留 `FAIL;ERROR;HardFault`。此类日志的 PASS 只代表注入行为符合预期,不代表标记集等价于日常门禁 |
|
||||||
| 证据可达性 | 台账**只准引用已入库的文件**。被忽略规则挡住的产物(如全局 `*.bin`)要么显式 force-add 入库,要么把行内引用改写成「本机产物(不入库)」——**两者都不做 = 让台账行指着仓库里不存在的证据**,按图索骥的人只能拿到 File Not Found |
|
| 证据可达性 | 台账**只准引用已入库的文件**。被忽略规则挡住的产物(如全局 `*.bin`)要么显式 force-add 入库,要么把行内引用改写成「本机产物(不入库)」——**两者都不做 = 让台账行指着仓库里不存在的证据**,按图索骥的人只能拿到 File Not Found |
|
||||||
|
|
||||||
|
|||||||
+18
-18
@@ -19,7 +19,7 @@ Python 环境、以及 `docs/任务规划/` 这类**全局唯一登记簿**都
|
|||||||
**做法**:每个任务一棵 `git worktree` + 一条任务分支,任务之间物理隔离;干完由固定命令合回主干。
|
**做法**:每个任务一棵 `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>` |
|
| 分支命名 | `task/<ID>-<slug>` | `worktree/<name>` |
|
||||||
|
|
||||||
两条**并发红线由本方案单方守住**(插件不感知):`build`(UV4 单实例)与 `bench`(COM6 / ST-LINK /
|
两条**并发红线由本方案单方守住**(插件不感知):`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)
|
会照旧自持自释,持有者名从 cwd 反推为 `pw:<name>`;`bench` 仍须手工占同一个名。共享联接(§3.3)
|
||||||
插件不会建,由本 skill 的 `plugin adopt <name>` 补(执行体命令行见 §8)。
|
插件不会建,由本 skill 的 `plugin adopt <name>` 补(执行体命令行见 §8)。
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ D:/Code/DTU Code/
|
|||||||
磁盘从来不是这条线的硬约束——实测一棵纯净检出 **84.0 MiB / 2857 个受控文件**(含 LFS smudge
|
磁盘从来不是这条线的硬约束——实测一棵纯净检出 **84.0 MiB / 2857 个受控文件**(含 LFS smudge
|
||||||
后的真图;`docs/` 独占 46 MiB),而 `.venv` 与 `simulator/SDL2` 走目录联接共享、不随树复制;同日 D
|
后的真图;`docs/` 独占 46 MiB),而 `.venv` 与 `simulator/SDL2` 走目录联接共享、不随树复制;同日 D
|
||||||
盘余 1.4 TB,**8 棵全开的最坏估算也只有约 3.2 GB**(每棵再叠一次构建产物:Keil 中间件 289 MB + 模拟器 35 MB)。
|
盘余 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` 追平与冲突面乘一遍。所以这条线现在是**管
|
的合并必须串行(§5 下方说明),而每多一棵树把 `sync` 追平与冲突面乘一遍。所以这条线现在是**管
|
||||||
理半径闸门**而非磁盘闸门:调到 8 = 把「同时盯几棵」交回人,两把锁的排队代价用 `$TW lock status` 与
|
理半径闸门**而非磁盘闸门:调到 8 = 把「同时盯几棵」交回人,两把锁的排队代价用 `$TW lock status` 与
|
||||||
§7 的台架台账观测;脑子里的名额够不够,看的是锁队列长度,不是磁盘。
|
§7 的台架台账观测;脑子里的名额够不够,看的是锁队列长度,不是磁盘。
|
||||||
@@ -152,8 +152,8 @@ D:/Code/DTU Code/
|
|||||||
| 编码 | 在新空间里改代码 | 可并行 |
|
| 编码 | 在新空间里改代码 | 可并行 |
|
||||||
| 单测产物 | `%TEMP%\dtu_host\<工作树哈希>\` 分家 + 每跑独占 `runs\<pid>-<ns>\` | **不需要锁,但必须按树分家**(旧固定共享名跨树互踩:A 树可能执行 B 树编出的同名 exe,summary 相同不可辨;build/bench 是真独占,继续用锁) |
|
| 单测产物 | `%TEMP%\dtu_host\<工作树哈希>\` 分家 + 每跑独占 `runs\<pid>-<ns>\` | **不需要锁,但必须按树分家**(旧固定共享名跨树互踩:A 树可能执行 B 树编出的同名 exe,summary 相同不可辨;build/bench 是真独占,继续用锁) |
|
||||||
| 门禁 | `scripts/ci_local.py`(静态门禁 + 主机侧纯逻辑单测,纯软件) | **可并行** |
|
| 门禁 | `scripts/ci_local.py`(静态门禁 + 主机侧纯逻辑单测,纯软件) | **可并行** |
|
||||||
| 构建 | `@build`(UV4 增量,需持 `build` 锁) | **必须串行** |
|
| 构建 | `/keil-build`(UV4 增量,需持 `build` 锁) | **必须串行** |
|
||||||
| 台架 | `@test` / 板级套件(需先持台架锁,见 §7) | **必须串行** |
|
| 台架 | `/serial-boot-test` / 板级套件(需先持台架锁,见 §7) | **必须串行** |
|
||||||
| 提交 | 本地 `git commit`(多行正文用 `-F <UTF-8 无 BOM 文件>`) | 可并行 |
|
| 提交 | 本地 `git commit`(多行正文用 `-F <UTF-8 无 BOM 文件>`) | 可并行 |
|
||||||
| `sync` | `rebase` 追平主干,把冲突控制在任务分支上 | 可并行 |
|
| `sync` | `rebase` 追平主干,把冲突控制在任务分支上 | 可并行 |
|
||||||
| `done` | 预演 → 合入主干 → 残留审计 → 重生成产物 → 门禁 → 清理工作树 | **必须串行** |
|
| `done` | 预演 → 合入主干 → 残留审计 → 重生成产物 → 门禁 → 清理工作树 | **必须串行** |
|
||||||
@@ -175,7 +175,7 @@ D:/Code/DTU Code/
|
|||||||
| `docs/任务规划/`(整个文件夹) | 全项目唯一登记簿,每个任务都要销项 | 由主工作树统一改,任务分支不碰 |
|
| `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`、`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` **重新生成**,不手工并 |
|
| `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` | 新增主机单测要加钩子 | 并集处理 |
|
| `.pre-commit-config.yaml` | 新增主机单测要加钩子 | 并集处理 |
|
||||||
| `docs/内存管理/RAM使用分析与优化方案.md`(及其 `mem-numbers` 快照) | `mem-numbers` 快照 | **绝不手工合数字**,合并后 `scripts/check_mem_numbers.py --write` 重生成 |
|
| `docs/内存管理/RAM使用分析与优化方案.md`(及其 `mem-numbers` 快照) | `mem-numbers` 快照 | **绝不手工合数字**,合并后 `scripts/check_mem_numbers.py --write` 重生成 |
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ D:/Code/DTU Code/
|
|||||||
|
|
||||||
### 7.1 台架锁
|
### 7.1 台架锁
|
||||||
|
|
||||||
以下物理资源**各只有一份**,故 `@test` / 板级套件 / 烧录必须串行:
|
以下物理资源**各只有一份**,故 `/serial-boot-test` / 板级套件 / 烧录必须串行:
|
||||||
|
|
||||||
- 串口 `COM6` @115200
|
- 串口 `COM6` @115200
|
||||||
- ST-LINK SN `0B250D12A218303030303032`
|
- 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}`。
|
**TTL 按锁分设**(2026-10-12):`LOCK_TTL_HOURS = {"build": 1.0, "bench": 6.0}`。
|
||||||
口径 = 兜底窗口应与**操作真实时长**同量级:编译是秒~分钟级(`keil_build.py` 自持自释,
|
口径 = 兜底窗口应与**操作真实时长**同量级:编译是秒~分钟级(`keil_build.py` 自持自释,
|
||||||
窗口 ≈ UV4 运行时长),1h 已足够宽松;bench 是**会话级**(flash→`@test`→探针),才配得上 6h。
|
窗口 ≈ UV4 运行时长),1h 已足够宽松;bench 是**会话级**(flash→`/serial-boot-test`→探针),才配得上 6h。
|
||||||
持有者还可用 `lock acquire ... --ttl <小时>` **自报**更精确的窗口——
|
持有者还可用 `lock acquire ... --ttl <小时>` **自报**更精确的窗口——
|
||||||
`keil_build.py` 即按其 `--timeout` 自报(`timeout/3600 + 15min`,下限 0.5h)。
|
`keil_build.py` 即按其 `--timeout` 自报(`timeout/3600 + 15min`,下限 0.5h)。
|
||||||
|
|
||||||
@@ -230,11 +230,11 @@ D:/Code/DTU Code/
|
|||||||
|
|
||||||
### 7.2 构建
|
### 7.2 构建
|
||||||
|
|
||||||
`@build`(UV4 增量)在新空间里是**独占目录**,但 **UV4 不能两个实例同时 `-b`**(2026-10-12 用户确认,
|
`/keil-build`(UV4 增量)在新空间里是**独占目录**,但 **UV4 不能两个实例同时 `-b`**(2026-10-12 用户确认,
|
||||||
无需实测)——因此构建**同样纳入串行锁**(`build`),与台架锁并列。
|
无需实测)——因此构建**同样纳入串行锁**(`build`),与台架锁并列。
|
||||||
|
|
||||||
> 结论:任务期真正可并行的只有**编码与静态门禁**(`ci_local.py` 的静态门禁 / 主机侧纯逻辑单测,纯软件、零硬件);
|
> 结论:任务期真正可并行的只有**编码与静态门禁**(`ci_local.py` 的静态门禁 / 主机侧纯逻辑单测,纯软件、零硬件);
|
||||||
> `@build` 与烧录/台架都必须排队。
|
> `/keil-build` 与烧录/台架都必须排队。
|
||||||
|
|
||||||
### 7.3 并行道与写范围规则(规划层 ↔ 执行层映射)
|
### 7.3 并行道与写范围规则(规划层 ↔ 执行层映射)
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ D:/Code/DTU Code/
|
|||||||
|
|
||||||
## 8. SKILL 设计
|
## 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`(纯标准库,
|
**执行体**:`.dsh/skills/task-worktree/scripts/task_worktree.py`(纯标准库,
|
||||||
照 `keil_build.py` 的写法,整目录可拷到别的项目复用)。**已落地并端到端实测**(见 §11)。
|
照 `keil_build.py` 的写法,整目录可拷到别的项目复用)。**已落地并端到端实测**(见 §11)。
|
||||||
@@ -298,19 +298,19 @@ D:/Code/DTU Code/
|
|||||||
| 层级 | 内容 | 时机 |
|
| 层级 | 内容 | 时机 |
|
||||||
| ------- | ------------------------------------ | ------------------------------ |
|
| ------- | ------------------------------------ | ------------------------------ |
|
||||||
| 任务期 | `scripts/ci_local.py`(静态门禁 + 主机侧纯逻辑单测,可并行) | 每个任务各自跑 |
|
| 任务期 | `scripts/ci_local.py`(静态门禁 + 主机侧纯逻辑单测,可并行) | 每个任务各自跑 |
|
||||||
| 任务期 | `@build`(0 错 0 警,**持 `build` 锁串行**) | 每个任务各自跑 |
|
| 任务期 | `/keil-build`(0 错 0 警,**持 `build` 锁串行**) | 每个任务各自跑 |
|
||||||
| **新空间首次** | `@build` 一次,生成 `.map` | 见下表下注释:不先构建,体积类门禁必然红 |
|
| **新空间首次** | `/keil-build` 一次,生成 `.map` | 见下表下注释:不先构建,体积类门禁必然红 |
|
||||||
| 任务期(可选) | `@test` / 板级套件(持 `bench` 锁) | 需真机验证的任务 |
|
| 任务期(可选) | `/serial-boot-test` / 板级套件(持 `bench` 锁) | 需真机验证的任务 |
|
||||||
| 回收期 | 重生成产物 → `ci_local.py` → `@build` | `done` 内强制(`--skip-gates` 可跳过) |
|
| 回收期 | 重生成产物 → `ci_local.py` → `/keil-build` | `done` 内强制(`--skip-gates` 可跳过) |
|
||||||
| 人工 | push 许可 | 回收后 |
|
| 人工 | push 许可 | 回收后 |
|
||||||
|
|
||||||
> ⚠ **新开工作树后先 `@build` 一次再谈门禁**(实测 2026-10-25):`.map` 是每棵树各自的构建产物、
|
> ⚠ **新开工作树后先 `/keil-build` 一次再谈门禁**(实测 2026-10-25):`.map` 是每棵树各自的构建产物、
|
||||||
> 不入版本库,所以依赖它的事实源的门禁——`mem-numbers`、`mem-budget`、以及 `ci_local.py` 的
|
> 不入版本库,所以依赖它的事实源的门禁——`mem-numbers`、`mem-budget`、以及 `ci_local.py` 的
|
||||||
> 体积门禁——在从未构建过的新空间里**必然红**。这不是代码缺陷,脚本此时也会把这句原因打出来
|
> 体积门禁——在从未构建过的新空间里**必然红**。这不是代码缺陷,脚本此时也会把这句原因打出来
|
||||||
> (`check_mem_numbers.py` / `ram_baseline.py` 的提示文案)。顺带一条同源提醒:`done` 的门禁
|
> (`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_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_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、跳过渲染" |
|
| `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_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_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 静态门禁 |
|
| `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` 即可,**无需修改仓库脚本** |
|
| 安装路径 | **任意目录** | `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 设备包提供 |
|
| 设备支持 | 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** |
|
| 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` |
|
| 命令行烧录 | `& '.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`,
|
> ⚠ **版本一致性关键**:.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` 即就绪 |
|
| 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);换端口只改这个文件 |
|
| 调试串口 | 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 重试仪式。
|
> 采集 = 端口常开 + daemon 全量日志(`.dsh/skills/serial-assistant/scripts/state/serial.log`),无采集窗口/first-byte/EOF 重试仪式。
|
||||||
> 命令契约与陷阱见 `docs/测试说明/测试工具/串口助手设计说明.md`、`docs/测试说明/流程与规范/自动化测试流程.md`。
|
> 命令契约与陷阱见 `docs/测试说明/测试工具/串口助手设计说明.md`、`docs/测试说明/流程与规范/自动化测试流程.md`。
|
||||||
@@ -423,7 +423,7 @@ workflow 见 `.gitea/workflows/ci.yml`;覆盖边界与判定细节见 [`../测
|
|||||||
| Actions 开关 | 实例 `[actions] ENABLED = true` | site admin 配置;未开启则 push 不触发任何 job |
|
| Actions 开关 | 实例 `[actions] ENABLED = true` | site admin 配置;未开启则 push 不触发任何 job |
|
||||||
| runner OS | **Windows**(`runs-on: windows`) | labels 须含 `windows`;与开发机同平台,避免脚本语法分叉 |
|
| 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) |
|
| 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 会照常跑该钩子 |
|
| `mem-numbers` | 有 `.map` 就跑,无则 `SKIP` | Keil 构建产物 `.map` 已 gitignore;自建带 Keil 的 runner 会照常跑该钩子 |
|
||||||
|
|
||||||
**runner 就绪前的过渡**:CI 只是"多一道网",**不能替代本地门禁**。提交前仍须在本地跑:
|
**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 设计约束,速度受限) |
|
| 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` |
|
| 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 删除) |
|
| 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` 占位宏已删) |
|
| 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 序表须按丝印对齐 |
|
| 状态 LED ×6 | GPIO | **PC5 / PB0 / PB1 / PB2 / PF11 / PF12**(LED1~LED6) | **低电平点亮**;网名↔实板丝印为 `LED(n+4)` 倒序(V0.1 网表关注点 V3),固件 LED 序表须按丝印对齐 |
|
||||||
|
|
||||||
### 3.3 资源预算(部署红线)
|
### 3.3 资源预算(部署红线)
|
||||||
|
|
||||||
实测(**2026-10-22 `@build` 快照**)。实时权威值恒以 `docs/内存管理/RAM预算台账.md` 与
|
实测(**2026-10-22 `/keil-build` 快照**)。实时权威值恒以 `docs/内存管理/RAM预算台账.md` 与
|
||||||
`docs/内存管理/RAM使用分析与优化方案.md` §2.1 受管快照为准,本节只是部署视角的摘要:
|
`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) |
|
| 2 | 网络连通 | PC ping 192.168.1.100 | 通(PHY 100M FULL) |
|
||||||
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
|
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
|
||||||
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
|
| 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 |
|
| 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 校准。缺这一步不报错,只是**读数带失调** |
|
| 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 时钟 | 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(事件驱动唤醒) |
|
| 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/功率需硬件补偏置或明确算法口径 |
|
| 前端偏置 | 每路 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 |
|
| 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-继电器保护单元分析》)。
|
> **参考来源**:`D:\Code\DTU\参考资料\huaiyuan\huaiyuan\Scatter_P_HV03.01_SV01.01\项目分析文档\04-算法与采样专题\ADC采集至数据使用全流程详解`(同专题另有《项目详细分析-交流采样与有效值计算》《傅氏算法详解》《项目详细分析-阶段6-继电器保护单元分析》)。
|
||||||
> **阅读前提**:先读 `docs/算法/傅氏算法详解.md`(算法原理)与 `docs/硬件平台/ADC通道映射说明.md`(采样通道权威口径)。
|
> **阅读前提**:先读 `docs/算法/傅氏算法详解.md`(算法原理)与 `docs/硬件平台/ADC通道映射说明.md`(采样通道权威口径)。
|
||||||
> **相关文档**:`docs/产品需求/产品需求设计文档.md`、`docs/任务规划/README.md`、`docs/内存管理/RAM使用分析与优化方案.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 |
|
| 已实现子系统 | 网络(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 单极性) |
|
| 采样硬件 | 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` 已出现 |
|
| 采样软件 | `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 上禁止跨转换器做相位/功率/矢量运算 |
|
| 已知硬件缺陷 | 登记簿条目「双转换器无共同采样触发」:8 路电气量分属两片转换器 → V0.1 上禁止跨转换器做相位/功率/矢量运算 |
|
||||||
|
|
||||||
### 1.3 复现范围界定
|
### 1.3 复现范围界定
|
||||||
@@ -65,9 +65,9 @@
|
|||||||
| 依赖 | 说明 | 状态 |
|
| 依赖 | 说明 | 状态 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| **B0 产品定义** | 采集是否持续 / 采样率与通道 / 转发目标 / 保护元件清单——决定 B1 与保护层的**规模** | ✅ 已确认(2026-10-10,见 §1.5) |
|
| **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) |
|
| **双转换器无共同采样触发(硬件缺陷)** | 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 确认)
|
### 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) |
|
| 周期 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 已实现并 `@test` 闭环(`adc_task.c`;台架 `idle=0`、`frst_err=0`) |
|
| 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**);台架 `@test` PASS——`adc1_seq` 每拍 +1、`adc1_err=0`、`adc1_hw=0`。⚠ 「9 通道码值随注入变化」已由 2026-10-27 台架已知电压注入直接证实(六项判据全 PASS,实测记录 `docs/硬件平台/ADC通道映射说明.md` §8.1);两点标定与端子/子板变比确认仍待做 |
|
| 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`(互异、非饱和;**两次开机读数不同** → 确为实时转换而非常量) |
|
| 通道语义映射 | 见 `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` | 注入标准源,读数误差 ≤ 目标精度 | ⬜ |
|
| 通道标定(一次系数 + 零漂) | 每通道 `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% | ⬜ |
|
| 等周波采样(软件测频 + 调频闭环) | 20 点/周波;测频范围建议 **45~55 Hz**(参考 40.5~60.5) | 频率偏移 ±2 Hz 时幅值误差 ≤ 1% | ⬜ |
|
||||||
| 零序通道接入(外部专用 / 自产切换) | 外部 `UN`/`IN`;自产 `3I0 = Ia+Ib+Ic`(V0.2 起) | 零序通道可切换来源,值正确 | ⬜ |
|
| 零序通道接入(外部专用 / 自产切换) | 外部 `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/`) |
|
| 二次谐波/五次谐波提取入口 | 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)。
|
> **「基波相量实例」实例数 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 保护判据元件
|
### 3.3 保护判据元件
|
||||||
|
|
||||||
@@ -362,7 +362,7 @@
|
|||||||
| 定值区 RAM | 元件数 × 定值项 × 字宽 | 可由 cfg_file 载入 |
|
| 定值区 RAM | 元件数 × 定值项 × 字宽 | 可由 cfg_file 载入 |
|
||||||
| 任务栈 | `adcTask` ✅ / `protectTask` ✅(B5:512 words,静态链 872 B / 余 57%) | 建任务前后各跑 `scripts/stack_report.py` |
|
| 任务栈 | `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 频率跟踪
|
### 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 相正确动作/返回;返回系数正确 |
|
| P1 | A 相过流 + A 相过负荷元件 + 定值模型 | 注入 1.2×/0.9× 定值,A 相正确动作/返回;返回系数正确 |
|
||||||
| P2 | 过压/欠压 + 过频/低频 + 零序过压元件 | 越限动作时间符合定值延时 |
|
| P2 | 过压/欠压 + 过频/低频 + 零序过压元件 | 越限动作时间符合定值延时 |
|
||||||
| P3 | 自检闭锁 + 采样通道自检;三相类元件建接口 | 自检失败闭锁;未使能元件不误动 |
|
| P3 | 自检闭锁 + 采样通道自检;三相类元件建接口 | 自检失败闭锁;未使能元件不误动 |
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ stateDiagram-v2
|
|||||||
|
|
||||||
## 11. 怎么验证
|
## 11. 怎么验证
|
||||||
|
|
||||||
**验证分工先说清**:台架 `@test` 只能证明「健康时不误报」(要求零 `[ERR]`),
|
**验证分工先说清**:台架 `/serial-boot-test` 只能证明「健康时不误报」(要求零 `[ERR]`),
|
||||||
**证不了「故障时必报」**——静默与倒退在健康台架上不可复现(复现 = 拔芯片或改驱动)。
|
**证不了「故障时必报」**——静默与倒退在健康台架上不可复现(复现 = 拔芯片或改驱动)。
|
||||||
「必报」这一半完全由主机侧单测承担,这就是本层必须是纯逻辑的原因(§4.4)。
|
「必报」这一半完全由主机侧单测承担,这就是本层必须是纯逻辑的原因(§4.4)。
|
||||||
|
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分
|
|||||||
|
|
||||||
### 12.2 板端闭环
|
### 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`
|
(`f=5000 valid=1`,`×0.01` 定点,MicroLIB 无 `%f`)、`ring` / `skip` / `adc1_*` / `tgap`
|
||||||
计数;健康判据 = **零 `[ERR]`**(`adc_health_report()` 的告警全部以 `[ERR]` 开头 → 误报会被闭环
|
计数;健康判据 = **零 `[ERR]`**(`adc_health_report()` 的告警全部以 `[ERR]` 开头 → 误报会被闭环
|
||||||
直接抓住)。浮空工况应为 `valid = 0` 且读数为保持值。
|
直接抓住)。浮空工况应为 `valid = 0` 且读数为保持值。
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ HEAD 是 `EF BB BF` 而工作区不是 → FAIL 并列出文件与补救动作
|
|||||||
## 2. 为什么要它(拦下过什么真事)
|
## 2. 为什么要它(拦下过什么真事)
|
||||||
|
|
||||||
- **立项直接来自一天内 5 次踩坑**:2026-10-25 道E 在 task-15(UI 页面批次)里连踩 4 次,
|
- **立项直接来自一天内 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` 里的行号指向中文注释所在的**上一行**,
|
本身是加注释或改常量;`MDK-ARM/build_log.txt` 里的行号指向中文注释所在的**上一行**,
|
||||||
照着报错去查会查不出任何东西。真因只是文件开头少了 3 个字节。
|
照着报错去查会查不出任何东西。真因只是文件开头少了 3 个字节。
|
||||||
- **别的门禁和编译器都不会响**:MSVC(模拟器侧)带不带 BOM 都编得过、0 警告;`no-mojibake`
|
- **别的门禁和编译器都不会响**:MSVC(模拟器侧)带不带 BOM 都编得过、0 警告;`no-mojibake`
|
||||||
@@ -61,7 +61,7 @@ HEAD 是 `EF BB BF` 而工作区不是 → FAIL 并列出文件与补救动作
|
|||||||
"HEAD 无 BOM、工作区加了 BOM" **不报**。那是另一个方向的改动,可能是有意的(把某个文件纳入
|
"HEAD 无 BOM、工作区加了 BOM" **不报**。那是另一个方向的改动,可能是有意的(把某个文件纳入
|
||||||
BOM 名单),也可能是无意的;报红只会逼人 `--no-verify`,而**养成绕门禁的习惯比漏检一次贵得多**。
|
BOM 名单),也可能是无意的;报红只会逼人 `--no-verify`,而**养成绕门禁的习惯比漏检一次贵得多**。
|
||||||
"新增文件没有 BOM" 同样不报:名单里的新文件(如新建 `ui_page_xxx.c`)由创建者负责加 BOM,
|
"新增文件没有 BOM" 同样不报:名单里的新文件(如新建 `ui_page_xxx.c`)由创建者负责加 BOM,
|
||||||
漏加会在 `@build` 时以真错误暴露——本门禁不假装自己能替代那一步。
|
漏加会在 `/keil-build` 时以真错误暴露——本门禁不假装自己能替代那一步。
|
||||||
|
|
||||||
### 6.2 名单三处互指(防漂移)
|
### 6.2 名单三处互指(防漂移)
|
||||||
AGENTS 铁律第 2 条的名单、钩子 `files` 正则、脚本内 `BOM_LIST` 必须一致;脚本每次运行都核一次,
|
AGENTS 铁律第 2 条的名单、钩子 `files` 正则、脚本内 `BOM_LIST` 必须一致;脚本每次运行都核一次,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user