From 4a4ad6bc5fbb87cc0e2fef18df196584a0cf1336 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Fri, 30 Oct 2026 13:03:12 +0800 Subject: [PATCH] =?UTF-8?q?chore(=E5=85=A8=E4=BB=93):=20@build/@test=20?= =?UTF-8?q?=E6=97=A7=E6=96=87=E6=A1=A3=E5=88=AB=E5=90=8D=E6=B8=85=E9=80=80?= =?UTF-8?q?=E4=B8=BA=E6=8A=80=E8=83=BD=E8=B0=83=E7=94=A8=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现状口径:/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 全过;字节卫生零漂移。 --- .dsh/skills/board-test-suite/SKILL.md | 6 +-- .dsh/skills/cubemx-regen/SKILL.md | 2 +- .../id-family-semantic-migration/SKILL.md | 4 +- .../reference/pitfalls.md | 2 +- .../reference/steps-chinese-prefix-family.md | 2 +- .dsh/skills/keil-build/SKILL.md | 24 ++++----- .../reference/executor-shell-forms.md | 2 +- .../reference/systemdrive-junk-dir.md | 4 +- .dsh/skills/keil-build/scripts/keil_build.py | 4 +- .dsh/skills/lvgl-simulator/SKILL.md | 2 +- .dsh/skills/mem-budget/SKILL.md | 8 +-- .../mem-budget/reference/backfill-pitfalls.md | 2 +- .dsh/skills/new-source-file/SKILL.md | 2 +- .../serial-assistant/scripts/serial_tool.py | 8 +-- .dsh/skills/serial-boot-test/SKILL.md | 4 +- .../serial-boot-test/scripts/run_boot_test.py | 4 +- .../serial-boot-test/scripts/verify_log.py | 4 +- .dsh/skills/task-worktree/SKILL.md | 10 ++-- .../reference/dsh-plugin-bridge.md | 2 +- .../task-worktree/reference/pitfalls.md | 22 ++++---- .../task-worktree/scripts/task_worktree.py | 28 +++++----- .gitea/workflows/ci.yml | 6 +-- .pre-commit-config.yaml | 4 +- AGENTS.md | 16 +++--- App/algo/README.md | 2 +- App/algo/wave_ring.c | 2 +- App/algo/wave_store.c | 2 +- App/app/app_main.c | 2 +- App/log/console_log.c | 2 +- App/log/console_log.h | 2 +- App/log/sys_log_mcu.c | 2 +- App/task/adc_task.c | 4 +- App/task/default_task.c | 4 +- App/task/iec104_serv.h | 2 +- App/task/protect_task.c | 6 +-- App/util/boot_stats.h | 2 +- Drivers/BSP/NET/lftpd/lftpd.c | 2 +- Drivers/BSP/NET/lftpd/private/lftpd_log.h | 2 +- Drivers/BSP/ScreenLink/sb_link.c | 2 +- Drivers/BSP/app_assert.h | 6 +-- MDK-ARM/README.md | 2 +- README.md | 4 +- docs/CI/远程CI完整指南.md | 12 ++--- docs/README.md | 8 +-- docs/UI设计/UI_中文字体生成与配置.md | 2 +- docs/UI设计/UI_数据供给设计说明.md | 8 +-- docs/UI设计/UI_界面制作流程.md | 18 +++---- docs/UI设计/UI_设计架构.md | 2 +- docs/UI设计/菜单页面设计.md | 6 +-- docs/UI设计/页面扩充详细方案.md | 2 +- docs/images/任务栈预算.svg | 2 +- docs/images/内存布局_FLASH.svg | 2 +- docs/images/内存布局_RAM.svg | 2 +- docs/产品需求/产品需求设计文档.md | 26 +++++----- docs/产品需求/参考产品复刻明细.md | 2 +- docs/代码规范/pre-commit使用说明.md | 2 +- docs/任务规划/故障录波.md | 2 +- docs/任务规划/数据可信.md | 8 +-- docs/内存管理/RAM使用分析与优化方案.md | 40 +++++++------- docs/内存管理/RAM预算台账.md | 4 +- docs/内存管理/内存内容分配策略.md | 4 +- docs/内存管理/存储栈RAM优化调研.md | 4 +- docs/子系统设计/IEC104上行设计说明.md | 2 +- docs/子系统设计/屏板链路设计说明.md | 4 +- docs/子系统设计/控制台单写者日志设计说明.md | 20 +++---- docs/子系统设计/故障录波设计说明.md | 2 +- docs/子系统设计/看门狗设计说明.md | 10 ++-- docs/子系统设计/运行与自检记录设计说明.md | 10 ++-- docs/子系统设计/配置文件设计说明.md | 10 ++-- docs/文档规范.md | 4 +- docs/架构设计/STM32F4-Base存储架构说明.md | 4 +- docs/架构设计/系统架构与硬件规格.md | 2 +- docs/架构设计/项目架构总览.md | 6 +-- docs/测试说明/专项验证/ADC前端注入验证流程.md | 16 +++--- .../专项验证/启动日志原子性复验流程.md | 10 ++-- docs/测试说明/专项验证/存储维护与容量恢复.md | 10 ++-- docs/测试说明/专项验证/模拟器差距清单.md | 24 ++++----- docs/测试说明/专项验证/链路延迟测试说明.md | 6 +-- docs/测试说明/单元测试/主机单测覆盖率口径.md | 10 ++-- docs/测试说明/单元测试/看门狗单元测试说明.md | 8 +-- .../测试说明/单元测试/系统时钟单元测试说明.md | 6 +-- .../测试说明/单元测试/配置文件单元测试说明.md | 4 +- docs/测试说明/板级测试/CH395F_Test_Guide.md | 2 +- docs/测试说明/板级测试/FTP_Test_Guide.md | 4 +- .../板级测试/GD5F2GQ5UE_Test_Guide.md | 2 +- docs/测试说明/板级测试/实机测试实施指南.md | 18 +++---- docs/测试说明/流程与规范/测试开发标准流程.md | 52 +++++++++---------- docs/测试说明/流程与规范/测试文档模板规范.md | 14 ++--- docs/测试说明/流程与规范/自动化测试流程.md | 16 +++--- docs/测试说明/测试工具/串口助手使用指南.md | 10 ++-- docs/测试说明/测试工具/串口助手设计说明.md | 20 +++---- .../测试说明/测试数据/boot_log_data/README.md | 6 +-- docs/环境与部署/并行任务工作树方案.md | 36 ++++++------- docs/环境与部署/本地工具路径环境变量方案.md | 2 +- docs/环境与部署/项目部署环境要求.md | 14 ++--- docs/硬件平台/ADC通道映射说明.md | 2 +- docs/算法/继电保护功能复现规划.md | 22 ++++---- docs/算法/采样有效性校验设计说明.md | 2 +- docs/算法/采集与算法层设计说明.md | 2 +- docs/门禁说明/bom-guard.md | 4 +- docs/门禁说明/cppcheck.md | 2 +- docs/门禁说明/cubemx-survival.md | 2 +- docs/门禁说明/host-unit-tests.md | 2 +- docs/门禁说明/mem-budget.md | 6 +-- docs/门禁说明/mem-numbers.md | 6 +-- docs/门禁说明/nested-comment.md | 6 +-- docs/问题记录/GD5F2GQ5UE_Trap_Records.md | 4 +- scripts/check_cubemx_survival.py | 6 +-- scripts/check_mem_numbers.py | 4 +- scripts/check_size_gate.py | 18 +++---- scripts/ci_local.py | 6 +-- scripts/ram_baseline.py | 18 +++---- scripts/rename_ids.py | 2 +- scripts/rename_ref.py | 2 +- scripts/v5_log_gap_report.py | 2 +- test/README.md | 6 +-- test/board/README.md | 2 +- test/board/test_common.h | 2 +- test/host/README.md | 6 +-- test/host/console_log/console_log_test.c | 2 +- test/host/lftpd/stub/dbg_log.h | 2 +- test/run_all_suites.py | 18 +++---- tools/arch_diagram/gen_memory_svg.py | 12 ++--- 123 files changed, 455 insertions(+), 455 deletions(-) diff --git a/.dsh/skills/board-test-suite/SKILL.md b/.dsh/skills/board-test-suite/SKILL.md index 8796e52d..a3627f1e 100644 --- a/.dsh/skills/board-test-suite/SKILL.md +++ b/.dsh/skills/board-test-suite/SKILL.md @@ -14,7 +14,7 @@ whenToUse: 要跑板级测试套件(CH395F/STORAGE/GD5F/RTC)回归、要开/ 实测 GD5F 整轮破坏性跑一次即多记一个 NAND 坏块(容量 95248→95200), 计数持久化在 dhara journal checkpoint 头里,普通格式化恢复不回来。 - 提交门禁: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 路径)——这是它存在的核心 理由:跑完忘还原 = 测试固件进库。`--keep-config` 仅调试用。 - 退出码 0=全 PASS / 1=有 FAIL / 2=环境错误;4 套件真机一轮数分钟起,**仅回归窗口用,绝不挂 pre-commit**。 -- 手工循环时的顺序(每轮):改宏(只开一个)→ `@build` 0错0警 → 烧录 → +- 手工循环时的顺序(每轮):改宏(只开一个)→ `/keil-build` 0错0警 → 烧录 → `serial-assistant` 采集(open→clear→烧录→wait-idle)→ 判读套件汇总行。 ## 跑后检查清单(必做) 1. `test_config.h` 恢复全注释(用 run_all_suites.py 则已自动); -2. `@build` 确认产品构建 0 错 0 警; +2. `/keil-build` 确认产品构建 0 错 0 警; 3. 若开过破坏性开关:核对开机日志容量数字(健康满值 96064 pages / 187 MB), 掉了 → 按 `docs/测试说明/专项验证/存储维护与容量恢复.md` 走恢复动作; 4. 提交前 `test-suite-switch` 门禁会再兜一道。 diff --git a/.dsh/skills/cubemx-regen/SKILL.md b/.dsh/skills/cubemx-regen/SKILL.md index 057a4e69..ecdf4689 100644 --- a/.dsh/skills/cubemx-regen/SKILL.md +++ b/.dsh/skills/cubemx-regen/SKILL.md @@ -52,7 +52,7 @@ whenToUse: 要用 CubeMX 重新生成工程(改 .ioc / 外设配置)、改 - 启用 FreeRTOS 后模板会生成 `USER CODE BEGIN 4 / 5` 区(默认空桩)——**钩子实现必须 整体写进这两个区**,且 helper 函数放更早的区(同名两处定义 = 编译错)。 - 生成文件行尾/末尾空行按工具自身风格,**勿用改写型钩子去修剪**(已豁免,勿"顺手格式化")。 -- 重生成后除审计脚本外,照常走 `@build`(0 错 0 警)+ 必要时 `@test` 闭环。 +- 重生成后除审计脚本外,照常走 `/keil-build`(0 错 0 警)+ 必要时 `/serial-boot-test` 闭环。 ## 其它 CubeMX 项目怎么套 diff --git a/.dsh/skills/id-family-semantic-migration/SKILL.md b/.dsh/skills/id-family-semantic-migration/SKILL.md index 95c5d12e..d9477aad 100644 --- a/.dsh/skills/id-family-semantic-migration/SKILL.md +++ b/.dsh/skills/id-family-semantic-migration/SKILL.md @@ -110,10 +110,10 @@ version: 1.1.0 2. 编号字面量与**不可判定引用绝对数**只降不升、不超 `undecidable_total_ceiling`;可判定比例不低于 `id_registry.json` 基线(86.0%)。 3. 本族剩余站点复扫归零:有识别面族从 `ck.refs` 按族正则过滤字面量逐条打印;除冻结历史/原始证据行外无残留。 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`);提交笔内不含他道文件。 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),且写明旧号永久作废、不得重新取走。 9. **二次清洗三道各有实测数**:同句重复短语 0 行、各空格形态先计数后归一(复扫 0)、固定正则残留 0;被代码头注释引用的设计约束章节仍在(`git diff` 里没被动过)。 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` 或受影响单测——**每个数字标明是本回合实测**。 diff --git a/.dsh/skills/id-family-semantic-migration/reference/pitfalls.md b/.dsh/skills/id-family-semantic-migration/reference/pitfalls.md index 4ce6098d..bfd01088 100644 --- a/.dsh/skills/id-family-semantic-migration/reference/pitfalls.md +++ b/.dsh/skills/id-family-semantic-migration/reference/pitfalls.md @@ -25,6 +25,6 @@ - **游标整型比较静默不生效**(字符串形态);抬完用 `--strict` 与 plain 双验。 - **`git add` 目录路径带进别道文件**、`git add -A` 卷进在飞改动;撤销自己的改动只按文件点名 `git restore scripts/archive/id_gate/id_registry.json`,不碰同树其它道文件。 - **amend 被 gitlint B1 拦**:正文行 >80,别把 ASCII 双引号写成正文首字符。 -- **改「只动注释」要拿机器证据**:`@build` 0 错 0 警且 `Program Size` 与改前**逐字节同数**(本回合真跑,引用历史提交正文自报的数字不算证据),别口头承诺。 +- **改「只动注释」要拿机器证据**:`/keil-build` 0 错 0 警且 `Program Size` 与改前**逐字节同数**(本回合真跑,引用历史提交正文自报的数字不算证据),别口头承诺。 - **冻结历史与原始证据不改**:boot 串口日志等采集现场是冻结证据,改了就是篡改;历史「修改记录」行按裁定保持改名后形态(号已全仓退役,留一个查无此号的引用比留改过名的条目更坏)。 - **`*.uvprojx` 抖动不入库**;`ci-artifacts/` 里的 spec 与扫描中间产物收尾删掉,别当交付物留下。 diff --git a/.dsh/skills/id-family-semantic-migration/reference/steps-chinese-prefix-family.md b/.dsh/skills/id-family-semantic-migration/reference/steps-chinese-prefix-family.md index 820aed0a..3bad5961 100644 --- a/.dsh/skills/id-family-semantic-migration/reference/steps-chinese-prefix-family.md +++ b/.dsh/skills/id-family-semantic-migration/reference/steps-chinese-prefix-family.md @@ -62,4 +62,4 @@ - **同句重复短语**:编号位置补上的语义短语与原句已有短语撞车 → 扫「同一行里同一短语出现 ≥2 次」(实测 12 行,如 `.pre-commit-config.yaml:401`「运行参数段(「数据可信」运行参数段交付)」、`App/algo/README.md:110`「遥测快照发布(L2 发布侧,「数据可信」遥测快照发布)」)→ 改法是**把括号里的引用降级**成不含该短语的写法(「道D 交付项」「波次」「该点 DoD 原句」),逐处断言命中 = 1,别删信息; - **空格与引号形态归一**:同一语义短语可能有 `端子 / 子板变比确认` 与 `端子/子板变比确认` 两种形态 → **先 `git grep -c -F` 分别数**(实测带空格 8 处),再整文件归一,归一后复扫到 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。 diff --git a/.dsh/skills/keil-build/SKILL.md b/.dsh/skills/keil-build/SKILL.md index d825c7b0..2d7173f1 100644 --- a/.dsh/skills/keil-build/SKILL.md +++ b/.dsh/skills/keil-build/SKILL.md @@ -1,7 +1,7 @@ --- 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% 字面量垃圾目录」的取证、清理与入库排除。" -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) @@ -10,7 +10,7 @@ whenToUse: "需要编译 Keil MDK 工程、跑 @build 验证代码改动(只 (纯标准库,任意 python3;拷整个 skill 目录到别的 Keil 项目即可用,不依赖仓库脚本)。 ```powershell -# cwd = 项目根目录;PowerShell 形态(AGENTS.md 里的 `@build` 写法) +# cwd = 项目根目录;PowerShell 形态(AGENTS「构建与验证」的编译入口) & $PY ".dsh\skills\keil-build\scripts\keil_build.py" # 覆盖:--project X.uvprojx --uv4 UV4.exe --target 名字 --log 路径 --timeout 600 ``` @@ -25,7 +25,7 @@ column 14` → **技能整份不加载**(`skill()` 读不到、也不进技能 ## 调用形态:先认执行器是哪个 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、别当环境错误排障 - 后台跑(run_in_background: true)要自己收码,且别把脚本接进管道:实测 $? 取到的是 tail 的码(恒 0);要看报错行就单独 grep MDK-ARM/build_log.txt - 解析顺序:命令行 > 项目档案 .serialtest/config.json > 环境 > 探测;UV4 显式设了但路径无效 = 立即报错,不静默回退 @@ -84,7 +84,7 @@ column 14` → **技能整份不加载**(`skill()` 读不到、也不进技能 但 stderr 是 `syntax error near unexpected token`(见「调用形态」)。**先看 stderr 再决定排障方向**, 两种 2 的处置完全不同。 - **改 `.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 警告"不代表安全。 ## build 锁:脚本自持自释(2026-10-12) @@ -107,21 +107,21 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被 - 锁工具 = 兄弟 skill `task-worktree/scripts/task_worktree.py`;**找不到则降级为不加锁 并告警**(保持本 skill 可独立搬运)。 - ⚠ 从 `_wt/-/` 反推持有者名,意味着"手工 `lock acquire build --task ` - 后再跑 `@build`"仍**幂等**(同名即视为自己持有),不会自我死锁。 + 后再跑 `/keil-build`"仍**幂等**(同名即视为自己持有),不会自我死锁。 - **退出码 4 的持有者名带 `pw:`(如 `pw:refactor-logging`)= 插件在 `.dsh-worktrees/worktree//` 里跑的构建占着 UV4,不是某个 `@task` 任务 ID**: 插件树**没有台账**,别拿它去 `lock release `(释放不掉别人的锁)、更别去杀 UV4 进程。正解 = 等那次构建自己 `finally` 释放(自持自释;崩溃则按自报 TTL 兜底),或本次加 `--lock-wait ` 排队; 想知道是哪棵树的谁:`"$PY" "$TW" plugin list`(缺联接再 `plugin adopt `)→ 细节归 `task-worktree` skill F 节(插件桥)。 - **只 build 锁自动;`bench` 锁仍须 Agent 手工持**(它是**会话作用域**,跨 - 烧录→`@test`→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。 + 烧录→`/serial-boot-test`→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。 ## 构建副产物:`%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 - 清理有安全阀:⚠ 绝不用 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*"`) - **要留证据就搬走、别就地删** —— ⚠ **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` 零命中;**绝不提交**(把本机缓存塞进历史) @@ -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`—— **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)`。 本 skill 另有超时保护、UV4 多级解析链(含 PATH 兜底)、失败只打报错行。 环境变量 `DTU_KEIL_UV4`(用户级,指向 UV4.exe)——注意**它设了不等于本会话能看见**,仍报退出码 2 时走「exit 2 实战闭环」那一节,别先去找用户。 @@ -142,7 +142,7 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被 - **任何代码改动后必须先编译验证(0 错误 0 警告)再谈烧录/提交**——编译不过就进 硬件闭环只会把编译错误伪装成运行问题。**只改 `.c` 里一行中文注释也算改动**(2026-10-30 实测: - 改注释 → `@build` → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。 + 改注释 → `/keil-build` → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。 - **警告也算失败**:判 PASS 只认退出码 0,别只看"有没有 error:"(取退出码的管道陷阱见「调用形态」)。 - **`MDK-ARM/*.uvprojx` 每次构建后被 UV4 自动改写,改动一律不入库**:UV4 会把工程 XML 归一化成本机风格(`` → ``、XML 声明加 `standalone="no"`)并把 @@ -169,6 +169,6 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被 - **build 锁的对面是谁**(`BUILD_LOCK_BUSY`、退出码 4、`pw:` 插件树、`@task plugin list` / `plugin adopt`、`_wt/` 台账与合并回收)归 `task-worktree` skill(F 节 = DSH worktree 插件桥);换机/新克隆时 `DTU_*` 工具路径变量的设立与「会话不继承」坑 = `new-machine-setup` skill。 - 烧录/回读 = `stlink-flash` skill;串口闭环 = `serial-boot-test` skill (其档案 `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` 静态段, 直到钩子全绿且 0 错误 0 警告(RAM/栈口径见 `mem-budget` skill)。 diff --git a/.dsh/skills/keil-build/reference/executor-shell-forms.md b/.dsh/skills/keil-build/reference/executor-shell-forms.md index f6d83645..74098ab9 100644 --- a/.dsh/skills/keil-build/reference/executor-shell-forms.md +++ b/.dsh/skills/keil-build/reference/executor-shell-forms.md @@ -2,7 +2,7 @@ > 本文由 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` 会直接语法错退出。 | 执行器 | 正确写法(cwd = 仓库根) | diff --git a/.dsh/skills/keil-build/reference/systemdrive-junk-dir.md b/.dsh/skills/keil-build/reference/systemdrive-junk-dir.md index 41a411da..940be1ba 100644 --- a/.dsh/skills/keil-build/reference/systemdrive-junk-dir.md +++ b/.dsh/skills/keil-build/reference/systemdrive-junk-dir.md @@ -2,7 +2,7 @@ > 本文由 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` (`cversions.2.db` + `{6AF0698E-…}` 图标缓存 + `{DDF571F2-…}` 字体缓存,合计约 1 MB)—— 就是本机 `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:`。构建后再跑 `check_percent_literal_junk()`,看到 `%SystemDrive%`/`%USERPROFILE%` 一类字面量目录就打 `WARN: JUNK_DIR:`(**只告警不自动删**——删目录不该由构建脚本代劳)。守卫是兜底不是许可: - **`@build` 仍要走 `ops_bash` / PowerShell(继承完整环境)**,非在 Node 里 spawn 就把 `env` 传全。 + **`/keil-build` 仍要走 `ops_bash` / PowerShell(继承完整环境)**,非在 Node 里 spawn 就把 `env` 传全。 diff --git a/.dsh/skills/keil-build/scripts/keil_build.py b/.dsh/skills/keil-build/scripts/keil_build.py index bde44308..150339f8 100644 --- a/.dsh/skills/keil-build/scripts/keil_build.py +++ b/.dsh/skills/keil-build/scripts/keil_build.py @@ -14,7 +14,7 @@ (环境变量**显式设了但路径无效 = 立即报错,不静默回退**) 日志 --log > 档案 "build_log" > <工程目录>\\build_log.txt -判定口径(实测教训沉淀;原仓库 MDK-ARM/build.bat 已删除,本脚本即 @build): +判定口径(实测教训沉淀;原仓库 MDK-ARM/build.bat 已删除,本脚本即仓库编译入口): 1. 先删旧日志——UV4 -o 只在真跑了构建才写日志,残留旧日志会被当成本次结果 2. UV4 单实例:IDE 开着时 -b 可能被转发给 IDE(退出码丢失)→ 只 WARN 不杀进程 3. PASS = 日志存在 + 无 "error:"(大小写不敏感)+ 含 "0 Error(s), 0 Warning(s)" @@ -157,7 +157,7 @@ def harden_windows_env(): if filled or expanded: print("WARN: ENV_SPARSE: 子进程环境残缺/含未展开字面量 —— 已回填[{}] 已展开[{}]".format( ", ".join(sorted(filled)) or "-", ", ".join(sorted(expanded)) or "-")) - print(" 正解:用继承**完整环境**的 shell 跑 @build(ops_bash / PowerShell)," + print(" 正解:用继承**完整环境**的 shell 跑编译(ops_bash / PowerShell)," "别从 Node spawn(空 process.env)直接起 UV4 或字体门禁脚本") diff --git a/.dsh/skills/lvgl-simulator/SKILL.md b/.dsh/skills/lvgl-simulator/SKILL.md index 224c5fdf..c273151a 100644 --- a/.dsh/skills/lvgl-simulator/SKILL.md +++ b/.dsh/skills/lvgl-simulator/SKILL.md @@ -33,7 +33,7 @@ PC 上编译运行与 MCU **同一份** UI 源码(CMake + MSVC + SDL2,x64, ## 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`。 黄金参考 `tests/ref/*.bin` **随仓库入库**(`.gitignore` 在全仓 *.bin 兜底之后又显式反选,2026-10-22 起;缺基准 = 新克隆跑 `ui-golden` 必 FAIL):不入库的只是派生目检图 `ref/*.png`,换机后先 `check_content_pages.py --update` 生成,再 `regen_ref_png.py` 出目检 PNG。 diff --git a/.dsh/skills/mem-budget/SKILL.md b/.dsh/skills/mem-budget/SKILL.md index 53dab76a..2b2122e3 100644 --- a/.dsh/skills/mem-budget/SKILL.md +++ b/.dsh/skills/mem-budget/SKILL.md @@ -1,7 +1,7 @@ --- 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 数字钩子红了、提交前台账怎么回填"的固定动作和判读口径。 -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/栈预算核算 @@ -9,7 +9,7 @@ whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 ## 固定动作(改前、改后各跑一次) ```powershell -# cwd = 项目根;先 @build 保证 .htm/.map 是最新的(产物按脚本自身位置定位,见下节) +# cwd = 项目根;先 /keil-build 保证 .htm/.map 是最新的(产物按脚本自身位置定位,见下节) & '.venv\Scripts\python.exe' ".dsh\skills\mem-budget\scripts\mem_check.py" # 等价于分别跑: # scripts/ram_report.py (.map → RAM/Flash 分区与按文件占用表) @@ -67,7 +67,7 @@ whenToUse: 新增任务/加大缓冲/改栈/接入日志之前之后、链接报 | `docs/内存管理/RAM预算台账.md` 的 `` 受管区 | `ram_baseline.py --write` | 从 .map 重渲染区域水位 + 子系统配额对照(打印「已同步台账受管区。」或「台账受管区已是最新。」) | | `docs/内存管理/RAM使用分析与优化方案.md` 的 `` 快照区 | `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` 两条钩子当场红。 `mem-budget` 的 entry 是 `python scripts/ram_baseline.py --gate`(`.pre-commit-config.yaml:571`)——它在 92% 阈值之外 还核**台账受管区与 .map 的一致性**,漂移即 `[FAIL] 台账受管区与 .map 不一致(区域水位或子系统配额表漂移)` @@ -144,7 +144,7 @@ git add docs/内存管理/RAM预算台账.md docs/内存管理/RAM使用分析 3. 树身份已按 `tree/commit + 时刻 + 产物来源` 写进提交正文/台账,而不是只留在终端输出里; 4. 结论里互比的数字必须来自**同一棵树同一次链接**;跨树比较要各自给出树与时刻; 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; 台账 / 文档 / tsv 的改动与代码同一笔入库,**未手改数字、未用 `--no-verify`**。 diff --git a/.dsh/skills/mem-budget/reference/backfill-pitfalls.md b/.dsh/skills/mem-budget/reference/backfill-pitfalls.md index 839f7af3..9089a27e 100644 --- a/.dsh/skills/mem-budget/reference/backfill-pitfalls.md +++ b/.dsh/skills/mem-budget/reference/backfill-pitfalls.md @@ -9,7 +9,7 @@ (`ram_baseline.py:232`),**拦下来的是受管区漂移**。所以"钩子绿"绝不等于"趋势行也补了",反之亦然,各查各的。 - **`--gate` 无 .map → SKIP,而脚本自己写明「SKIP 不等于 PASS」**(`ram_baseline.py:177`): 新开的 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`。 - **`--map ` 只换读、不换写**:`LEDGER`(`ram_baseline.py:45`)与 `TARGETS`(`check_mem_numbers.py:53-54`) 都由 `REPO_ROOT = Path(__file__).resolve().parent.parent` 算出。拿主仓脚本配 `--map <另一棵树>/…map` 去 `--write`, diff --git a/.dsh/skills/new-source-file/SKILL.md b/.dsh/skills/new-source-file/SKILL.md index 0542fcd2..8f5cbf03 100644 --- a/.dsh/skills/new-source-file/SKILL.md +++ b/.dsh/skills/new-source-file/SKILL.md @@ -36,5 +36,5 @@ cmake --build simulator/build --config Debug # 0 警告 - **禁止**把源文件复制进 `simulator/`——两端共用 `Drivers/BSP/` 原件(UI_界面制作流程.md C7) - 只注册 `.c`(含 `.s`);`.h` 靠 include 路径找到,不加工程 - 漏注册的典型症状:一端编译失败(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` 配置 diff --git a/.dsh/skills/serial-assistant/scripts/serial_tool.py b/.dsh/skills/serial-assistant/scripts/serial_tool.py index 617e2471..c953c0e3 100644 --- a/.dsh/skills/serial-assistant/scripts/serial_tool.py +++ b/.dsh/skills/serial-assistant/scripts/serial_tool.py @@ -19,7 +19,7 @@ status 全状态(单条 JSON) log --tail N --grep P 读日志(先过滤后截尾;-i 忽略大小写 / --regex 正则) 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) selftest 无头自检(行拆分/解码回退/截头算法,无需硬件) @@ -615,10 +615,10 @@ class Daemon: def wait_idle(self, settle_s, max_s, first_timeout_s=None): """等 RX 静默,返回 reason ∈ {idle, no_data, timeout}。 - 2026-10-09 修三处(旧实现会让 @test 在"板子全程没输出"时误判收尾): + 2026-10-09 修三处(旧实现会让 /serial-boot-test 在"板子全程没输出"时误判收尾): 1. no_data 判据:旧实现比的是"进入函数时 daemon 累计 rx_bytes 是否为 0"。 daemon 常驻(本机实测 rx_bytes=1.2MB)→ 永远 >0 → 板子一个字节都不发 - 也返回 idle:true,@test 的 no_data 拦截形同虚设。现按**本次增量**判。 + 也返回 idle:true,/serial-boot-test 的 no_data 拦截形同虚设。现按**本次增量**判。 2. 首字节门:旧实现从进入即开始计静默时间,板子第一行比 settle_ms 来得 晚就提前收尾("开机日志缺尾巴"的另一半成因)。现首字节到达前不启动 settle 计时,改由 first_timeout_s 兜底(默认 = max_s)。 @@ -1353,7 +1353,7 @@ def build_parser(): 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, help="静默判据毫秒(默认 8000:boot 有 3~4s 自然静默间隙)") p.add_argument("--max-ms", type=int, default=30000, help="最长等待毫秒(默认 30000)") diff --git a/.dsh/skills/serial-boot-test/SKILL.md b/.dsh/skills/serial-boot-test/SKILL.md index 5ca0b380..6c0e6a25 100644 --- a/.dsh/skills/serial-boot-test/SKILL.md +++ b/.dsh/skills/serial-boot-test/SKILL.md @@ -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/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=` 是显式逃生口,不是默认)。子串 `--required` 不能用于"可能丢行首/被插字节"的行。来龙去脉与固定动作见本仓 `docs/测试说明/专项验证/启动日志原子性复验流程.md`。 diff --git a/.dsh/skills/serial-boot-test/scripts/run_boot_test.py b/.dsh/skills/serial-boot-test/scripts/run_boot_test.py index 369380ca..c246f4a2 100644 --- a/.dsh/skills/serial-boot-test/scripts/run_boot_test.py +++ b/.dsh/skills/serial-boot-test/scripts/run_boot_test.py @@ -226,7 +226,7 @@ def _ftp_quit(ftp): def _prune_out_dir(out_dir, keep): """只保留最近 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)。 文件名含时间戳 → 按名字排序即按时间排序。 """ @@ -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 / error_perm 继承自 Exception 而非 OSError —— 台账"仅故障才写",干净设备上 /log/selftest.log 根本不存在,RETR 返回 450 直接抛出未捕获异常,令整个 - `@test --require-selftest-pass` 在最后一步崩掉(而它本该是 PASS 路径)。 + `run_boot_test.py --require-selftest-pass` 在最后一步崩掉(而它本该是 PASS 路径)。 现把"设备侧无此文件"单列为正常路径(INFO),其余 FTP 错误保留 WARN。 """ local = Path(out_dir) / "selftest_log_{}.log".format(stamp) diff --git a/.dsh/skills/serial-boot-test/scripts/verify_log.py b/.dsh/skills/serial-boot-test/scripts/verify_log.py index d454fb7b..700bcbf5 100644 --- a/.dsh/skills/serial-boot-test/scripts/verify_log.py +++ b/.dsh/skills/serial-boot-test/scripts/verify_log.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """verify_log.py - 对抓取的 MCU 调试日志做标记校验(纯标准库,2026-09-20 替代 verify_log.ps1)。 -用于 @test 闭环:烧录后从串口助手日志提取的开机日志交给本脚本 grep 标记, +用于 /serial-boot-test 闭环:烧录后从串口助手日志提取的开机日志交给本脚本 grep 标记, 输出结构化判定(格式与旧 PowerShell 版逐字一致,下游文档/习惯不受影响)。 用法: @@ -16,7 +16,7 @@ [--require-order 'App init OK>Network Init>net init: OK'] [--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 零命中,汇总行要求满足) 1 = FAIL(**设备侧真有坏消息**:缺 Required / 命中 Forbidden / 汇总行不满足 / 顺序或耗时超限) 2 = **测量没成立**(日志不存在或不可读;或开了 --max-boot-count 却零启动锚点 = 采集器没开、 diff --git a/.dsh/skills/task-worktree/SKILL.md b/.dsh/skills/task-worktree/SKILL.md index 345983fc..6dd002d3 100644 --- a/.dsh/skills/task-worktree/SKILL.md +++ b/.dsh/skills/task-worktree/SKILL.md @@ -59,7 +59,7 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr ```bash "$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" done N21 # 回主工作树(铁律 1);先 --dry-run 读全量 ``` @@ -110,7 +110,7 @@ git add <上面刷出来的基线/快照文件> # 显式列文件,别 -A git commit -F # 提交属 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` 并列出待提交文件(不代提交)。上面的手工全链仍是权威兜底。 6. **重跑 `done` 前主树必须干净**,否则 `失败:主工作树不干净(…)——合并会把未提交改动卷进来` → 第 5 步产物先提交。分支已并时重跑打印 `[i] task/- 已合入 ,跳过合并,直接清场`——**该路径在门禁之前就返回,不会替你复绿**:上一次门禁没跑到的尾巴(主树 `ci_local.py` + `check_doc_links.py --all`;体积/文档那半可 `postmerge` 一把跑)必须自己补。主树脏且那份脏不是你的 → **C2**。 7. 清场后再敲一次 `done ` 得 `前置/环境错误:任务 不存在(缺台账 …json)`(exit 2)——**不是失败**:清场最后一步 unlink 了台账。**判完成以 `git log` 为准**。 @@ -153,7 +153,7 @@ git commit -F # 提交属 Ask first(AGENTS.md 1. **`done` 只能在主工作树执行**——主干分支检出在主树,git 不允许同一分支两处检出。 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 合回)要在交付报告里点名这条例外。 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 步。 @@ -163,11 +163,11 @@ git commit -F # 提交属 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`、改上限数字后的全形态扫描)。 ## 与其它 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` - DSH worktree 插件(`/worktree` / `worktree_create`,仓内 `.dsh-worktrees/`)与本 skill 的分工、桥接命令、锁归属 → F 节 · 完整机制与目录分配 → `docs/环境与部署/并行任务工作树方案.md` diff --git a/.dsh/skills/task-worktree/reference/dsh-plugin-bridge.md b/.dsh/skills/task-worktree/reference/dsh-plugin-bridge.md index e7b6575b..8a9b6c75 100644 --- a/.dsh/skills/task-worktree/reference/dsh-plugin-bridge.md +++ b/.dsh/skills/task-worktree/reference/dsh-plugin-bridge.md @@ -42,7 +42,7 @@ 它按缺的那一样分别给后果,`adopt` 没补齐也只报「还不能算就绪」,不许说「现在可以跑」。 - **adopt 只碰联接**:已联的跳过;`simulator/SDL2` 若已是实体目录只告警不动;`.dsh-worktrees/` 之外的路径 一律 exit 2(实测 `plugin adopt ".."` 被拒)——这道闸防的是把主树或 `_wt` 目录当插件树遮蔽掉真实内容。 -- **锁归属现在认得出来**:`@build` 在插件树里自持自释时,持有者名从 cwd 反推成 `pw:`(多段名压平成 +- **锁归属现在认得出来**:`/keil-build` 在插件树里自持自释时,持有者名从 cwd 反推成 `pw:`(多段名压平成 `-`),与 `plugin adopt` 打印的那个名字逐字相同(`keil_build.py: plugin_tree_owner()` ↔ `task_worktree.py: plugin_lock_owner()`)→ `lock status` 不再一律落成 `build-cli`。 - 上台架仍须手工占锁、用同一个名:`lock acquire bench --task pw: --note <说明>` → diff --git a/.dsh/skills/task-worktree/reference/pitfalls.md b/.dsh/skills/task-worktree/reference/pitfalls.md index 838a1947..a53bea4f 100644 --- a/.dsh/skills/task-worktree/reference/pitfalls.md +++ b/.dsh/skills/task-worktree/reference/pitfalls.md @@ -25,7 +25,7 @@ subprocess,含空格的路径(本项目 `DTU Code`)由 subprocess 自动加引号。 但**读属性**用 ctypes 没问题:`GetFileAttributesW` + `FILE_ATTRIBUTE_REPARSE_POINT` 就是 E 第 3 步的预扫手段。 - **联接建失败不中止 `new`**:只告警并打印手工补建命令,避免留下半成品任务空间; - 但该任务里跑不了 `.venv\Scripts\python.exe`,`@build` / `ci_local.py` 会找不到解释器。 + 但该任务里跑不了 `.venv\Scripts\python.exe`,`/keil-build` / `ci_local.py` 会找不到解释器。 - **`done` 前主工作树必须干净**:否则未提交改动会被卷进合并(补完基线再重跑 `done` 时最容易忘,见 C 第 6 步)。 - **⚠ 任务树里 `check_doc_links.py --all` 的红多半是 `simulator/SDL2` 目录联接的假红**:全库扫描会吞下那份 「仓库里的第二份仓库副本」(实测任务树 222 个文档 / 35 处问题全在联接下、RC=1,而主树同命令基线全绿)。 @@ -39,11 +39,11 @@ `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):**不许用 stash / 提交 / 删除去清场**(项目级 Never, 你也无从判断它写没写完)→ 走 **C2**:取证(mtime + diff + `hash-object` 存进 ci-artifacts)→ `merge-tree` 预演 → 主树 `merge --no-ff -F` → 合并前后 blob id 与 `diff --stat` 对账。**「它看着像废弃的」不是丢弃判据**。 -- **⚠ `done` 的门禁跑在 `@build` 之前 → 依赖 `.map` 的钩子会误报**(2026-10-12 实测): - `done` 的顺序是「合并 → 重生成 → **门禁(ci_local)** → `@build`」,而 +- **⚠ `done` 的门禁跑在 `/keil-build` 之前 → 依赖 `.map` 的钩子会误报**(2026-10-12 实测): + `done` 的顺序是「合并 → 重生成 → **门禁(ci_local)** → `/keil-build`」,而 `mem-budget`(登记簿条目「缩堆迁 CCM 后半(暂缓)」)/ `mem-numbers` 是拿**文档快照 vs `.map`** 比对 —— 门禁跑时 `.map` 还是**主工作树合并前**的旧产物,与刚合并进来的新台账**必然不一致** → - 误报 `[FAIL] 台账受管区与 .map 不一致`。`@build` 跑完后 `.map` 才是新的。 + 误报 `[FAIL] 台账受管区与 .map 不一致`。`/keil-build` 跑完后 `.map` 才是新的。 **处置(2026-10-25 补全)**:按 C 节第 5 步的完整链补——`check_mem_numbers.py --write` → `ram_baseline.py --write` → `ram_baseline.py --update` → `check_test_coverage.py --update-baseline` → `check_doc_links.py --all` → 提交 → 重跑 `done`(它会检测到「已合入」直接清场)。 @@ -57,15 +57,15 @@ - **⚠ 新建工作树从未构建过 → 体积类门禁必然红**(2026-10-25 实测):`.map` 是每棵树各自的 构建产物、不入版本库,所以 `new` 出来的空间里 `mem-numbers` / `mem-budget` / `ci_local.py` 的体积门禁步都拿不到事实源,报 `[FAIL] 找不到 .map 事实源`(脚本提示已写明原因,不是代码缺陷)。 - 同理 `done` 的重生成步骤会 `[skip] 未找到 .map,跳过内存快照同步(需先 @build)`。 - **处置**:新空间第一次提交前先 `@build` 一次(`build` 锁自动排队,与别的树串行)。与上一条 + 同理 `done` 的重生成步骤会 `[skip] 未找到 .map,跳过内存快照同步(需先 /keil-build)`。 + **处置**:新空间第一次提交前先 `/keil-build` 一次(`build` 锁自动排队,与别的树串行)。与上一条 的区别要分清:上一条是「有 `.map` 但是旧的」,本条是「根本没有 `.map`」,处置动作不同。 - **⚠ 只改注释里的文档路径,也会把「体积同码凭证」刷红**(2026-10-30 实测,`done testdoc`): `scripts/check_size_gate.py` 的判据是**索引 blob 指纹**(实测 1466 个文件),**不是体积数字**—— 那笔全库路径同步顺带改了 20 个 `.c/.h` 的注释,`Program Size` 逐项没变 (Code=246160 / RO-data=29816 / RW-data=2036 / ZI-data=149700),凭证照旧红: `本次代码(指纹 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.tsv` 连趋势行都不会新增),**别顺手 `--write` 去动内存受管快照**——数字没变就改受管区, 等于给别的在飞分支造冲突面(台账归属口径见 `mem-budget` skill)。 @@ -75,7 +75,7 @@ 落主干后的全量证据得自己跑:`scripts/ci_local.py`(实测 47 runner / 3896 断言全绿) + 主树 `check_doc_links.py --all`(实测 215 个文档 / 2993 处章节引用 rc=0)。 - **⚠ 增量构建可能不重链 → `.map` 数字瞬态不一致**(2026-10-12 实测): - `@build` 若判定无需重链,`.map` 保持旧值,此时 `--write` 生成的快照是**瞬态值** + `/keil-build` 若判定无需重链,`.map` 保持旧值,此时 `--write` 生成的快照是**瞬态值** (实测同一份源码两次读到 223,560 与 223,724,差 164 B)。**以主工作树(集成点) 的 `.map` 为准**;改完 RAM 相关代码后确认重链再刷新快照。 - **`ram_baseline.py` 的 help 串里有未转义的 `%`**(2026-10-25 实测:某些直调姿势会渲染 argparse help 并崩)—— @@ -120,15 +120,15 @@ 同任务重复 acquire 是幂等的(不会把自己锁死)。 - **TTL 按锁分设**(2026-10-12):`build` = **1h**、`bench` = **6h**(`LOCK_TTL_HOURS`)。 口径是「兜底窗口应与操作真实时长同量级」——编译是秒~分钟级,6h 的兜底太钝; - bench 是会话级(flash→`@test`→探针),才配得上小时级。持有者还可用 + bench 是会话级(flash→`/serial-boot-test`→探针),才配得上小时级。持有者还可用 `lock acquire ... --ttl <小时>` **自报**更精确的窗口(`keil_build.py` 即按其 `--timeout` 自报)。 - **陈旧锁自动接管**:超过该锁 TTL 未释放即视为陈旧,下次 `acquire` 会接管并提示; 未到 TTL 的活锁需 `--force`。`done` 的收口前置也会**自动释放陈旧锁**(不再硬失败),但 **未到 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"这条在构建路径上已消除。 `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 --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 实测):对含中文文件名的文档跑 `check_doc_links.py`,终端回显整段乱码,但 **rc 与判定计数全部正确**。只认 `[SUMMARY]` 行与退出码,别误诊成「文档丢失」;脚本化批量调用走 python subprocess + 显式 `encoding`,别经命令行中转中文名。 diff --git a/.dsh/skills/task-worktree/scripts/task_worktree.py b/.dsh/skills/task-worktree/scripts/task_worktree.py index 84703a01..089daf17 100644 --- a/.dsh/skills/task-worktree/scripts/task_worktree.py +++ b/.dsh/skills/task-worktree/scripts/task_worktree.py @@ -32,7 +32,7 @@ LOCK_NAMES = ("build", "bench") # 按锁分设陈旧窗口(小时)——兜底窗口应与「操作真实时长」同量级: # build:编译是秒~分钟级(keil_build.py 自持自释,持有窗口 ≈ UV4 运行时长, # 上限 = --timeout,默认 600s);崩溃泄漏时应在数十分钟内即可接管 → 1h。 -# bench:会话级(flash → @test → 探针,天然几十分钟~数小时)→ 保留 6h。 +# bench:会话级(flash → /serial-boot-test → 探针,天然几十分钟~数小时)→ 保留 6h。 # 另:锁 payload 里持有者可用 `ttl` 字段自报窗口(见 lock_acquire(ttl=...)), # 优先于这里的默认值——这样 build 锁的窗口能自动跟随其 --timeout。 LOCK_TTL_HOURS = {"build": 1.0, "bench": 6.0} @@ -404,7 +404,7 @@ def bootstrap(wt: Path): # 插件建的树(.dsh-worktrees/worktree/)缺三样东西,下面的命令逐条补。 # 回收**不在这里**:插件树由「/worktree remove|prune」收,done/abort 也不该拿去处理它。 # 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 个活跃名额也只数登记任务; # 3. 独占资源归属——build 锁由 keil_build.py 自持自释(按 cwd 反推持有者名, # 插件树里算成 pw:),bench 锁仍要人占,占锁名得和「lock status」对得上。 @@ -519,7 +519,7 @@ def print_plugin_trees() -> int: def miss(rel): return any(t["jn"].get(rel) == "absent" for t in trees) if miss(".venv"): - print(" 缺 .venv:进去跑 @build / ci_local.py 会**静默**落到机器级解释器(无 " + print(" 缺 .venv:进去跑 /keil-build / ci_local.py 会**静默**落到机器级解释器(无 " "fontTools,generated-fresh 假红的老根因)") if miss("simulator/SDL2"): print(" 缺 simulator/SDL2:模拟器 build_sim.bat 直接报错——CMakeLists.txt 的 " @@ -562,7 +562,7 @@ def cmd_plugin(a) -> int: print("({} 下没有插件工作树)".format(PLUGIN_SUBDIR)) print(" 建法:新会话输入框选「Worktree模式」/ 让模型调 worktree_create / " "/worktree create ") - print(" 建完回来「plugin adopt 」补共享联接,才能在里面跑 @build 与门禁。") + print(" 建完回来「plugin adopt 」补共享联接,才能在里面跑 /keil-build 与门禁。") return EXIT_OK wt = plugin_dir_for(a.name) @@ -584,7 +584,7 @@ def cmd_plugin(a) -> int: print("[warn] 仍缺 {}:主线里该路径不存在或联接失败——先补主线再复跑 adopt" "(这棵树还不能算就绪)".format("、".join(still))) else: - print("该目录现在可以跑 @build / ci_local.py / @test") + print("该目录现在可以跑 /keil-build / ci_local.py / /serial-boot-test") print("build 锁由 keil_build.py 自持自释,持有者名就是 {}".format(owner)) print("上台架前:$TW lock acquire bench --task {} --note <说明>".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")): run_script("scripts/check_mem_numbers.py", ["--write"], cwd, "同步内存/Flash 快照") else: - print("[skip] 未找到 .map,跳过内存快照同步(需先 @build)") + print("[skip] 未找到 .map,跳过内存快照同步(需先 /keil-build)") def run_gates(cwd: Path, skip_build: bool) -> bool: ok = run_script("scripts/ci_local.py", [], cwd, "静态门禁 + 主机侧纯逻辑单测 本地等价 CI") if skip_build: - print("[skip] 按 --skip-build 跳过 @build") + print("[skip] 按 --skip-build 跳过 /keil-build") return ok got, info = lock_acquire("build", "__done__", wait=900) if not got: - print("!! build 锁被 {} 占用(UV4 不能并发构建),跳过 @build".format(info.get("task"))) + print("!! build 锁被 {} 占用(UV4 不能并发构建),跳过 /keil-build".format(info.get("task"))) return False - # 告知 @build「调用方已持锁」:新版 keil_build.py 自持自释(见其 docstring), + # 告知 /keil-build「调用方已持锁」:新版 keil_build.py 自持自释(见其 docstring), # 读到本变量即跳过,避免自我死锁;**旧版根本不看此变量、也不会自锁**, # 故此处对「工作树里还是旧脚本」的在途任务同样安全(向后兼容)。 os.environ["DTU_LOCK_HELD"] = "build" try: if not run_script(".dsh/skills/keil-build/scripts/keil_build.py", [], cwd, - "@build(0 错 0 警)"): + "/keil-build(0 错 0 警)"): ok = False finally: os.environ.pop("DTU_LOCK_HELD", None) @@ -789,7 +789,7 @@ def cmd_new(a) -> int: print() print("下一步:把新会话的工作目录切到") 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( a.task_id, a.task_id)) print("回收用 `done {}`(**必须在主工作树执行**)。".format(a.task_id)) @@ -978,7 +978,7 @@ def cmd_done(a) -> int: return EXIT_OK 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"])) print("[ok] 前置检查通过(领先 {} 共 {} 个提交)".format(base, ahead)) @@ -1125,7 +1125,7 @@ def cmd_postmerge(a) -> int: if Path.cwd().resolve() != main.resolve(): raise EnvError("postmerge 必须在主工作树跑:{}".format(main)) 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" if not a.force and run_script(gate, ["--local"], main, "体积凭证检查(同码)", tail=8): print("[ok] 凭证同码新鲜,无需补链") @@ -1234,7 +1234,7 @@ def build_parser(): p.add_argument("task_id") p.add_argument("--base", default=None) 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("--force", action="store_true", help="清场时强制移除工作树") p.set_defaults(func=cmd_done) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 0048ba23..039bd979 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -6,14 +6,14 @@ # 它们在 .pre-commit-config.yaml 里带 stages: [manual],提交期不跑,由本 job 强制) # 主机侧纯逻辑单测 host-unit = test/host/**(MSVC 离线编译,零硬件) # -# ⛔ 板端测试套件(test/board/,需真机 + ST-LINK)/ 主机侧回归(@test 闭环)/ 人工目检 +# ⛔ 板端测试套件(test/board/,需真机 + ST-LINK)/ 主机侧回归(/serial-boot-test 闭环)/ 人工目检 # **不上 CI** —— 需要开发板与烧录器,只能在本地跑。 # # 明确不做的两件事(都是刻意为之): -# 1. `@build`(Keil UV4)—— 需本机 Keil 授权,runner 上不具备;且静态门禁的 cppcheck + +# 1. `/keil-build`(Keil UV4)—— 需本机 Keil 授权,runner 上不具备;且静态门禁的 cppcheck + # 代码规范已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(AGENTS.md)。 # 2. `mem-numbers` / `mem-budget` 两根钩子 —— 事实源都是 `MDK-ARM/**/*.map`(Keil -# 构建产物、已 gitignore),无 `@build` 即无 `.map`。故本 workflow 在**检测不到 +# 构建产物、已 gitignore),无 `/keil-build` 即无 `.map`。故本 workflow 在**检测不到 # .map 时自动跳过它们**(见下方 "决定跳过哪些钩子" 步骤),有 .map 的 runner 照常跑。 # # 🔴 体积门禁:盲区已关闭为**强制路径**(2026-10-25 起;原先只做到"声明") diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1aa27463..c3a76b82 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -818,8 +818,8 @@ repos: # /* 就是第二次注释的起点(C 的块注释不嵌套)。 # 为什么要闸:它**只在重编译那个 TU 时才暴露**,历史「0 警告」不代表安全;而且按包含者 # 数量刷屏——一份被广泛 include 的头里一处嵌套 = 每个包含者一行警告(实测一例扩散到 16 - # 条,把整批真警告挤到看不见)。有了这根,提交时就报,不用等 @build。 - # 接线当日实证(冻结引用,别去翻构建日志——它每次 @build 就被覆盖、也不入库):当日 02:22 + # 条,把整批真警告挤到看不见)。有了这根,提交时就报,不用等 /keil-build。 + # 接线当日实证(冻结引用,别去翻构建日志——它每次 /keil-build 就被覆盖、也不入库):当日 02:22 # 那次构建日志报过 ..\App\algo\run_par.c(267): warning: #9-D: nested comment is not # allowed ——那是别人**未提交**的在飞状态。原文抄在 docs/门禁说明/nested-comment.md §2.1; # 已提交历史回放 281 份旧字节 0 命中 ⇒ 这类问题一直靠人工在编译后清,从没被机器拦过。 diff --git a/AGENTS.md b/AGENTS.md index b1f23792..13e56856 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ **仅 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` 同此要求)。 @@ -78,10 +78,10 @@ ## 构建与验证 -- **门禁时机(用户裁定 2026-10-13)**:`@build` / `scripts/ci_local.py` / `check_doc_links.py` **只在准备提交时跑**(用户让 commit 后统一跑一遍),**不是每次中间改动后都跑**——多步改动期间直接改即可,验证集中在提交门禁;`@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` -- `@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` +- **门禁时机(用户裁定 2026-10-13)**:`/keil-build` / `scripts/ci_local.py` / `check_doc_links.py` **只在准备提交时跑**(用户让 commit 后统一跑一遍),**不是每次中间改动后都跑**——多步改动期间直接改即可,验证集中在提交门禁;`/serial-boot-test` 硬件闭环仍按任务需要跑 +- **`/keil-build` / `/serial-boot-test` / `/task-worktree` 是 DSH 会话的技能调用名、不是 shell 命令**(bash 里直接敲是 command not found / syntax error)——下面 `=` 后的命令行才是可执行形态(PowerShell 形态,bash 下路径正斜杠) +- **编译**(`/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` +- **串口回归**(`/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 即本次不通过**(刻意) - **烧录必须 `-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 程序\...` 出现即失效(仓库已搬迁),一律改用仓库相对路径 @@ -99,7 +99,7 @@ - ⚠ **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/` - 提交信息:标题 ≤100 字符、**正文每行 ≤80**(`.gitlint` + gitlint 默认)→ 长说明一律用**短标题 + `git commit -F ` 多行正文**(中文长标题极易超限,且 `-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` - **就绪屏障位必须 `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` -- **内存预算紧张**:新增任何任务/缓冲/栈前先核算 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** **采集链(ADC / RS-485)** @@ -133,7 +133,7 @@ **日志与工具链** - **`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** ## 硬件引脚 diff --git a/App/algo/README.md b/App/algo/README.md index d1411daf..bdee4e21 100644 --- a/App/algo/README.md +++ b/App/algo/README.md @@ -223,7 +223,7 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」 路径——**别拿旧副本当本次产物**(踩过:拷过一次之后没刷,别人读到的是上一轮的字节)。 ### 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`); 健康判据 = **零 `[ERR]`**(健康告警与标定哨兵全以 `[ERR]` 开头,误报会被闭环直接抓住; `vl` 在未标定默认构建必须为 0x00,未标定即闭锁落地后 `cal:` 行就是台架期望的直接证据)。 diff --git a/App/algo/wave_ring.c b/App/algo/wave_ring.c index 2d0d91a2..07e8726a 100644 --- a/App/algo/wave_ring.c +++ b/App/algo/wave_ring.c @@ -19,7 +19,7 @@ #include "wave_ring.h" /* 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 * 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */ #include diff --git a/App/algo/wave_store.c b/App/algo/wave_store.c index 76a9d7a2..823d7a40 100644 --- a/App/algo/wave_store.c +++ b/App/algo/wave_store.c @@ -14,7 +14,7 @@ #include "wave_store.h" /* 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 * 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */ #include diff --git a/App/app/app_main.c b/App/app/app_main.c index ce4d702c..3d9948d9 100644 --- a/App/app/app_main.c +++ b/App/app/app_main.c @@ -130,7 +130,7 @@ int app_main_init(void) { /* 固件身份戳(2026-10-10):__DATE__/__TIME__ 由编译器展开,与前面的字面量做 * C 字符串拼接 → .rodata 里是一段连续 ASCII,可直接在 .hex 里原样搜到。 - * 用途:@test 把"芯片里正在跑的镜像"和"刚编出来的镜像"对上号。此前开机标记 + * 用途:/serial-boot-test 把"芯片里正在跑的镜像"和"刚编出来的镜像"对上号。此前开机标记 * 全是固定串(App init OK / Network Init / ...),旧镜像也照样全中,于是 * "反复重下载、芯片一直是旧镜像"这类事故在自动闭环里完全不可见 * (AGENTS.md 烧录铁律记的正是它)。代价:1 条 DBG ≈ 90 B Flash + 30 B 常量。 diff --git a/App/log/console_log.c b/App/log/console_log.c index 66941f80..4780fb6e 100644 --- a/App/log/console_log.c +++ b/App/log/console_log.c @@ -427,7 +427,7 @@ void dbg_console_tx_end(void) { * · 拿到所有权 -> 整行一次发完,中途不会有别人的字节插进来; * · 拿不到 -> **本行不丢**,改投既有环形缓冲延后发。竞争失败不该变成丢行,更不该 * 变成 [ERR]:台架那个未注册窗口每 3 次启动命中 2 次,按"丢弃 + [ERR]"处理只会 - * 把静默缺陷换成常红的门禁([ERR] 是 @test 的 Forbidden 标记)。 + * 把静默缺陷换成常红的门禁([ERR] 是 /serial-boot-test 的 Forbidden 标记)。 * 入环的是**同一批字节原文**,本模块与 drain 都不补前缀、不重打时间戳(DBG_* 的 * 前缀在调用点就已 snprintf 进调用方缓冲)-> 延后发出去的行与它当场直发时 * 逐字节相同(判据⑦),取证时不会出现"证据说谎"; diff --git a/App/log/console_log.h b/App/log/console_log.h index 1dec33ae..5b678259 100644 --- a/App/log/console_log.h +++ b/App/log/console_log.h @@ -50,7 +50,7 @@ extern "C" { * (道G task-23)。诊断;[ERR] CONSOLE dropped 行报的就是它。 * ⚠ 抢不到所有权但成功让路入环的那条行**不入本账**(它没丢, * 只是晚一点发)——这本账必须保持"红得有意义",因为 - * [ERR] 是 @test 的 Forbidden 标记) + * [ERR] 是 /serial-boot-test 的 Forbidden 标记) * dbg_console_tx_fault - 因僵尸通道软复位自愈的次数(诊断,语义不变) * dbg_console_tx_timeout - 写者排空时观察到的块超时次数(TXE / TC 两条硬上界都算)。 * 三本账分开:看日志有没有在丢 -> dropped;看外设自愈了几次 diff --git a/App/log/sys_log_mcu.c b/App/log/sys_log_mcu.c index af213c9e..a68d43c5 100644 --- a/App/log/sys_log_mcu.c +++ b/App/log/sys_log_mcu.c @@ -181,7 +181,7 @@ static uint32_t s_quiet_reported; /* drain 侧已上报水位(唯一 * 的那一支),一旦成立,后面每一次取锁都零互斥;若"计数每前进一次报一条" * 就等于每轮 drain 一条记录 + 一次 NAND 写——诊断自己变成负载,正是上面 s_quiet_drop * 那条纪律要避免的事(同款先例另有 App/fs/vol_idle_sync.c 的 s_vol_sync_fail_latch)。 - * ⚠ **不打 DBG 错级、不打 [ERR]**:[ERR] 在本仓是"设备出故障"的唯一信号通道(@test 的 + * ⚠ **不打 DBG 错级、不打 [ERR]**:[ERR] 在本仓是"设备出故障"的唯一信号通道(/serial-boot-test 的 * Forbidden 标记也依赖它,四个 Fault 的崩溃日志都以 [ERR] 开头)。把一个可观测性事件 * 塞进 [ERR],它就同时不再是故障、也不再是禁令,两边一起失去意义——与 task-23 里 * "日志让路不计 [ERR]"是同一根判据。机器判据 = TC-SYSLOG-137 里的 g_sys_err_count == 0。 diff --git a/App/task/adc_task.c b/App/task/adc_task.c index 93a29873..49dd80a8 100644 --- a/App/task/adc_task.c +++ b/App/task/adc_task.c @@ -576,7 +576,7 @@ void adc_task_func(void *arg) { s_wave_bench_on = 1u; s_wave_bench_t0 = now; 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)", (unsigned int)WAVE_BENCH_PERIOD_MS, (unsigned int)(WAVE_BENCH_MAX_MS / 1000u)); @@ -621,7 +621,7 @@ void adc_task_func(void *arg) { } /* 4) 首拍相量上报:攒满一周波(20 ms)后立即落一次,作为 全周傅氏相量链路的开机证据。 - * 不依赖 10 s 心跳 → 台架 @test 的 settle 窗口内即可见(否则会被静默判据 + * 不依赖 10 s 心跳 → 台架 /serial-boot-test 的 settle 窗口内即可见(否则会被静默判据 * 截断,见 docs/测试说明/流程与规范/自动化测试流程.md 的 settle 口径)。 */ if ((first_phasor_logged == 0u) && (dbg_console_writer_ready() != 0u)) { phasor_t p; diff --git a/App/task/default_task.c b/App/task/default_task.c index 7d04413e..642c3f8e 100644 --- a/App/task/default_task.c +++ b/App/task/default_task.c @@ -359,7 +359,7 @@ void default_task_func(void *argument) { * 节奏:首次 STACK_PROBE_FIRST_MS 后报告(bring-up 深链已跑过),此后每 * STACK_PROBE_PERIOD_MS 采样一次,只在出现更深峰值时打印——既不改串口静默 * 窗口,也不漏掉之后才走到的更深分支(如卷修复/配置保存路径)。 - * 输出(纯 ASCII,便于 @test 采集): + * 输出(纯 ASCII,便于 /serial-boot-test 采集): * [STACK] peak_used=/ free_min= (%) * [STACK] heap free= min_ever= * 未覆盖 idle/timer 任务(内核静态小栈、纯循环,未取句柄)。 @@ -826,7 +826,7 @@ static void wave_store_drain(void) { s_wheld_ms_max = held_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) */ DBG_INFO("[WAVE][WARN] seg held %lu ms (idx=%u)", (unsigned long)held_ms, (unsigned)s_wnext_idx); diff --git a/App/task/iec104_serv.h b/App/task/iec104_serv.h index 0403c0e0..c0200355 100644 --- a/App/task/iec104_serv.h +++ b/App/task/iec104_serv.h @@ -7,7 +7,7 @@ #ifndef IEC104_SERV_H #define IEC104_SERV_H -#include /* uint32_t(观测计数声明):头文件自含——2026-10-27 @build 实测 +#include /* uint32_t(观测计数声明):头文件自含——2026-10-27 /keil-build 实测 * 本头此前全靠包含者先带 stdint,feed 换 include 即断链报 #20 */ /* 监听端口的宏出口已删除(道G task-9 收口 task-8 交接痕):运行期真源 = * [comm] 段 getter(App/protocol/iec104/iec104_comm.h 的 iec104_comm_port(), diff --git a/App/task/protect_task.c b/App/task/protect_task.c index 591f8974..2cb2e5c4 100644 --- a/App/task/protect_task.c +++ b/App/task/protect_task.c @@ -15,7 +15,7 @@ * (eng=0),拿去比二次值定值必然错判 → 复位元件 + 本拍不 eval。 * 默认构建(未标定)恒闭锁是**期望行为**:边沿用 DBG_INFO 而非 * [ERR](未标定是出厂常态、不是故障;每次开机都走这条沿, - * [ERR] 还会误触 @test 禁用标记);台架旁路只走编译期宏 + * [ERR] 还会误触 /serial-boot-test 禁用标记);台架旁路只走编译期宏 * DTU_BENCH(「数据可信」§4.3:不进 cfg、不进 104,本仓任何 * 提交目标都不定义它),且该宏**只作用于判定本体** * adc_cal_prot_blocked()(App/algo/adc_cal.c,全仓唯一一处 #ifndef; @@ -37,7 +37,7 @@ * VALID 口径,docs/任务规划/数据可信.md §4.3): * 采样恢复分支内、巡检/压板之前判;边沿 INFO 对 * + 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() * 删除,改调 App/algo/adc_cal.c 的 adc_cal_prot_blocked() @@ -234,7 +234,7 @@ void protect_task(void *argument) { /* 启动锚点行:控制台写者注册后首拍补打(2026-10-10 门控纪律,adcTask * 首帧同款)。写者注册前 con_append 走无锁裸发,与 defaultTask 启动日志 * 逐字节交错——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)) { DBG_INFO("protect task start: cycle=%ums elm=%u", (unsigned)PROTECT_TASK_CYCLE_MS, (unsigned)PE_COUNT); diff --git a/App/util/boot_stats.h b/App/util/boot_stats.h index a3126336..7dbe7cbb 100644 --- a/App/util/boot_stats.h +++ b/App/util/boot_stats.h @@ -101,7 +101,7 @@ const char *boot_stats_cause_str(uint8_t cause); * 本函数做 1 次 I2C 读 + 至多 1 次 I2C 写:I2C1 与 RTC 同总线,严禁再把它 * 搬进 iec104/adc 任何关键链或 ISR。重复调用直接返回,不重复计数。 * 函数说明:① 全程**不打日志**:I2C 失败/电池换过都是可观测性事件,不是装置故障; - * `[ERR]` 在本仓是"设备出故障"的唯一信号通道(@test 的 Forbidden 依赖它), + * `[ERR]` 在本仓是"设备出故障"的唯一信号通道(/serial-boot-test 的 Forbidden 依赖它), * 把可观测性事件塞进去就变成第二个信号源——与"日志让路不计 [ERR]"、 * 卷锁 fail-open 只投自检记录是同一条口径。状态一律留在结构体里。 * ② 落盘只写 8 字节到 RTC 用户 SRAM:无 FTL 写放大、不取卷锁、不进 /log; diff --git a/Drivers/BSP/NET/lftpd/lftpd.c b/Drivers/BSP/NET/lftpd/lftpd.c index 85e3c10e..df22c78b 100644 --- a/Drivers/BSP/NET/lftpd/lftpd.c +++ b/Drivers/BSP/NET/lftpd/lftpd.c @@ -506,7 +506,7 @@ static int cmd_pass(lftpd_client_t *client, const char *arg) send_simple_response(client->socket, 230, STATUS_230); return 0; } - /* 预期事件(爆破/配错口令):用 INFO 而非 ERROR——[ERR] 是 @test Forbidden 标记 */ + /* 预期事件(爆破/配错口令):用 INFO 而非 ERROR——[ERR] 是 /serial-boot-test Forbidden 标记 */ if (res == FTP_AUTH_LOCKED) { lftpd_log_info("auth locked after %u failures", (unsigned)FTP_AUTH_FAIL_MAX); } else { diff --git a/Drivers/BSP/NET/lftpd/private/lftpd_log.h b/Drivers/BSP/NET/lftpd/private/lftpd_log.h index 30d6bf3e..053eed4a 100644 --- a/Drivers/BSP/NET/lftpd/private/lftpd_log.h +++ b/Drivers/BSP/NET/lftpd/private/lftpd_log.h @@ -8,7 +8,7 @@ * 对端行为级别(既不是缺陷,也不该长得像缺陷)—— * 控制连接被对端关闭 / 客户端连上就走 / PASV 之后不来数据连接 / 网络重建导致 * 监听重建,这些都是**外部输入决定的正常收尾**。它们原先记 error 级,代价实测: - * ① 本仓 @test 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀), + * ① 本仓 /serial-boot-test 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀), * 于是"FileZilla 关了个窗口"就能把一次启动测试判 FAIL;② 任何人对着 21 端口 * 连一下就走,就能无限制造固件级 ERROR 日志(日志面 + 门禁面都能被外部驱动)。 * 所以这一类降为 info,并单独起一个名字,让"谁是真故障"在读代码时就一目了然。 diff --git a/Drivers/BSP/ScreenLink/sb_link.c b/Drivers/BSP/ScreenLink/sb_link.c index 10e5620f..c9d0605a 100644 --- a/Drivers/BSP/ScreenLink/sb_link.c +++ b/Drivers/BSP/ScreenLink/sb_link.c @@ -446,7 +446,7 @@ void sb_link_on_error(UART_HandleTypeDef *p_huart) * 2. 上报每 SB_LINK_REPORT_MS 一次,**只有计数有变化才打**;RX 与 TX 各一行, * 分开是因为控制台单行上限 192 字符,塞一行会被截断(截断的行比不打印更误导)。 * 3. 第一帧成功收到时立刻单独打一行,台架刚上电就能确认"链路通了、长度对、CRC 过"。 - * 4. ⚠ TX 行的字段名**不能含 fail**:`@test` 的 forbidden 模式是 + * 4. ⚠ TX 行的字段名**不能含 fail**:`/serial-boot-test` 的 forbidden 模式是 * `FAIL;ERROR;HardFault;[ERR]` 且**大小写不敏感**,写 `fail=0` 会让每次启动 * 自检都判红(2026-10-29 实测,被自己的日志字段坑了一次)。故用 kerr / abor。 */ diff --git a/Drivers/BSP/app_assert.h b/Drivers/BSP/app_assert.h index 3a20e10a..9cf38776 100644 --- a/Drivers/BSP/app_assert.h +++ b/Drivers/BSP/app_assert.h @@ -20,7 +20,7 @@ * 自洽假绿(见 test/host/net_socket/run_unit_test.py 的注释)。 * ⚠ 本注释块内禁止出现"斜杠紧跟星号"的连写(Markdown 粗体收尾在路径末尾时最容易 * 撞上,例如目录通配那种写法):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)同标准 * 作者:王建锋 * 创建日期:2026-10-25 @@ -43,7 +43,7 @@ extern "C" { * 开关:**默认关闭 = 产品构建**。 * 打开方式 = 取消下一行注释。策略与 test/board/test_config.h 的 * ENABLE_DESTRUCTIVE_TESTS 同级:默认必须关闭,严禁以打开态提交。 - * ⚠ 关闭态是 @build 的验收口径:"0 错误 0 警告" 且 .map 无新增符号 + * ⚠ 关闭态是 /keil-build 的验收口径:"0 错误 0 警告" 且 .map 无新增符号 * (规格 §2.1 完成判据)。本宏不新增任何 .c,故不涉及双端注册。 */ //#define APP_ASSERT_ENABLE @@ -119,7 +119,7 @@ extern void vApplicationAssertFailed(const char *file, long line); * 里打开,**跑完必须还原成注释态**。 * 4. 断言条件**不得带副作用**:关闭态不求值、打开态才求值,带副作用等于让行为 * 随开关漂移——这是本宏唯一真正危险的误用形态。 - * 5. 台架口径:MCU 钩子打印以 [ERR] 开头 → 真机上断言一旦触发,@test 必然判本次 + * 5. 台架口径:MCU 钩子打印以 [ERR] 开头 → 真机上断言一旦触发,/serial-boot-test 必然判本次 * 失败,这是刻意的(AGENTS「Forbidden 标记含 [ERR]」/ 出现 Fault 即不通过)。 */ diff --git a/MDK-ARM/README.md b/MDK-ARM/README.md index 07886f30..49f57a0b 100644 --- a/MDK-ARM/README.md +++ b/MDK-ARM/README.md @@ -15,7 +15,7 @@ ## 构建与烧录 ```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 # 烧录 + 复位运行(@download = stlink-flash skill) diff --git a/README.md b/README.md index 3921fe47..4468b364 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ STM32F4-Base/ │ │ ├── storage_test_task.c/h # 存储性能测试 │ │ ├── net_test_task.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 通信测试 │ ├── ftp_test.py / ftp_up_dl_test.py / ftp_regression.py / ftp_integrity_test.py / ftp_capture.py │ ├── e02_probe.py # 队头阻塞专项探针 @@ -395,7 +395,7 @@ pre-commit run --all-files | `Drivers/BSP/NET/net_events.c` | 芯片边沿事件处理 + 周期对账 | | `Drivers/BSP/NET/net_listener.c` | 监听生命周期(单一重建所有者) | | `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` | 自动化测试脚本 | ## 已知问题 diff --git a/docs/CI/远程CI完整指南.md b/docs/CI/远程CI完整指南.md index 87766c31..c753b537 100644 --- a/docs/CI/远程CI完整指南.md +++ b/docs/CI/远程CI完整指南.md @@ -40,8 +40,8 @@ > **曾经的盲区已关闭为强制路径(2026-10-25,「体积门禁 CI 强制路径」残余销项,`task/A5-size-gate`→564715c)。** > `mem-numbers` 钩子的事实源是 Keil 构建产物 `MDK-ARM/**/*.map`(已 gitignore),CI 刻意不跑 -> `@build` → 该钩子在远端只会被跳过(现以 `SKIP` 名单诚实显示 Skipped,不再伪装 Passed)。 -> 补上的强制路径 = **同码凭证**:本地 `@build` 0/0 后 `ram_baseline.py --update` 顺手盖 +> `/keil-build` → 该钩子在远端只会被跳过(现以 `SKIP` 名单诚实显示 Skipped,不再伪装 Passed)。 +> 补上的强制路径 = **同码凭证**:本地 `/keil-build` 0/0 后 `ram_baseline.py --update` 顺手盖 > `scripts/size_attestation.json`(三道前置:`.map` 可解析 / `.map` 不早于指纹内源码 mtime / > 受管区 ≤92%);CI 无需 Keil,静态门禁作业末尾的「体积门禁」步用 `scripts/check_size_gate.py --ci` > 复核两件事——**工作树指纹 == 凭证指纹**(改了代码从没测过 = 拦)+ **按凭证数字复跑 92% 红线**。 @@ -51,7 +51,7 @@ > (只复核同码性与红线复算);编译器选项(`.uvprojx`)不在指纹内;凭证由本地真实构建产生, > 链路本身不经 Keil 授权就无法伪造,但"盖戳后换机器环境"的差异只能靠 mtime 前置兜。 > -> 本地兜底动作不变(每次 `@build` 后):`ram_baseline.py --update`(含盖戳)+ +> 本地兜底动作不变(每次 `/keil-build` 后):`ram_baseline.py --update`(含盖戳)+ > `check_mem_numbers.py --scan`。若 runner 以后装了 Keil,`mem-numbers` 会在有 `.map` 时照常执行。 --- @@ -657,7 +657,7 @@ runner 是一台干净的 Windows,**下面这些必须装**,否则 job 会 | **cppcheck** | 🔴 必需 | 同上(`cppcheck --version` 探测不过) | `winget install Cppcheck.Cppcheck` | | **pre-commit** | 🔴 必需 | 同上(`python -m pre_commit --version` 探测) | `pip install pre-commit` 或 `uv tool install pre-commit` | | **MSVC(VS + Windows SDK)** | 🟡 建议 | 缺则 `host-unit-tests` **告警降级跳过**(不硬失败,但 主机侧纯逻辑单测裸奔) | `winget install Microsoft.VisualStudio.2022.BuildTools`,勾"使用 C++ 的桌面开发" | -| Keil / ST-LINK | ⛔ 不需要 | CI 刻意不跑 `@build` | — | +| Keil / ST-LINK | ⛔ 不需要 | CI 刻意不跑 `/keil-build` | — | | 开发板 / 串口 | ⛔ 不需要 | 板端测试套件/主机侧回归/人工目检不上 CI | — | Git 另算:`git --version` 任意版本即可(你已在用)。 @@ -1066,7 +1066,7 @@ date +%s # 对比 last_online | `static-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) | | `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §5.3 | | `l05` 日志出现 `::warning::runner 未安装 MSVC` | VS 装在非默认路径或未装 | 见 §4.2(默认路径 / 同步改两处根目录) | -| `mem-numbers` 显示 `Skipped` | 正常(CI 无 Keil,无 `.map`) | 无需处理;但注意 **CI 未守体积门禁**(见 §0.0),改代码后本地 `@build` + `check_mem_numbers.py --scan` 兜底 | +| `mem-numbers` 显示 `Skipped` | 正常(CI 无 Keil,无 `.map`) | 无需处理;但注意 **CI 未守体积门禁**(见 §0.0),改代码后本地 `/keil-build` + `check_mem_numbers.py --scan` 兜底 | | 主机侧纯逻辑单测显示 `2 runner(s), X/Y ...` 但 X.log` | | 主机侧纯逻辑单测报 `produced no '=== ... N/M PASSED ===' summary line` | 该模块运行器未输出汇总行(改了 harness 或运行器崩溃) | 核对 `test/host/common/unit_test_harness.py` 的 `summary_marker` 与该模块 `TestBuild` 是否一致 | | `Tee-Object` 步骤报 `TerminatorExpectedAtEndOfString` / 字符串缺少终止符 | `run:` 块混入了非 ASCII(GBK 解码错位) | 见文件头 🔴🔴;`run:` 内必须纯 ASCII | @@ -1211,7 +1211,7 @@ runner 的日志走 **stderr**,于是上面两个日志文件都栽在这两 带 `stages: [manual]` 的三根(`cppcheck` / `test-cppcheck` / `test-coverage`)也不在其中, 靠 static-gates 的「手动阶段门禁」一步逐根 `--hook-stage manual` 跑(新增 manual 钩子须同步加进 该步的 `$hooks` 列表,否则只声明不执行); -- **`@build` / 板端测试套件 / 主机侧回归**:永远留在本地,不要往 CI 加(需 Keil 授权与真机); +- **`/keil-build` / 板端测试套件 / 主机侧回归**:永远留在本地,不要往 CI 加(需 Keil 授权与真机); - **runner 长时间不可用**时:CI 只是"多一道网",**提交前的本地 `ci_local.py` 仍是 静态门禁的唯一保障**, 不能因"已经配了 CI"放宽(见 [docs/任务规划/登记簿.md](../任务规划/登记簿.md) 的条目「CI 远端闭环未验证」)。 diff --git a/docs/README.md b/docs/README.md index f9589081..7ee99cbe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -45,7 +45,7 @@ docs/ | 2 | [`项目架构总览.md`](架构设计/项目架构总览.md) | 六层分层、启动时序、存储/网络/UI 三栈、目录组织 | | 3 | [`项目启动顺序管理.md`](架构设计/项目启动顺序管理.md) | Event Flags 就绪屏障(取代魔法延时)的任务启动模型 | | 4 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) | 开发机/目标板/现场部署的完整环境核对清单 | -| 5 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) → 构建 | 跑通 `@build` / `@test`(见根 `AGENTS.md`) | +| 5 | [`项目部署环境要求.md`](环境与部署/项目部署环境要求.md) → 构建 | 跑通 `/keil-build` / `/serial-boot-test`(见根 `AGENTS.md`) | | 6 | [`测试开发标准流程.md`](测试说明/流程与规范/测试开发标准流程.md) | 测试分层 静态门禁~保护判据与门禁决策表 | | 7 | [`嵌入式C语言代码规范_V1.0.md`](代码规范/嵌入式C语言代码规范_V1.0.md) | 命名/格式/注释规范(提交门禁强制) | | 8 | [`项目架构总览.md`](架构设计/项目架构总览.md) → 按需 | 进入具体子系统设计文档 | @@ -124,7 +124,7 @@ docs/ | 文档 | 内容 | |---|---| | [`测试开发标准流程.md`](测试说明/流程与规范/测试开发标准流程.md) | 测试开发总流程:规划→板端套件→主机工具→门禁→沉淀;门禁决策表 | -| [`自动化测试流程.md`](测试说明/流程与规范/自动化测试流程.md) | `@test` 闭环细节:命令 / 串口标记 / 失败模式 | +| [`自动化测试流程.md`](测试说明/流程与规范/自动化测试流程.md) | `/serial-boot-test` 闭环细节:命令 / 串口标记 / 失败模式 | | [`测试文档模板规范.md`](测试说明/流程与规范/测试文档模板规范.md) | 测试文档模板与命名规则(TM / TC / DP) | **单元测试/**(主机侧纯逻辑单测说明与覆盖率口径) @@ -149,7 +149,7 @@ docs/ | [`GD5F2GQ5UE_Test_Guide.md`](测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md) | GD5F2GQ5UE SPI NAND 存储测试 | | [`SD2506_Test_Guide.md`](测试说明/板级测试/SD2506_Test_Guide.md) | SD2506 RTC 测试 | | [`FTP_Test_Guide.md`](测试说明/板级测试/FTP_Test_Guide.md) | FTP 服务测试 | -| [`实机测试实施指南.md`](测试说明/板级测试/实机测试实施指南.md) | **上板(真机)测试执行流程**(按顺序:环境预检 → `@test`+自检判定 → IWDG 注入/长跑 → 堆告警 → GD5F 错误通道 → 网络基线 → 工程卫生;每步含判据/命令/状态);已完成事项证据去向见 §10 | +| [`实机测试实施指南.md`](测试说明/板级测试/实机测试实施指南.md) | **上板(真机)测试执行流程**(按顺序:环境预检 → `/serial-boot-test`+自检判定 → IWDG 注入/长跑 → 堆告警 → GD5F 错误通道 → 网络基线 → 工程卫生;每步含判据/命令/状态);已完成事项证据去向见 §10 | **专项验证/**(针对一个缺陷或一次复验的台架专项流程;做完即定稿) @@ -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]` 片段,含逐帧尾批明细与方法学约束) | | `m3_data/` | 链路时延 M3 各档原始 JSON(quiet / stress / pending / 尾批实验;无独立 README,口径见 [`链路延迟测试说明.md`](测试说明/专项验证/链路延迟测试说明.md) 附表) | diff --git a/docs/UI设计/UI_中文字体生成与配置.md b/docs/UI设计/UI_中文字体生成与配置.md index 5a168c7b..c56ea7cd 100644 --- a/docs/UI设计/UI_中文字体生成与配置.md +++ b/docs/UI设计/UI_中文字体生成与配置.md @@ -140,7 +140,7 @@ OK 67325 bytes -> ...\Drivers\BSP\UI\ui_font_cn_12.c ## 6. 编译验证 ```bash -@build # = keil-build skill(.dsh/skills/keil-build/scripts/keil_build.py),调用 UV4 全量重建 +/keil-build # = keil-build skill(.dsh/skills/keil-build/scripts/keil_build.py),调用 UV4 全量重建 ``` 要求:`0 Error(s), 0 Warning(s)`。重新生成/修改字体或 `lv_conf.h` 后务必重建验证。当前已验证通过,`ui_font_cn_12.c` 正常编入。 diff --git a/docs/UI设计/UI_数据供给设计说明.md b/docs/UI设计/UI_数据供给设计说明.md index 49949ff4..814b7360 100644 --- a/docs/UI设计/UI_数据供给设计说明.md +++ b/docs/UI设计/UI_数据供给设计说明.md @@ -258,7 +258,7 @@ typedef struct { 以及「禁写终身次数」「跨开机累计不做」这两条口径,落在 `Drivers/BSP/UI/ui_page_runreport.c` 文件头;口径出处只在 `App/util/boot_stats.h` 四数台账与本节各述一次(同一事实只在一处详述,`docs/文档规范.md` 同此要求)。 - 代价(本批 -`@build` 实测,0 错 0 警): +`/keil-build` 实测,0 错 0 警): `Program Size` 由本批前 Code=235536 RO=26392 RW=1572 ZI=136924 变为 Code=236456 RO=28124 RW=1876 ZI=137084(ROM 合计 +2,652 B) → **RAM 合计 +464 B**(RW +304 / ZI +160)。逐对象(.map):5 个记录页各 60 B @@ -271,7 +271,7 @@ typedef struct { 随帧发布不新增拷贝节拍。水位: `RW_IRAM1` 62.2% / `RW_CCM` 87.9%(阈值 92%), `mem-budget` PASS, - 台账已由 `scripts/ram_baseline.py --update` / `--write` 回填(docs/内存管理/RAM预算台账.md)。 `@build` 后回填),未触 `mem-budget` 92% 红线。 + 台账已由 `scripts/ram_baseline.py --update` / `--write` 回填(docs/内存管理/RAM预算台账.md)。 `/keil-build` 后回填),未触 `mem-budget` 92% 红线。 > 另一处落地细化:`ui_data_rt_t` 除 `yc[]`(语义通道序基波有效值)外还带一条 `ph[]` 相量实例槽带(`ph_n` 给实际槽数)。原因是谐波(`Ia` 的二次/五次)无法在"语义通道"下标空间里表达——同一通道的谐波会与基波撞位,而交流量页要显示它们(`docs/UI设计/页面扩充详细方案.md` §3.1.1 第 4/5 行)。两条带的元素同形(数值 + 品质),页面行表按需选一条。 @@ -606,7 +606,7 @@ typedef struct { **结论**:代价 = 约 1.5 KB 静态 + 每 200 ms 一次 memcpy,换来整个 UI 层的数据可及性与一条 可测的写路径。区域水位(RW_IRAM1 / RW_CCM / Flash)**不在本文复制**——那是受管快照, -由 `scripts/ram_baseline.py` 从每次 `@build` 的 `.map` 生成,第二处写死就是一份会过期的 +由 `scripts/ram_baseline.py` 从每次 `/keil-build` 的 `.map` 生成,第二处写死就是一份会过期的 真源(本仓已有一条门禁在钉这个,口径见 `docs/内存管理/RAM使用分析与优化方案.md` §2.1 与 `docs/内存管理/RAM预算台账.md`)。本次新增约 1.5 KB 静态 + 一条 LVGL 池定时器,量级无压力。 @@ -617,7 +617,7 @@ typedef struct { | 步 | 内容 | 验证 | 依赖 | |---|---|---|---| | **步骤1** | `ui_data.h/.c` + 主机单测(无任何消费者) | `test/host/ui_data` 全绿;`layer-deps` 无新增 | 无 | -| **步骤2** | `App/app/ui_data_feed.c` + `ui_task.c` 接线(每轮调 tick) | 串口打印首帧(临时锚点);`@build` 0/0 | 步骤1 | +| **步骤2** | `App/app/ui_data_feed.c` + `ui_task.c` 接线(每轮调 tick) | 串口打印首帧(临时锚点);`/keil-build` 0/0 | 步骤1 | | **步骤3** | 交流量 / 直流量回炉(接快照 + 行表加 `src/dot/unit`) | 模拟器固定帧 golden 更新;台架看真实码值变化 | 步骤2 | | **步骤4** | 遥信量 + 定值/控制字/软压板查看(4 页) | 主站改定值 → 页面 200 ms 内变 | 步骤3 | | **步骤5** | 写路径(请求/状态)+ 整定 4 页 | 就地改定值 → 保存 → 掉电重启 → 值保持 | 步骤4。**已落**(道F task-1 落本层队列与状态机、task-2 落整定 4 页与模拟器侧,2026-10-26);「掉电重启值保持」只能上板收口,见 §12 判据 6 | diff --git a/docs/UI设计/UI_界面制作流程.md b/docs/UI设计/UI_界面制作流程.md index 77745727..c593346e 100644 --- a/docs/UI设计/UI_界面制作流程.md +++ b/docs/UI设计/UI_界面制作流程.md @@ -21,7 +21,7 @@ flowchart TD P2 --> C2{"输出图 ↔ 参考图
同构?"} C2 -- "否: 按根因修正(修代码 / 回 P0 修规格)→ 再出图" --> P2 C2 -- "是(Gate: 导航符合按键语义 + 自动化回归 PASS)" --> P3["P3 MCU 验证
→ 日志 + 上机目检结论"] - P3 -- "Gate: @build 0 错 0 警 + @test PASS + 上机目检通过" --> P4(["P4 归档
→ 受影响章节现状化/台账 + 随代码提交(代码+字体+字集+规格+文档)"]) + P3 -- "Gate: /keil-build 0 错 0 警 + /serial-boot-test PASS + 上机目检通过" --> P4(["P4 归档
→ 受影响章节现状化/台账 + 随代码提交(代码+字体+字集+规格+文档)"]) ``` > 小改动(文案/间距/纯交互/字体重生成)走「小改动快速通道」一节(见文末第十节),无需全流程。 @@ -33,7 +33,7 @@ flowchart TD | P0 需求录入 + 设计规格 | 提供图片(每批 1~3 屏);说明各屏作用与菜单位置;回答问题;确认布局参数表 | 图片分析、界面登记、字体度量核对、字符集核对、布局计算(验证等式)、规格制作 | 台账 + `_说明.md`(参数表/字符集/问题/映射/翻译要点) | | P1 规格→代码 | (可选)抽查代码 | 逐参数翻译、字体重生成、内存预算、双端注册 | 代码 + 字体 | | P2 模拟器 | **视觉确认**(窗口模式/出图),指出差异 | 构建、离线出图、出图与原图逐项比对、依规格改码 | 比对结论 | -| P3 MCU | **真机目检**(DTU-RemoteLCD) | @build / @test 执行、日志解析 | 日志 + 结论 | +| P3 MCU | **真机目检**(DTU-RemoteLCD) | /keil-build / /serial-boot-test 执行、日志解析 | 日志 + 结论 | | P4 归档 | — | 把受影响章节改成改动后的现状 + 更新台账 | 文档 | > 用户视觉确认点由旧"浏览器打开 HTML 样机"改为 **P2 模拟器窗口/出图**:P0 确认**参数表**(数值层面),P2 确认**渲染结果**(视觉层面);渲染结果与意图不符时按 §5.1 根因修正(代码错 → 修代码;参数表错 → 回 P0 修规格再修代码)。 @@ -248,8 +248,8 @@ flowchart TD ## 六、P3:MCU 验证 -1. `@build` → **0 错误 0 警告**(AGENTS.md 强制;有警告看 `MDK-ARM/build_log.txt`) -2. `@test`(一键 `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`): +1. `/keil-build` → **0 错误 0 警告**(AGENTS.md 强制;有警告看 `MDK-ARM/build_log.txt`) +2. `/serial-boot-test`(一键 `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`): - 采集窗口**先于烧录武装**(脚本 `--flash-cmd` 已保证顺序) - Required:`UI port init: OK;UI menu init: OK` + 本界面定制标记(按屏增补,如页头标题字样) - Forbidden:`FAIL;ERROR;HardFault` @@ -257,8 +257,8 @@ flowchart TD 4. 串口日志:池水位、任务栈高水位(`uxTaskGetStackHighWaterMark`,uiTask 预算 16384 words) ### Gate(进入 P4) -- [ ] @build 0错0警 -- [ ] @test PASS +- [ ] /keil-build 0错0警 +- [ ] /serial-boot-test PASS - [ ] 真机目检通过(用户确认) --- @@ -278,7 +278,7 @@ flowchart TD 2. **按界面族批量**: - 列表页(复用现有级联框自绘架构)→ 数据显示页(数值行框架)→ 参数编辑页(编辑/确认交互)→ 曲线/状态页 - 每族先做**族内第一屏**产出可复用的页面框架,再批量铺同族界面 -3. **每批收尾**:字体统一重生成(各屏字符集**并集**,一次生成)→ 全量回归(已完成界面重跑 @test) +3. **每批收尾**:字体统一重生成(各屏字符集**并集**,一次生成)→ 全量回归(已完成界面重跑 /serial-boot-test) 4. 字符集只增不减;字体文件每次**全量重生成**,禁止手工拼合 ### 8.1 规格投入分流(按屏型) @@ -384,8 +384,8 @@ P2 [ ] 模拟器构建 0 错误 [ ] shot.png 与参考图/规格参数表逐项比对(几何/文本/状态);不符按根因(修码/修规格)再出图(§5.1) [ ] 导航行为验证(窗口模式按键) [ ] 自动化回归:check_menu_rows.py + check_content_pages.py PASS -P3 [ ] @build 0 错 0 警 - [ ] @test PASS(Required:____ / Forbidden:FAIL;ERROR;HardFault) +P3 [ ] /keil-build 0 错 0 警 + [ ] /serial-boot-test PASS(Required:____ / Forbidden:FAIL;ERROR;HardFault) [ ] 真机目检通过 P4 [ ] UI_设计架构.md 受影响章节已改成现状(不记流水) [ ] 台账状态 → 已完成(代码+字体+字符集+规格+文档 一并提交) diff --git a/docs/UI设计/UI_设计架构.md b/docs/UI设计/UI_设计架构.md index 566008d8..7b82ffbc 100644 --- a/docs/UI设计/UI_设计架构.md +++ b/docs/UI设计/UI_设计架构.md @@ -48,7 +48,7 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓 3. 文本一律用静态字符串(`lv_label_set_text_static`),不 strdup 4. UI 相关文件为 **UTF-8 带 BOM**(`Drivers/BSP/UI/*`、`ui_task.c`、`Lib/lv_conf.h`),其余源码无 BOM;禁止 PowerShell 整文件重写(编码铁律,见 AGENTS.md) 5. 模拟器与 MCU **共用** UI 源码(`ui_menu*.c` / `ui_font_cn_12.c`),禁止在 `simulator/` 下复制副本;新增文件须同时注册进 Keil `.uvprojx` 与模拟器 CMakeLists(双端注册) -6. 验证顺序:模拟器预览 → `@build`(0 错 0 警)→ `@test`(Required 加 `UI port init: OK;UI menu init: OK`) +6. 验证顺序:模拟器预览 → `/keil-build`(0 错 0 警)→ `/serial-boot-test`(Required 加 `UI port init: OK;UI menu init: OK`) 7. **`LV_DRAW_SW_COMPLEX` 必须为 1**(两端均显式声明):主界面顶部"开口浅 V"装饰含斜线,`lv_line` 的斜段仅在复杂渲染器下绘制(`draw_line_skew`),关闭则只画水平线、两腿消失。且 `LV_DRAW_SW_CIRCLE_CACHE_SIZE` 须非零(默认 4),否则零长数组编译报错(C2466) 8. **I1 绘制缓冲必须含 palette 头(2026-09-21 实证,别再踩)**:LVGL 9.3 索引色格式的 draw buffer 布局是 `[palette 8B][pixel data stride×h]`—— `lv_draw_buf_create()` 按 `stride×h + LV_COLOR_INDEXED_PALETTE_SIZE(cf)×4` 分配,`lv_draw.c:497 → lv_draw_buf_goto_xy()` 取绘制指针时先 `+= palette`,`lv_refr.c get_max_row()` 也从 `data_size` 里扣掉 palette。 diff --git a/docs/UI设计/菜单页面设计.md b/docs/UI设计/菜单页面设计.md index 887d05e0..33d3122b 100644 --- a/docs/UI设计/菜单页面设计.md +++ b/docs/UI设计/菜单页面设计.md @@ -114,7 +114,7 @@ sequenceDiagram L-->>U: 0(成功) ``` -- 日志标记:`UI menu init: OK`(uiTask 打印)——`@test` 回归可作 Required 标记。 +- 日志标记:`UI menu init: OK`(uiTask 打印)——`/serial-boot-test` 回归可作 Required 标记。 - LVGL 池实测(模拟器 16 KB 池):根框建完 `used=5368 B`(旧行对象实现为 13312 B,自绘 + 容器收敛省 8.3 KB);最深四级级联 `used=6040 B`(增量 1256 B = 3 个子框体),远小于池余量。 ### 1.6 层间不变量(每层"绝不做") @@ -787,8 +787,8 @@ ESC/LEFT : d>0 → menu_back() (根框 d=0 不响 |---|---| | 模拟器无头 | `lvgl_sim.exe --screenshot shot.bin` / `--keys shot.bin`;I1 原始像素(160×160 = 3200 B)转 PNG(PIL)目检 | | 对照参考图 | 7 状态(根页 + 6 个一级子框)逐一对照参考图 01~07;反色行/框几何用像素统计核验(偏差见 §8.2 注 5) | -| 编译 | `@build`(ARMCC,0 错 0 警,强制) | -| 真机 | `@test`(Required 含 `UI port init: OK;UI menu init: OK`;屏幕内容仍需人工目检) | +| 编译 | `/keil-build`(ARMCC,0 错 0 警,强制) | +| 真机 | `/serial-boot-test`(Required 含 `UI port init: OK;UI menu init: OK`;屏幕内容仍需人工目检) | ### 8.2 陷阱与保真度说明 diff --git a/docs/UI设计/页面扩充详细方案.md b/docs/UI设计/页面扩充详细方案.md index 1224b17a..234912ac 100644 --- a/docs/UI设计/页面扩充详细方案.md +++ b/docs/UI设计/页面扩充详细方案.md @@ -571,7 +571,7 @@ void ui_w_soe_refresh(ui_w_soe_t *inst, uint16_t row, char *name, char *valu | 步 | 内容 | 验证 | |---|---|---| | 0-1 | `ui_data` + 主机单测 | `test/host/ui_data` 全绿 | -| 0-2 | `ui_data_feed` + `ui_task` 接线 | 首帧串口锚点;`@build` 0/0 | +| 0-2 | `ui_data_feed` + `ui_task` 接线 | 首帧串口锚点;`/keil-build` 0/0 | | 0-3 | `ui_w_editor`(数值编辑器) | 模拟器可改值 | | 0-4 | `ui_page_action`(确认操作页) | 模拟器确认/取消 | diff --git a/docs/images/任务栈预算.svg b/docs/images/任务栈预算.svg index 03d53e9e..079d10de 100644 --- a/docs/images/任务栈预算.svg +++ b/docs/images/任务栈预算.svg @@ -8,7 +8,7 @@ FreeRTOS 任务栈预算(9 任务;栈全部从 56KB FreeRTOS 堆分配,堆位于 CCM) -数据: 2026-10-22 @build + 同日真机 [STACK] 水位(data/stack_report_20261022.md);最坏 = max(静态最坏链, 实测峰值);判定线 = 余量 ≥ max(1024B ISR, 配置×30%) +数据: 2026-10-22 构建 + 同日真机 [STACK] 水位(data/stack_report_20261022.md);最坏 = max(静态最坏链, 实测峰值);判定线 = 余量 ≥ max(1024B ISR, 配置×30%) 浅色条 = 分配栈 深色条 = 最坏占用(max(静态最坏, 实测峰值)) 下方文字 = 实测余量/判定 defaultTask diff --git a/docs/images/内存布局_FLASH.svg b/docs/images/内存布局_FLASH.svg index dd848eb6..7120ab67 100644 --- a/docs/images/内存布局_FLASH.svg +++ b/docs/images/内存布局_FLASH.svg @@ -7,7 +7,7 @@ .bar-seg { stroke:#fff; stroke-width:0.5; } -Flash (1MB) 使用布局 — 2026-10-23 @build +Flash (1MB) 使用布局 — 2026-10-23 构建 代码段 (Code) 208.7KB diff --git a/docs/images/内存布局_RAM.svg b/docs/images/内存布局_RAM.svg index ee20aeab..25d10256 100644 --- a/docs/images/内存布局_RAM.svg +++ b/docs/images/内存布局_RAM.svg @@ -7,7 +7,7 @@ .bar-seg { stroke:#fff; stroke-width:0.5; } -SRAM (192KB) 使用布局 — 2026-10-22 @build +SRAM (192KB) 使用布局 — 2026-10-22 构建 主 SRAM RW_IRAM1 @0x20000000(128KB,DMA 可达) LVGL 池 16KB diff --git a/docs/产品需求/产品需求设计文档.md b/docs/产品需求/产品需求设计文档.md index 1aba8694..dc131565 100644 --- a/docs/产品需求/产品需求设计文档.md +++ b/docs/产品需求/产品需求设计文档.md @@ -53,7 +53,7 @@ | **IWDG** | Independent Watchdog,独立看门狗(LSI 时钟源) | | **就绪屏障** | 用 CMSIS-RTOS2 Event Flags 表达"子系统就绪"的启动顺序机制 | | **双端** | MCU(Keil/ARMCC)与 PC 模拟器(CMake/MSVC)共用同一份 UI/配置源码 | -| **@build / @test** | 仓库约定的构建 / 自动化测试命令别名 | +| **/keil-build / /serial-boot-test** | 仓库约定的构建 / 自动化测试命令别名 | | **语义取数与有效性 / 标定折算 / 全周傅氏相量 / 测频与遥测快照 / 保护判据** | 采集链路分层:语义取数与有效性(语义取数与有效性)→ 标定折算(标定折算)→ 全周傅氏相量(全周傅氏相量)→ 测频与遥测快照(测频与遥测快照)→ 保护判据(保护判据) | | **全周傅氏** | 单频 DFT 取基波相量,窗长 = 整周波(20 点);幅值即基波有效值 | | **相量** | `{fRe, fIm, fAm, fAmSQ}`,实部 / 虚部 / 幅值 / 有效值 | @@ -168,7 +168,7 @@ flowchart LR | G2 | **可维护**:分层解耦、单一事实源、双端复用、门禁强制 | 六层架构、就绪屏障、生成器(菜单/字体/任务表)、pre-commit 钩子族 | | G3 | **可移植**:UI/配置源码 MCU 与 PC 模拟器共用 | 平台后端抽象(`*_mcu.c` / `*_sim.c`),核心零平台依赖 | | G4 | **可扩展**:新子系统/新配置段/新页面按模板接入 | 事件位扩展、段注册三步、页面插件描述符、`App/protocol/` 预留 | -| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `@build` / `@test` / 静态门禁 / 主机侧纯逻辑单测 / 台架验证 | +| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `/keil-build` / `/serial-boot-test` / 静态门禁 / 主机侧纯逻辑单测 / 台架验证 | **设计原则**(贯穿全项目): @@ -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 | -| 用途 | printf 日志 + `@test` 采集通道 | +| 用途 | printf 日志 + `/serial-boot-test` 采集通道 | | 配置源 | `.serialtest/config.json`(单一配置源) | | 工具 | 仓库内置 Python 串口助手(`.dsh/skills/serial-assistant/`,常驻 daemon + CLI) | -**启动标记(`@test` 判据)**: +**启动标记(`/serial-boot-test` 判据)**: - Required:`App init OK` / `Network Init` / `net init: OK` / `PHY: 100M FULL` - Forbidden:`FAIL` / `ERROR` / `HardFault` / `[ERR]` @@ -1407,7 +1407,7 @@ mac=00:00:00:00:00:00 | 网络重建生效 | 2~5 s(含等链路 ≤3 s) | 配置设计 | | UI 刷新 | 10 ms 循环 | `ui_task_func` | | IWDG 停喂到复位 | ≤2.5 s | IWDG 设计 | -| **采样节拍** | **1000 Hz**(每 10 s 帧号 +10000,零丢帧) | 台架 `@test` | +| **采样节拍** | **1000 Hz**(每 10 s 帧号 +10000,零丢帧) | 台架 `/serial-boot-test` | | **单拍采集消费** | ≤10 µs(DMA 搬运后实测 8 µs) | `adc_task` 实测 | | **相量刷新** | 1 ms(每拍整窗重算 5 实例) | `phasor_run` | | **遥测快照周期** | 200 ms(经过时间分频) | `meas_tick` | @@ -1459,14 +1459,14 @@ mac=00:00:00:00:00:00 | 静态门禁 | pre-commit 钩子族:格式 / 编码 / 静态分析 / 文档链接 / 体积 / 生成物新鲜度 / 编号 | 本地 / CI | | 主机侧纯逻辑单测 | MSVC 离线编译真实源码 | 本地 / CI | | 板端测试套件 | 板端套件(testTask 串口 `[PASS]/[FAIL]` 汇总) | 本地(需真机 + 串口) | -| 主机侧回归 | 模拟器像素 / 池水位回归 + 真机 `@test` 闭环(含探针) | 本地(模拟器免硬件;`@test` 需真机 + ST-LINK) | +| 主机侧回归 | 模拟器像素 / 池水位回归 + 真机 `/serial-boot-test` 闭环(含探针) | 本地(模拟器免硬件;`/serial-boot-test` 需真机 + ST-LINK) | **构建 / 测试命令**: ```powershell -# 构建(@build):0 错 0 警 +# 构建(/keil-build):0 错 0 警 & '.venv\Scripts\python.exe' .dsh/skills/keil-build/scripts/keil_build.py # 退出码 0=PASS / 1=构建失败 / 2=环境错误 -# 自动化测试(@test):串口采集 → 烧录 → 日志校验 +# 自动化测试(/serial-boot-test):串口采集 → 烧录 → 日志校验 & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py # 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法错误 # 全库门禁 pre-commit run --all-files @@ -1497,7 +1497,7 @@ pre-commit run --all-files | 构建 | Keil MDK-ARM v5(ARMCC) | CMake + SDL2 + MSVC | | 共用源码 | `Drivers/BSP/UI/*`、`Drivers/BSP/NETCFG/net_cfg.c`、`App/cfg/cfg_file.c` | 同左 | | 平台后端 | `*_mcu.c`(CH395F/FatFS) | `*_sim.c`(Winsock/宿主文件) | -| 验证 | `@build` 0/0 → `@test` 串口闭环 | `check_menu_rows.py` + `check_content_pages.py`(像素黄金参考) | +| 验证 | `/keil-build` 0/0 → `/serial-boot-test` 串口闭环 | `check_menu_rows.py` + `check_content_pages.py`(像素黄金参考) | > **新源文件必须双端注册**(Keil `.uvprojx` + `simulator/CMakeLists.txt`),否则不参与编译。 @@ -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) | | 主机侧单测 | **全部套件全绿**(看门狗、时钟、配置、相量、采集层、标定、遥测、测频、定值、元件、运行参数、104、SOE 等;套件与断言数随代码演进,以 `.pre-commit-config.yaml` 与 `test/host/` 为准) | | 栈门禁 | `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 警 | | 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]` 开头,出现即不通过) | | 自检记录 | `selftest: no fault record` | | 固件身份 | `fw build` 身份戳匹配 + 烧录后回读 `No difference found` | -| 退出码 | `@test` → 0(PASS) | +| 退出码 | `/serial-boot-test` → 0(PASS) | ### 10.3 现场验收清单 | # | 项目 | 方法 | 通过标准 | |---|---|---|---| -| 1 | 上电自启动 | 断电上电 + 串口采集 | `@test` PASS,无 Forbidden 标记 | +| 1 | 上电自启动 | 断电上电 + 串口采集 | `/serial-boot-test` PASS,无 Forbidden 标记 | | 2 | 网络连通 | PC ping 设备 IP | 通(PHY 100M FULL) | | 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 | | 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 | diff --git a/docs/产品需求/参考产品复刻明细.md b/docs/产品需求/参考产品复刻明细.md index 203019ec..86691054 100644 --- a/docs/产品需求/参考产品复刻明细.md +++ b/docs/产品需求/参考产品复刻明细.md @@ -792,7 +792,7 @@ tTimer_NoUI 无压无流判据(CN_ > **一条物理前置(已完成 2026-10-29)+ 一条实现纪律**: > ① **台架串口线已挪位**——日志口迁到 RS-232 侧后,台架那根线改接 CN1 的 RS232#1 端子 > (`CN1.10` 接适配器 TXD、`CN1.12` 接适配器 RXD、`CN1.7` 或 `CN1.8` 接 GND),台架日志口 -> 因此由 `COM6` 变为 **`COM3`**,`.serialtest/config.json` 的 `portName` 已同步,`@test` 与 +> 因此由 `COM6` 变为 **`COM3`**,`.serialtest/config.json` 的 `portName` 已同步,`/serial-boot-test` 与 > `serial-assistant` 不必再手工指端口。这些端子是 **RS-232 电平**,TTL 的 CH340 直连不了, > 用的是 USB 转 RS-232 适配器;接法与端子出处见 `docs/子系统设计/屏板链路设计说明.md` §3.2。 > **这条要在第 16 条动代码之前完成。** diff --git a/docs/代码规范/pre-commit使用说明.md b/docs/代码规范/pre-commit使用说明.md index 76567adc..cb6f7c10 100644 --- a/docs/代码规范/pre-commit使用说明.md +++ b/docs/代码规范/pre-commit使用说明.md @@ -145,7 +145,7 @@ git commit | 一把梭(上面多数静态项 + 主机单测) | `& '.venv\Scripts\python.exe' scripts/ci_local.py` | 非 0 就别抢跑(但它**不含** `stack_report` / `ram_baseline`,数字与安全边界要另跑) | > ⚠ **这张表为什么必须存在**:`stack_report` 与 `ram_baseline --gate` 这类**根本不在钩子清单里**(它们要 Keil 构建产物, -> 只能靠人在 `@build` 后跑),所以「人漏跑一次」直接等于「这道检查这一次不存在」。想机械化,先要解决「磁盘上的 +> 只能靠人在 `/keil-build` 后跑),所以「人漏跑一次」直接等于「这道检查这一次不存在」。想机械化,先要解决「磁盘上的 > `.htm` / `.map` 属于最后一次构建的人」这个归因问题——与 §2.2「stash 覆盖面」那条同源,不是加一根钩子就完事 > (`mem-budget` 今天红在「台账与 `.map` 不同码」正是同一个形状)。 diff --git a/docs/任务规划/故障录波.md b/docs/任务规划/故障录波.md index 31c106fd..2d1da202 100644 --- a/docs/任务规划/故障录波.md +++ b/docs/任务规划/故障录波.md @@ -60,7 +60,7 @@ flowchart TD |---|---| | 跑 `scripts/ram_report.py` 与 `scripts/stack_report.py` 前后各一次,回填 `scripts/ram_baseline.py --update` | 录波环与落盘缓冲约 5.9 KB 静态增量,**大于 4 KB 触发 R6 规则**(`docs/内存管理/内存内容分配策略.md` §6 台账与 R6);CCM 只剩七千余字节 → 落位必须进 SRAM1(`docs/子系统设计/故障录波设计说明.md` §7) | | 新 .c 双端注册 | `new-source-file`:`MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 都是显式文件列表 | -| `@build` 0 错误 0 警告 | 任何代码改动后的固定动作 | +| `/keil-build` 0 错误 0 警告 | 任何代码改动后的固定动作 | | 主机侧单测新增三套并挂总闸 | 环、段格式、触发状态机都是纯逻辑,能被 `test-coverage` 真跑;别把该测的部分留给台架 | | 菜单与字库生成物重跑 | 新增叶子改叶数 → 同步 `docs/UI设计/页面补齐规划.md` 与 `docs/任务规划/UI补齐.md` 的口径闭合,重跑 `tools/menu_gen/gen_menu.py` 与中文字库链,过 `generated-fresh` 与 `font-charset` | | 台账回填与文档现状化 | `docs/任务规划/README.md` 登记簿销项时同步清理引用;`docs/子系统设计/故障录波设计说明.md` §11 的三处口径要回写 `docs/任务规划/数据可信.md` | diff --git a/docs/任务规划/数据可信.md b/docs/任务规划/数据可信.md index e4881783..5a680ad8 100644 --- a/docs/任务规划/数据可信.md +++ b/docs/任务规划/数据可信.md @@ -240,7 +240,7 @@ M4 的六条退出判据(逐条对齐现状,别整格打勾): `adc_cal_prot_blocked()`,`App/task/protect_task.c:245` 消费并打 INFO + 记 SOE)。 - 与"自检失败 / 采样故障闭锁"同构(`PROT_HF_LOCK_MASK` 那一套),**不新增机制**。 -- 现有台架 `@test` 的期望会因此变化——**这是有意的**,任务卡里同步更新判据。 +- 现有台架 `/serial-boot-test` 的期望会因此变化——**这是有意的**,任务卡里同步更新判据。 - 台架调试要旁路只能走编译期开关 `DTU_BENCH`,不进 cfg、不进 104。 ⚠ **`DTU_BENCH` 只旁路这一处**:它**不**旁路 §2.3 #1 的测频幅值门,也不产生任何读数出口。 @@ -320,7 +320,7 @@ typedef struct { | **波形轮** | 读数是否只跟基波走 | 同幅值改方波、三角 | 同上 | 期望值 = **基波分量有效值**(同峰值下的基波 / 峰值比:方波 ≈ **0.90**,闭式 4/π ÷ √2 = 0.900、20 点整周期 DFT 实算 0.904;三角 ≈ **0.573**,闭式 8/π² ÷ √2 = 0.573、实算 0.578),**不是**真有效值(方波 = 1.000、三角 = 0.583 峰值);偏差 ≤2 %。算术与逐波形对照表见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的波形卡。顺带把"这台仪表读的是基波"这条口径钉进文档——用户拿真有效值表去对会以为差了 10 % | | **相位轮** | 相位 | UA 与 UN 两路**同片**(都走 TPAFE)注同幅、同相 | **定量档**:录波段文件 `test/host/wave_read.py fft`(浮点重算基波相位;段内表序 `UA` = 0、`UN` = 3);**定性档**:`phasor(hb)` 各行 `Re=` / `Im=` | **定量档**:同段内两路相位差 ≤2°,把发生器相位改 90° 后差值跟到 ≈90°。**定性档只判"跟随"**(差值落进 80°~100°)——`phasor` 出口是 ×0.1 定点,1.000 Vrms 时每格 0.1 V,角度量化最坏 ≈ atan(0.1 / 0.707) ≈ 8°,判不动 2°(§4.6.3)。⚠ 段与发生器**无绝对相位锁定**(触发瞬间钉在段内点 159),只能比**同段内两路**的相对差,跨段比相位或拿发生器相位当绝对基准都不成立。**只许比同片内**——跨转换器无同刻性(§9),STM32 那五路还有 1 拍乒乓滞后 | | **测频轮** | 测频 | 同幅值轮的 1 V/50 Hz | `freq(hb):` 与 104 `0x401D` | **预期就是"不出数"**(幅值门 50 V 在引脚域恒不过,§2.3 #1)→ 这条的判据是"如实报不可测"(`freq_valid=0`、UI `Hz` 显示 `--`、104 上送 IV),**不是**报出一个错误的 50.00。想真验 45~55 Hz 精度(PC 侧判据 0.02 Hz)须临时把 `FREQ_AMP_MIN` 降到引脚域量级(如 0.5 V)单跑一轮 → §7 #8 + §8 风险行 | -| **削顶轮** | 削顶与品质路由(**负向用例**) | 给 UB(R52 / `AIN2`)注 **DC −1.00 V 并保持 ≥5 s**;或 1.000 Vrms 而频率 **2.0 Hz** 的正弦——两档都能造出"连续贴同一侧轨带"。⚠ **注 50 Hz 同幅正弦造不出锁存,且这是预期而非故障**:1 kHz 下一个周波只 20 点、正半周必落中带,而 `adc_health_rail_watch()` 任何一拍回中带就把计数清零(`App/algo/adc_health.c`),连续贴底最长 ≈ 209/360 × 20 ≈ 11 拍 < 20。TPAFE 侧在 ±2 V 判据上限内造不出这条(轨带 ±32112 码 ≈ ±4.9 V),只有 STM32 那五路能做 | `cal:` 行的 `rail=` / `edge=`;`rail: edge=…` 的 `[ERR]` 行;104 `0x4012`;段文件档另加 `wave_read.py shape` 的三把尺子(`--strict` / 再加 `--clip-superset` / 不带),逐条配法见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的削顶卡 | 负半周钉下轨 → **连续 20 拍同侧后该路贴轨位锁存**(`ADC_RAIL_N_RUN = 20`,`App/algo/adc_health.h:174`)、`edge` 起沿、104 上该路 IV 且值清零。锁存另有**释放沿前提**:该通道此前须出现过中带样本 → **先上电见基线摆动、再开发生器输出**,开机就注会测出假不通过。撤掉注入后 `rail` 仍非 0 是 sticky 语义(只有 `adc_health_rail_init()` / 复位清)——**复位后仍不清才是 bug**。⚠ 这条会**故意造出 `[ERR]`**,而 `@test` 的 forbidden 标记含 `[ERR]` → 人工判读、排在最后单独一轮(§8 第一行) | +| **削顶轮** | 削顶与品质路由(**负向用例**) | 给 UB(R52 / `AIN2`)注 **DC −1.00 V 并保持 ≥5 s**;或 1.000 Vrms 而频率 **2.0 Hz** 的正弦——两档都能造出"连续贴同一侧轨带"。⚠ **注 50 Hz 同幅正弦造不出锁存,且这是预期而非故障**:1 kHz 下一个周波只 20 点、正半周必落中带,而 `adc_health_rail_watch()` 任何一拍回中带就把计数清零(`App/algo/adc_health.c`),连续贴底最长 ≈ 209/360 × 20 ≈ 11 拍 < 20。TPAFE 侧在 ±2 V 判据上限内造不出这条(轨带 ±32112 码 ≈ ±4.9 V),只有 STM32 那五路能做 | `cal:` 行的 `rail=` / `edge=`;`rail: edge=…` 的 `[ERR]` 行;104 `0x4012`;段文件档另加 `wave_read.py shape` 的三把尺子(`--strict` / 再加 `--clip-superset` / 不带),逐条配法见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的削顶卡 | 负半周钉下轨 → **连续 20 拍同侧后该路贴轨位锁存**(`ADC_RAIL_N_RUN = 20`,`App/algo/adc_health.h:174`)、`edge` 起沿、104 上该路 IV 且值清零。锁存另有**释放沿前提**:该通道此前须出现过中带样本 → **先上电见基线摆动、再开发生器输出**,开机就注会测出假不通过。撤掉注入后 `rail` 仍非 0 是 sticky 语义(只有 `adc_health_rail_init()` / 复位清)——**复位后仍不清才是 bug**。⚠ 这条会**故意造出 `[ERR]`**,而 `/serial-boot-test` 的 forbidden 标记含 `[ERR]` → 人工判读、排在最后单独一轮(§8 第一行) | | **谐波轮** | 谐波轨 | 基波 + 20 % 二次 + 20 % 五次(IA 上注)。⚠ 峰值预算:1.000 Vrms 基波 + 两个 20 % 分量的最坏对齐 ≈ 1.98 V 已顶 ±2 V 红线 → 基波用 **0.800 Vrms**、二次与五次各 0.160 Vrms | **定量档**:段文件 `test/host/wave_read.py fft --ch 4 --all`(`IA` = 段内表序 4,直接给基波与 2 / 5 次谐波 RMS,单位 mV);**旁证**:UI 交流量页 `Ia2` / `Ia5`(1 mV)与 `phasor(hb) C04-IA-F2` / `C05-IA-F5`(×0.1 定点,只证这根轨不是恒 0) | 谐波轨读数 = 注入分量(**±2 %,走定量档**;周期行档判不动:0.160 Vrms 在 ×0.1 出口只有 1~2 格)。⚠ 这两根轨**目前没有任何下游消费者**(`prot_elm.c` 无引用、`meas` 只算 `harm=1`),所以谐波轮只证"算得对",**不证"有人用"**——出口归 §9 的谐波轨立项 | **先做幅值 / 波形 / 削顶三轮**(纯读数、无跨通道约定),相位 / 谐波次之,测频轮视 §7 #8 的裁定。每条的两档跑法(周期行标量档 / @@ -433,9 +433,9 @@ flowchart TD | 风险 | 触发条件 | 缓解 | |---|---|---| -| **削顶负向用例被当成固件 bug** | 做削顶轮时 STM32 那五路必然钉下轨 → `rail: edge=…` 是 `[ERR]` 行,而 `@test` 的 forbidden 标记含 `[ERR]` → 整轮被判 FAIL | 判读规则写进流程文档:"做削顶轮的这一轮 `[ERR] rail:` 属预期,只人工判读、不计入 `@test` 结论",或把这五路排在最后单独一轮并事先声明。同时别忘 `cl=0xE6` 是**来源派生**(§2.6),不是本轮新削出来的 | +| **削顶负向用例被当成固件 bug** | 做削顶轮时 STM32 那五路必然钉下轨 → `rail: edge=…` 是 `[ERR]` 行,而 `/serial-boot-test` 的 forbidden 标记含 `[ERR]` → 整轮被判 FAIL | 判读规则写进流程文档:"做削顶轮的这一轮 `[ERR] rail:` 属预期,只人工判读、不计入 `/serial-boot-test` 结论",或把这五路排在最后单独一轮并事先声明。同时别忘 `cl=0xE6` 是**来源派生**(§2.6),不是本轮新削出来的 | | **拿发生器的精度当标准用** | 用发生器自报 Vrms 当基准值去比 | 基准 = 万用表 AC 档实测(§7 #9);发生器只当"波形干净、频率可锁"的信号源,它自己的幅值误差正是要被校的量 | -| **临时改了 `FREQ_AMP_MIN` 忘了还原** | 走 §7 #8 的 B 方案 | 改动落在产品头文件里、会编进固件。单列一条收尾判据:还原后 `@build` 0 错误 0 警告 + `git diff` 里 `App/algo/freq.h` 必须为空;并在 §7 #8 那行留一句"本轮台架已还原" | +| **临时改了 `FREQ_AMP_MIN` 忘了还原** | 走 §7 #8 的 B 方案 | 改动落在产品头文件里、会编进固件。单列一条收尾判据:还原后 `/keil-build` 0 错误 0 警告 + `git diff` 里 `App/algo/freq.h` 必须为空;并在 §7 #8 那行留一句"本轮台架已还原" | | **峰值当成有效值,顶到量程外** | 只看 Vrms 数字:1.000 Vrms 峰值已 1.414 V | 接线前按**峰值**核一遍:TPAFE 侧 ≤±2 V、STM32 侧峰值必须 <3.3 V(且负半周本就钉轨,只作削顶轮用) | | **发生器与板不共地 / 地环** | 两台设备各自接电网地 | 单点共地,必要时发生器浮地;读数漂或出现与幅度无关的偏置先查这里 | | **非整 50 Hz 注入被当成 `meas` 抖动 bug** | 注 49.5 / 50.5 Hz | `meas` 是**瞬时基波有效值透传、200 ms 是上送节拍不是平均窗**(§4.5)→ 快照轨会有拍频式周期间跳动,属预期。测频腿要扫频时读 `freq(hb)`,别读 `meas:` | diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index 9ae0bf0b..962f0a33 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -14,7 +14,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` > 已按合并后的 `.map` 同步(RW_IRAM1 75,272 / ER_IROM1 232,512 / RW_CCM 57,376 / Flash 233,704)。 > 变更:v1.2 数据现状全量同步——§2.1/§2.2/§5/§6/§8.6 等"现状"数据全部更新为 2026-09-21 lftpd 静态化后的最新 map 与实测值;清除 2026-09-11 历史口径残留(历史快照保留于 `data/`) > 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2) -> 数据来源:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(`@build` 重建)+ `scripts/ram_report.py`(静态台账)+ `scripts/stack_report.py`(静态调用图 × 运行时栈水位)+ 源码静态分析 + 网络调研 +> 数据来源:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(`/keil-build` 重建)+ `scripts/ram_report.py`(静态台账)+ `scripts/stack_report.py`(静态调用图 × 运行时栈水位)+ 源码静态分析 + 网络调研 > **本文档是内存管理主文档**(2026-09-21 合并):原《STM32F4-Base 内存使用分析报告》(现状审计/快照)与《内存使用分析规划》(执行规划,已全部闭环)已并入本文;**区域放置与 DMA 可达性规范**单列于 `内存内容分配策略.md`(本文只引用、不重复)。 > 快照产物:`data/ram_report_<日期>.md`(静态台账)、`data/stack_report_<日期>.md`(栈预算)。 @@ -29,7 +29,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` - F407 另有 **64KB CCM RAM**:**已于 2026-09-21 启用**并把 FreeRTOS 堆迁入(§4.1.2) - 起步时没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋 -当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成;2026-10-24 `@build`):RW_IRAM1 **75,632 / 131,072 B(57.7%)**、RW_CCM **57,376 / 65,280 B(87.9%)**。✅ **任务栈:`stack_report.py` 9/9 PASS**(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位,`defaultTask` 最坏 3024B/5120(余 40%)、`protectTask` 872B/2048(余 57%),见 `data/stack_report_20261022.md`);**卡点已从 SRAM1 移到 CCM**(余 7,904 B)。历史:`defaultTask` 曾于 2026-10-04 达 3664B/4096 ≈10%,曾登记在案,2026-10-10 削链后销项(详见"2026-10-10 复测"小节)。 +当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成;2026-10-24 `/keil-build`):RW_IRAM1 **75,632 / 131,072 B(57.7%)**、RW_CCM **57,376 / 65,280 B(87.9%)**。✅ **任务栈:`stack_report.py` 9/9 PASS**(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位,`defaultTask` 最坏 3024B/5120(余 40%)、`protectTask` 872B/2048(余 57%),见 `data/stack_report_20261022.md`);**卡点已从 SRAM1 移到 CCM**(余 7,904 B)。历史:`defaultTask` 曾于 2026-10-04 达 3664B/4096 ≈10%,曾登记在案,2026-10-10 削链后销项(详见"2026-10-10 复测"小节)。 本方案三个目标: @@ -116,7 +116,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` > timeSyncTask 链 **1264 B**(同一控制台回显尾),512 words 余 784 B < ISR 底线 → **抬栈** > defaultTask 1024→**1280**、timeSyncTask 512→**640**(堆 +1536 B,台账 §6 已登记); > 台架实测 `heap free=16400 / min_ever=8464`(与预算 −1536 吻合,>3072 告警线), -> `stack_report` 9 任务 0 FAIL + `@test` PASS(开机 641 ms)。瘦链候选(§4 ①②)刻意未做。 +> `stack_report` 9 任务 0 FAIL + `/serial-boot-test` PASS(开机 641 ms)。瘦链候选(§4 ①②)刻意未做。 | 任务 | 配置栈 | 字节 | 实际职责 | 2026-10-10 实测峰值 / 静态最坏链 | |---|---|---|---|---| @@ -126,7 +126,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` | testTask | `2048*4` | 8KB | 测试套件(产品构建不创建;存储套件深链需要) | 未创建 / 静态 496 B | | defaultTask | `1280*4` | 5KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2152 B(余 41%)/ 静态 **3016 B**(栈余量收口时抬栈 1024→1280,2026-10-12) | | timeSyncTask | `640*4` | 2.5KB | TCP 时间同步 | 616 B(余 50%)/ 静态 **1264 B**(栈余量收口时抬栈 512→640,2026-10-12) | -| adcTask | `640*4` | 2.5KB | TPAFE5160 采样消费 + 全周傅氏相量 + 有效性校验(TIM3 1 kHz 节拍,B1→全周傅氏相量→「采样有效性校验」 2026-10-11) | **952 / 2560**(余 1608 B / 62%,台架 `@test` 实测 2026-10-11);静态 **1320 B**(2026-10-12 复测,保护判据接线后) | +| adcTask | `640*4` | 2.5KB | TPAFE5160 采样消费 + 全周傅氏相量 + 有效性校验(TIM3 1 kHz 节拍,B1→全周傅氏相量→「采样有效性校验」 2026-10-11) | **952 / 2560**(余 1608 B / 62%,台架 `/serial-boot-test` 实测 2026-10-11);静态 **1320 B**(2026-10-12 复测,保护判据接线后) | | iec104Task | `640*4` | 2.5KB | IEC104 上行服务(2404 监听 + link 状态机,B4 2026-10-12) | 未上板 / 静态 **1456 B**(拆链→重建监听→net_bind→SPI 故障→日志尾,日志主导;512 words 时余 592 B < ISR 底线 → 抬至 640,余 1104 B / 43%) | | protectTask | `512*4` | 2KB | 保护判据节拍(prot_elm_run 消费者 + 「自检失败闭锁」采样闭锁,B5 2026-10-12) | 未上板 / 静态 **872 B**(float 判据 + phasor 快照 + 日志尾,余 1176 B / 57%) | | idle(内核) | `configMINIMAL_STACK_SIZE=128` | 0.5KB | 空闲 | 未观测(未取句柄) | @@ -197,7 +197,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` | 菜单根框建完 | 5,368 B | 33% | `simulator/tests/check_menu_rows.py` 的 `MENU_POOL_BASELINE` | | 开页-关页往返(一次性残留 +80 B) | 5,448 B | 33% | `simulator/tests/check_content_pages.py` 的 `POOL_AFTER_ROUNDTRIP` | | **最重页面峰值**(网口设置保存态) | **6,568 B** | **40%** | 同上脚本的 `pool after keys: used=… free=7,080` | -| **真机开机**(2026-10-15,16 KB 池,`@test` PASS) | **3,192 B** | 21% | 台架日志 `menu build done: total=15020 free=11828 used=3192` | +| **真机开机**(2026-10-15,16 KB 池,`/serial-boot-test` PASS) | **3,192 B** | 21% | 台架日志 `menu build done: total=15020 free=11828 used=3192` | | 真机开机(2026-09-21,40 KB 池时代) | 2,976 B | — | `ui_menu.c` 的 `lv_mem_monitor` 日志 `menu build done: used=2976` | ⚠ 16,384 B 建池后 `lv_mem_monitor` 报的 `total` 是 **15,020 B**——TLSF 控制块与对齐元数据吃掉 ≈1,364 B(约 8%);核算池余量用 15,020,不是 16,384。 @@ -227,7 +227,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` > **2026-10-11 裁剪 LVGL 死代码(原审查报告的三条裁剪建议,已销项)**:`Lib/lv_conf.h` 显式关掉 10 个 `LV_DRAW_SW_SUPPORT_*`(只留 I1)、5 个未用控件类(label / button / buttonmatrix / bar / textarea)、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX` / `LV_USE_GRID` → **`ER_IROM1` 274,724 → 210,148 B(省 64,576 B = 旧镜像的 23.5%)**,Flash 占比 26.3% → **20.1%**。 > 对象级对照(`.map` 组件表,Code+RO+RW):`blend_to_*` 40,582 → 1,338(其中 `blend_to_i1` 自身 5,272 → 1,338——它按**源**格式的分支也一并消失);控件类 13,397 → 0;`lv_theme_simple` 866 → 0;`lv_draw_sw_transform` 4,340 → 598;`lv_theme_mono` 1,400 → 1,036;镜像内对象数 227 → 212。实测比原报告估算(54,630 B)多省约 10 KB,差额正是这些"锚点消失后连带消失的分支"。 > ⚠ **配套本地补丁**:`lv_draw_sw_blend_to_i1.c` 的 `lv_color16_from_u16` 上游 9.3.0 漏了定义处守卫(唯一使用点全在 `#if LV_DRAW_SW_SUPPORT_RGB565_SWAPPED` 内)→ 关掉该格式后 ARMCC 报 `#177-D declared but never referenced`(破坏"0 警告"门禁)。已按使用点补同一守卫,升级 LVGL 时若上游已修可去掉。 -> 验证:ui-verify 五步全过——**11 页内容黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,LVGL 池水位 5,440 → 5,368 B(−72 B,样式表更小,一次性残留仍 80 B);`@build` 0 错 0 警;`@test` 真机 PASS。详见 `../UI设计/UI_设计架构.md` §4.1.4 第 10 条。 +> 验证:ui-verify 五步全过——**11 页内容黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,LVGL 池水位 5,440 → 5,368 B(−72 B,样式表更小,一次性残留仍 80 B);`/keil-build` 0 错 0 警;`/serial-boot-test` 真机 PASS。详见 `../UI设计/UI_设计架构.md` §4.1.4 第 10 条。 > **长效防复发**:已建门禁 `scripts/check_lv_conf_sync.py`(pre-commit 钩子 `lv-conf-sync`)——比对两份 `lv_conf.h` 的**生效值**(未定义则取 `lv_conf_internal.h` 兜底默认),并抓无效宏名与重复定义;首次跑揪出两端共 26 个 9.3 不存在的选项、3 处重复定义、10 项靠默认值隐式生效的开关,已清理(本次清理对镜像体积中性)。详解见 `docs/门禁说明/lv-conf-sync.md`(含四类判定、端专属白名单与首次落地实测)。 > ⚠ **MicroLIB 的 `_fp_digits` 与双精度 helper(约 1,018 B)删不掉,别再去搜 `%f`**:全库 `%f` 命中 **0**, @@ -325,7 +325,7 @@ void vApplicationMallocFailedHook(void) 行为:挂在 defaultTask 主循环——首次 5s 后报告一次(bring-up 深链 mount/mkfs/探针/cfg/syslog 均已跑过),此后每 2s 采样,**只在出现更深峰值时打印**:既不破坏串口静默窗口,也不漏掉 -之后才走到的更深分支(卷修复、配置保存等)。输出纯 ASCII,便于 `@test` 采集: +之后才走到的更深分支(卷修复、配置保存等)。输出纯 ASCII,便于 `/serial-boot-test` 采集: ``` [STACK] peak_used=/ free_min= (%) @@ -335,7 +335,7 @@ void vApplicationMallocFailedHook(void) 要点: - `uxTaskGetStackHighWaterMark` 返回**任务创建以来**的最小剩余栈(单位 word=4B);`used = 配置 − 剩余` 即该任务历史峰值栈占用。 -- **测量必须覆盖全功能路径**(高水位是历史最小值,只反映跑过的路径):全菜单遍历、FTP 上传/下载大文件、CH395F 断链重连、NAND 格式化、时间同步。建议 `@test` 全回归 + 1 小时浸泡。 +- **测量必须覆盖全功能路径**(高水位是历史最小值,只反映跑过的路径):全菜单遍历、FTP 上传/下载大文件、CH395F 断链重连、NAND 格式化、时间同步。建议 `/serial-boot-test` 全回归 + 1 小时浸泡。 - 未覆盖 idle/timer(内核静态小栈、纯循环,未取句柄);构建期静态侧见 §4.0.6。 #### 4.0.3 map 静态占用报告脚本 @@ -360,7 +360,7 @@ void vApplicationMallocFailedHook(void) #### 4.0.6 静态栈分析 + 汇总工具(构建期,零目标依赖;2026-09-21 落地) **UV4 每次构建已自动生成静态调用图** `MDK-ARM/STM32F407-Demo/STM32F407-Demo.htm` -(标题 "Static Call Graph",随 `@build` 刷新,**无需**给 armlink 加 `--callgraph`)。每个函数 +(标题 "Static Call Graph",随 `/keil-build` 刷新,**无需**给 armlink 加 `--callgraph`)。每个函数 条目自带 `Stack size N bytes`(该函数自身帧)与 `Max Depth = N`(其子树最坏累计深度) 以及完整调用链——即"不烧板子也能看到最坏链有多深、多宽"。 @@ -459,7 +459,7 @@ void vApplicationMallocFailedHook(void) `name[49]/num[16]/rev[16]` 改 static + 原地反转、去掉 `HAL_Delay`(断言现场 tick 未必可信)。 代价与复核:静态 BSS **+4,128 B**(ZI 133,524 → 137,652;RW_IRAM1 64.9% → 68.1%),Flash 反而 −36 B; -FreeRTOS 堆不变(`free=17248 / min_ever=9312`)。验证链:`@build` 0/0 → `@test` 真机 PASS(含烧录回读) +FreeRTOS 堆不变(`free=17248 / min_ever=9312`)。验证链:`/keil-build` 0/0 → `/serial-boot-test` 真机 PASS(含烧录回读) → `stack_report.py` **6/6 PASS**。 顺带揪出并修掉的**两个静态虚高源**:① `dbg_console_drain` 故障路径调 `syslog_post` → @@ -540,7 +540,7 @@ Linker run 时刻**——commit hash 不是稳定身份(重写正文即换 has #### 4.1.1 LVGL 内存池 40KB → 16KB(✅ 已执行 2026-10-15,省 24,576 B)→ 8KB(待评估) - 依据:屏幕仅 160×160×1bpp,菜单为轻量控件树;LVGL 内存池只装对象/样式/字符串缓冲,与像素缓冲无关。40KB 来自模板默认值,无适配依据。 -- **已落地**:`Lib/lv_conf.h` 与 `simulator/lv_conf.h` 同步改 `(16 * 1024)`——两端必须同值,`lv-conf-sync` 门禁按**生效值**比对。实测水位见 §2.6.2(最重页面峰值 6,568 B = 池的 40%);`@build` 0 错 0 警,ZI 精确 −24,576 B → SRAM1 由 74.9% 降到 **56.1%**(余 57,504 B)。 +- **已落地**:`Lib/lv_conf.h` 与 `simulator/lv_conf.h` 同步改 `(16 * 1024)`——两端必须同值,`lv-conf-sync` 门禁按**生效值**比对。实测水位见 §2.6.2(最重页面峰值 6,568 B = 池的 40%);`/keil-build` 0 错 0 警,ZI 精确 −24,576 B → SRAM1 由 74.9% 降到 **56.1%**(余 57,504 B)。 - 验证口径(本次即用它):`simulator/tests/check_menu_rows.py`(菜单 8 状态像素 + 池基线 5,368 B)+ `simulator/tests/check_content_pages.py`(当时 11 页黄金参考逐字节 + 往返池水位 5,448 B)+ 真机 `menu build done: used=` 日志。 - **再降 8KB 的前置**:全量 UI 回归(PC 模拟器 + 真机)无 `LV_ASSERT_MALLOC`/分配失败;若 8KB 不够就停在 16KB,不勉强。 @@ -596,7 +596,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/ 缩减后任务栈合计 38,912 → 21,504 B(**省 17,408 B / ~17 KB**,全部在 CCM 堆内,主 SRAM 不受影响)。 -每改一批 → `@build` + 全功能回归 + 复测高水位(`free_min` 应 ≥ 新栈×30%),不满足则回调。 +每改一批 → `/keil-build` + 全功能回归 + 复测高水位(`free_min` 应 ≥ 新栈×30%),不满足则回调。 #### 4.1.4 lftpd 大栈局部变量 → 静态缓冲(✅ 2026-09-21 完成;ftpTask 峰值 3480 → 1184 B) @@ -613,7 +613,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/ - **结果**:ftpTask 实测峰值 **3480 → 1184 B**(余量 616 → **2912 B / 71%**,判定 PASS);静态调用图 2528 → 472 B;帧宽榜中 lftpd 全部退出前 15。 - **代价(2026-09-21 实施时)**:静态 BSS **+4,616 B**(RW_IRAM1 77,432 → 82,048 B / 128 KB;CCM 与 Flash 不变)。 -- **验证**:`@build` 0/0 → 烧录 → `@test` PASS → `ftp_up_dl_test.py` 10/10 PASS + `ftp_speed_test.py --size 128` 逐字节一致(功能与搬迁前完全一致,反证无别名/重入问题)。 +- **验证**:`/keil-build` 0/0 → 烧录 → `/serial-boot-test` PASS → `ftp_up_dl_test.py` 10/10 PASS + `ftp_speed_test.py --size 128` 逐字节一致(功能与搬迁前完全一致,反证无别名/重入问题)。 - **结论:原 ① 方案(ftpTask 涨到 1600 words)不再需要**,栈保持 1024 words(4096 B)。 > lftpd 属第三方代码(pre-commit 排除范围),只做最小侵入改动(每处仅加 `static` + 单行注释),改动点在本台账登记。 @@ -645,8 +645,8 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/ 1. **RAM 预算台账**(✅ **2026-10-12 落地** `docs/内存管理/RAM预算台账.md`,模板见 §8.2):记录每个 Region/缓冲/栈的计划值、实测值、变更历史;每次改内存相关代码必须更新 2. **AGENTS.md 增补**(✅ 2026-09-21 已完成:"内存预算紧张"条目已含 `ram_report.py`/`stack_report.py` 数据源与"改栈前后各跑一次"要求) 3. **pre-commit 门禁**(✅ **2026-10-12 落地** `mem-budget` 钩子 = `scripts/ram_baseline.py --gate`):钩子解析最新 map,RW_IRAM1 使用率 >92% 或 RW_CCM 超限 → 提交拦截并附 Top-10 RW+ZI 对象表 -4. **@test 扩展**:`verify_log.py` 增加可选 Required 标记 `"[MEM] heap free"`,把堆余量下限纳入自动化回归 -5. **map diff 常态化**:每次 `@build` 后跑 `ram_report.py`,ZI 增量 >1KB 的改动必须在提交说明中解释 +4. **/serial-boot-test 扩展**:`verify_log.py` 增加可选 Required 标记 `"[MEM] heap free"`,把堆余量下限纳入自动化回归 +5. **map diff 常态化**:每次 `/keil-build` 后跑 `ram_report.py`,ZI 增量 >1KB 的改动必须在提交说明中解释 ## 5. 预期效果 @@ -668,17 +668,17 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/ | 步骤 | 内容 | 验证 | 状态 | |---|---|---|---| -| 步骤 1 | 0.1 + 0.2(溢出钩子 + `stack_probe_poll` 栈水位探针,`Src/freertos.c` USER CODE 区) | `@build` 0错0警;`@test` 串口出现每任务 `[STACK]` 报告 | ✅ 2026-09-21 | +| 步骤 1 | 0.1 + 0.2(溢出钩子 + `stack_probe_poll` 栈水位探针,`Src/freertos.c` USER CODE 区) | `/keil-build` 0错0警;`/serial-boot-test` 串口出现每任务 `[STACK]` 报告 | ✅ 2026-09-21 | | 步骤 2 | 0.3 `ram_report.py` + 基线 | 脚本输出与 map 人工核对一致 | ✅ 2026-09-21(`data/ram_report_20260921.md`) | | 步骤 3 | 全功能回归跑数(菜单/FTP/网络/格式化/浸泡 1h) | 拿到各任务 `free_min` 与堆 free 底值 | ◐ boot + FTP 路径已实测(09-21);全菜单遍历 + 1h 浸泡待补 | | 步骤 4 | §4.1.4 lftpd 静态化 + §4.2.1 malloc 修复 | FTP STOR/RETR 大文件 + 长路径回归 | ✅(§4.2.1 于 2026-09-10、§4.1.4 于 2026-09-21 完成) | -| 步骤 5 | §4.1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 | ✅ **2026-10-15**(11 页黄金参考 + 池水位 5,448 B + `@test` 真机 `used=3192`;实测峰值 6,568 B = 池的 40%,§2.6.2) | +| 步骤 5 | §4.1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 | ✅ **2026-10-15**(11 页黄金参考 + 池水位 5,448 B + `/serial-boot-test` 真机 `used=3192`;实测峰值 6,568 B = 池的 40%,§2.6.2) | | 步骤 6 | §4.1.2 CCM(scatter + section + 门禁宏) | map 显示 `RW_CCM`;关 `APP_CCM_ENABLE` 也能编译通过 | ⏸ **暂缓(2026-10-15 裁定)**;已完成部分仅 `RW_CCM` + 堆迁入(09-21),缓冲 section 迁移与门禁宏不再排期 | | 步骤 7 | §4.1.3 栈缩减(按步骤 3 数据)+ 复测 | 每任务 `free_min ≥ 新栈×30%`,无溢出钩子触发 | ⏸ **暂缓(2026-10-15 裁定)**(目标值已按 09-21 实测修正,重开时可直接用) | | 步骤 8 | §4.2.2 堆 48K→32K + 复测 | 全负载 `heap free ≥ 8KB` | ⏸ **暂缓(2026-10-15 裁定)**;⚠ 2026-10-12 B5 反向扩容至 56 KB,重开时须重跑 `stack_report.py` + 上板 `min_ever` | -| 步骤 9 | §4.1.5 小项 + 台账/门禁/AGENTS.md | `@build` + `@test` 全绿 | ◐ AGENTS.md 内存条目已增补(09-21);台账 / 趋势 / `mem-budget` 三件套 ✅ 2026-10-12(**常开,不受暂缓影响**) | +| 步骤 9 | §4.1.5 小项 + 台账/门禁/AGENTS.md | `/keil-build` + `/serial-boot-test` 全绿 | ◐ AGENTS.md 内存条目已增补(09-21);台账 / 趋势 / `mem-budget` 三件套 ✅ 2026-10-12(**常开,不受暂缓影响**) | -每步都必须 `@build` 通过(0 错误 0 警告)后再进入下一步;涉及 UI 的步骤用 `simulator` 先过一遍再上实机。 +每步都必须 `/keil-build` 通过(0 错误 0 警告)后再进入下一步;涉及 UI 的步骤用 `simulator` 先过一遍再上实机。 ## 7. 风险与注意事项 @@ -758,7 +758,7 @@ V1 链接视图(map:Grand Totals / Image component sizes / Symbol Table) V4 逐项相加须与 V1 的 `Image component sizes` 逐 .o 吻合;不等则以 V1 为准并记录差异原因。 -**数据来源清单**:`STM32F407-Demo.sct`(区域)、`Inc/FreeRTOSConfig.h`(堆/优先级/静态分配开关)、`cmsis_os2.c:472-476`(**栈大小单位 = 字节**:内部 `/ sizeof(StackType_t)`)、`Src/freertos.c`(任务/队列/事件组属性)、`Lib/lv_conf.h`、`Lib/FatFs/ffconf.h`、`test/board/test_config.h`(构建口径;`App/app/app_cfg.h` 自 2026-10-22 起无宏,不再是口径来源)、`Drivers/BSP/**` + `App/**`(缓冲语义/用途/DMA 归属);**权威数据 = `@build` 重建后的 `.map`**(历史 map 不得用于结论)。 +**数据来源清单**:`STM32F407-Demo.sct`(区域)、`Inc/FreeRTOSConfig.h`(堆/优先级/静态分配开关)、`cmsis_os2.c:472-476`(**栈大小单位 = 字节**:内部 `/ sizeof(StackType_t)`)、`Src/freertos.c`(任务/队列/事件组属性)、`Lib/lv_conf.h`、`Lib/FatFs/ffconf.h`、`test/board/test_config.h`(构建口径;`App/app/app_cfg.h` 自 2026-10-22 起无宏,不再是口径来源)、`Drivers/BSP/**` + `App/**`(缓冲语义/用途/DMA 归属);**权威数据 = `/keil-build` 重建后的 `.map`**(历史 map 不得用于结论)。 ### 8.5 图表与台账重生成 diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index 43a32a4f..9b4ab92d 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -35,7 +35,7 @@ 无 `.map` 时自适应跳过(CI 侧跳过 `mem-numbers` 的边界 = `scripts/check_size_gate.py` 的 docstring,唯一正文)。 - **阈值依据**:92% 由 2026-09 体系评审提出、2026-10-12 落地为门禁(提出时的报告条目已随销项删除,闭环说明见 `RAM使用分析与优化方案.md` §4 阶段 3「长效机制」与本文)——留 ~8% 给突发缓冲与栈抬升, 触线即触发排期,而不是静默逼近溢出。 -- **软提醒**(不拦提交):tsv 末行落后于 `.map`(忘跑 `--update`)、`.map` 早于最新源码(忘 `@build`)。 +- **软提醒**(不拦提交):tsv 末行落后于 `.map`(忘跑 `--update`)、`.map` 早于最新源码(忘 `/keil-build`)。 - **子系统归集为参考值**:按对象名前缀归集(口径见 §2),合计与区域总量可差几十字节 (链接器填充行与 `UNINIT_CCM` 等小区分段所致);区域行才是门禁事实源。 @@ -81,7 +81,7 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名 ## 4. 趋势基线(`scripts/ram_baseline.tsv`) -- **每次 `@build` 后**:`& '.venv\Scripts\python.exe' scripts/ram_baseline.py --update` +- **每次 `/keil-build` 后**:`& '.venv\Scripts\python.exe' scripts/ram_baseline.py --update` 追加一行(Code/RO/RW/ZI + 三区水位 + Top-10 RW+ZI 对象;同日重跑覆盖当日行)。 - **看趋势 / 找增量**:`scripts/ram_baseline.py --trend`——高亮较上一行 RW+ZI 增长 >1 KB 的对象, 即 `docs/内存管理/RAM使用分析与优化方案.md` §4.0.3 "ZI 增量 >1KB 必须在提交说明中解释"的机械化;提交说明直接抄 `--trend` 输出行。 diff --git a/docs/内存管理/内存内容分配策略.md b/docs/内存管理/内存内容分配策略.md index 5dd2f9f9..63405add 100644 --- a/docs/内存管理/内存内容分配策略.md +++ b/docs/内存管理/内存内容分配策略.md @@ -143,8 +143,8 @@ LVGL 集合明细(均在 SRAM):LVGL 池 16,384B(`lv_mem_core_builtin.o` 任何 scatter / CCM / 堆大小变更后,依次通过以下全部关卡方可认定生效: -1. `@build`(0 错误 0 警告) -2. 产品构建 `@test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):标记校验 PASS,且**零 `[ERR]`**(默认 Forbidden 已含 `[ERR]`) +1. `/keil-build`(0 错误 0 警告) +2. 产品构建 `/serial-boot-test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`):标记校验 PASS,且**零 `[ERR]`**(默认 Forbidden 已含 `[ERR]`) 3. 存储套件(`test_config.h` → `TEST_SUITE_STORAGE`):只读模式实测 **3/3 PASSED**(破坏性关,卷内容不变);开启 `ENABLE_DESTRUCTIVE_TESTS` 后为全量集成用例(2026-09-20 实测 **20/20 PASSED**,含 TC-STORAGE-01/02/03/05/06) 4. GD5F 套件(`TEST_SUITE_GD5F`):只读模式实测 **16/16 PASSED**(含只读的守卫负路径 TC-GD5F-704);开启 `ENABLE_DESTRUCTIVE_TESTS` 后 **68/68 PASSED**,并确认日志出现 2 条守卫负路径 `[ERR]`(TC-GD5F-704/705)——破坏性模式运行须放宽 Forbidden(`--forbidden "FAIL;HardFault"`) 5. 恢复跑(`ENABLE_STORAGE_RECOVERY_TESTS`):期望 **3/3 PASSED** diff --git a/docs/内存管理/存储栈RAM优化调研.md b/docs/内存管理/存储栈RAM优化调研.md index 02d2e63d..de48521e 100644 --- a/docs/内存管理/存储栈RAM优化调研.md +++ b/docs/内存管理/存储栈RAM优化调研.md @@ -20,9 +20,9 @@ | # | 方案 | 省 RAM | 区域 | 风险 | 验证 | |---|---|---:|---|---|---| | `s_copy_buf` 分块 | `s_copy_buf` 2048→512:GC 拷贝改分段 read→program_load(`program_load`/`read_from_cache` 支持列地址);纯 RAM 优化(GC 拷贝暂存减半) | **−1,536B(可选,不计入当前预期)** | SRAM1 | 低(仅动拷贝机制) | 待 RAM 余量需要时再议 | -| lftpd 缓冲收紧 | lftpd 缓冲收紧:response 512→256、read_buffer 512→256、arg_buf 512→256、name 256→128 ×2(本卷仅 8.3 短名,路径深度浅);数据块 2×1024 与 message 不动 | **−1,024B** | SRAM1 | 低(FTP 客户端实测行长远低于新值) | `@build` + FTP 上传/下载/LIST 闭环 + `@test` | +| lftpd 缓冲收紧 | lftpd 缓冲收紧:response 512→256、read_buffer 512→256、arg_buf 512→256、name 256→128 ×2(本卷仅 8.3 短名,路径深度浅);数据块 2×1024 与 message 不动 | **−1,024B** | SRAM1 | 低(FTP 客户端实测行长远低于新值) | `/keil-build` + FTP 上传/下载/LIST 闭环 + `/serial-boot-test` | | `FF_FS_TINY` 共享窗 | `FF_FS_TINY=1`:删每 FIL 私有的 512B buf,文件数据走 `FatFs->win` 共享窗 | 静态 **−528B**;且栈上每处 FIL 峰值 **−528B**(defaultTask 最坏链 2,776→~2,250,实测峰 2,136→~1,600) | SRAM1 + CCM 栈 | 中(所有文件 I/O 经 512B 窗 bounce,FTP 吞吐 −20~40%;LIST 与文件读交错只影响速度) | 存储套件 + FTP 吞吐实测 + 日志/配置闭环;**与 RAM方案 实施步骤 7 的缩栈捆绑**(直接支撑 defaultTask 降档) | -| `APP_CCM_ENABLE` 迁 CCM | `APP_CCM_ENABLE` 门(与 RAM方案 实施步骤 6 同宏):①CPU-only 缓冲迁 CCM——lftpd static 3.6K + cfg_file 0.7K + FATFS win 0.6K + lftpd_io ≈ **−4.9K** 零代价;②DMA 页缓冲(nand_ftl 4.6K + gd5f 位图/页 0.8K)迁 CCM 走轮询 ≈ **−5.4K**,需实测吞吐 | **−10.3K** | SRAM1→CCM | 低①/中② | 分段实施;每步跑 `ram_report.py` + `@test` + FTP 吞吐 | +| `APP_CCM_ENABLE` 迁 CCM | `APP_CCM_ENABLE` 门(与 RAM方案 实施步骤 6 同宏):①CPU-only 缓冲迁 CCM——lftpd static 3.6K + cfg_file 0.7K + FATFS win 0.6K + lftpd_io ≈ **−4.9K** 零代价;②DMA 页缓冲(nand_ftl 4.6K + gd5f 位图/页 0.8K)迁 CCM 走轮询 ≈ **−5.4K**,需实测吞吐 | **−10.3K** | SRAM1→CCM | 低①/中② | 分段实施;每步跑 `ram_report.py` + `/serial-boot-test` + FTP 吞吐 | | NAND copy-back | NAND copy-back 命令(0x15→0x10)替代主机搬页:省 `s_copy_buf` 的拷贝用途 + 减半 SPI 流量 | −512B(`s_copy_buf` 分块后) | SRAM1 | 高(片上 ECC 在 copy-back 下的语义须台架实证) | 仅评估,暂缓 | | lftpd 数据块减半 | lftpd 数据块 2×1024→512 | −1,024B | SRAM1 | 吞吐 −~30% | RAM 仍紧时才做 | | 共享静态 FIL | syslog/cfg 共享一个静态 FIL(同任务串行) | 净≈0(静态+528/栈−528) | — | 不推荐:`FF_FS_TINY` 那条已覆盖同一收益面 | — | diff --git a/docs/子系统设计/IEC104上行设计说明.md b/docs/子系统设计/IEC104上行设计说明.md index a836f330..4d9c0dbd 100644 --- a/docs/子系统设计/IEC104上行设计说明.md +++ b/docs/子系统设计/IEC104上行设计说明.md @@ -231,7 +231,7 @@ |---|---| | 主机侧纯逻辑单测 | `test/host/iec104/`:APCI 重组/坏帧、STARTDT 握手、总召全流程(含 RBT/分片/预算)、COV、k/w 窗、t1/t2/t3(假时基)、遥控两段式与 PN、YK12 软压板两段转真 + 置脏(B7)、定值两段写 + set_sealed 后 selfcheck、C_RD(含本地项不可寻址)、C_CS(含闰年)、错地址/错类型 EVI;**板端默认源真实链**(ring→phasor→freq→meas 快照,非桩):值/品质透传、CLIPPED→IV、0x401D 绑频(收敛 ±0.02 Hz / 未新鲜 IV)、死区运行时化 + 2 点确认/回摆撤销、seq 去重(104 改读快照与频率点绑定,TC-IEC104-025~028)+ 运行参数段路由/两段式/RMW(TC-IEC104-029)+ `[comm]` 段真链(道G task-8,TC-IEC104-031~033:注册/出厂模板/沉降重载/带外自愈/setter 守卫/ca 端到端 = C_RD 正答与旧写死 ca=1 转 COT13+EVI);`test/host/iec104_serv/` 钉端口与 ca 取值来源(TC-I104SRV-001b/c);`test/host/cfg_file/` 钉段/键容量上限(TC-CONFIG-056/057);`test/host/run_par/`:[run_par] 注册/落盘往返/守卫/系数直通合法带(TC-RUNPAR-001~008);`test/host/prot_set/`:blob 往返 + 损坏整表拒绝(all-or-nothing)| | 静态门禁 | `iec104-unit-test` 钩子 + cppcheck/coding-standard | -| 台架 | `@test` 锚点(104 服务启动/监听行);**PC 主站客户端 `test/host/iec104_master.py`(纯标准库,B6 2026-10-12 就绪)**:STARTDT/总召四组+actterm/C_SE 定值两段写→YX15·16 变位(COT=3)/恢复复归/YK11 两段正答/C_RD 读回,台架往返 **18/18 PASS**(证据 `../测试说明/测试数据/boot_log_data/20261012_B6_iec104_roundtrip_log.txt`);**B7 持久化模式 `--b7-pre/--b7-post/--b7-mark/--b7-check`**(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、**定值与压板态跨复位保持**(含非默认 7.5 往返),**21/21 + 13/13 + 4/4+4/4 PASS**(证据 `../测试说明/测试数据/boot_log_data/20261012_B7_iec104_persist_log.txt`);**B9 SOE 腿**(同工具同轮回归):自发 YX 断言 **M_SP_TB_1 带 ts**(工具解析 type 30/1 双型)+ FTP 下载 `/log/soe.log` 经 `test/host/soe/decode_soe.py` 解码断言(校验和/seq 单调/CP56↔epoch UTC 交叉),pre 21/21 + post 13/13 不回归;lib60870 级第三方全流程归 B10 | +| 台架 | `/serial-boot-test` 锚点(104 服务启动/监听行);**PC 主站客户端 `test/host/iec104_master.py`(纯标准库,B6 2026-10-12 就绪)**:STARTDT/总召四组+actterm/C_SE 定值两段写→YX15·16 变位(COT=3)/恢复复归/YK11 两段正答/C_RD 读回,台架往返 **18/18 PASS**(证据 `../测试说明/测试数据/boot_log_data/20261012_B6_iec104_roundtrip_log.txt`);**B7 持久化模式 `--b7-pre/--b7-post/--b7-mark/--b7-check`**(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、**定值与压板态跨复位保持**(含非默认 7.5 往返),**21/21 + 13/13 + 4/4+4/4 PASS**(证据 `../测试说明/测试数据/boot_log_data/20261012_B7_iec104_persist_log.txt`);**B9 SOE 腿**(同工具同轮回归):自发 YX 断言 **M_SP_TB_1 带 ts**(工具解析 type 30/1 双型)+ FTP 下载 `/log/soe.log` 经 `test/host/soe/decode_soe.py` 解码断言(校验和/seq 单调/CP56↔epoch UTC 交叉),pre 21/21 + post 13/13 不回归;lib60870 级第三方全流程归 B10 | ## 8. V0.1 限制与待办(如实登记) diff --git a/docs/子系统设计/屏板链路设计说明.md b/docs/子系统设计/屏板链路设计说明.md index ddfe51c3..761f18d6 100644 --- a/docs/子系统设计/屏板链路设计说明.md +++ b/docs/子系统设计/屏板链路设计说明.md @@ -78,7 +78,7 @@ flowchart TB - 台架须用 **USB 转 RS-232 适配器**(TTL 的 CH340 直连不了,且 RS-232 的 ±电平可能打坏 `U13` TPT3232E 或适配器): 适配器 `TXD` → `CN1.10`(板子 `RS232_RX1`)、`RXD` → `CN1.12`(板子 `RS232_TX1`)、`GND` → `CN1.7` 或 `CN1.8`(`CGND`)。 -- 台架串口因此是 **`COM3`**,`.serialtest/config.json` 的 `portName` 已按此配置,`@test` 与 `serial-assistant` 不必手工指端口。 +- 台架串口因此是 **`COM3`**,`.serialtest/config.json` 的 `portName` 已按此配置,`/serial-boot-test` 与 `serial-assistant` 不必手工指端口。 - RS232#1 **没有 TTL 出口**(`ST_TX1`/`ST_RX1` 只连 `U9` 隔离器与 MCU),唯一外部出口是 CN1 的 RS-232 端子; `U14` 是 RS232#3 的网口座,本通道用不上。 @@ -259,7 +259,7 @@ flowchart TB ## 10. 坑账本 -1. **`@test` 的 forbidden 标记(`FAIL;ERROR;HardFault;[ERR]`)大小写不敏感**——日志字段名带 fail / error / abort +1. **`/serial-boot-test` 的 forbidden 标记(`FAIL;ERROR;HardFault;[ERR]`)大小写不敏感**——日志字段名带 fail / error / abort 会把启动自检判红,本模块统计行用 `kerr=` / `abor=` / `off=` 命名。 2. **`ReceiveToIdle_IT` 是一次性的**:漏重挂 = 链路永久静默,且没有任何报错;ORE 不自己清,一次噪声打死接收。 3. **NE/FE 非阻塞、ORE 阻塞**:重挂判据用 `RxState == READY` 而不是错误位;台架"NE 每帧出现 + `rx_ok` 照涨" diff --git a/docs/子系统设计/控制台单写者日志设计说明.md b/docs/子系统设计/控制台单写者日志设计说明.md index 8b752146..7e8a820b 100644 --- a/docs/子系统设计/控制台单写者日志设计说明.md +++ b/docs/子系统设计/控制台单写者日志设计说明.md @@ -2,7 +2,7 @@ > **版本**:v1.8(已实施;修订史见文末 §11「实施时间线」) > **日期**:2026-10-30(v1.8:控制台端口由 USART1 改为 **UART5/RS232#1**,全文端口口径同步——物理口与台架端子见 `屏板链路设计说明.md` §2 / §3.2;v1.7 于 2026-10-26:崩溃上报归一进单写者 + 多段裸发收口,道G task-26;v1.6 同日:未注册期裸发加「行级单一所有者」;v1.5 于 2026-10-24;初版 v1.0 于 2026-09-25) -> **状态**:已实施并验证——`@build` 0 错误 0 警告、`@test` 硬件闭环 PASS(2026-09-25,见 §10);设计 2026-09-24 加固实施(当时位于 `Src/usart.c` USER CODE 区),2026-09-25 实现迁出至 `App/log/`(HAL 生成文件不含应用代码);同日写者任务体自 `Src/freertos.c` 迁至 `App/task/default_task.c`(与其余 5 任务文件同址归位) +> **状态**:已实施并验证——`/keil-build` 0 错误 0 警告、`/serial-boot-test` 硬件闭环 PASS(2026-09-25,见 §10);设计 2026-09-24 加固实施(当时位于 `Src/usart.c` USER CODE 区),2026-09-25 实现迁出至 `App/log/`(HAL 生成文件不含应用代码);同日写者任务体自 `Src/freertos.c` 迁至 `App/task/default_task.c`(与其余 5 任务文件同址归位) > **关联文档**:`项目架构总览.md` · `运行与自检记录设计说明.md`(姊妹模块:持久化日志,同一写者任务) · `docs/测试说明/专项验证/链路延迟测试说明.md`(D6 日志税 / 步骤 8 非阻塞日志,需求出处) · AGENTS.md「平台约束」 ## 1. 背景:实测故障与根因 @@ -11,7 +11,7 @@ | # | 症状 | 实测证据 | 后果 | |---|------|---------|------| -| F1 | 日志行字符级交错 | 崩溃前两行日志逐字符交错:`"[2026-[2026-0909-24 15:2-24 15:27:37:39]…"` | @test 必检标记可能假性丢失、日志不可读 | +| F1 | 日志行字符级交错 | 崩溃前两行日志逐字符交错:`"[2026-[2026-0909-24 15:2-24 15:27:37:39]…"` | /serial-boot-test 必检标记可能假性丢失、日志不可读 | | F2 | HardFault(脏指针直接进入 UART) | BFAR=0x10010000(恰为 CCM 末尾+1),PC 落在 `HAL_UART_Transmit` 的缓冲读取处 | 崩溃上报路径自身再崩;被破坏的字符串指针被 UART 逐字节读取 | 前兆:2026-09-22 链路延迟测试实测**开机连续丢行**(多任务并发 `printf` → 非重入 `fputc`/`HAL_UART_Transmit` 丢整行,~2s 自恢复;`链路延迟测试说明.md` §9.2)——同类竞态的温和形态,当时列为待办(日志非阻塞化)。 @@ -36,7 +36,7 @@ HAL 并发语义(本仓库实现实测,`stm32f4xx_hal_uart.c:1135`):轮 | G2 | 指针可控:进入 `HAL_UART_Transmit` 的缓冲恒受本模块控制 | 调用方指针只经拷贝入环、到不了 UART;写者发出的缓冲恒为环形缓冲或模块本地静态缓冲 | | G3 | 不新增任务、不用 C 库堆、不新增内核对象 | 复用既有 defaultTask 作写者;环形缓冲 = 4KB 静态 BSS;复用 `sys_evt` 事件标志位 | | G4 | 内核启动前 / 写者未注册 / ISR / 异常钩子路径不降级 | 回退:有界直发(`dbg_console_write_raw`,≤256B 截断、不等待、不死锁、零内核对象) | -| G5 | 缓冲满丢行必须可观测 | 丢弃计数 + 写者刷出 `[ERR] CONSOLE dropped N lines` —— 含 `[ERR]` 模式,`@test` Forbidden 判 FAIL,丢失永不静默 | +| G5 | 缓冲满丢行必须可观测 | 丢弃计数 + 写者刷出 `[ERR] CONSOLE dropped N lines` —— 含 `[ERR]` 模式,`/serial-boot-test` Forbidden 判 FAIL,丢失永不静默 | ### 2.2 约束 @@ -114,7 +114,7 @@ flowchart LR | 写者未注册(内核启动 → attach) | 任何任务都可能记日志(2026-10-26 起这是**常态而非违规**) | **整行独占**(行级单一所有者,§4.4.1):同一时刻只有一个上下文在写 `CON_UART->DR`,抢不到独占的整行转入环形缓冲延后发 → 交错结构性消除,且不丢行 | | 写者已注册(稳态) | 任务上下文唯一调用者 = 写者 | 任务上下文零竞争;残余窗口 = ISR 直发撞写者发送中(丢失、不挂死,§7) | -**纪律(2026-10-10 固化,2026-10-10 / 2026-10-26 两次台架实证其不足)**:周期 / 常驻任务**不得**在写者注册前记日志——用 `dbg_console_writer_ready()`(§5)门控自身日志(`adcTask` 的首帧与 10 s 心跳、`protectTask` 的启动锚点行即按此门控——后者 2026-10-12 B6 台架实证:B5 接线时启动行未门控,注册前裸发被交错损坏致 `@test` 锚点 MISS,见 §11 变更记录)。内核前代码仍可直接裸发(单线程,无并发)。 +**纪律(2026-10-10 固化,2026-10-10 / 2026-10-26 两次台架实证其不足)**:周期 / 常驻任务**不得**在写者注册前记日志——用 `dbg_console_writer_ready()`(§5)门控自身日志(`adcTask` 的首帧与 10 s 心跳、`protectTask` 的启动锚点行即按此门控——后者 2026-10-12 B6 台架实证:B5 接线时启动行未门控,注册前裸发被交错损坏致 `/serial-boot-test` 锚点 MISS,见 §11 变更记录)。内核前代码仍可直接裸发(单线程,无并发)。 > > ⚠ **自愿门控挡不住边沿留痕**——它在 2026-10-12(B6,启动锚点)与 2026-10-26(未标定闭锁边沿,见 §4.4.1)两次被同一个窗口咬到, > 因为"这条行要不要留痕"是**业务边沿**决定的,调用方无法预知它是否落在未注册期。2026-10-26 起该窗口改由**机制**兜住 @@ -156,7 +156,7 @@ flowchart TD - **整行原子入环**:拷贝与 head 推进在同一次 `taskENTER_CRITICAL` 内完成 → 写者永不见"半行";临界区时长有界(≤192B 拷贝,微秒级)。 - **自等待规避**:写者自身记录日志时先 `drain` 腾空(否则"等写者腾出的空间"会等自己,死锁);腾空后入环必立即成功。 -- **丢弃可观测**:丢弃累计 `s_con_drop`,写者每次 drain 后把累计值刷成 `[ERR]` 行(§4.3)——`[ERR]` 触发 `@test` Forbidden 判定,丢行永不静默。 +- **丢弃可观测**:丢弃累计 `s_con_drop`,写者每次 drain 后把累计值刷成 `[ERR]` 行(§4.3)——`[ERR]` 触发 `/serial-boot-test` Forbidden 判定,丢行永不静默。 - **唤醒**:入环后置 `SYS_EVT_CONSOLE_TX` 命令位 → 写者立即被唤醒(零时延);写者循环另有 50ms 超时兜底(事件位异常也不滞留日志)。 ### 4.3 写者路径(`attach` / `drain`) @@ -250,7 +250,7 @@ sequenceDiagram 2. 判据**只用原子性**,不碰 `[ERR]` 计数:① 崩溃行**一整行**逐字节完整(`[ERR] MEMFAULT PC=… LR=… xPSR=… R0..R3=… CFSR=… HFSR=…` 字段齐全、无邻居行字节插入);② 该行在本次启动日志里**恰好出现一次**(既不丢也不重);③ `CONSOLE dropped` = 0。首个字符应为 `[`;前面若出现 ≤1 个移位寄存器残留字节,记为已知残余,不判失败。 3. ⚠ **不得在同一条腿要求 `[ERR]` = 0**:崩溃上报本身就以 `[ERR]` 起头,在同一路径上要求「无 `[ERR]`」等于要求这条路径别工作(本次就是这么被台架纠正的)。`[ERR]` 零命中的判据只属于腿 B 之外的**常规回归腿**(3~5 次仅复位不重烧 + 两行整行锚定)。 4. **故意复位的注入腿不得并入「连续 N 次仅复位不重烧」的计数**:注入会主动 `NVIC_SystemReset()`,混进常规序列会把正常行为判成异常(栈溢出钩子同理)。两条腿分开跑、分开记台账。 -5. `@test` 在注入腿**只在命令行运行时**放宽(故障注入的既有台架口径见 `docs/测试说明/板级测试/实机测试实施指南.md` §4,其中已写明"故障注入用例会复位设备多次 → 确认设备非在用/非已部署"):放宽 `--forbidden` / `--max-boot-count`;**不改仓库配置**(改了就等于把"永不出崩溃日志"设成期望状态)。 +5. `/serial-boot-test` 在注入腿**只在命令行运行时**放宽(故障注入的既有台架口径见 `docs/测试说明/板级测试/实机测试实施指南.md` §4,其中已写明"故障注入用例会复位设备多次 → 确认设备非在用/非已部署"):放宽 `--forbidden` / `--max-boot-count`;**不改仓库配置**(改了就等于把"永不出崩溃日志"设成期望状态)。 ### 4.5 `fputc` 重定向(MicroLIB `printf`) @@ -267,7 +267,7 @@ sequenceDiagram | 单行 192B(最坏) | ≈17 ms | `DBG_CON_LINE_MAX` | | 生产者最长等待 | 200 ms(200 × 1ms tick) | ≈9 个发送块的时间,稳态下环恒被及时排空 | | 写者兜底周期 | 50 ms | 事件位失效时日志滞留上限 | -| 实测日志时延 | 事件唤醒级(无丢失) | 2026-09-25 @test:67 行开机日志完整、无交错、无丢行告警 | +| 实测日志时延 | 事件唤醒级(无丢失) | 2026-09-25 /serial-boot-test:67 行开机日志完整、无交错、无丢行告警 | ## 5. API 参考 @@ -304,7 +304,7 @@ sequenceDiagram | 失效 | 行为 | 可观测性 | 残余风险 | |------|------|---------|---------| -| 缓冲满(日志突发 / 写者卡滞) | 生产者等 200ms 后丢行 | `s_con_drop` 累计 → 写者刷 `[ERR] CONSOLE dropped N lines`(@test FAIL,永不静默) | — | +| 缓冲满(日志突发 / 写者卡滞) | 生产者等 200ms 后丢行 | `s_con_drop` 累计 → 写者刷 `[ERR] CONSOLE dropped N lines`(/serial-boot-test FAIL,永不静默) | — | | 写者未注册(内核前) | 单线程直发 | — | 无(无并发) | | 未注册期其他任务直发 | **已消除**(2026-10-26):整行独占,抢不到就整行入环延后发(§4.4.1) | 让路不计数;只有"抢不到且环也满"才进 `s_con_drop` → `[ERR] CONSOLE dropped N lines` | 残余 = 让路行在流里位置后移(内容与直发逐字节相同);主机单测 `TC-CONLOG-170 至 TC-CONLOG-179` 钉死 | | 崩溃上报(四个 Fault)原走独立 HAL 发送口 | **已归一**(2026-10-26 task-26):`hard_fault_report` 的 14 段裸发改走 `dbg_console_write_raw`(整行独占**不**用在这里——异常态空转,见 §4.4.1);ORE 快速失败(= 证据整条蒸发)与 `HAL_GetTick`/`HAL_Delay` 的 tick 依赖随之消失 | 崩溃行 `[ERR] PC=… CFSR=…` 现在是单写者输出;实机验证设计见 §4.4.1 末(待台架窗口) | 极早期(`MX_UART5_Init()` 之前)崩溃仍完全静默(NULL 短路);ORE 脏标志本身未消除(§12) | @@ -410,7 +410,7 @@ sequenceDiagram - `项目架构总览.md` —— 六层分层 / 启动时序 / 6 任务(写者任务与就绪屏障位置) - `运行与自检记录设计说明.md` —— sys_log 持久化日志(姊妹模块:同写者任务、不同通道) - `docs/测试说明/专项验证/链路延迟测试说明.md` —— D6 日志税 / 步骤 8 非阻塞日志(需求出处与 09-22 丢行实证) -- `docs/测试说明/流程与规范/自动化测试流程.md` —— `@test` 标记体系(Required / Forbidden 模式,`[ERR]` 语义) +- `docs/测试说明/流程与规范/自动化测试流程.md` —— `/serial-boot-test` 标记体系(Required / Forbidden 模式,`[ERR]` 语义) - AGENTS.md —— 平台约束(内核前禁内核对象 / C 堆禁令 / 事件位语义 / 内存预算) ## 附录:日志静默观察项 × 步骤 8 合并评估(2026-10-25,道F 批 2 / task-18,零代码改动) @@ -457,7 +457,7 @@ D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏 - 判据②(静默 → 可观测)= **已具备**:控制台三本账 `dbg_console_dropped / dbg_console_tx_fault / dbg_console_tx_timeout` + syslog 的 `queue full, drop` / `preboot full, drop` / `hold-over drop` - (`App/log/sys_log_mcu.c`);任何“静默丢行”现形为 `[ERR]` → `@test` FAIL。 + (`App/log/sys_log_mcu.c`);任何“静默丢行”现形为 `[ERR]` → `/serial-boot-test` FAIL。 - 判据①(复现 49s 并定位):V5 30min 未复现;实测(`docs/测试说明/板级测试/实机测试实施指南.md` §7.2) 运行期 >10s 静默 24 处**全部落在 e02_probe 窗口、长度 ≈28s**——其 同源阻塞机制(`drop_conn` 同步占用 netTask 400-600ms)已随登记式拆链改造修复 diff --git a/docs/子系统设计/故障录波设计说明.md b/docs/子系统设计/故障录波设计说明.md index 562b187d..e45ad518 100644 --- a/docs/子系统设计/故障录波设计说明.md +++ b/docs/子系统设计/故障录波设计说明.md @@ -323,7 +323,7 @@ flowchart TD | 跨工具链分叉(实证,写进来是为了下一个模块别重踩) | 两份录波 .c 只 include 自身头(链上只有 stdint.h)时,ARMCC 报 22 条 identifier "NULL" is undefined;而主机 MSVC / gcc 会传递性把 stddef.h 拉进来,**主机单测完全照不出这个分叉** → 新增模块需要用 NULL 就自己 include stddef.h(与 `App/algo/sample_ring.c` 同口径,别塞公共头) | | 板级 | 信号发生器注 1 V / 50 Hz → 手动一段 → FTP 拉回 → PC 脚本量出周波数、幅值、极性与注入一致;再把 PC 重算的基波有效值与板上 `meas:` 与 104 浮点 YC 对表。**判据只量四样:周波数、幅值、极性、钉轨路数——不比绝对相位**(理由见 §4.2「段内点序」:触发瞬间在工频周期内任意落点,段字节里没有绝对时间参考;真要比相位必须让发生器给同步触发,本波不做)。**钉轨对表的合格判据 = 头位图 ⊇ 实测钉轨路数,且差异必须能由注入条件解释**——因为 `clip_mask` 是按来源派生的链路可信性标记(§6.2 偏移 24 行),不是本段实测值:只加直流或小幅注入时那五路本就不该钉轨,硬取等式会假红。**同段另起一条强观察项**:V0.1 下这五路若在做**交流注入**时反而没钉轨,先怀疑接线或该路根本没被驱动——这是告警面,不是失败面(等式仍作为默认尺子留在 PC 侧 `shape --strict` 里,因为它有这个信号价值;要放行差异用那条判据的解释权,不靠改尺子)。**§5.2 第四条的板级观察项(代码与单测面已有、板级未测)**:bench 连续自抓期间人为把卷占住 2 s 以上(例如同一段跑 FTP 大文件读),看串口 `wave:` 行的 `err` 是否加一、是否只出现**一条** `[ERR] … seg drop: ring abandoned` 留痕(不是每拍刷屏)、且段号不推进(下一段仍写同号文件) | | UI | `ui-verify` 五步;回放页入 `ui-golden` 黄金参考(**参考图一旦入库只能改代码对齐,禁止刷参考掩盖**);新叶走 `gen_menu.py` 重生成 + 字库链 | -| 构建 | `@build` 要求 0 错误 0 警告;新 .c 双端注册走 `new-source-file`(`MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 两处显式列表) | +| 构建 | `/keil-build` 要求 0 错误 0 警告;新 .c 双端注册走 `new-source-file`(`MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 两处显式列表) | | 台账 | R6 三处同步 + `scripts/ram_baseline.py --update` + `mem-numbers` 与 `mem-budget` 钩子 | | 板级(读路径耗时,2026-10-29 补) | 进回放页后**连续切段 + 连续切道**,读 `task_prof` 里 uiTask 的单轮工作耗时峰值与 800 ms 窗的余量;**不要为此新加埋点**(现成读数够用)。判读:峰值贴着窗走就是读路径该再加节流(先想缓存与分次,别去动心跳窗) | | 图与生成物 | 本设计文档的配图生成器受 `generated-fresh` 逐字节幂等约束;脚本与产物同一笔提交 | diff --git a/docs/子系统设计/看门狗设计说明.md b/docs/子系统设计/看门狗设计说明.md index c49c3bd5..e3753b43 100644 --- a/docs/子系统设计/看门狗设计说明.md +++ b/docs/子系统设计/看门狗设计说明.md @@ -284,7 +284,7 @@ static HAL_StatusTypeDef iwdg_deferred_start(const IWDG_HandleTypeDef *a_h) { (v | 通道 | 现状 | |---|---| -| 串口 | 每次开机第一行区打 `[APP] reset cause: <类型> (CSR=0x…)`(`@test` 已把它当启动锚点并校验次数与期望值) | +| 串口 | 每次开机第一行区打 `[APP] reset cause: <类型> (CSR=0x…)`(`/serial-boot-test` 已把它当启动锚点并校验次数与期望值) | | `selftest.log` | **看门狗/窗口看门狗复位 → `syslog_post(SYSLOG_SELFTEST, "watchdog reset")`**(只记故障,符合既有约定) | | `run.log` | 仍是"上电 / 重启"二分类(靠末条 epoch 时间窗推断),**未接入硬件复位原因**——细粒度原因不上 `run.log`/UI(避免中文字符集重生成),见 §9 O2 | | `/log/wdev.log` | 停喂现场证据(`sys_log_evidence()`,>8 KB 先 unlink 重建,防长跑无界增长) | @@ -406,7 +406,7 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗 | 标记 | 时机 | 含义 | |---|---|---| -| `[APP] reset cause: <类型> (CSR=0x%08X)` | `app_main_init` 早期 | 复位原因 + `RCC_CSR` 原值(`@test` 启动锚点,出现 0 次或 >1 次判 FAIL) | +| `[APP] reset cause: <类型> (CSR=0x%08X)` | `app_main_init` 早期 | 复位原因 + `RCC_CSR` 原值(`/serial-boot-test` 启动锚点,出现 0 次或 >1 次判 FAIL) | | `PREVRUN seg=… seq=… t=…` | 开机(裸 UART) | 上一轮 defaultTask 最后进段(magic 命中才打,一次复位只报一次) | | `TICKRUN uw=… n=… duw=…` | 紧随其后 | 上一轮 HAL 时基终值;`duw≈0` → TIM7 与主循环同刻停摆;`duw≈2000+` → 另有阻塞点 | | `[WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms` | `task_wd_start()` | IWDG 已启动(真机自检必查标记) | @@ -417,7 +417,7 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗 | `[ERR] HARDFAULT / MEMFAULT / BUSFAULT / USAGEFAULT PC=… CFSR=… [BFAR=] [MMFAR=]` | 4 个 Fault | 崩溃现场 + 故障名归因 | | `[ERR] STACK OVERFLOW task=` / `[ERR] MALLOC FAILED …` | 内核钩子 | 栈溢出(复位)/ 堆耗尽(仅告警) | -> **与 `@test` 禁项的关系**:`run_boot_test.py` 的 Forbidden 含 `[ERR]` → **任何崩溃日志都会让本次运行判 FAIL**(刻意)。四个 Fault 打各自名字的意义在于**归因**,不必再从笼统 `HARDFAULT` 反推 `CFSR` 子域。 +> **与 `/serial-boot-test` 禁项的关系**:`run_boot_test.py` 的 Forbidden 含 `[ERR]` → **任何崩溃日志都会让本次运行判 FAIL**(刻意)。四个 Fault 打各自名字的意义在于**归因**,不必再从笼统 `HARDFAULT` 反推 `CFSR` 子域。 > 日志标签:`[APP]` = `app_main.c` 的 `DBG_TAG`;`[WD]` = `task_watchdog.c` 的 `DBG_TAG`。全部纯 ASCII(规避 ARMCC CP936 字符串陷阱)。 ### 6.2 判读顺序(现场"设备反复重启") @@ -433,7 +433,7 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗 | # | 场景 | 结果 | 证据 / 备注 | |---|---|---|---| -| V1 | 正常启动、长期不复位 | ✅ 常态成立 | 每次 `@test` 开机日志均含 `[WD] armed:`;2026-10-10 复测开机 524 ms 内完成、无 stall | +| V1 | 正常启动、长期不复位 | ✅ 常态成立 | 每次 `/serial-boot-test` 开机日志均含 `[WD] armed:`;2026-10-10 复测开机 524 ms 内完成、无 stall | | V2 | **任务饿死**(netTask 插 `while(1)`) | ✅ **2026-10-03 闭环** | 800 ms 声明 `task stall: mask=0x00000001 req=0x00000037` → ≤2.5 s IWDG 复位(`reset cause: IWDG`,`RCC_CSR=0x24000000`)。首轮曾不复位,根因是证据落盘自身喂狗 → 加门闩(§5.3) | | V3 | 断言失败 | 🟡 未单独注入 | 宏与钩子已在用(`configASSERT` 全内核可达);行为经编译与代码路径核对,未做定向注入用例 | | V4 | **关中断死循环** | ✅ **2026-10-03 闭环** | `__disable_irq()` + `while(1)` → 每 ~4.9 s 稳定复位(20+ 周期,`IWDG`)→ 证实 IWDG 独立于中断 | @@ -533,7 +533,7 @@ python scripts/check_cubemx_survival.py --write # 确认改动无误后重建 - **清单 21 项**覆盖 P0/P1 全部防线(iwdg 延后启动、4 个 Fault Handler 采集、故障使能、两个加固宏、断言改造 ×2、看门狗模块与工程挂载、复位原因锁存、HAL 驱动与使能宏、`.ioc` 关键项、钩子落点三项)。 - **漂移检测**:各项内容哈希存 `.cubemx_survival_baseline.json`;有意改动确认后 `--write` 重建。 -- ⚠ `MDK-ARM/*.uvprojx` **排除在漂移检测之外**(`no_baseline=True`)——该文件每次 `@build` 后被 UV4 改写(XML 归一化 + `PackID` 降级),参与检测会每次构建后都被拦;只保留"源文件仍挂载"断言。 +- ⚠ `MDK-ARM/*.uvprojx` **排除在漂移检测之外**(`no_baseline=True`)——该文件每次 `/keil-build` 后被 UV4 改写(XML 归一化 + `PackID` 降级),参与检测会每次构建后都被拦;只保留"源文件仍挂载"断言。 - **注释过滤**:断言前剥掉 C 注释,故 `protected` 项的 `must_contain` **只能断言代码符号**(锚点注释会被剥掉,断言它等于没断言)。 - 已挂 pre-commit `cubemx-survival`(改 `.ioc`/`Src/`/`Inc/`/`task_watchdog`/`.uvprojx` 触发)。 diff --git a/docs/子系统设计/运行与自检记录设计说明.md b/docs/子系统设计/运行与自检记录设计说明.md index 4c17d8ad..1b017313 100644 --- a/docs/子系统设计/运行与自检记录设计说明.md +++ b/docs/子系统设计/运行与自检记录设计说明.md @@ -2,7 +2,7 @@ > **版本**:v1.3(§4.4 新增"卷锁 fail-open 边沿补投自检记录"一条,道G task-40;v1.2 为 §4.3 随七类记录更新,v1.1 的原始设计口径不变) > **日期**:2026-09-14(v1.2 更新 2026-10-26) -> **状态**:步骤 1–9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 @test 待上电回归(§8.2) +> **状态**:步骤 1–9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 /serial-boot-test 待上电回归(§8.2) > **关联文档**:`项目架构总览.md` · `STM32F4-Base存储架构说明.md` · `系统墙钟时间维护说明.md` · `UI设计/内容页架构.md`(如存在)· `docs/UI设计/mockups/`(待机显示页面.jpg / 运行记录页.jpg / 自检记录页.jpg / 密码页面实际机器的示例图片.jpg) ## 1. 背景与需求 @@ -186,7 +186,7 @@ defaultTask(唯一写者,挂载成功后): 复位 latch 后故障复发仍会再投(否则第二次开机起永久静默)。文本形如 `vollock failopen=3`(累计值就是证据,不带态号),落 `selftest.log`。 ⚠ 三条约束(与 task-23「日志让路不计 `[ERR]`」同一根判据):① 不在失败调用点就地打日志; - ② 不打 DBG 错级、不打 `[ERR]`——`[ERR]` 是"设备出故障"的唯一信号通道(`@test` 的 Forbidden + ② 不打 DBG 错级、不打 `[ERR]`——`[ERR]` 是"设备出故障"的唯一信号通道(`/serial-boot-test` 的 Forbidden 也依赖它),塞进可观测性事件就同时不再是故障、不再是禁令;③ 记录自带的时间戳是 **被观测到的时刻**(粒度 = 一轮 `syslog_drain()`),不是发生时刻,所以本模块刻意不再另造 `obs_first_ms`/`obs_last_ms` 字段去"看起来更精确"。主机判据:`TC-SYSLOG-137` 至 @@ -253,7 +253,7 @@ defaultTask 挂载成功、排空待发队列**之后**写一条: ``` - 阈值 `SYSLOG_REBOOT_WINDOW_S = 600`:断电 <10min 会被误判为重启——无掉电检测硬件时该歧义不可消除,取工程折中并在此声明。 -- 串口标记(供 @test):`runlog boot entry: 装置上电记录 / 装置重启记录`(`sys_log_mcu.c` `DBG_INFO`,与落盘描述串同源)。 +- 串口标记(供 /serial-boot-test):`runlog boot entry: 装置上电记录 / 装置重启记录`(`sys_log_mcu.c` `DBG_INFO`,与落盘描述串同源)。 ### 5.4 R4 自检记录(v1 项表) @@ -311,7 +311,7 @@ defaultTask 挂载成功、排空待发队列**之后**写一条: | 记录页像素回归 | `--keys` 导航至 node 39/40 → 截图 + 文本断言(时间戳行/描述行/页脚分页),纳入 ui-verify 门禁 | | 密码错误提示 | `--keys` 输错密码序列 → 截图断言"密码输入错误"可见;再按方向键 → 提示消失 | -### 8.2 真机(@test 串口标记) +### 8.2 真机(/serial-boot-test 串口标记) Required:`App init OK`、`Network Init`、`net init: OK`、**`runlog boot entry:`**(装置上电记录 / 装置重启记录)、**`UI standby: ready (idle=60s)`**; Forbidden:`FAIL`/`ERROR`/`HardFault`。 @@ -341,5 +341,5 @@ Forbidden:`FAIL`/`ERROR`/`HardFault`。 | 步骤 5 | 待机屏 `ui_standby`(空闲检测 + 覆盖层)+ 键挂钩 | ui_menu/ui_content_page 各一行通知 | | 步骤 6 | 记录页 ×2 + 内容页框架接线(枚举/描述符表/node 映射) | ui_page_runlog/selftest、ui_content_page.h/.c、ui_menu.c | | 步骤 7 | 双端注册新文件(uvprojx + CMakeLists)+ 构建 0/0 | new-source-file skill | -| 步骤 8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 @test | 测试通过记录 | +| 步骤 8 | 模拟器无头回归(--logtest/--standby/截图断言)+ 真机 /serial-boot-test | 测试通过记录 | | 步骤 9 | 文档同步(项目架构总览、AGENTS.md、本文档状态) | docs | diff --git a/docs/子系统设计/配置文件设计说明.md b/docs/子系统设计/配置文件设计说明.md index d2831bce..766f9b78 100644 --- a/docs/子系统设计/配置文件设计说明.md +++ b/docs/子系统设计/配置文件设计说明.md @@ -495,13 +495,13 @@ void adc_cfg_init(void) | 阶段 | 内容 | 涉及文件 | 验收标准 | |---|---|---|---| | **P1** | 设计文档 + 架构图 | 本文档 + `gen_cfg_arch_svg.py` + SVG | 用户确认 §2 决策 | -| **P2** | FS 线程安全 + 时间戳 | `ffconf.h`、`Lib/FatFs/ffsystem.c`(新)、`App/util/sys_clock.c`(sys_clock_get_time;移入中间件层)、`uvprojx`、架构总览 §1.1(sys_clock 改归 ④) | `@build` 0/0;`@test` 启动 PASS(无死锁) | -| **P3** ✅ 2026-09-14 | cfg_file 引擎(双端)+ net_cfg 集成 + 旧档迁移 | `App/cfg/cfg_file.{h,c}`、`App/cfg/cfg_file_store.h`、`App/cfg/cfg_file_store_mcu.c`(新)、`simulator/sim/cfg_file_store_sim.c`(新)、`net_cfg.{h,c}`(段注册 + 引擎化 load/save)、`net_cfg_legacy.c`(新,NCFG 迁移)、删 `net_cfg_store*`、`uvprojx`、`CMakeLists.txt` | Keil `@build` 0/0;模拟器生成 `sim_config.ini` 且编辑回读一致;旧档 NCFG 自动迁移三态正确 —— **已实测通过**(工厂模板 / 编辑回读 / 非法键回退 / 迁移三态 / 双端 0 警告;`cfg_file.c` 含中文注释块 → UTF-8 **带 BOM**) | -| **P4** | 轮询 + D15 协议 + 测试标记 | `net_task.c`(沉降去抖轮询 + 落盘收敛 + 串口标记)、`lftpd.c`(STOR tmp+rename) | `@build`;`@test` 启动新标记(`cfg file: loaded OK` 等);FTP 上传无半截可见(rename 原子) | +| **P2** | FS 线程安全 + 时间戳 | `ffconf.h`、`Lib/FatFs/ffsystem.c`(新)、`App/util/sys_clock.c`(sys_clock_get_time;移入中间件层)、`uvprojx`、架构总览 §1.1(sys_clock 改归 ④) | `/keil-build` 0/0;`/serial-boot-test` 启动 PASS(无死锁) | +| **P3** ✅ 2026-09-14 | cfg_file 引擎(双端)+ net_cfg 集成 + 旧档迁移 | `App/cfg/cfg_file.{h,c}`、`App/cfg/cfg_file_store.h`、`App/cfg/cfg_file_store_mcu.c`(新)、`simulator/sim/cfg_file_store_sim.c`(新)、`net_cfg.{h,c}`(段注册 + 引擎化 load/save)、`net_cfg_legacy.c`(新,NCFG 迁移)、删 `net_cfg_store*`、`uvprojx`、`CMakeLists.txt` | Keil `/keil-build` 0/0;模拟器生成 `sim_config.ini` 且编辑回读一致;旧档 NCFG 自动迁移三态正确 —— **已实测通过**(工厂模板 / 编辑回读 / 非法键回退 / 迁移三态 / 双端 0 警告;`cfg_file.c` 含中文注释块 → UTF-8 **带 BOM**) | +| **P4** | 轮询 + D15 协议 + 测试标记 | `net_task.c`(沉降去抖轮询 + 落盘收敛 + 串口标记)、`lftpd.c`(STOR tmp+rename) | `/keil-build`;`/serial-boot-test` 启动新标记(`cfg file: loaded OK` 等);FTP 上传无半截可见(rename 原子) | | **P5** ✅ 2026-09-14 | 模拟器迁移 | `simulator/sim/cfg_file_store_sim.c`(新)、删 `net_cfg_store_sim.c`、`simulator/main.c`(2s 轮询 + `--cfgtest` 自检)、`CMakeLists.txt` | sim 构建 0 警告 + 4 场景无头 PASS(工厂 / 有效旧档迁移 / 无效旧档保留 / UI 无头);`sim_netcfg.bin` 自动迁移 + 轮询沉降重载 + 外科保存无损回环 —— **已实测通过** | | **P6** | 文档同步 | `网络配置说明.md`(§0/3/6/7 重写)、`AGENTS.md`、`README.md`、`项目架构总览.md` + `gen_arch_svg.py`、`内容页架构.md`、BSD 指南、`simulator/README.md`、`ui_page_net_cfg.c` 头注释、`docs/测试说明/` | pre-commit 全绿;全库无"Flash"旧表述残留 | | **P7** | FTP 闭环脚本 | `[计划] test/ftp_config_test.py`(尚未创建,见 §11.2 设计稿)+ 测试说明 | 真机闭环 PASS(§11.2) | -| **P8** | 总回归 + 提交 | `@build` + `@test` + 模拟器 ui-verify + `pre-commit --all-files` | 0 错误 0 警告;全 PASS | +| **P8** | 总回归 + 提交 | `/keil-build` + `/serial-boot-test` + 模拟器 ui-verify + `pre-commit --all-files` | 0 错误 0 警告;全 PASS | **建议提交边界**:C1 = P2+P3(FS 线程安全 + 配置引擎)/ C2 = P4+P5(网络配置迁移 + 模拟器)/ C3 = P6+P7(文档 + 测试脚本);P8 发现的收尾问题并入最近一次提交。 @@ -511,7 +511,7 @@ void adc_cfg_init(void) | 串口标记 | 场景 | 属性 | |---|---|---| -| `cfg file: loaded OK` | 上电加载成功 | Required(P4 起纳入 `@test`) | +| `cfg file: loaded OK` | 上电加载成功 | Required(P4 起纳入 `/serial-boot-test`) | | `cfg file: factory template written` | 首启 / 删文件 | 条件(手工) | | `cfg file: legacy NCFG migrated` | 旧文件迁移 | **已作废**(2026-10-10 旧档迁移删除,该标记不再产生) | | `cfg file: saved OK` | UI 保存 | 条件(手工) | diff --git a/docs/文档规范.md b/docs/文档规范.md index db031d5e..60985d1a 100644 --- a/docs/文档规范.md +++ b/docs/文档规范.md @@ -165,7 +165,7 @@ 1. **昵称式引用**(「内存方案 §2.1」)与正文里的裸文件名散文 → 落盘后 `git grep -n '<旧文件名>'` 扫一遍; 2. **生成物**(`docs/images/引用关系图.*`)不改写 → 重跑 `& 'tools\refgraph\gen_refgraph.bat'`; -3. 改的是 `.c/.h` 且**新增了目录** → 还要把目录加进 `uvprojx` 的 ``(→ `new-source-file` skill),随后 `@build` 验证。 +3. 改的是 `.c/.h` 且**新增了目录** → 还要把目录加进 `uvprojx` 的 ``(→ `new-source-file` skill),随后 `/keil-build` 验证。 4. ~~搬整目录时会改写目录内部文件的内容~~ **已修**(2026-10-30,回归见 `--selftest` 案例 A):根因是 「相对引用文件所在目录」那条解析规则不检验目标是否存在,于是内部文件里**不是路径的斜杠片段**被 当成旧目录下的子路径并插上目录名 —— 分层那次 `1/1 PASSED` 改成 `test_data/1/1 PASSED`、日志日期占位 @@ -262,7 +262,7 @@ ``` > **改栈流程**:先改 `Src/freertos.c` 的 `osThreadAttr_t` → 跑 `gen_task_table.py --write` → 提交。 -> **改内存流程**:先改代码 + `@build` 出 `.map` → 跑 `check_mem_numbers.py --write` → 提交。 +> **改内存流程**:先改代码 + `/keil-build` 出 `.map` → 跑 `check_mem_numbers.py --write` → 提交。 > 钩子报不一致时**不要手改受管区**——跑 `--write` 让脚本重算。 #### 陈旧数字扫描(`mem-numbers --scan`) diff --git a/docs/架构设计/STM32F4-Base存储架构说明.md b/docs/架构设计/STM32F4-Base存储架构说明.md index 672b7f59..3bba0b62 100644 --- a/docs/架构设计/STM32F4-Base存储架构说明.md +++ b/docs/架构设计/STM32F4-Base存储架构说明.md @@ -610,9 +610,9 @@ FTP 根目录 = 卷根,lftpd 是卷的网络文件入口(`Drivers/BSP/NET/lf lftpd 原先把全部异常路径一律按 error 级打(串口上带 `[ERR]`),其中**九处其实由外部输入决 定**:客户端关窗口、连上就走、PASV 之后不建数据连接、运维改 IP 触发监听重建。代价实测两条: -1. `@test` 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀)——"FileZilla 关了 +1. `/serial-boot-test` 的 Forbidden 标记含 `[ERR]`(它同时是四个 Fault 崩溃日志的前缀)——"FileZilla 关了 个窗口"就能把一次启动测试判 FAIL。实测就是这样踩到的:抓自检台账基线那次 FTP 会话结束后, - 设备打 `[ERR] [FTP] error reading next command`,本次 `@test` 直接 FAIL。 + 设备打 `[ERR] [FTP] error reading next command`,本次 `/serial-boot-test` 直接 FAIL。 2. 任何对 21 端口"连一下就断开"的外部行为,都能**无限制造固件级 ERROR 日志**——日志面与门禁 面都可被外部输入驱动,这在工业现场是过不了审的。 diff --git a/docs/架构设计/系统架构与硬件规格.md b/docs/架构设计/系统架构与硬件规格.md index b3a266c9..62c48edd 100644 --- a/docs/架构设计/系统架构与硬件规格.md +++ b/docs/架构设计/系统架构与硬件规格.md @@ -159,7 +159,7 @@ sequenceDiagram | **USART1(U5 直连 TTL)** | UART | PA9 TX / PA10 RX | **700000 8N1 → 屏板链路**(TTL 直连、无 DE,`../产品需求/产品需求设计文档.md` §5.16.2);PA10 双向均已接线;日志已迁 UART5 | | **UART4(RS-485)** | UART | PC10 TX / PC11 RX / **PA15 DE** | 半双工通信(V0.1 轮换后 RS485#4 = UART4,`../硬件平台/V0.1_网表详细分析报告.md` §4.2) | | **USART2(RS-232 #3)** | UART | PD5 TX / PD6 RX | 经 U10 隔离 → U13 TPT3232E → CN1;固件目前未占用(日志实际走 UART5/RS232#1) | -| **UART5(RS-232 #1)** | UART | PC12 TX / PD2 RX | 经 U9 隔离 → U13 → CN1;**日志口**(115200,`@test` 采集通道;台架端子 `../子系统设计/屏板链路设计说明.md` §3.2) | +| **UART5(RS-232 #1)** | UART | PC12 TX / PD2 RX | 经 U9 隔离 → U13 → CN1;**日志口**(115200,`/serial-boot-test` 采集通道;台架端子 `../子系统设计/屏板链路设计说明.md` §3.2) | | **状态 LED ×6** | GPIO | PC5 / PB0 / PB1 / PB2 / PF11 / PF12 | **低电平点亮** | | SWD | — | PA13 SWDIO / PA14 SWCLK | V0.1 已修复 V0.0 的 SWCLK 引脚缺陷 | | 调试口 | — | CN2(XH-3A,1=SWDIO/2=SWCLK/3=GND) | V0.1 由 H2 排针改为 XH 端子 | diff --git a/docs/架构设计/项目架构总览.md b/docs/架构设计/项目架构总览.md index fffd6b32..e394cca0 100644 --- a/docs/架构设计/项目架构总览.md +++ b/docs/架构设计/项目架构总览.md @@ -351,15 +351,15 @@ STM32F4-Base/ ```mermaid flowchart LR SRC["共享源码
Drivers/BSP/UI/*
Drivers/BSP/NETCFG/net_cfg.c
App/cfg/cfg_file.c"] - SRC --> MCU["MCU 端
Keil MDK-ARM (ARMCC v5)
@build → BUILD_OK"] + SRC --> MCU["MCU 端
Keil MDK-ARM (ARMCC v5)
/keil-build → BUILD_OK"] SRC --> SIM["PC 模拟器
CMake + SDL2 (MSVC)
lvgl_sim.exe --keys"] - MCU --> TEST["@test:串口采集 + 烧录 + 日志校验"] + MCU --> TEST["/serial-boot-test:串口采集 + 烧录 + 日志校验"] SIM --> REG["UI 回归:
check_menu_rows.py
check_content_pages.py"] ``` | 目标 | 工具链 | 验证 | |---|---|---| -| MCU 固件 | Keil MDK-ARM v5(`@build` = `keil-build` skill) | `@build` 0 错 0 警 → `@test` 串口闭环 | +| MCU 固件 | Keil MDK-ARM v5(`/keil-build` = `keil-build` skill) | `/keil-build` 0 错 0 警 → `/serial-boot-test` 串口闭环 | | PC 模拟器 | CMake + SDL2 + MSVC(`simulator/build/`) | `check_menu_rows.py` + `check_content_pages.py`(文本断言 + 像素黄金参考) | **纯逻辑模块第三端**:录波(`wave_ring`/`wave_store`)、保护判据(`prot_set`/`prot_elm`)、遥测快照(`meas`)、SOE 编解码(`soe_rec`)、屏板协议层(`sb_proto`)等零平台依赖,被 `test/host/` 原样 `#include` 免桩直跑(覆盖率总闸全量真跑)——主机侧就是它们的"第三端"。 diff --git a/docs/测试说明/专项验证/ADC前端注入验证流程.md b/docs/测试说明/专项验证/ADC前端注入验证流程.md index ecf05068..c2f4c141 100644 --- a/docs/测试说明/专项验证/ADC前端注入验证流程.md +++ b/docs/测试说明/专项验证/ADC前端注入验证流程.md @@ -328,9 +328,9 @@ TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性, | 信号发生器 | 正弦 / 方波 / 三角,**并有 DC 档**;频率可锁 50.000 Hz、也能设到 2 Hz;幅度 0~3 Vpp | **只要求"波形干净、幅度稳、频率锁得住"**,它的刻度不当基准(`docs/任务规划/数据可信.md` §7 #9)。DC 档是给削顶轮改法 A 用的 | | ⚠ 发生器输出端接设置 | 接板子时设成**高阻负载**,不要按 50 Ω 端接 | 按 50 Ω 端接而实际接的是高阻板输入 → 面板值与真实电压差 2 倍;这是台架"差一倍"最常见来源 | | 万用表 AC 档 | 能读到 mV;**记明型号与响应类型**(真 RMS / 均值响应) | 基准就是它;波形轮那一步两种表的读数都与基波期望差很多,不记响应类型就说不清是谁错 | -| 板子 | 产品构建(`test/board/test_config.h` 四个 `TEST_SUITE_*` 全注释)+ `@build` 0 错误 0 警告 + 烧录 `-Rst -Run` 与回读校验 | → `stlink-flash` skill 与 AGENTS.md「构建与验证」 | +| 板子 | 产品构建(`test/board/test_config.h` 四个 `TEST_SUITE_*` 全注释)+ `/keil-build` 0 错误 0 警告 + 烧录 `-Rst -Run` 与回读校验 | → `stlink-flash` skill 与 AGENTS.md「构建与验证」 | | `[adc_cal]` 状态 | **保持未标定**(`eng=0`),别为"读数好看"先写标定值 | 现在 `meas:` / `phasor` 的量纲是 **ADC 引脚电压 V**(`App/algo/meas.h` 文件头的 A-lite 口径);标定一生效就换成二次值,本节全部期望值作废 | -| 串口 | 全程抓 RX 落盘:用 `serial-assistant` skill(`open` → `clear` → 跑 → 读日志文件或 `log --tail`) | 日志文件就是证据本体,§8.10 归档归档的就是它;**长跑别用 `@test`**——它绑定一轮启动语义(静默 8 s、总超时 30 s、`max_boot_count` = 1,且 forbidden 含 `[ERR]`,而削顶轮故意造 `[ERR]`),约 135 min 的 session 必然超时或误判;daemon 全量写 `state/serial.log`(20 MB 上限自动截头保尾 10 MB)→ `docs/测试说明/流程与规范/自动化测试流程.md` 长时间采集一节 | +| 串口 | 全程抓 RX 落盘:用 `serial-assistant` skill(`open` → `clear` → 跑 → 读日志文件或 `log --tail`) | 日志文件就是证据本体,§8.10 归档归档的就是它;**长跑别用 `/serial-boot-test`**——它绑定一轮启动语义(静默 8 s、总超时 30 s、`max_boot_count` = 1,且 forbidden 含 `[ERR]`,而削顶轮故意造 `[ERR]`),约 135 min 的 session 必然超时或误判;daemon 全量写 `state/serial.log`(20 MB 上限自动截头保尾 10 MB)→ `docs/测试说明/流程与规范/自动化测试流程.md` 长时间采集一节 | | 网口(可选) | 要读 104 那一路就插网线、PC 与板同网段(出厂 192.168.1.100),跑现成主站 `test/host/iec104_master.py`(`--ip` / `--port` / `--addr`) | 104 是浮点无量化 + 带品质,是 `meas:` 之外的第二条腿 | | FTP 客户端 | 走现成 21 端口取段文件(出厂 `[ftp]` 账号,连续 5 次失败锁 60 s) | 段文件是相位 / 谐波轮定量档的样本出口;段文件名 `/wave/WAVE.BIN`(00~99 轮转),**取完立刻改名归档**,否则下一次触发把它盖掉 | | 录波触发路径 | 产品构建里唯一**免改代码**的路径 = UI 菜单第 26 叶「手动录波」确认页(`Drivers/BSP/UI/ui_page_wave_manual.c` → `App/task/ui_task.c:605`) | 保护启动上升沿也算一次触发,但保护判据现处**未标定闭锁**态(AGENTS.md「现状」),别指望它自动来段;bench 定时自抓在双层编译门 `DTU_BENCH` 且 `WAVE_BENCH_AUTOCAP` 非 0 之内(默认 0,`App/task/adc_task.c:153`),开它就走 §8.9 的还原判据 | @@ -389,7 +389,7 @@ flowchart LR ```text 日期 ____ / 操作人 ____ / 板子序列号 ____ -固件 commit ________(@build 0 错误 0 警告:是 / 否) +固件 commit ________(/keil-build 0 错误 0 警告:是 / 否) 发生器:型号 ______ 输出端接 = 高阻 / 50 Ω 波形 ____ 频率设定 ____ 万用表:型号 ______ AC 响应类型 = 真 RMS / 均值 共地方式 = 单点 / 发生器浮地 注入点:路名 ____(端子侧 U5 / 引脚侧外串 ____ Ω) @@ -521,7 +521,7 @@ flowchart TD | 读数 | `freq(hb): f=.. valid=..`;UI 交流量页 `Hz`;104 `0x401D` | | 期望(A 档 = 现状) | **就是不出数**:幅值门 `FREQ_AMP_MIN 50.0f`(`App/algo/freq.h:62`,`App/algo/freq.c:231` 拿它与基波有效值比)在引脚电压域恒不过 → `valid=0`、`f=0`、UI `Hz` 显 `--`、104 `0x401D` 上送 IV | | 判据 | **如实报不可测**,而不是报一个看起来对的 50.00。若门限没改却报出 50.00 → 反而是 bug,停下查 `App/algo/freq.c` | -| B 档(要用户先点头) | 临时把 `FREQ_AMP_MIN` 由 `50.0f` 降到 `0.5f` → 重编(`@build`,要 build 锁)+ 重烧 → 扫 45.000 / 50.000 / 55.000 Hz 三点,PC 侧判据 0.02 Hz → 跑完**必须还原**,收尾判据见 §8.9 | +| B 档(要用户先点头) | 临时把 `FREQ_AMP_MIN` 由 `50.0f` 降到 `0.5f` → 重编(`/keil-build`,要 build 锁)+ 重烧 → 扫 45.000 / 50.000 / 55.000 Hz 三点,PC 侧判据 0.02 Hz → 跑完**必须还原**,收尾判据见 §8.9 | | 坑 | 非整 50 Hz 时 `meas:` 会拍频式跳动,属预期 → **扫频读 `freq(hb)`,别读 `meas:`**(`docs/任务规划/数据可信.md` §8 风险表同一条);测频的取源顺序是先 `UA`(相量实例 C02)再退 `IA`(C01)(`App/algo/freq.c:47`)——本卡注 `UA` 走的就是 UA 源;若哪天只注 `IA`,测频会跟着 IA 走,别以为它锁在 UA | 记录表(A 档): @@ -627,8 +627,8 @@ flowchart TD ### 8.7 `[ERR]` 的判读规则(削顶轮单独跑、单独归档) -- 削顶轮会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `@test` 的 forbidden 标记含 - `[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `@test` 的标记校验当结论**:改人工判读,并在归档文件名与 +- 削顶轮会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `/serial-boot-test` 的 forbidden 标记含 + `[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `/serial-boot-test` 的标记校验当结论**:改人工判读,并在归档文件名与 抬头里写明「预期 [ERR]:贴轨锁存负向用例」;§8.6 已把它排在最后单独一轮(`docs/任务规划/数据可信.md` §8 风险表「削顶负向用例被当成固件 bug」行同一条)。 - 除这一条 `[ERR] rail:` 之外,**任何 `[ERR]` 都是不通过**;四个 Fault 的崩溃日志也以 `[ERR]` 开头 → 出现 Fault 立即停线取现场(`docs/子系统设计/看门狗设计说明.md` §10),别再继续注。 @@ -659,9 +659,9 @@ flowchart TD | 临时改动 | 收尾判据 | |---|---| -| 测频轮 B 档:`FREQ_AMP_MIN` 50.0f → 0.5f(`App/algo/freq.h:62`) | 还原 + `@build` 0 错误 0 警告 + **`git diff` 里 `App/algo/freq.h` 必须为空** + 在 `docs/任务规划/数据可信.md` §7 #8 那行留一句"本轮台架已还原" | +| 测频轮 B 档:`FREQ_AMP_MIN` 50.0f → 0.5f(`App/algo/freq.h:62`) | 还原 + `/keil-build` 0 错误 0 警告 + **`git diff` 里 `App/algo/freq.h` 必须为空** + 在 `docs/任务规划/数据可信.md` §7 #8 那行留一句"本轮台架已还原" | | 相位轮改倍数:`log_phasors()` 的 `* 10.0f` → `* 1000.0f`(`App/task/adc_task.c:209`) | 同上,`git diff` 里 `App/task/adc_task.c` 必须为空 | -| 为 bench 定时自抓开过编译门(`DTU_BENCH` + `WAVE_BENCH_AUTOCAP` 非 0,`App/task/adc_task.c:153`) | 还原成默认 0 + `@build` 0 错误 0 警告 + `git diff` 里 `App/task/adc_task.c` 必须为空(产品镜像里这段必须零存在) | +| 为 bench 定时自抓开过编译门(`DTU_BENCH` + `WAVE_BENCH_AUTOCAP` 非 0,`App/task/adc_task.c:153`) | 还原成默认 0 + `/keil-build` 0 错误 0 警告 + `git diff` 里 `App/task/adc_task.c` 必须为空(产品镜像里这段必须零存在) | | 只动接线与发生器、没动代码 | `git status` 除归档目录外干净 | | 全程 | `test/board/test_config.h` 四个套件宏保持全注释(产品构建);烧录仍须 `-Rst -Run` + 回读校验 | diff --git a/docs/测试说明/专项验证/启动日志原子性复验流程.md b/docs/测试说明/专项验证/启动日志原子性复验流程.md index da67b13e..23a5f98f 100644 --- a/docs/测试说明/专项验证/启动日志原子性复验流程.md +++ b/docs/测试说明/专项验证/启动日志原子性复验流程.md @@ -39,7 +39,7 @@ | `--selftest` | 跑内置 **22 条**判别用例后退出,无需日志与硬件(含下表三计数分类器) | "不可能失败的 PASS 就是空跑"——用例里含交错、缺行首、残留 1/2、配对错序,以及**"旧子串口径在同一条缺陷行上判 PASS"** 这一条盲区证明 | 用法示例(一次仅复位启动的原子性判定)。四个行级开关**也已接进 `run_boot_test.py`**, -所以 @test 闭环里直接加 `--exact ... --pair ... --allow-residual 1 --head-char '['` 即可,无需单独跑本脚本: +所以 /serial-boot-test 闭环里直接加 `--exact ... --pair ... --allow-residual 1 --head-char '['` 即可,无需单独跑本脚本: ```powershell & '.venv\Scripts\python.exe' .dsh\skills\serial-boot-test\scripts\verify_log.py ` @@ -75,9 +75,9 @@ 2. **出像**:像必须来自**已提交 commit + 干净工作树**。主树脏(别人在飞)时走仓外只读 detached worktree: `git worktree add --detach <_wt>/bench-- ` + `mklink /J \.venv
\.venv`; 建树后 `git -C status --short` 必须为空,全程只读、不 commit、不跑生成器。 -3. **@build**:0 错误 0 警告;随后**从 .hex 二进制里抠身份戳**(不是从 build_log 里读),与之后线上读到的 +3. **/keil-build**:0 错误 0 警告;随后**从 .hex 二进制里抠身份戳**(不是从 build_log 里读),与之后线上读到的 `fw build:` 逐字对上——这是"烧进去的就是这份像"的唯一硬证据。 -4. **烧录启动**:`@test` 已保证 flash(`-Rst -Run`) → 采日志 → 回读(`-CmpFile`) 的次序(`-CmpFile` 会复位目标, +4. **烧录启动**:`/serial-boot-test` 已保证 flash(`-Rst -Run`) → 采日志 → 回读(`-CmpFile`) 的次序(`-CmpFile` 会复位目标, 必须排最后)。 5. **N 次仅复位对照**(N≥3):每次 `clear`(记下 gen,证明窗口不重叠)→ `stlink_flash.py reset`(`-c SWD -Rst -Run`, **零重烧**)→ `wait-idle` → 取日志逐块存档。"保存的值重启后还在"用重烧去验是**无意义**的。 @@ -98,7 +98,7 @@ 所以"跑 5 次正常启动、[ERR] 零命中"**证不到 IPSR**——那是定义上的必然,是空跑。 IPSR 主张交给主机侧套件(假 USART + IPSR + 临界区深度水位);要硬件证据就得由代码道**提交触发器**, 判据写"崩溃遗言一整行、恰好一次、`dropped=0`",并按 `../测试数据/boot_log_data/README.md` §2「故障注入日志」 - 运行时放宽 `@test`(`--forbidden` 去掉 `[ERR]`、`--max-boot-count 2`)——**不改仓库配置**。 + 运行时放宽 `/serial-boot-test`(`--forbidden` 去掉 `[ERR]`、`--max-boot-count 2`)——**不改仓库配置**。 ## 5. 栈判据(task-32 定稿:三档分权,不再是"待定") @@ -145,7 +145,7 @@ SHA 只当坐标(2026-10-26 那天 SHA 被 amend 挪了三轮)。同镜像 ## 7. 台架卫生清单(每一条都是实测踩过的,不是设想) 1. **每个台架窗口开头、以及每一圈复位之前**:`serial_tool.py open` + `status` 断言 `port.ready=true`,并**观察 `rx_lines` 在几秒内增长**。 - 实测:常驻 daemon 会死(今晚 pid 18020 跑了 156589 s 后没了),而 `status` 会**自动拉起一个新 daemon**,于是它一直"正常"地报 `ready=false / rx_lines=0` —— 端口状态**不会**跨小时存活,`@test` 开过也不算数。 + 实测:常驻 daemon 会死(今晚 pid 18020 跑了 156589 s 后没了),而 `status` 会**自动拉起一个新 daemon**,于是它一直"正常"地报 `ready=false / rx_lines=0` —— 端口状态**不会**跨小时存活,`/serial-boot-test` 开过也不算数。 2. **`serial_tool.py log / status / clear` 返回 JSON,不是纯文本**。按 `[RX]` 行首过滤纯文本会得到**恒 0 行**且看起来"跑成功了"。 3. **Forbidden 令牌不得落在英文单词内部**:裸 `Fault` 命中 `de`+`fault` → 打到 `[FTP] using factory default password` 与 `[STACK] default peak_used`(实测假红两次)。四个 Fault 的崩溃日志都以 `[ERR]` 开头,列 `[ERR]` 就够,要加就加 `MEMFAULT/BUSFAULT/USAGEFAULT/HardFault`。 4. **工具输出别混流**:非零退出时 stdout(结果)与 stderr(诊断)拼接后再 `JSON.parse` 必崩;解析只吃 stdout。 diff --git a/docs/测试说明/专项验证/存储维护与容量恢复.md b/docs/测试说明/专项验证/存储维护与容量恢复.md index 9cb8a4e1..cf27c214 100644 --- a/docs/测试说明/专项验证/存储维护与容量恢复.md +++ b/docs/测试说明/专项验证/存储维护与容量恢复.md @@ -31,7 +31,7 @@ 1. `test/board/test_config.h`:取消注释 `TEST_SUITE_STORAGE`; 2. `test/board/storage_test.h`:取消注释 `ENABLE_STORAGE_RECOVERY_TESTS` 与 `STORAGE_RECOVERY_FULL_WIPE`; -3. `@build`(要求 0 错误 0 警告); +3. `/keil-build`(要求 0 错误 0 警告); 4. 采集顺序 `open` → `clear` → 烧录 → `wait-idle`(擦除 2044 块约 4 秒,静默判据给 20~30s); 5. 确认日志: @@ -46,7 +46,7 @@ ``` 6. **断电重启**(`stlink_flash.py reset` 无参即可)——本次启动才会重建干净 journal; -7. 恢复产品构建(两个头文件重新注释)→ `@build` → 烧录 → `@test`,确认: +7. 恢复产品构建(两个头文件重新注释)→ `/keil-build` → 烧录 → `/serial-boot-test`,确认: ``` [NAND_FTL] No valid map found, creating fresh (err=3) @@ -74,14 +74,14 @@ - **判据:只删显式点名的文件,绝不 glob**。清理脚本里写死 27 个文件名逐个 `DELE`;保留清单 必须含 **`/config.ini`**(网口与 FTP 鉴权配置)、**`/PROT_SET.BIN`**(保护定值 blob, `PROT_SET_BLOB_SZ` = 176 B,删了等于把定值清零)、**`/log`** 目录(运行与自检记录)。 -- 三步验收:① 清完 `LIST` 只剩 `LOG` + `CONFIG.INI` + `PROT_SET.BIN`;② `@test` PASS(定值巡检 +- 三步验收:① 清完 `LIST` 只剩 `LOG` + `CONFIG.INI` + `PROT_SET.BIN`;② `/serial-boot-test` PASS(定值巡检 与配置加载都在这条链上);③ 跑 `test/host/ftp_list_fill_test.py` 确认 FTP 面正常。 - 这属于**动作 D(清文件)**,与本文 §1 的 A(重建 BBT + 格式化)/ B(全片擦除)/ C(容量恢复) 都不同:A/B 会丢卷内容,D 不动文件系统结构。 ### 4.2 自检台账清零,重建联合判定审计基线(2026-10-22 实测) -`@test --require-selftest-pass --selftest-scope all`(累计口径)在台架上**永远 FAIL**: +`run_boot_test.py --require-selftest-pass --selftest-scope all`(累计口径)在台架上**永远 FAIL**: `/log/selftest.log` 里躺着 29 条历史记录,`2026-10-10 09:24:12 → 2026-10-14 06:55:02`, 组成为 **16 × `watchdog reset` + 13 × `heap low water`**——都是看门狗引入期与堆迁移期 留下的,两件事后来都已结案。新判据(只看本次新增)已不受影响,但**审计口径要能用,就得 @@ -97,7 +97,7 @@ 256 种字节值全部出现、前 64 字节全 `FF` → 就是一份 0..255 图案的写盘校验填充,没有任何 产品语义;留档后再 `DELE`(判据同 §4.1:先知道删的是什么)。 - 清理后 `/log` 只剩三条、**全是产品数据**:`run.log`(运行记录)、`wdev.log`(掉电取证)、 - `soe.log`(SOE 动作记录)。之后再跑 `@test --require-selftest-pass` → **PASS** + `soe.log`(SOE 动作记录)。之后再跑 `run_boot_test.py --require-selftest-pass` → **PASS** (699 ms、`SELFTEST_BASELINE: 既有记录 0 条`)。 - 验收(两条判定路径都要过):`--require-selftest-pass` → `SELFTEST_BASELINE: 既有记录 0 条 (status=absent)` → PASS;`--selftest-scope all` → `selftest: no fault record` → **PASS** diff --git a/docs/测试说明/专项验证/模拟器差距清单.md b/docs/测试说明/专项验证/模拟器差距清单.md index fa8c4a0d..60289f4a 100644 --- a/docs/测试说明/专项验证/模拟器差距清单.md +++ b/docs/测试说明/专项验证/模拟器差距清单.md @@ -13,7 +13,7 @@ > 模拟器覆盖的是 **「纯逻辑 + 平台抽象之上」的那一层**:UI 场景/渲染/导航、配置引擎、日志记录格式、 > `net_*` BSD API 之上的协议状态机。它**不覆盖**「硬件时序 / 中断 / 真实复位 / CH395F 与 NAND 芯片行为」。 -> 因此 **模拟器回归与板端测试套件 / 主机侧回归(`@test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。 +> 因此 **模拟器回归与板端测试套件 / 主机侧回归(`/serial-boot-test` 闭环)互补而非替代**——见 §3 的"什么时候必须回真机"。 --- @@ -34,16 +34,16 @@ ## 2. 差距清单 > **列含义**:`模拟器行为` / `真机行为` 描述差异本身;`影响` 说明它会让什么 bug 逃过模拟器回归; -> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`@test` / 板端套件 / 目检)。 +> `回归补测` 给出"模拟器过 ≠ 真机过"时应补的真机手段(`/serial-boot-test` / 板端套件 / 目检)。 ### 2.1 网络(`net_socket_sim.c` vs `net_socket.c`) | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | |---|---|---|---|---| -| **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | accept 轮询计时失真、lftpd 控制通道 errno 分类、timeSync errno 分级等在模拟器上无法复现 | `@test` + FTP 套件;ACCEPT 决定不 pending 化(2026-10-15),不再专门建模 | -| **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) | +| **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | accept 轮询计时失真、lftpd 控制通道 errno 分类、timeSync errno 分级等在模拟器上无法复现 | `/serial-boot-test` + FTP 套件;ACCEPT 决定不 pending 化(2026-10-15),不再专门建模 | +| **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `/serial-boot-test` 网络重配置用例(UI 改 IP 免重启生效) | | **消息泵缺失**:~~`net_recv_to` 缺失~~(**已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 | -| **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten` + 对账形态③④自动重建(无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` | +| **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten` + 对账形态③④自动重建(无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `/serial-boot-test` | | **单连接 backlog** | `NET_SIM_BACKLOG = 1` | CH395F 多 socket 并发 | 多客户端并发场景(FTP + 推流 + timeSync 同时)不可在模拟器验证 | 板端 NET 套件 + 台架并发场景 | | **非阻塞语义模拟** | 创建即 `FIONBIO`(Winsock 句柄恒非阻塞,UI 主循环安全底线);**阻塞/非阻塞按控制块 `blocking` 属性在包装层模拟**(`net_recv` 无限轮询等待 / `net_recv_nb` / `NET_MSG_DONTWAIT` 立即 WOULDBLOCK,`net_setblocking` 双端同名) | 中断驱动 + 状态机 + 挂起 recv(等待睡任务通知,netTask 零占用) | 外部语义**对齐**(登记以说明"为何模拟器能测非阻塞路径与显式预算路径");内部挂起模型(无 pending 机制)仍不建模 | —— | @@ -51,9 +51,9 @@ | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | |---|---|---|---|---| -| **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `@test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §4.1 | +| **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `/serial-boot-test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §4.1 | | **mtime 粒度不同** | 宿主文件 mtime(**秒级**) | FAT 打包时间(`fdate<<16\|ftime`,**分钟级**) | `cfg_file_poll` 的 `(size, mtime)` 变更令牌在两端触发时机不同;分钟级粒度下的"同分钟内两次改动"在真机可能被判为无变化 | 板端 STORAGE 套件;`配置文件设计说明.md` §变更检测 | -| **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `@test`(配置保存 → UI 生效) | +| **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `/serial-boot-test`(配置保存 → UI 生效) | | **「清除记录」两端时序不同** | `syslog_request_clear()` 在调用内**同步**删除宿主文件(`sim_syslog.c` 单线程,没有卷锁、没有队列),返回后状态立刻是 `OK`/`FAIL`,`SYSLOG_CLEAR_WAIT` 永不出现 | 非写者上下文只置标志,真正的 `f_unlink` 排在 defaultTask 的 `syslog_drain()` 尾部(卷锁 try:拿不到锁就顺延,见 `App/log/sys_log_mcu.c`) | 「按了按钮 → 下一拍才见结果」这条异步链、卷占用顺延、以及删除与写者重建的交错,在模拟器上**结构上不存在**;UI 页面若把 WAIT 当失败,模拟器上看不出来 | 主机单测 `test/host/sys_log_mcu/`(TC-SYSLOG-110..122:请求侧零盘触点 / 落地只在 drain / 卷占用顺延 / 幂等 / 不误删;TC-SYSLOG-133/133b:清除成功投一条操作记录、清 oper 自己时不投)+ 台架装置维护页目检 | | **两条投递口在宿主端合一** | `syslog_post_quiet()` = `syslog_post()` 的别名(直写宿主文件,没有队列/待发数组),`syslog_post_dropped()` **恒返 0** | 两个口分开且判据不同:quiet 的调用链上不许内联日志行缓冲(protectTask / iec104Task 的静态最坏链硬线),丢弃只累加计数、由 defaultTask 在 `syslog_drain()` 里按差值补留痕 | 五类记录(事故 / 告警 / 启动 / 操作 / 遥控)在模拟器上**永不丢弃**,"队列满丢记录"、"丢了几条"、"投递侧不许打日志"这三件事在模拟器上结构上不可见;UI 若把"投了"当成"落盘了",模拟器看不出来 | 主机单测 `test/host/sys_log_mcu/`(TC-SYSLOG-132 / 134 / 135:队列侧与启动前数组侧各自的计数与零留痕)+ `scripts/stack_report.py` 静态最坏链(真机侧唯一手段) | | **宿主 cwd 的记录文件互相污染** | 七类记录在模拟器落**当前工作目录**的 `sim_.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 断言)
`看门狗设计说明.md` §4.7 / §7 | -| **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `@test` | -| **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `@test` | +| **IWDG 硬件行为** | **不编译看门狗模块**(`simulator/` 无 `task_watchdog.c`,也无 IWDG 桩) | 独立看门狗,LSI 时钟驱动,超时真复位 | **真实复位行为、LSI 漂移、`RCC_CSR` 标志均不可建模**;位图/判定逻辑也不在模拟器内跑——改由主机侧单测覆盖 | 真机 `/serial-boot-test`(复位原因日志 + 故障注入 V2/V4)+ 看门狗主机侧纯逻辑单测(39 断言)
`看门狗设计说明.md` §4.7 / §7 | +| **CH395F 芯片** | 无(Winsock 代其 BSD API) | SPI 寄存器/命令/中断、PHY 协商、真实以太网帧 | 寄存器时序、命令回复、中断风暴、PHY 100M 全双工强制 | 板端 CH395F 套件(52 断言点)+ `/serial-boot-test` | +| **GD5F NAND** | 无 | SPI NAND:BBT 扫描、ECC、坏块管理、dhara FTL | 坏块/ECC/写入失败等路径完全不可测 | 板端 GD5F 套件(76 断言点)+ `/serial-boot-test` | | **SD2506 RTC** | 无 | I2C RTC:时间读写、闹钟、温度、电池电压 | 时间源相关(`sys_clock_init` 降级路径)在模拟器上不可触发 | 板端 RTC 套件(18 断言点)+ `sys_clock` 主机侧纯逻辑单测(已覆盖算术面) | -| **开机账本 boot_stats(上电次数 / 复位原因快照)** | **不编译** `App/util/boot_stats.c`(依赖 SD2506 用户 SRAM 与 `RCC_CSR`);UI「运行报告」页的四个数在模拟器上是 `simulator/main.c` 的**固定假数据**(`boot_count=7`、`seq_state=延续`、`rst_cause=上电复位`、104 四枚 3 / 128 / 0 / 4096、`uptime_s=0`) |`boot_stats_init()` 读 SRAM → 自增 → 写回 → 快照驻留;四个 104 观测计数由 iec104Task 真实累加(跨重连不清零、断电归零) | 「自增有没有真落 SRAM」「电池域掉电后是否落 RESTARTED」「写失败是否只少计不重复」「104 计数是否跨重连累计」四类事在模拟器上**结构上不可见**;黄金参考只拍得到显示口径(范围标签在不在屏上、值有没有串行、`boot_ready=0` 是否显 `--` 而不是 0) | 主机单测 `test/host/boot_stats/`(SRAM 状态机与四态判定)+ 真机 `@test`:上电一次看 `boot_count` +1、拔电池后落 RESTARTED、IWDG 复位后 `rst_cause=IWDG`、主站连一次看 `conn_up` +1 | +| **开机账本 boot_stats(上电次数 / 复位原因快照)** | **不编译** `App/util/boot_stats.c`(依赖 SD2506 用户 SRAM 与 `RCC_CSR`);UI「运行报告」页的四个数在模拟器上是 `simulator/main.c` 的**固定假数据**(`boot_count=7`、`seq_state=延续`、`rst_cause=上电复位`、104 四枚 3 / 128 / 0 / 4096、`uptime_s=0`) |`boot_stats_init()` 读 SRAM → 自增 → 写回 → 快照驻留;四个 104 观测计数由 iec104Task 真实累加(跨重连不清零、断电归零) | 「自增有没有真落 SRAM」「电池域掉电后是否落 RESTARTED」「写失败是否只少计不重复」「104 计数是否跨重连累计」四类事在模拟器上**结构上不可见**;黄金参考只拍得到显示口径(范围标签在不在屏上、值有没有串行、`boot_ready=0` 是否显 `--` 而不是 0) | 主机单测 `test/host/boot_stats/`(SRAM 状态机与四态判定)+ 真机 `/serial-boot-test`:上电一次看 `boot_count` +1、拔电池后落 RESTARTED、IWDG 复位后 `rst_cause=IWDG`、主站连一次看 `conn_up` +1 | | **TPAFE5160 ADC** | 无 | SPI ADC + BUSY EXTI 中断 | 中断驱动采集链路 | 待功能实现(上游 B1) | | **RS-485 半双工** | 无 | UART4 + DE 方向控制,DWT 微秒延时 | DE 收发切换时序(特有陷阱) | 待功能实现(上游 B2) | | **启动段硬件初始化** | 只跑纯软件部分 | I2C/SPI 外设初始化、BBT 扫描耗时、FatFS 挂载 | 启动段死机(无看门狗保护窗口)**只能在真机复现** | 真机开机日志判读(`项目启动顺序管理.md` §6 已落地) | @@ -87,7 +87,7 @@ ## 3. 什么时候必须回真机(决策规则) -模拟器回归通过后,**满足以下任一条件即必须走真机**(`@test` / 板端套件): +模拟器回归通过后,**满足以下任一条件即必须走真机**(`/serial-boot-test` / 板端套件): 1. **改动触及硬件访问层**:`Drivers/BSP/` 下任何驱动的寄存器/时序/中断代码 2. **改动触及 §2.3 硬件专属差距的任一子系统**的初始化或运行时行为 diff --git a/docs/测试说明/专项验证/链路延迟测试说明.md b/docs/测试说明/专项验证/链路延迟测试说明.md index 080a28f0..be876f5b 100644 --- a/docs/测试说明/专项验证/链路延迟测试说明.md +++ b/docs/测试说明/专项验证/链路延迟测试说明.md @@ -222,7 +222,7 @@ DTU-RemoteLCD 客户端;输出每帧 `req2first / total / recv 批次到达时 产品代码(`net_socket.c`/`ui_net_sink.c`/`ui_task.c`/`ui_port.c`)内各数行打点, 经静态环形缓冲(总计 <1.5KB BSS)上报;**产品构建下该宏未定义,探针代码编译消失**, -两种构建配置都必须过 `@build`(0 错 0 警)。 +两种构建配置都必须过 `/keil-build`(0 错 0 警)。 - 多打点站点必须用 `lat_probe_add_seg()` 按段名过滤(探针窗口是串行全局窗口,直接打点会跨段污染,D18 教训); - 启用探针的构建必须在用例入口调 `lat_time_init()`(漏调 = 全 0 假绿,D19 教训;`med==0` 判无效兜底)。 @@ -729,7 +729,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 ## 11. 约束核对(AGENTS.md) -- 每个测试阶段/修复 `@build`(0 错 0 警)+ `@test` 硬件闭环; +- 每个测试阶段/修复 `/keil-build`(0 错 0 警)+ `/serial-boot-test` 硬件闭环; - 新 `.c` 注册:`test/*.c` 仅 MCU 端编译 → **只注册 `MDK-ARM/STM32F407-Demo.uvprojx`**(Test 组; 模拟器 CMake 不含 test 文件,与 `ch395f_test_task.c` 等现状一致); - 新增 C 字符串字面量**纯 ASCII**(`[LAT]` 报告行均为英文),中文只写注释; @@ -737,7 +737,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 - `testTask` 栈 2048 words:用例函数只用几十字节局部变量; - 套件互斥宏体系不动:Phase 12 挂 HW 组,13/14 挂 NET 组,`#error` 自动防两层同启; - 非破坏性:不改 IP/卷/BBT;HW 层阶段独占 CH395F(netTask 自挂起)→ 标注"台架专用"; -- 生产代码探针全部在 `#ifdef TEST_LATENCY_PROBE` 内,产品构建编译消失,两种构建都须过 `@build`。 +- 生产代码探针全部在 `#ifdef TEST_LATENCY_PROBE` 内,产品构建编译消失,两种构建都须过 `/keil-build`。 --- diff --git a/docs/测试说明/单元测试/主机单测覆盖率口径.md b/docs/测试说明/单元测试/主机单测覆盖率口径.md index 18249476..9d94ed28 100644 --- a/docs/测试说明/单元测试/主机单测覆盖率口径.md +++ b/docs/测试说明/单元测试/主机单测覆盖率口径.md @@ -411,7 +411,7 @@ uint16_t ch395f_link_change_count(void); 对账循环使用"),假件要保留"对未在用槽位也能拿到项"的行为,否则对账兜底路径测不到。 **验收顺序**:`net_listener` 绿 → 立刻登记基线(哪怕只有它一个)→ `net_pending` → `net_events`; -每步都要 @build 0/0 + 全部套件绿 + --update-baseline + 补建 `TC-HOST-*` 族(本规格落地时族数 +每步都要 /keil-build 0/0 + 全部套件绿 + --update-baseline + 补建 `TC-HOST-*` 族(本规格落地时族数 16 → 22、套件 16 → 28,实测见上方落地进度),并且**先补族再往文档里引这些号**,否则 `id-check` 会按台账外新账卡提交 (本会话已被 `TC-NETCFG-028` 实测拦过一次)。 @@ -538,7 +538,7 @@ uint16_t ch395f_link_change_count(void); - 自愈触发集里混着 `FR_INVALID_NAME` / `FR_NO_PATH` / `FR_NO_FILE` / `FR_INVALID_OBJECT`——**比原报告的 6 号严重**:这类码下 unlink 自己的文件必然同样失败(同名同因),后果是**每轮排空删一次自己的历史日志且永远修不好**,正是 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 第 10 条严禁的归集。已新增代码侧唯一入口 `s_is_selfheal_code()`:触发集收敛为 `{FR_DISK_ERR, FR_INT_ERR, FR_NO_FILESYSTEM, FR_DENIED}`,逐码依据(含 `Lib/FatFs/ff.c` 行号)与显式排除清单写在资料块里;`soe_store.c` 侧同源但**取更保守子集**。TC-SYSLOG-101/101b/102/103/103b、TC-SOESTORE-022b。 **本轮刻意没做(后续项,含理由)**:① UDP 改走 `netMsgQueue`(需先设计 per-task 结果槽:`net_msg_t` 按值拷入队列、错误码只搭任务通知值回传,AGENTS 明文禁止给 `net_msg_t` 加回传字段,且必须配真机回归); -② `simulator/sim/sim_syslog.c:171` 那条与 3 号**同源的不可达截断支**(本轮编译验证不到它——模拟器走 CMake+VS,不在 `@build` 覆盖内,不交没编译过的改动);③ `syslog_post` 的**丢弃计数**(属新增对外可见信息,本轮没有实测频率支撑阈值); +② `simulator/sim/sim_syslog.c:171` 那条与 3 号**同源的不可达截断支**(本轮编译验证不到它——模拟器走 CMake+VS,不在 `/keil-build` 覆盖内,不交没编译过的改动);③ `syslog_post` 的**丢弃计数**(属新增对外可见信息,本轮没有实测频率支撑阈值); ④ `dbg_console_tx_timeout()` 的**读端接线**(现无生产调用者;候选位置 `task_watchdog.c` 的 `STALL snap=… req=… h=… mg=… cf=… seg=…` 是**被解析的证据行**,加字段要连解析端与台架判据一起改,本批不碰台架);⑤ 给 104 加来源地址日志(现网 `iec104_serv.c` 根本不打印来源 IP、无 `getpeername/ntoa`,**没有输出点可看**)。 **必须上台架复验(本轮按指示未碰台架;主机测覆盖不到的部分逐条列出)**: @@ -594,7 +594,7 @@ uint16_t ch395f_link_change_count(void); `net_listener` 125/126、`lftpd` 304/473、`iec104_link` 367/405 …);每套 runner 的断言数改前后相同 (如 `iec104` 97/97、`sys_clock` 100/100、`adc_input` 103/103、`net_cfg` 45/45)。**基线本轮不需要重登**(生产码零改动)。 -⚠ **`@build` 对本轮没有覆盖力**:`test/host/**` 不进 Keil 编译面也不进模拟器 CMake,字面量改动只能靠 +⚠ **`/keil-build` 对本轮没有覆盖力**:`test/host/**` 不进 Keil 编译面也不进模拟器 CMake,字面量改动只能靠 16 个 `*-unit-test` 钩子 + `test-cppcheck` + `test-coverage` 这三根闸验证——**别把"编译过了"当测试面改动的证据**。 **本轮新手法(值得抄进任何"批量改标签"作业)**: @@ -714,9 +714,9 @@ uint16_t ch395f_link_change_count(void); | 类别 | 体量 | 为什么不做 | 那它靠什么把关 | |---|---|---|---| -| 芯片驱动(寄存器/时序) | `ch395f.c`(801)、`gd5f2gq5ue.c`(689)、`sd2506.c`(264)、`tpafe5160.c`(157)、`rs485.c`(79) = **1,990 行** | 把 HAL 与寄存器假到底 ≈ **重写一遍驱动**,测的是自己的桩而不是硬件行为(SPI 分频必须 8、DMA 流、I2C 时序在桩里怎么编都"对") | 板级套件(`board-test-suite` skill)+ `@test` 真机日志 + 4 个 Fault 现场;陷阱逐条登记在 `docs/问题记录/CH395F_Trap_Records.md`、`docs/问题记录/GD5F2GQ5UE_Trap_Records.md`、`docs/问题记录/SD2506_Trap_Records.md` | +| 芯片驱动(寄存器/时序) | `ch395f.c`(801)、`gd5f2gq5ue.c`(689)、`sd2506.c`(264)、`tpafe5160.c`(157)、`rs485.c`(79) = **1,990 行** | 把 HAL 与寄存器假到底 ≈ **重写一遍驱动**,测的是自己的桩而不是硬件行为(SPI 分频必须 8、DMA 流、I2C 时序在桩里怎么编都"对") | 板级套件(`board-test-suite` skill)+ `/serial-boot-test` 真机日志 + 4 个 Fault 现场;陷阱逐条登记在 `docs/问题记录/CH395F_Trap_Records.md`、`docs/问题记录/GD5F2GQ5UE_Trap_Records.md`、`docs/问题记录/SD2506_Trap_Records.md` | | UI 侧 | 17 个 `.c` = **1,942 行** | 与 PC 模拟器是**同一份源码**,行为回归已由 `ui-golden`(菜单 8 状态 + 内容页 66 张像素黄金参考)覆盖;再写单测是第三份重复投资 | `ui-verify` skill 五步 + `docs/UI设计/UI_自动化测试流程.md` | -| 任务编排 | `default_task.c`(281)、`adc_task.c`(171)、`net_task.c`(123)、`protect_task.c`(90)、`ui_task.c`(71)、`rs485_task.c`(11) = **747 行** | 每个只有 4~5 个函数,主体是"按顺序调别人";真逻辑在被调模块里(那些已测或在 P1/P2)。给编排层写单测最容易写成"断言我调了几次"的自证 | 真机启动锚点计数 + IWDG `required_mask`(`@test`)+ `docs/架构设计/项目启动顺序管理.md` | +| 任务编排 | `default_task.c`(281)、`adc_task.c`(171)、`net_task.c`(123)、`protect_task.c`(90)、`ui_task.c`(71)、`rs485_task.c`(11) = **747 行** | 每个只有 4~5 个函数,主体是"按顺序调别人";真逻辑在被调模块里(那些已测或在 P1/P2)。给编排层写单测最容易写成"断言我调了几次"的自证 | 真机启动锚点计数 + IWDG `required_mask`(`/serial-boot-test`)+ `docs/架构设计/项目启动顺序管理.md` | | 内核前装配 | `app_main.c`(135) | 被测对象本身就是"`osKernelStart` 之前不许调内核 API"的时序,主机侧无法忠实复现(PC 上没有那个约束) | 同上 + `cubemx-survival` 审计 | **四个数字对得上**:`47 = 13(P1) + 5(P2) + 29(P3)`、`9,657 = 2,617 + 2,226 + 4,814`。 diff --git a/docs/测试说明/单元测试/看门狗单元测试说明.md b/docs/测试说明/单元测试/看门狗单元测试说明.md index 3adf036d..1ad646bf 100644 --- a/docs/测试说明/单元测试/看门狗单元测试说明.md +++ b/docs/测试说明/单元测试/看门狗单元测试说明.md @@ -3,7 +3,7 @@ > **模块**:Task Watchdog(IWDG + 任务心跳位图,`App/util/task_watchdog.c`) > **测试类型**:**主机侧纯逻辑单元测试**(见 §1.5)——在 PC 上离线验证 > 判定状态机,零硬件、零烧录、秒级、不占 MCU 的 RAM。 -> **状态**:✅ 已落地(47 断言,`@build` 0/0,已挂 pre-commit 钩子) +> **状态**:✅ 已落地(47 断言,`/keil-build` 0/0,已挂 pre-commit 钩子) > **日期**:2026-10-01(2026-10-25 v1.5 按位分账改造;改造史与设计依据见 `docs/子系统设计/看门狗设计说明.md` 变更记录——本文档不再设变更记录节) > **关联文档**: > [`../../子系统设计/看门狗设计说明.md`](../../子系统设计/看门狗设计说明.md)(被测行为的设计依据)、 @@ -55,7 +55,7 @@ test/host/watchdog/watchdog_test.c | `wd_platform_tick_ms()` | `HAL_GetTick()` | 返回单测可控的 `s_tick` | > **生产端零增量**:三个钩子在 MCU 构建下就是原来的寄存器写 / HAL 调用,行为逐字一致, -> 无额外分支、无额外符号(`@build` 0/0 已验)。平台默认实现由 +> 无额外分支、无额外符号(`/keil-build` 0/0 已验)。平台默认实现由 > `#ifndef TASK_WATCHDOG_UNIT_TEST` 守卫;测试专用复位 `task_wd_test_reset()` > 仅在主机侧编译(MCU 构建零代码、零符号)。 @@ -93,7 +93,7 @@ test/host/watchdog/watchdog_test.c | **门禁层级** | **主机侧纯逻辑单测**(介于静态门禁与板端测试套件之间):主机侧、秒级、纯逻辑,比板端套件便宜、比静态检查更强 | | **pre-commit 钩子** | `watchdog-unit-test`(本地钩子,`.pre-commit-config.yaml` §7) | | **汇总行** | `=== Task Watchdog Tests: N/N PASSED (failed=0) ===` | -| **与板端测试的关系** | 互补:本单测保证**判定逻辑**正确;板端 `@test` 故障注入(V2/V4,见 `docs/子系统设计/看门狗设计说明.md` §7 验证记录)保证**真机复位链路**正确 | +| **与板端测试的关系** | 互补:本单测保证**判定逻辑**正确;板端 `/serial-boot-test` 故障注入(V2/V4,见 `docs/子系统设计/看门狗设计说明.md` §7 验证记录)保证**真机复位链路**正确 | --- @@ -174,4 +174,4 @@ if (s_miss_streak >= WD_MISS_TOLERANCE) { DBG_ERROR("[WD] task stall: ..."); } | 长跑(主机侧回归) | ≥30 min 无假复位(`miss_count` 稳定) | ✅ 已台架验收(V5,30 min / 120×FTP 零复位,2026-10-03) | > 本单测**不能**替代板端验证:IWDG 实际溢出时间、LSI 频率漂移、`SHCSR`/复位原因 -> 寄存器的真机行为,仍需 `@test` 台架闭环确认。 +> 寄存器的真机行为,仍需 `/serial-boot-test` 台架闭环确认。 diff --git a/docs/测试说明/单元测试/系统时钟单元测试说明.md b/docs/测试说明/单元测试/系统时钟单元测试说明.md index 0c23222a..c6bd01ec 100644 --- a/docs/测试说明/单元测试/系统时钟单元测试说明.md +++ b/docs/测试说明/单元测试/系统时钟单元测试说明.md @@ -3,7 +3,7 @@ > **模块**:System Clock(墙钟时间维护,`App/util/sys_clock.c`) > **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证时间换算与 > 墙钟基准逻辑,零硬件、零烧录、秒级、不占 MCU 的 RAM。 -> **状态**:✅ 已落地(20 用例 / 100 断言点,`@build` 0/0,已挂 pre-commit 钩子) +> **状态**:✅ 已落地(20 用例 / 100 断言点,`/keil-build` 0/0,已挂 pre-commit 钩子) > **日期**:2026-10-02 > **关联文档**: > [`../../../AGENTS.md`](../../../AGENTS.md)(平台约束)、 @@ -234,7 +234,7 @@ static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d) { /* m: 1-based > 注:`5J ≡ -2J (mod 7)`,取 `5J` 保证各项非负(无符号 `%` 安全)。 > 修复后经 Python `datetime` 独立核对 **1970~2099 全部 47,482 天零错误**; -> `@build` 0 错误 0 警告。 +> `/keil-build` 0 错误 0 警告。 ### 5.5 校验数据来源 @@ -252,4 +252,4 @@ TC-SYSCLK-019 的期望星期由 Python `datetime.date(...).weekday()` 独立生 | 板端测试套件 / 主机侧回归 | 真机 SD2506 I2C 读写、掉电保持、与 PC 对时联动 | 见 [`SD2506_Test_Guide.md`](../板级测试/SD2506_Test_Guide.md) | > 本单测**不替代**板端验证:I2C 时序、BCD 编解码、掉电保持、温度读等真机行为, -> 仍需 `@test` 台架闭环确认。 +> 仍需 `/serial-boot-test` 台架闭环确认。 diff --git a/docs/测试说明/单元测试/配置文件单元测试说明.md b/docs/测试说明/单元测试/配置文件单元测试说明.md index 506e88cb..b5fe3290 100644 --- a/docs/测试说明/单元测试/配置文件单元测试说明.md +++ b/docs/测试说明/单元测试/配置文件单元测试说明.md @@ -3,7 +3,7 @@ > **模块**:Config File Engine(通用配置文件引擎,`App/cfg/cfg_file.c`) > **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证 INI 解析、 > 值类型编解码、段注册校验与 load/save/poll 契约,零硬件、零烧录、毫秒级、不占 MCU 的 RAM。 -> **状态**:✅ 已落地(20 用例 / 139 断言点,`@build` 0/0,已挂 pre-commit 钩子) +> **状态**:✅ 已落地(20 用例 / 139 断言点,`/keil-build` 0/0,已挂 pre-commit 钩子) > **日期**:2026-10-02 > **关联文档**: > [`../../../AGENTS.md`](../../../AGENTS.md)(平台约束 · FatFs/内存铁律)、 @@ -269,7 +269,7 @@ if (s_encode(p_key, ..., v, sizeof(v)) >= 0) { } ``` -修复后 `TC-CONFIG-041` / `TC-CONFIG-042` 转 PASS,`@build` 0 errors 0 warnings。 +修复后 `TC-CONFIG-041` / `TC-CONFIG-042` 转 PASS,`/keil-build` 0 errors 0 warnings。 ### 5.5 建议后续动作 diff --git a/docs/测试说明/板级测试/CH395F_Test_Guide.md b/docs/测试说明/板级测试/CH395F_Test_Guide.md index 22215f3b..a817d260 100644 --- a/docs/测试说明/板级测试/CH395F_Test_Guide.md +++ b/docs/测试说明/板级测试/CH395F_Test_Guide.md @@ -1121,7 +1121,7 @@ CLOSE 才落 00。与 TRAP-NET-07 的 `0x05/0x00` 驻留签名同源;`sock_res WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠 TRAP-NET-09 open 重试吸收。 **运行方法**:`ch395f_test.h` 启用 `ENABLE_PHASE15_TESTS`(自动进入硬件层模式, -勿与 NET 层 Phase 混选)→ `@build` → 烧录 → 串口看 `=== Phase 15: 20/20 PASSED ===`。 +勿与 NET 层 Phase 混选)→ `/keil-build` → 烧录 → 串口看 `=== Phase 15: 20/20 PASSED ===`。 --- diff --git a/docs/测试说明/板级测试/FTP_Test_Guide.md b/docs/测试说明/板级测试/FTP_Test_Guide.md index dc2c882b..9cf49a63 100644 --- a/docs/测试说明/板级测试/FTP_Test_Guide.md +++ b/docs/测试说明/板级测试/FTP_Test_Guide.md @@ -40,7 +40,7 @@ ## 3. 编译与运行(固件侧) - **默认构建即可提供 FTP Server**,无需切换测试套件。 -- 任何改动后必须执行 `@build`(即 `keil-build` skill:`.dsh/skills/keil-build/scripts/keil_build.py`),确认 **0 Error(s) 0 Warning(s)** 再烧录(遵循 AGENTS.md 铁律)。 +- 任何改动后必须执行 `/keil-build`(即 `keil-build` skill:`.dsh/skills/keil-build/scripts/keil_build.py`),确认 **0 Error(s) 0 Warning(s)** 再烧录(遵循 AGENTS.md 铁律)。 - 若需跑**自动化 FTP 文件传输用例**:在 `test/board/test_config.h` 选择 `TEST_SUITE_CH395F`,并在 `test/board/ch395f_test.h` 打开 `ENABLE_PHASE8_TESTS`(文件传输阶段),重新编译烧录。 - 烧录后打开串口(115200bps)观察: - `net_init` 完成、PHY link up; @@ -192,7 +192,7 @@ - **串口日志 + `DBG_*`**:在 `lftpd.c` / `net_socket` 关键路径加 `DBG_ERROR/INFO` 打印(注意编码安全铁律——只用 Edit 改中文源文件,禁止用 `Get/Set-Content` 整文件重写)。 - **抓包**:`ftp_capture.py` 或 Wireshark 过滤 `tcp.port==21`。 -- **编译验证**:`@build` 必须 0/0 再烧录(AGENTS.md 铁律)。 +- **编译验证**:`/keil-build` 必须 0/0 再烧录(AGENTS.md 铁律)。 - **套件切换**:`test/board/test_config.h` 改 `TEST_SUITE_CH395F` / `TEST_SUITE_GD5F` / `TEST_SUITE_STORAGE`(三选一,互斥)。 --- diff --git a/docs/测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md b/docs/测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md index 1f026a6f..c867848b 100644 --- a/docs/测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md +++ b/docs/测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md @@ -93,7 +93,7 @@ ECC 状态(`ECCS1:ECCS0`):`00`=无错;`01/10/11`=纠正 1/2/3 bit;`10` 1. 在 `test/board/test_config.h` 取消注释所需的 `TEST_SUITE_*` 宏(如 `TEST_SUITE_GD5F`), 需要写/擦类用例时再打开 `ENABLE_DESTRUCTIVE_TESTS`; -2. 编译(`@build`)确认 0 错误 0 警告; +2. 编译(`/keil-build`)确认 0 错误 0 警告; 3. 烧录后串口观察 `mode: ...` 横幅、`[NAND-TEST]` 日志与各 TC 的 `PASS/FAIL`,结尾打印 `=== GD5F2GQ5UE Tests: X/Y PASSED ===`; 4. 不选任何 `TEST_SUITE_*` 时为正常产品构建(`ch395fTestTask` 空闲,不跑测试)。 diff --git a/docs/测试说明/板级测试/实机测试实施指南.md b/docs/测试说明/板级测试/实机测试实施指南.md index c0bc5c07..b26956c6 100644 --- a/docs/测试说明/板级测试/实机测试实施指南.md +++ b/docs/测试说明/板级测试/实机测试实施指南.md @@ -22,14 +22,14 @@ | 步骤 | 事项 | 状态 | 章节 | |---|---|---|---| | **步骤 1** | 环境预检(P0-A,每次上台架必跑) | 常设 | §2 | -| **步骤 2** | 产品 `@test` 闭环 + 自检结果判定(P0-B) | ✅ 已闭环 | §3 | +| **步骤 2** | 产品 `/serial-boot-test` 闭环 + 自检结果判定(P0-B) | ✅ 已闭环 | §3 | | **步骤 3** | IWDG 故障注入 V2/V4 + 长跑 V5(P1-A) | ✅ 已闭环 | §4 | | **步骤 4** | 堆低水位告警实测(P1-B) | ✅ 已闭环 | §5 | | **步骤 5** | GD5F 错误通道回归(P2-A) | ✅ 已闭环 | §6 | | **步骤 6** | 网络稳定性与基线重锁(P2-B / 日志静默观察项 + 队列往返基线待重验) | ◐ 部分闭环(7.1/7.2 ✅;7.3 待 PC peer) | §7 | | **步骤 7** | 工程卫生(P3 + 待核实项) | ✅ 已闭环 | §8 | -> **每步出口**:`@build` 0/0 → 一轮 `@test` PASS → ST-LINK 全片回读 `No difference found` → +> **每步出口**:`/keil-build` 0/0 → 一轮 `/serial-boot-test` PASS → ST-LINK 全片回读 `No difference found` → > 上游登记销项 + 把结论写进正文对应小节。 > > **参考**(非执行步骤):§9 执行注意(实测陷阱)· §10 已完成事项与证据去向 · §11 引用闭环。 @@ -51,7 +51,7 @@ | A7 | 串口端口正确 | 检查 `.serialtest/config.json` 的 `portName` | 端口单一配置源 = 此文件(当前 COM6,2026-09-21 台架重插后由 COM7 变更);变更后先杀旧 daemon 再 `open` | | A8 | 串口助手 daemon 可用 | `& '.venv\Scripts\python.exe' .dsh/skills/serial-assistant/scripts/serial_tool.py status` | 返回 JSON,`open: true/false` 可读 | -> ⚠️ **A4 的陷阱**(2026-10-02 实测):`@build` 是**增量**构建,`run_all_suites.py` 跑完 +> ⚠️ **A4 的陷阱**(2026-10-02 实测):`/keil-build` 是**增量**构建,`run_all_suites.py` 跑完 > 套件后 `.map` 会停在**测试套件体积**;仅还原 `test_config.h` 不够,**必须再编一次产品固件** > (`run_all_suites.py` 已在 `finally` 内自动补这一步)。 @@ -59,13 +59,13 @@ --- -## 3. 步骤 2:产品 @test 闭环 + 自检结果判定(P0-B) · ✅ 已闭环 +## 3. 步骤 2:产品 /serial-boot-test 闭环 + 自检结果判定(P0-B) · ✅ 已闭环 > S7 判定已落地并投用;A1~A4 已闭环。**实施细节与真机验证证据见 §10 状态与去向**,本节只给复跑命令与判据。 ### 3.1 执行 -`@test` 闭环除串口标记外,还判定设备侧自检结论(v1 只记故障、不记通过): +`/serial-boot-test` 闭环除串口标记外,还判定设备侧自检结论(v1 只记故障、不记通过): `selftest.log` **缺失 / 0 字节 → 无故障 → PASS**;`filesize/64 > 0`(任一 64B 定长记录) = 有故障 → FAIL,并打印故障条目(时间戳 + 描述)便于定位。 @@ -121,11 +121,11 @@ ```bash # --- V2/V4(故障注入,需临时改码)--- -# 1) 基线:跑一轮产品 @test 确认无复位 +# 1) 基线:跑一轮产品 /serial-boot-test 确认无复位 & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py # 2) 注入:按 `docs/子系统设计/看门狗设计说明.md` §7 在 netTask 循环插 while(1);(或 taskDISABLE_INTERRUPTS(); while(1);) -# —— 改完 @build,然后烧录 +# —— 改完 /keil-build,然后烧录 & '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py & '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex @@ -135,7 +135,7 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L & $cli -c SWD -CmpFile MDK-ARM\STM32F407-Demo\STM32F407-Demo.hex 0x08000000 # 判据:No difference found -# 5) 撤销注入,@build 回产品态,再跑一轮 @test 确认正常 +# 5) 撤销注入,/keil-build 回产品态,再跑一轮 /serial-boot-test 确认正常 # --- V5(长跑,需真实负载)--- # 1) 用 test/host/ftp_speed_test.py / ftp_up_dl_test.py 持续传输大文件 @@ -172,7 +172,7 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L **执行步骤**: ```bash # 1) 代码改动(约 15 行 + 1 个编译期常量)在 defaultTask 低频循环内 -# 2) @build 0/0 后,与 §4.2 V5 长跑合并执行 +# 2) /keil-build 0/0 后,与 §4.2 V5 长跑合并执行 # 3) 长跑结束读取 min_ever: # - 串口 [HEAP] 告警行(若有) # - 或 FTP 取 /log/selftest.log diff --git a/docs/测试说明/流程与规范/测试开发标准流程.md b/docs/测试说明/流程与规范/测试开发标准流程.md index 6d44c784..99a5f8b8 100644 --- a/docs/测试说明/流程与规范/测试开发标准流程.md +++ b/docs/测试说明/流程与规范/测试开发标准流程.md @@ -7,7 +7,7 @@ | 文档 | 内容 | |---|---| -| `docs/测试说明/流程与规范/自动化测试流程.md` | @test 闭环细节(命令 / 标记 / 失败模式) | +| `docs/测试说明/流程与规范/自动化测试流程.md` | /serial-boot-test 闭环细节(命令 / 标记 / 失败模式) | | `docs/测试说明/流程与规范/测试文档模板规范.md` | 测试文档模板、命名规则(TM / TC / DP) | | `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试 / 采集工具与陷阱(Python 串口助手) | | `docs/测试说明/测试工具/串口助手使用指南.md` | 串口助手命令用法 / 工作流 / 排障 | @@ -25,10 +25,10 @@ | 名称 | 载体 | 抓住什么 | |---|---|---| -| 静态门禁 | pre-commit 钩子(cppcheck / 代码规范 / 乱码 / 空白 / gitlint)+ `@build` | 编码规范、静态缺陷、任何编译警告 | +| 静态门禁 | pre-commit 钩子(cppcheck / 代码规范 / 乱码 / 空白 / gitlint)+ `/keil-build` | 编码规范、静态缺陷、任何编译警告 | | **主机侧纯逻辑单测** | `test/host/<模块>/run_unit_test.py` + MSVC 离线编译真实源码(`#include` 纳入)+ 平台桩 | **纯逻辑模块的边界穷举**(节流窗口、计数阈值、整数回绕)——板端无法覆盖、无需硬件 | | 板端测试套件 | testTask + `test/board/test_config.h` 套件 | 驱动 / 协议功能(串口 `[PASS]/[FAIL]` + `=== ... PASSED` 汇总) | -| 主机侧回归 | `@test` 串口日志标记 + LVGL 模拟器像素 / 池水位回归 | 启动序列、UI 渲染、LVGL 内存池水位 | +| 主机侧回归 | `/serial-boot-test` 串口日志标记 + LVGL 模拟器像素 / 池水位回归 | 启动序列、UI 渲染、LVGL 内存池水位 | | 人工目检 | 上机屏幕、PC 端交互工具 | 视觉内容、自动化无法覆盖的场景 | > **主机侧纯逻辑单测与主机侧回归的区别**:主机侧回归的模拟器回归依赖 SDL / LVGL 与图形输出;主机侧纯逻辑单测是**纯计算**单测 @@ -43,9 +43,9 @@ | 改动类型 | 必跑门禁 | |---|---| -| 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 `@build` | -| App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 `@test`(模块标记) | -| **有主机侧纯逻辑单测的模块(如看门狗)** | G0(含该模块单测钩子,自动)+ G1 `@build`(+ 台架项另按需 G2) | +| 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 `/keil-build` | +| App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 `/serial-boot-test`(模块标记) | +| **有主机侧纯逻辑单测的模块(如看门狗)** | G0(含该模块单测钩子,自动)+ G1 `/keil-build`(+ 台架项另按需 G2) | | UI(菜单 / 内容页 / 字体 / 样式 / 几何) | G0 + G1 + G2 + G3 ui-verify 五步 | | UI 新增中文文案 | 另需 font-regen(字体重生成 + 回归),**先于提交** | | `test/` 套件或 PC 端工具代码 | G1 + G2(套件标题标记 / 工具自测) | @@ -74,7 +74,7 @@ G0~G3 命令详见 §6 / §7;ui-verify 完整命令见 `UI_自动化测试流 ### 2.3 三条铁律 1. **先写判定标准,再写测试代码** —— 文档里先写"什么算过 / 什么算失败"(精确到返回值 / 日志字符串 / 像素参考),再实现,避免"测了但不知道算不算过"。 -2. **判定标准必须可机器判定** —— 必须能落到:串口日志标记(→ `@test -Required`)、返回值 / 寄存器值(→ `TEST_CHECK`)、像素参考(→ 模拟器回归)、池水位(→ 基线常量)之一。无法机器判定的标准只能放 人工目检人工层。 +2. **判定标准必须可机器判定** —— 必须能落到:串口日志标记(→ `run_boot_test.py --required`)、返回值 / 寄存器值(→ `TEST_CHECK`)、像素参考(→ 模拟器回归)、池水位(→ 基线常量)之一。无法机器判定的标准只能放 人工目检人工层。 3. **每条设计原则必须有对应用例** —— 覆盖表中每个 DP-xx 至少一个 TC;新功能设计时先定 DP,再设计 TC。 --- @@ -170,7 +170,7 @@ static void phaseN_run(void) { } ``` -**套件运行结束行的 `=== <名称> Tests: N/N PASSED (failed=0) ===` 即该套件 `@test` 回归的默认标记**(各套件确切字符串见对应模块 Test_Guide,如 `=== Storage Tests:` / `=== GD5F2GQ5UE Tests:`)。 +**套件运行结束行的 `=== <名称> Tests: N/N PASSED (failed=0) ===` 即该套件 `/serial-boot-test` 回归的默认标记**(各套件确切字符串见对应模块 Test_Guide,如 `=== Storage Tests:` / `=== GD5F2GQ5UE Tests:`)。 ### 3.3 编码约定 @@ -204,9 +204,9 @@ static void phaseN_run(void) { 2. `test/board/test_config.h`:补 `TEST_SUITE_XXX` 注释开关 + 互斥 `#error` 加项 + 分发分支(include + `TEST_SUITE_NAME`); 3. `test/board/test_config.c`:`start_test_task()` 加分发分支; 4. **双端注册核查**(`new-source-file` skill):新 `.c` 必须加入 `MDK-ARM/STM32F407-Demo.uvprojx`;test/ 代码仅 MCU 端编译,**不**登记模拟器 CMakeLists(UI / App 共用代码才需双端); -5. `@build` → 0 错 0 警; +5. `/keil-build` → 0 错 0 警; 6. 立 `docs/测试说明/<模块>_Test_Guide.md`(§2 模板); -7. `@test` 以套件标题行为 `-Required` 标记跑通回归。 +7. `/serial-boot-test` 以套件标题行为 `-Required` 标记跑通回归。 ### 3.6 多套件回归编排(`test/run_all_suites.py`,2026-10-01 加) @@ -229,7 +229,7 @@ static void phaseN_run(void) { #### 3.6.1 `--check-build`:只编译不烧录(2026-10-02 加) -**解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `@build` 天天跑, +**解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `/keil-build` 天天跑, 但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件 7 个配置实例)。 @@ -239,9 +239,9 @@ static void phaseN_run(void) { | 项 | 说明 | |---|---| -| 做什么 | 逐套件:改 `test_config.h` 宏 → `@build` → 记 PASS/FAIL;结束后无条件还原 | +| 做什么 | 逐套件:改 `test_config.h` 宏 → `/keil-build` → 记 PASS/FAIL;结束后无条件还原 | | **不需要** | MCU / 串口 / ST-LINK(**纯离机**,可在 CI 或任何有 Keil 的机器上跑) | -| 判定 | `@build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL | +| 判定 | `/keil-build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL | | RAM 溢出 | "编译过但链接失败"这类问题会**自然暴露**为 `L6406E`——这正是想要的 | | 退出码 | 同完整模式:0=全 PASS / 1=有套件编不过 / 2=用法缺文件 | @@ -310,7 +310,7 @@ static void phaseN_run(void) { **约束**: -- 平台层的抽取必须**零行为变化、零额外开销**(MCU 端默认实现即原逻辑;`@build` 须 0/0); +- 平台层的抽取必须**零行为变化、零额外开销**(MCU 端默认实现即原逻辑;`/keil-build` 须 0/0); - 测试专用钩子(如静态态复位)用 `#ifdef <模块>_UNIT_TEST` 守卫,MCU 构建不编入(零符号); - 单测**不能替代**板端验证:真机寄存器 / 复位原因 / 时序仍须走板端测试套件 / 主机侧回归台架闭环。 @@ -348,7 +348,7 @@ static void phaseN_run(void) { - **test/ 自动化脚本为纯 Python 标准库**(2026-09-20 由 PowerShell 迁移:绕开控制台代码页/执行策略陷阱,全部 I/O 显式 UTF-8/字节级处理;`requirements.txt` 仍须纯 ASCII——pip 按 GBK 代码页解码); - **Python 统一用仓库 `.venv`**;第三方依赖必须进 `requirements.txt`(纯 ASCII); - 落位:板端交互工具 → `test/`;模拟器回归 → `simulator/tests/`; -- 新工具引入新的"通过标记"时,必须登记到 §6.2 标记表,供 `@test` 闭环复用。 +- 新工具引入新的"通过标记"时,必须登记到 §6.2 标记表,供 `/serial-boot-test` 闭环复用。 --- @@ -386,13 +386,13 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警 --- -## 6. 自动化闭环(@test) +## 6. 自动化闭环(/serial-boot-test) ### 6.1 流程 ```mermaid flowchart TD - A[代码改完] --> B["G1 @build(0 错 0 警,否则修复重编)"] + A[代码改完] --> B["G1 /keil-build(0 错 0 警,否则修复重编)"] B --> C["run_boot_test.py:烧录前武装采集(daemon 全量日志兜底)"] C --> D["@download 烧录(Reset & Run 触发重启)"] D --> E[提取日志 + verify_log 标记校验] @@ -477,7 +477,7 @@ flowchart TD | 代码变更影响判定标准 | 立即把模块 Test_Guide **正文**改到与代码一致(现状说了算,不再记流水) | | 有意改视觉 | 刷黄金参考 + 重生成目检 PNG + 把新口径写进 `UI_自动化测试流程.md` 正文对应小节 | | 新增测试工具 / 新标记 | `自动化测试流程.md` 命令清单登记(§4.2) | -| 栈 / 堆预算相关改动或基线重采 | 把 `@test` 的 boot_log 快照(`%TEMP%\serial-boot-test\boot_log_*.txt`)存入 `docs/测试说明/测试数据/boot_log_data/` 并在其 §3 清单登记(防临时目录被清理 / 长跑日志截头丢 `[STACK]` 行) | +| 栈 / 堆预算相关改动或基线重采 | 把 `/serial-boot-test` 的 boot_log 快照(`%TEMP%\serial-boot-test\boot_log_*.txt`)存入 `docs/测试说明/测试数据/boot_log_data/` 并在其 §3 清单登记(防临时目录被清理 / 长跑日志截头丢 `[STACK]` 行) | > 文档与代码一体两面:**代码改了文档没同步 = 未完成**。 @@ -488,8 +488,8 @@ flowchart TD ```mermaid flowchart LR A["文档:规划 + 判定标准
(Test_Guide)"] --> B["代码:套件 / 工具实现
+ 双端注册核查"] - B --> C["G1 @build
0 错 0 警"] - C --> D["G2 @test
采集→烧录→校验"] + B --> C["G1 /keil-build
0 错 0 警"] + C --> D["G2 /serial-boot-test
采集→烧录→校验"] D -->|PASS| E["G3 ui-verify
(UI 改动时)"] E --> F["人工目检上机目检"] F --> G["提交
G0 门禁 + 逻辑提交"] @@ -514,21 +514,21 @@ flowchart LR | **静态门禁** | ✅ | `pre-commit run --all-files`(提交期 47 根快钩子)+「手动阶段门禁」步逐根 `--hook-stage manual` 跑 `cppcheck` / `test-cppcheck` / `test-coverage` | 纯 Python + cppcheck,零硬件 | | **主机侧纯逻辑单测** | ✅ | `test/host/**/run_unit_test.py`(MSVC 离线编译) | 纯标准库 + `cl.exe`,零硬件 | | 板端测试套件 | ⛔ | `test/board/` | 需真机 + 串口 + ST-LINK | -| 主机侧回归 `@test` 闭环 | ⛔ | 烧录 + 串口采集 | 同上 | +| 主机侧回归 `/serial-boot-test` 闭环 | ⛔ | 烧录 + 串口采集 | 同上 | | 人工目检 | ⛔ | 上机屏幕 | 人的判断 | **明确不做的两件事(都是刻意为之)**: -1. **不跑 `@build`(Keil UV4)**——需本机 Keil 授权,runner 上不具备。静态门禁的 cppcheck + 代码规范 +1. **不跑 `/keil-build`(Keil UV4)**——需本机 Keil 授权,runner 上不具备。静态门禁的 cppcheck + 代码规范 已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(`AGENTS.md`)。 2. **`mem-numbers` 钩子按 `.map` 有无自适应跳过**——它的事实源是 `MDK-ARM/**/*.map` - (Keil 构建产物,已 `.gitignore`),无 `@build` 即无 `.map`。故 workflow 在**检测不到 + (Keil 构建产物,已 `.gitignore`),无 `/keil-build` 即无 `.map`。故 workflow 在**检测不到 `.map` 时自动跳过该钩子**(`SKIP=mem-numbers`,钩子显示 `Skipped` 而非 `Passed`—— 是"不适用",不是"通过");有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。 > 🔴 **门禁盲区(2026-10-02 记录)**:跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**—— > 新增任务/栈/缓冲/代码导致的体积增长**不会在 CI 被拦**。故该跳过以 `::warning::` 明示 -> (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `@build`(0/0) +> (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `/keil-build`(0/0) > → `python scripts/check_mem_numbers.py --scan`。详见 > [`../../CI/远程CI完整指南.md`](../../CI/远程CI完整指南.md) §0.0。 @@ -578,7 +578,7 @@ CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runn - **`.map` 自适应与 CI 完全一致**:本地有 `.map` 就照常跑 `mem-numbers`,没有就跳过。 - 退出码:`0` 通过 / `1` 门禁或单测失败 / `2` 环境错误。 -> ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`@build`、板端测试套件/主机侧回归台架项都只存在于本地。 +> ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`/keil-build`、板端测试套件/主机侧回归台架项都只存在于本地。 > 提交前跑一遍 `ci_local.py` 仍是**强制**动作,见 §7.1(`pre-commit run --all-files` 只覆盖提交期 47 根, > 走不到 `commit-msg` 与 manual 阶段)。 @@ -620,7 +620,7 @@ CI 就静默停摆(2026-10-11 实测 run 16~21 共 12 个 job 零日志)。 | `docs/测试说明/板级测试/SD2506_Test_Guide.md` | RTC(TC-RTC-xxx) | | `docs/测试说明/板级测试/FTP_Test_Guide.md` | FTP 服务(手动 + 阶段 8 自动化) | | `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试工具(Python 串口助手) | -| `docs/测试说明/流程与规范/自动化测试流程.md` | @test 闭环细节 | +| `docs/测试说明/流程与规范/自动化测试流程.md` | /serial-boot-test 闭环细节 | | `docs/测试说明/流程与规范/测试文档模板规范.md` | 测试文档模板与命名 | | `docs/测试说明/单元测试/看门狗单元测试说明.md` | 看门狗判定状态机(主机侧纯逻辑单测) | | `docs/测试说明/单元测试/系统时钟单元测试说明.md` | 系统时钟换算(主机侧纯逻辑单测) | diff --git a/docs/测试说明/流程与规范/测试文档模板规范.md b/docs/测试说明/流程与规范/测试文档模板规范.md index 2445e96b..a79bc1a8 100644 --- a/docs/测试说明/流程与规范/测试文档模板规范.md +++ b/docs/测试说明/流程与规范/测试文档模板规范.md @@ -1,6 +1,6 @@ # 测试文档模板规范 -本文档定义本项目测试文档的统一模板与编写规范,确保各模块(CH395F、GD5F2GQ5UE、SD2506 RTC、存储等)测试文档格式一致、可追溯,并能直接接入 `@test` 自动化闭环与门禁体系。 +本文档定义本项目测试文档的统一模板与编写规范,确保各模块(CH395F、GD5F2GQ5UE、SD2506 RTC、存储等)测试文档格式一致、可追溯,并能直接接入 `/serial-boot-test` 自动化闭环与门禁体系。 --- @@ -9,7 +9,7 @@ | 文档 | 关系 | |---|---| | `测试开发标准流程.md` | 测试开发总流程(规划 → 实现 → 门禁 → 沉淀);本文是其"测试规划(文档先行)"环节的可执行模板 | -| `自动化测试流程.md` | `@test` 闭环命令、参数与失败模式;本文"回归接入"章节引用它 | +| `自动化测试流程.md` | `/serial-boot-test` 闭环命令、参数与失败模式;本文"回归接入"章节引用它 | | `docs/UI设计/UI_自动化测试流程.md` | UI 类改动的模拟器回归门禁 | | `docs/问题记录/<模块>_Trap_Records.md` | 陷阱记录;测试文档"已知陷阱"章节引用它 | @@ -27,7 +27,7 @@ ### 1.2 设计原则覆盖表(原则短语 → 描述 → 验证用例) ### 1.3 测试环境(拓扑、工具链、硬件版本、PC 端依赖) ### 1.4 启用方法(编译开关 + 运行命令) -### 1.5 回归接入(@test 标记 / PC 端脚本 / 门禁层级) +### 1.5 回归接入(/serial-boot-test 标记 / PC 端脚本 / 门禁层级) ## 2~N. 阶段 x:[阶段名] - 阶段元信息表(含阶段宏) @@ -57,7 +57,7 @@ | 项 | 说明 | |---|---| -| `@test` 标记 | 套件结尾汇总行(如 `=== Storage Tests:`)或阶段标题行;**必须与串口实际输出逐字一致**(`verify_log.py` 子串匹配) | +| `/serial-boot-test` 标记 | 套件结尾汇总行(如 `=== Storage Tests:`)或阶段标题行;**必须与串口实际输出逐字一致**(`verify_log.py` 子串匹配) | | 禁止模式 | 默认 `FAIL;ERROR;HardFault;assert`,可按模块增补 | | PC 端脚本 | `test/*.py` 工具清单与调用方式 | | 门禁层级 | 板端测试套件 / 主机侧回归(串口标记 + 模拟器回归)/ 人工目检,见 `测试开发标准流程.md` §1 | @@ -76,7 +76,7 @@ | **类型** | 独立运行 / 需 PC 配合 / 需物理操作 / 在 xxTask 中运行 | | **耗时** | 预估执行时间 | | **入口** | 代码入口函数或宏定义 | -| **出口** | 阶段结束标志(如串口输出内容,即 `@test` 可校验的标记) | +| **出口** | 阶段结束标志(如串口输出内容,即 `/serial-boot-test` 可校验的标记) | | **阶段宏** | 对应使能宏(如 `ENABLE_PHASE1_TESTS`) | --- @@ -195,7 +195,7 @@ TC-{MOD}-{NNN} | 通道 | 判定方 | 适用 | |---|---|---| -| 串口日志标记 | `@test` / `verify_log.py` 子串匹配 | 启动序列、阶段标题行、关键事件 | +| 串口日志标记 | `/serial-boot-test` / `verify_log.py` 子串匹配 | 启动序列、阶段标题行、关键事件 | | 返回值 / 寄存器 / 数据 | 板端 `XXX_TEST_CHECK` 断言 | 驱动命令、协议交互、数据完整性 | | 像素参考 | LVGL 模拟器回归(`simulator/tests/`) | UI 渲染、字体、样式 | | 内存池水位 | 模拟器池基线 / 往返常量 | LVGL 对象泄漏、结构漂移 | @@ -256,7 +256,7 @@ TC-{MOD}-{NNN} | 项 | 值 | |---|---| -| `@test` 标记 | `=== XXX Tests:`(套件结尾汇总行的确切字符串) | +| `/serial-boot-test` 标记 | `=== XXX Tests:`(套件结尾汇总行的确切字符串) | | PC 端脚本 | `test/xxx_test.py` | | 门禁层级 | 板端测试套件 + 主机侧回归 | diff --git a/docs/测试说明/流程与规范/自动化测试流程.md b/docs/测试说明/流程与规范/自动化测试流程.md index f9a14d00..2d7f1f24 100644 --- a/docs/测试说明/流程与规范/自动化测试流程.md +++ b/docs/测试说明/流程与规范/自动化测试流程.md @@ -5,7 +5,7 @@ ## 总体流程 ``` -@test 命令触发 +/serial-boot-test 触发 │ ├─ Step 0 预检 .venv + serial_tool(selftest/ports 确认 COM) ├─ Step 1 构建验证 keil-build skill(keil_build.py) (0 错误 0 警告才继续) @@ -20,9 +20,9 @@ | 名称 | 位置 | 作用 | |---|---|---| -| `@build` | `keil-build` skill(`.dsh/skills/keil-build/`,自带 `keil_build.py`) | 仅编译验证(0 错误 0 警告) | +| `/keil-build` | `keil-build` skill(`.dsh/skills/keil-build/`,自带 `keil_build.py`) | 仅编译验证(0 错误 0 警告) | | `@download` | `stlink-flash` skill(`.dsh/skills/stlink-flash/`,自带 `stlink_flash.py`) | 编译并烧录(ST-Link) | -| `@test` | `serial-boot-test` skill(`.dsh/skills/serial-boot-test/`) | 完整闭环:build → capture+download → verify → iterate | +| `/serial-boot-test` | `serial-boot-test` skill(`.dsh/skills/serial-boot-test/`) | 完整闭环:build → capture+download → verify → iterate | | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | **一键闭环**:open + clear → 烧录 → 固件身份戳(从 hex 抠 `fw build:`)→ wait-idle(静默 8s/超时 30s/首字节 12s)→ 提取 `[RX]`(每行加 `[+Nms]` 时基)→ 回读校验(`stlink_flash.py cmp`)→ verify;`--keep-out` 清旧产物;退出码 0=PASS/1=FAIL/2=采集·烧录·回读错误/3=用法错误 | | `.dsh/skills/stlink-flash/scripts/stlink_flash.py` | `stlink-flash` skill | ST-LINK CLI 封装:`flash [hex]`(烧 + `-Rst -Run`)/ **`cmp [hex]`(回读比对,2026-10-10 加)**/ `reset`(只复位)/ `hard`(nRST 脉冲,CLI 拒 `RM=` 自动降级)/ `halt`/`run`/`list`;原 `MDK-ARM/reset.bat` 已于 2026-10-10 删除并全并入本 skill | | `.dsh/skills/serial-assistant/scripts/serial_tool.py` | `.dsh/skills/serial-assistant/` | Python 串口助手(常驻 daemon + 薄 CLI):ports/open/send/status/log/clear/wait-idle/selftest;全量日志 `state/serial.log`(20MB 自动截头) | @@ -30,14 +30,14 @@ ## 判定标准 -- **构建**:`@build` 退出码 0(0 错误 0 警告),否则不进入硬件环节。 +- **构建**:`/keil-build` 退出码 0(0 错误 0 警告),否则不进入硬件环节。 - **烧录**:`@download` 退出码 0(`run_boot_test.py` 内部调用)。 - **日志**:`verify_log.py` 退出码 - `0` = PASS:所有 `-Required` 标记均出现,且无 `-Forbidden` 模式命中 - `1` = FAIL:存在缺失标记(`[MISS]`)或违规模式(`[HIT]`) - `2` = 日志文件不可读 - **固件身份戳(2026-10-10 加)**:`App/app/app_main.c` 开机打印 `fw build: <__DATE__> <__TIME__>`(编译期展开成一段**连续 ASCII**,`.hex` 里可原样搜到)。`run_boot_test.py` 从**待烧的 hex** 里抠出该串并追加成 Required 标记 → 芯片里跑的必须就是这份构建。反例实测:把 hex 里的戳改成 `Oct 9 2026 01:00:00` 再跑,其余标记全中但 `[MISS] fw build: Oct 9 2026 01:00:00` → FAIL——这正是过去"反复重下载、芯片一直是旧镜像"在闭环里**完全隐形**的场景。`--no-fw-check` 关闭;hex 里没有该串(旧构建)只 WARN 跳过、不假判。 -- **戳覆盖构建配置(2026-10-11 加)**:`__DATE__`/`__TIME__` 只在**该 TU 被重编**时才刷新——增量构建下只切 `test/board/test_config.h` 的套件宏**不会**重编 `app_main.c`,于是产品镜像与测试镜像顶着同一个戳(实测:切宏后的测试构建与产品构建戳同为 `Oct 11 2026 14:44:11`,只有回读校验才发现镜像不同)。两条防线:① `app_main.c` `#include "test_config.h"` 建立头依赖 → 改宏必重编本 TU、戳必刷新;② 戳串追加构建变体 ` suite= dstr=`(`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= dstr=`(`FW_BUILD_VARIANT`)→ 产品 = `suite=NONE dstr=off`、CH395F 测试 = `suite=CH395F dstr=on`,**即便戳时间相同也能一眼分辨**。实测:切宏前后 `Oct 11 2026 20:36:11 suite=NONE dstr=off` → `Oct 11 2026 20:36:40 suite=CH395F dstr=on`;`/serial-boot-test` 闭环对新格式匹配通过(Required 行就是完整戳串,脚本按"标记后连续可打印 ASCII"抠取,无需改脚本)。 - **烧录回读(2026-10-10 加)**:采集结束后自动跑 `stlink_flash.py cmp`(= `-CmpFile` 全片逐字节比对),判定按**行首** `No difference found`(进度条与帮助文本里也含该短语,故不能全文匹配);不一致 → `RUN_TEST_ERROR: flash readback mismatch` + 退出码 2(本次运行作废:日志是"某个未知镜像"跑出来的)。⚠ **顺序不能颠倒**——`-CmpFile` 连接时会复位目标(实测板子当场重打一遍开机日志)。`--no-verify-flash` 关闭。 - **启动锚点计数(2026-10-10 加)**:`reset cause:` 在一次采集里出现 **0 次**(采集没从复位开始,标记全中也可能只是上一轮残留)或 **>1 次**(板子中途自己复位:IWDG 咬狗 / Fault 后 `NVIC_SystemReset` / 掉电重启)→ FAIL;`--max-boot-count N` 放宽(跑故意复位的套件时用),`--expect-reset-cause 'software;power-on'` 对第一个锚点做原因白名单(SWD 软复位实测为 `software (NVIC_SystemReset)`,CSR=0x14000000)。 - **判定可读性 + 时序(2026-10-10 第三批)**: @@ -105,7 +105,7 @@ | daemon 无响应 / 自动拉起失败 | 查 `.dsh/skills/serial-assistant/scripts/state/daemon.log`;`serial_tool.py restart` 重建 daemon;检查 COM 是否被其他程序占用 | | 抓到日志缺 `Network Init` | `-SettleMs` 太小(boot 任务间有 ~3-4s 自然静默间隙),用默认 8000ms;2026-10-10 起首字节到达前不启动静默计时(`--first-wait-ms` 默认 12000),首行晚到不再被提前收尾 | | 烧录后无数据(`no_data`) | 2026-10-10 起 `no_data` = **本次等待期间零字节**(旧实现比 daemon 累计字节,常驻 daemon 下永不触发,只能靠"日志里没有 [RX] 行"兜底);开串口 + clear 必须在烧录**前**完成(`run_boot_test.py` 已保证);核对 COM 号、板子供电,看 `state/serial.log` 有无 SYS 异常行 | -| `[MISS] fw build: <戳>`(其余标记全中) | 芯片里跑的不是这份构建:烧录没落地 / 烧错文件 / hex 未重编译。处置:`stlink_flash.py flash ` 重烧并看 `READBACK: OK`;确认 `@build` 已重跑(戳取自编译时刻)。⚠ 若戳**命中**但里面的 `suite=` 与本次要跑的套件不符(想跑 CH395F 却显示 `suite=NONE dstr=off`)→ 烧的是产品镜像,`@build` 不会替你切宏(→ `board-test-suite` skill) | +| `[MISS] fw build: <戳>`(其余标记全中) | 芯片里跑的不是这份构建:烧录没落地 / 烧错文件 / hex 未重编译。处置:`stlink_flash.py flash ` 重烧并看 `READBACK: OK`;确认 `/keil-build` 已重跑(戳取自编译时刻)。⚠ 若戳**命中**但里面的 `suite=` 与本次要跑的套件不符(想跑 CH395F 却显示 `suite=NONE dstr=off`)→ 烧的是产品镜像,`/keil-build` 不会替你切宏(→ `board-test-suite` skill) | | `READBACK: FAIL` / `flash readback mismatch` | 芯片内容与 hex 逐字节不一致(写入未落地、选项字节/RDP 异常、烧错目标)。处置:按 `docs/测试说明/测试工具/ST-LINK_CLI_复位使用说明.md` §7 排查;`stlink_flash.py flash` 重烧后闭环会自动复核 | | `boot banner x2 > allowed 1` | 采集窗口内板子自己复位过(IWDG 咬狗 / Fault 后 `NVIC_SystemReset` / 掉电重启)。处置:看判定里逐条打印的 `reset cause:` 定位;跑**故意复位**的套件用 `--max-boot-count N` 放宽 | | `marker order violated` | 标记全中但**先后顺序颠倒** = 某步在另一步之后补做(就绪屏障失序 / 失败重试补跑)。处置:看判定打印的各标记首次行号定位;`--require-order` 只在需要强约束时启用 | @@ -118,13 +118,13 @@ ## UI 屏幕自动化 - **初始化回归**:`App/task/ui_task.c` 已有 `UI port init: OK` / `UI menu init: OK` / `UI net sink: started` - 打印(见 `@test` 命令示例);将其加入 `-Required` 即可经串口自动回归 UI 初始化。 + 打印(见 `run_boot_test.py` 命令示例);将其加入 `--required` 即可经串口自动回归 UI 初始化。 - **屏幕内容回归**:由 LVGL 模拟器像素回归覆盖(文本级断言 + 66 张黄金参考 + 池水位门禁, 见 `docs/UI设计/UI_自动化测试流程.md`);上机屏幕仍需人工目检(见 `测试开发标准流程.md` §1)。 ## 扩展用法 -- **单模块回归**:`@test` 后接 `-Required` 定制,例如网络回归用 +- **单模块回归**:`run_boot_test.py` 后接 `--required` 定制,例如网络回归用 `-Required "App init OK;Network Init;net init: OK;PHY: 100M FULL"`。 - **测试套件构建**:`test/board/test_config.h` 开启某个 `TEST_SUITE_*` 后,把套件打印的 `=== ... Test:` 标题作为 Required 标记,配合 `test/` 下各用例输出判断。 diff --git a/docs/测试说明/测试工具/串口助手使用指南.md b/docs/测试说明/测试工具/串口助手使用指南.md index b2827da8..34908804 100644 --- a/docs/测试说明/测试工具/串口助手使用指南.md +++ b/docs/测试说明/测试工具/串口助手使用指南.md @@ -1,7 +1,7 @@ # Python 串口助手(serial_tool)使用指南 > **定位**:面向使用者的操作手册——命令怎么敲、输出怎么看、常见场景怎么组合、出问题怎么排查。 -> **关联文档**:设计决策与实现细节见 `串口助手设计说明.md`;@test 自动化闭环见 `自动化测试流程.md`;`serial-assistant` skill 有一页速查(用到自动加载,AGENTS.md 不再重复收录)。 +> **关联文档**:设计决策与实现细节见 `串口助手设计说明.md`;/serial-boot-test 自动化闭环见 `自动化测试流程.md`;`serial-assistant` skill 有一页速查(用到自动加载,AGENTS.md 不再重复收录)。 > **版本**:v1.1(与 `.dsh/skills/serial-assistant/scripts/serial_tool.py` 当前实现一致,2026-09-15) ## 1. 它是什么(60 秒版) @@ -54,7 +54,7 @@ | `status` | 全状态一条 JSON(含 `state`、`rx_age_ms` = 距最后一个 RX 字节的毫秒数,`null` = 这个 daemon 还没收过字节) | 是 | | `log` | 读日志文件(`--tail N` / `--grep P`) | **否**(直接读文件,daemon 死了也能用) | | `clear` | 清空日志 + 代际 +1 | 是 | -| `wait-idle` | 等 RX 静默(@test 的收尾判据) | 是 | +| `wait-idle` | 等 RX 静默(/serial-boot-test 的收尾判据) | 是 | | `start` / `stop` / `restart` | daemon 生命周期 | — | | `selftest` | 无头自检(行拆分/解码/截头算法,无需硬件) | 否 | @@ -198,7 +198,7 @@ Select-String -Path '.dsh\skills\serial-assistant\scripts\state\serial.log' -Pat - **首字节门(2026-10-10 加)**:首字节到达**之前**不启动静默计时——旧实现从进入即计时,板子第一行比 `--settle-ms` 来得晚就提前收尾("开机日志缺尾巴"的另一半成因)。`--first-timeout-ms`(0 = 同 `--max-ms`)到点仍零字节 → `no_data` 快速失败。 - **`rx_bytes` 是本次增量**(旧实现回的是 daemon 累计值,常驻 daemon 下动辄百万,无判读价值)。 - **CLI 超时自动推导**:未显式带 `--timeout` 时按 `--max-ms/1000 + 5` 取值,不再需要手工加(旧版固定 5s 会在 daemon 还在等时掐断连接 → CLI exit 2 + daemon.log 刷 `ConnectionAbortedError`)。 -- @test 流程(`run_boot_test.py`)用它作为"烧录后日志收齐"的判据(`--first-wait-ms` 默认 12000)。 +- /serial-boot-test 流程(`run_boot_test.py`)用它作为"烧录后日志收齐"的判据(`--first-wait-ms` 默认 12000)。 ### 3.9 `start` / `stop` / `restart` — daemon 生命周期 @@ -273,7 +273,7 @@ Select-String -Path '.dsh\skills\serial-assistant\scripts\state\serial.log' -Pat ... serial_tool.py close ``` -### 5.2 抓一次开机日志(手动版 @test) +### 5.2 抓一次开机日志(手动版 /serial-boot-test) ```powershell ... serial_tool.py open --port COM6 @@ -322,7 +322,7 @@ Get-Content '.dsh\skills\serial-assistant\scripts\state\daemon.log' -Tail 30 # | `baudRate` | 默认波特率 | 115200 | | `httpPort` | daemon HTTP 端口(一般不动) | 18765 | -**换端口只改这个文件**(`@test` 与手动调试都读它)。`httpPort` 冲突时才需要改。 +**换端口只改这个文件**(`/serial-boot-test` 与手动调试都读它)。`httpPort` 冲突时才需要改。 ## 7. 故障排查 diff --git a/docs/测试说明/测试工具/串口助手设计说明.md b/docs/测试说明/测试工具/串口助手设计说明.md index 66803407..46f111bd 100644 --- a/docs/测试说明/测试工具/串口助手设计说明.md +++ b/docs/测试说明/测试工具/串口助手设计说明.md @@ -14,7 +14,7 @@ | # | 缺陷 | 影响 | |---|---|---| -| F1 | 二进制绑定**用户档案**:`opencode.json` 的 `mcp.serial` 指向 `C:\Users\16241\AppData\Local\serial-mcp-server\*.exe`,本机实际用户为 **Administrator**(该档案不存在,二进制缺失) | serial MCP 工具不可用、@test 采集步骤断裂;换机器/换用户无法复现 | +| F1 | 二进制绑定**用户档案**:`opencode.json` 的 `mcp.serial` 指向 `C:\Users\16241\AppData\Local\serial-mcp-server\*.exe`,本机实际用户为 **Administrator**(该档案不存在,二进制缺失) | serial MCP 工具不可用、/serial-boot-test 采集步骤断裂;换机器/换用户无法复现 | | F2 | **无历史缓冲**:`read` 只能读调用**之后**到达的字节 | 抓开机日志被迫走"采集窗口 + first-byte 触发 + EOF 重试"仪式(6 个参数 + JSON 解析 + CH340 释放后重开陷阱特判) | Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机器/用户可复现);常驻 daemon 把**全量** RX/TX 持续写入日志文件,历史 = 日志文件本身,采集窗口仪式整体消失。 @@ -28,7 +28,7 @@ Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机 | G3 | Agent 一切操作 = 带硬超时(5s)的短命令;串口所有权归常驻 daemon | | G4 | 一条 `status` 命令看全状态(daemon/端口/计数/最后 RX/日志文件/错误) | | G5 | 随时清空日志(原子 + 代际号,新旧内容可区分) | -| G6 | @test 一键流程切换新工具:open → clear → 烧录 → 读文件 → verify,无采集窗口 | +| G6 | /serial-boot-test 一键流程切换新工具:open → clear → 烧录 → 读文件 → verify,无采集窗口 | | G7 | 全流程**不关端口**(ST-Link 烧录不占 COM),从根上规避 CH340 释放/重开陷阱 | ## 2. 需求清单 @@ -45,7 +45,7 @@ Python 重写一并解决:依赖收敛到仓库 `.venv + pyserial`(任意机 | R8 | 日志 20MB 上限,超限**原地截头**保留尾部 10MB(不重开端口/不 rename) | P0 | 补充(用户确认) | | R9 | 开端口不翻转 DTR/RTS(防 STM32 自动复位电路误复位),可配置 | P0 | 补充 | | R10 | 串口读错/拔线 → daemon 存活,state=error,可重开;daemon 被杀可自恢复 | P0 | 补充 | -| R11 | @test 集成:`run_boot_test.py` 采集步改写 + `verify_log.py` 兼容 + 旧文档/配置迁移 | P0 | 用户场景 | +| R11 | /serial-boot-test 集成:`run_boot_test.py` 采集步改写 + `verify_log.py` 兼容 + 旧文档/配置迁移 | P0 | 用户场景 | | R12 | `send`(TX 进日志)、`log --tail/--grep`、`start/stop/restart`、`selftest` 无头自检 | P1 | 补充 | ## 3. 总体方案 @@ -84,7 +84,7 @@ flowchart LR | `send` | 发 TX 字节(进日志);默认**精确字节**不加换行 | 位置参数文本(UTF-8)或 `--hex 0D0A`;`--nl` 追加 CRLF | `{"ok":true,"bytes":12}` | | `status` | 全状态(见 §4.2) | — | 见 §4.2 | | `log` | 读日志(**先过滤后截尾**,2026-10-10) | `--tail N`(默认 50;`0` = 全部)`--grep P` `-i` `--regex` | `{"ok":true,"gen":8,"lines":["…"],"scanned":N,"matched":M}` | -| `wait-idle` | 等 RX 静默(@test 收尾判据) | `--settle-ms`(默认 8000)`--max-ms`(默认 30000)`--first-timeout-ms`(默认同 max) | `{"ok":true,"rx_bytes":本次增量,"idle":bool,"waited_ms":N,"reason":"idle\|no_data\|timeout"}` | +| `wait-idle` | 等 RX 静默(/serial-boot-test 收尾判据) | `--settle-ms`(默认 8000)`--max-ms`(默认 30000)`--first-timeout-ms`(默认同 max) | `{"ok":true,"rx_bytes":本次增量,"idle":bool,"waited_ms":N,"reason":"idle\|no_data\|timeout"}` | | `clear` | 清空日志:原地截断 + 写 `[SYS] log cleared (gen=N+1)` | — | `{"ok":true,"gen":8}` | | `start` / `stop` / `restart` | daemon 生命周期 | — | `{"ok":true,"pid":…}` / stop 后 `{"ok":true,"stopped":true}` | | `selftest` | 无头自检(行拆分器/解码回退/截头算法 单测回环,无需硬件) | — | 0=PASS | @@ -175,13 +175,13 @@ flowchart LR | 重开退避 | 错误恢复路径重开端口前 ≥1.5s 退避(CH340 特性);正常流程不重开 | | `stop` | 关端口(flush 尾行)→ 写 `[SYS] port closed` → 退出 | | 进程被强杀 | Windows 进程退出自动释放 COM 端口;下次命令自动拉起新实例(`open` 幂等路径) | -| `wait-idle` 判据(2026-10-10 修) | `no_data` = **本次等待期间零字节**(旧实现比的是 daemon 累计 `rx_bytes` 是否为 0 → 常驻 daemon 下永不触发,板子全程静默也误报 `idle:true`,@test 的 no_data 拦截形同虚设);**首字节到达前不启动 settle 计时**(`first_timeout_ms` 兜底,默认 = `max_ms`);返回 `rx_bytes` = 本次增量 | +| `wait-idle` 判据(2026-10-10 修) | `no_data` = **本次等待期间零字节**(旧实现比的是 daemon 累计 `rx_bytes` 是否为 0 → 常驻 daemon 下永不触发,板子全程静默也误报 `idle:true`,/serial-boot-test 的 no_data 拦截形同虚设);**首字节到达前不启动 settle 计时**(`first_timeout_ms` 兜底,默认 = `max_ms`);返回 `rx_bytes` = 本次增量 | | CLI 超时与断连噪声(2026-10-10) | `wait-idle` 未显式带 `--timeout` 时按 `max_ms/1000 + 5` 自动推导(旧版固定 5s 会在 daemon 还在等时掐断连接);handler 侧 `ConnectionError`(客户端已断开)不再往 daemon.log 打 traceback(本机曾累积 16 次);`daemon.log` 超 1MB 在拉起新实例前原地保尾 256KB | | 配置读取(2026-10-10) | 按 `utf-8-sig` 读(容忍 BOM,与 `run_boot_test.py` 同口径);文件缺失 / JSON 语法错 / `baudRate`·`httpPort` 非整数不再静默回退成"没配端口",错误文本进 `configError`,由 `status` / `ports` / `open` 显式回显 | -## 7. 集成与迁移(@test 切换) +## 7. 集成与迁移(/serial-boot-test 切换) -### 7.1 新 @test 流程(`run_boot_test.py` 采集步改写,退出码约定不变:0/1/2/3) +### 7.1 新 /serial-boot-test 流程(`run_boot_test.py` 采集步改写,退出码约定不变:0/1/2/3) ```mermaid flowchart LR @@ -193,7 +193,7 @@ flowchart LR ``` - 对比旧流程:6 个采集窗口参数 + JSON 解析 + EOF 重试 **全部删除**;采集 = 端口常开 + 读文件 -- `verify_log.py` 与纯文本日志契约一致(2026-09-20 随 @test 迁移 Python,判定语义不变) +- `verify_log.py` 与纯文本日志契约一致(2026-09-20 随 /serial-boot-test 迁移 Python,判定语义不变) - 参数保留:`-Port` `-BaudRate` `-FlashCmd` `-Required` `-Forbidden` `-OutDir`;删除 `-DurationMs` 等窗口参数,新增 `-SettleMs`(静默判据,**默认 8000**——boot 任务间有 3~4s 自然静默间隙,2000 会截断)`-MaxWaitMs`(默认 30000) ### 7.2 配套变更清单 @@ -230,7 +230,7 @@ flowchart LR | A4 | `stop` 后强杀 daemon(taskkill)→ 再发任意命令 | 自动拉起成功,`open` 幂等路径无 CH340 EOF 错误 | | A5 | 日志灌至 >20MB | 自动截头至 ~10MB,写入连续、无重开端口,`[SYS] truncated` 行存在 | | A6 | 运行中拔 CH340 → `status` | daemon 存活,`state=error`;重插 + `open` 恢复 | -| A7 | **@test E2E ×3**(含一轮"上轮端口未关"场景) | 三轮全 PASS(Required 标记齐、无 Forbidden),boot 日志完整 | +| A7 | **/serial-boot-test E2E ×3**(含一轮"上轮端口未关"场景) | 三轮全 PASS(Required 标记齐、无 Forbidden),boot 日志完整 | | A8 | `selftest` | 0=PASS | ## 10. 实施步骤(按序执行) @@ -241,7 +241,7 @@ flowchart LR | 步骤 2 | daemon 核心:HTTP 服务 + 串口读线程 + 行日志写(含 clear/截头/DTR 策略) | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(daemon 部分) | `start→status→send→stop` 手动走通 ✅ | | 步骤 3 | CLI 全子命令 + 自动拉起 + 超时契约 + `selftest` | serial_tool.py(CLI 部分) | `selftest` 0=PASS + 命令走查 ✅ | | 步骤 4 | 验收 A1–A6、A8 | 测试通过记录 | A1–A5/A8 PASS ✅(本机无 CH340,A6 待硬件) | -| 步骤 5 | @test 集成:`run_boot_test.py` 采集步改写 + 参数收敛 | run_boot_test.py | 负路径(端口缺失→exit 2)+ 假烧录全链路 + verify 正/负判定 ✅;**A7 正路径待 CH340 接入** | +| 步骤 5 | /serial-boot-test 集成:`run_boot_test.py` 采集步改写 + 参数收敛 | run_boot_test.py | 负路径(端口缺失→exit 2)+ 假烧录全链路 + verify 正/负判定 ✅;**A7 正路径待 CH340 接入** | | 步骤 6 | 文档/配置迁移(§7.2 全清单) | docs + 配置 | 全库 grep 无 serial-mcp-server 残留引用(本文 §12 工具历史除外) ✅ | 提交:全部完成后单提交(pre-commit 门禁;纯宿主侧 Python,不涉及 Keil 构建)。 diff --git a/docs/测试说明/测试数据/boot_log_data/README.md b/docs/测试说明/测试数据/boot_log_data/README.md index 35c811c2..058a96ac 100644 --- a/docs/测试说明/测试数据/boot_log_data/README.md +++ b/docs/测试说明/测试数据/boot_log_data/README.md @@ -1,11 +1,11 @@ # 启动日志(boot_log)存档 -本目录保存 `@test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`)产出的**开机串口日志快照**, +本目录保存 `/serial-boot-test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`)产出的**开机串口日志快照**, 用于**跨构建的栈 / 堆水位对照**(解析其中的 `[STACK]` 行)。 ## 1. 为什么需要存档 -- `@test` 默认把 boot_log 写到临时目录 `%TEMP%\serial-boot-test\boot_log_<时间戳>.txt`——**会被系统清理**; +- `/serial-boot-test` 默认把 boot_log 写到临时目录 `%TEMP%\serial-boot-test\boot_log_<时间戳>.txt`——**会被系统清理**; - 台架长跑的 20MB 串口日志**截头会丢掉早期 `[STACK]` 水位行**(开机首次约 5s 后才报告); - 两者叠加导致**无法事后回溯**"改代码前后"的栈水位变化。 @@ -19,7 +19,7 @@ | 命名 | `<日期>_<构建标识>_boot_log.txt`,例:`20261005_prod_none_boot_log.txt` | | 构建标识 | 建议含:产品 / 测试套件(`prod_none` / `suite_ch395f` …)+ 负载(`idle` / `v5_30min` / `ftp_stress`) | | 随文件登记 | 在下方 §3 清单登记日期、构建、负载、关键 `[STACK]` 水位(如 defaultTask 峰值) | -| 入库门槛 | **只存有对照价值的快照**——常规 `@test` 通过日志不必入库 | +| 入库门槛 | **只存有对照价值的快照**——常规 `/serial-boot-test` 通过日志不必入库 | | 故障注入日志 | 含**刻意产生**的 `[ERR]` 行(如兜底门闭锁)的日志用自定义标记集采集:`--required` 加闭锁/复归两行、`--forbidden` 去掉 `[ERR]` 保留 `FAIL;ERROR;HardFault`。此类日志的 PASS 只代表注入行为符合预期,不代表标记集等价于日常门禁 | | 证据可达性 | 台账**只准引用已入库的文件**。被忽略规则挡住的产物(如全局 `*.bin`)要么显式 force-add 入库,要么把行内引用改写成「本机产物(不入库)」——**两者都不做 = 让台账行指着仓库里不存在的证据**,按图索骥的人只能拿到 File Not Found | diff --git a/docs/环境与部署/并行任务工作树方案.md b/docs/环境与部署/并行任务工作树方案.md index dbb9b5e0..032d5378 100644 --- a/docs/环境与部署/并行任务工作树方案.md +++ b/docs/环境与部署/并行任务工作树方案.md @@ -19,7 +19,7 @@ Python 环境、以及 `docs/任务规划/` 这类**全局唯一登记簿**都 **做法**:每个任务一棵 `git worktree` + 一条任务分支,任务之间物理隔离;干完由固定命令合回主干。 **范围**:只解决「开发期隔离 + 回收期合并」的机械流程。**不改变**既有的分支纪律 -(push 需许可)、门禁口径(pre-commit 51 钩子 + `@build` + `@test`)与文档约定。 +(push 需许可)、门禁口径(pre-commit 52 钩子 + `/keil-build` + `/serial-boot-test`)与文档约定。 --- @@ -108,7 +108,7 @@ D:/Code/DTU Code/ | 分支命名 | `task/-` | `worktree/` | 两条**并发红线由本方案单方守住**(插件不感知):`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:`;`bench` 仍须手工占同一个名。共享联接(§3.3) 插件不会建,由本 skill 的 `plugin adopt ` 补(执行体命令行见 §8)。 @@ -137,7 +137,7 @@ D:/Code/DTU Code/ 磁盘从来不是这条线的硬约束——实测一棵纯净检出 **84.0 MiB / 2857 个受控文件**(含 LFS smudge 后的真图;`docs/` 独占 46 MiB),而 `.venv` 与 `simulator/SDL2` 走目录联接共享、不随树复制;同日 D 盘余 1.4 TB,**8 棵全开的最坏估算也只有约 3.2 GB**(每棵再叠一次构建产物:Keil 中间件 289 MB + 模拟器 35 MB)。 -真正随并行数上涨的是**排队与合并**:`@build` 与台架 `@test` 各被一把独占锁串行化(见 §5),`done` +真正随并行数上涨的是**排队与合并**:`/keil-build` 与台架 `/serial-boot-test` 各被一把独占锁串行化(见 §5),`done` 的合并必须串行(§5 下方说明),而每多一棵树把 `sync` 追平与冲突面乘一遍。所以这条线现在是**管 理半径闸门**而非磁盘闸门:调到 8 = 把「同时盯几棵」交回人,两把锁的排队代价用 `$TW lock status` 与 §7 的台架台账观测;脑子里的名额够不够,看的是锁队列长度,不是磁盘。 @@ -152,8 +152,8 @@ D:/Code/DTU Code/ | 编码 | 在新空间里改代码 | 可并行 | | 单测产物 | `%TEMP%\dtu_host\<工作树哈希>\` 分家 + 每跑独占 `runs\-\` | **不需要锁,但必须按树分家**(旧固定共享名跨树互踩:A 树可能执行 B 树编出的同名 exe,summary 相同不可辨;build/bench 是真独占,继续用锁) | | 门禁 | `scripts/ci_local.py`(静态门禁 + 主机侧纯逻辑单测,纯软件) | **可并行** | -| 构建 | `@build`(UV4 增量,需持 `build` 锁) | **必须串行** | -| 台架 | `@test` / 板级套件(需先持台架锁,见 §7) | **必须串行** | +| 构建 | `/keil-build`(UV4 增量,需持 `build` 锁) | **必须串行** | +| 台架 | `/serial-boot-test` / 板级套件(需先持台架锁,见 §7) | **必须串行** | | 提交 | 本地 `git commit`(多行正文用 `-F `) | 可并行 | | `sync` | `rebase` 追平主干,把冲突控制在任务分支上 | 可并行 | | `done` | 预演 → 合入主干 → 残留审计 → 重生成产物 → 门禁 → 清理工作树 | **必须串行** | @@ -175,7 +175,7 @@ D:/Code/DTU Code/ | `docs/任务规划/`(整个文件夹) | 全项目唯一登记簿,每个任务都要销项 | 由主工作树统一改,任务分支不碰 | | `AGENTS.md`、`docs/README.md`(索引/登记类) | 每个任务都要在 AGENTS.md 的「按主题查哪份文档」表、`docs/README.md` §9.2 环境表、`docs/README.md` 的文档索引表里**各加一行** → 同一张表的新增行两任务必撞(2026-10-12 实测:主工作树里本任务的行与他任务的行已并排混在**同一 hunk**)。变更记录那一路**不再是冲突面**:2026-10-28 起新文档一律不设变更记录节(`docs/文档规范.md` §5.1),存量也只剩 5 篇台账/证据文档 | 由主工作树统一追加;提交时**只 `git add` 本任务的行**(`git apply --cached` 选择性入库),勿整文件 `git add` | | `AGENTS.md` 的 `TASK_TABLE` 块 | 生成物,两个任务都抬栈就冲突 | 合并后重跑 `scripts/gen_task_table.py` **重新生成**,不手工并 | -| `MDK-ARM/STM32F407-Demo.uvprojx` | 新源文件注册 + UV4 自动改写 | 手工并 `` 条目 → 重跑 `registration-check` → `@build` | +| `MDK-ARM/STM32F407-Demo.uvprojx` | 新源文件注册 + UV4 自动改写 | 手工并 `` 条目 → 重跑 `registration-check` → `/keil-build` | | `.pre-commit-config.yaml` | 新增主机单测要加钩子 | 并集处理 | | `docs/内存管理/RAM使用分析与优化方案.md`(及其 `mem-numbers` 快照) | `mem-numbers` 快照 | **绝不手工合数字**,合并后 `scripts/check_mem_numbers.py --write` 重生成 | @@ -204,7 +204,7 @@ D:/Code/DTU Code/ ### 7.1 台架锁 -以下物理资源**各只有一份**,故 `@test` / 板级套件 / 烧录必须串行: +以下物理资源**各只有一份**,故 `/serial-boot-test` / 板级套件 / 烧录必须串行: - 串口 `COM6` @115200 - ST-LINK SN `0B250D12A218303030303032` @@ -219,7 +219,7 @@ D:/Code/DTU Code/ **TTL 按锁分设**(2026-10-12):`LOCK_TTL_HOURS = {"build": 1.0, "bench": 6.0}`。 口径 = 兜底窗口应与**操作真实时长**同量级:编译是秒~分钟级(`keil_build.py` 自持自释, -窗口 ≈ UV4 运行时长),1h 已足够宽松;bench 是**会话级**(flash→`@test`→探针),才配得上 6h。 +窗口 ≈ UV4 运行时长),1h 已足够宽松;bench 是**会话级**(flash→`/serial-boot-test`→探针),才配得上 6h。 持有者还可用 `lock acquire ... --ttl <小时>` **自报**更精确的窗口—— `keil_build.py` 即按其 `--timeout` 自报(`timeout/3600 + 15min`,下限 0.5h)。 @@ -230,11 +230,11 @@ D:/Code/DTU Code/ ### 7.2 构建 -`@build`(UV4 增量)在新空间里是**独占目录**,但 **UV4 不能两个实例同时 `-b`**(2026-10-12 用户确认, +`/keil-build`(UV4 增量)在新空间里是**独占目录**,但 **UV4 不能两个实例同时 `-b`**(2026-10-12 用户确认, 无需实测)——因此构建**同样纳入串行锁**(`build`),与台架锁并列。 > 结论:任务期真正可并行的只有**编码与静态门禁**(`ci_local.py` 的静态门禁 / 主机侧纯逻辑单测,纯软件、零硬件); -> `@build` 与烧录/台架都必须排队。 +> `/keil-build` 与烧录/台架都必须排队。 ### 7.3 并行道与写范围规则(规划层 ↔ 执行层映射) @@ -264,7 +264,7 @@ D:/Code/DTU Code/ ## 8. SKILL 设计 -**名称**:`task-worktree` **文档别名**:`@task`——**只是沿用 `@build` / `@test` 惯例的文档记号,没有任何执行机制**:shell 里敲 `@task` 直接 command not found,`dsh task` 会被当 profile 名报错(2026-10-30 实测);DSH 会话里的显式调用是 `/task-worktree`,可执行形态只有下面的执行体命令行。 +**名称**:`task-worktree` **文档别名**:`@task`——**只是文档记号,没有任何执行机制**:shell 里敲 `@task` 直接 command not found,`dsh task` 会被当 profile 名报错;DSH 会话里的显式调用是 `/task-worktree`,可执行形态只有下面的执行体命令行。 **执行体**:`.dsh/skills/task-worktree/scripts/task_worktree.py`(纯标准库, 照 `keil_build.py` 的写法,整目录可拷到别的项目复用)。**已落地并端到端实测**(见 §11)。 @@ -298,19 +298,19 @@ D:/Code/DTU Code/ | 层级 | 内容 | 时机 | | ------- | ------------------------------------ | ------------------------------ | | 任务期 | `scripts/ci_local.py`(静态门禁 + 主机侧纯逻辑单测,可并行) | 每个任务各自跑 | -| 任务期 | `@build`(0 错 0 警,**持 `build` 锁串行**) | 每个任务各自跑 | -| **新空间首次** | `@build` 一次,生成 `.map` | 见下表下注释:不先构建,体积类门禁必然红 | -| 任务期(可选) | `@test` / 板级套件(持 `bench` 锁) | 需真机验证的任务 | -| 回收期 | 重生成产物 → `ci_local.py` → `@build` | `done` 内强制(`--skip-gates` 可跳过) | +| 任务期 | `/keil-build`(0 错 0 警,**持 `build` 锁串行**) | 每个任务各自跑 | +| **新空间首次** | `/keil-build` 一次,生成 `.map` | 见下表下注释:不先构建,体积类门禁必然红 | +| 任务期(可选) | `/serial-boot-test` / 板级套件(持 `bench` 锁) | 需真机验证的任务 | +| 回收期 | 重生成产物 → `ci_local.py` → `/keil-build` | `done` 内强制(`--skip-gates` 可跳过) | | 人工 | push 许可 | 回收后 | -> ⚠ **新开工作树后先 `@build` 一次再谈门禁**(实测 2026-10-25):`.map` 是每棵树各自的构建产物、 +> ⚠ **新开工作树后先 `/keil-build` 一次再谈门禁**(实测 2026-10-25):`.map` 是每棵树各自的构建产物、 > 不入版本库,所以依赖它的事实源的门禁——`mem-numbers`、`mem-budget`、以及 `ci_local.py` 的 > 体积门禁——在从未构建过的新空间里**必然红**。这不是代码缺陷,脚本此时也会把这句原因打出来 > (`check_mem_numbers.py` / `ram_baseline.py` 的提示文案)。顺带一条同源提醒:`done` 的门禁 -> 排在 `@build` 之前(见下文陷阱条目),同一族误报在回收期也会重现一次。 +> 排在 `/keil-build` 之前(见下文陷阱条目),同一族误报在回收期也会重现一次。 -**`done` 的完成判据**:主干上 `git merge --no-ff` 成功 + `ci_local.py` 全绿 + `@build` 0 错 0 警 +**`done` 的完成判据**:主干上 `git merge --no-ff` 成功 + `ci_local.py` 全绿 + `/keil-build` 0 错 0 警 - 残留审计无死规则/重复条目 + 工作树已移除。 diff --git a/docs/环境与部署/本地工具路径环境变量方案.md b/docs/环境与部署/本地工具路径环境变量方案.md index c4f1d1af..b7c45f33 100644 --- a/docs/环境与部署/本地工具路径环境变量方案.md +++ b/docs/环境与部署/本地工具路径环境变量方案.md @@ -9,7 +9,7 @@ | 变量 | 必需性 | 值的形态 | 使用者 | 默认/回退 | |---|---|---|---|---| -| `DTU_KEIL_UV4` | MCU 构建机必需 | UV4.exe 完整路径,例如 `D:\Tools\Keil\UV4\UV4.exe` | `keil_build.py`(`@build`) | 不设即报错(exit 2);不从 C 盘旧路径猜测 | +| `DTU_KEIL_UV4` | MCU 构建机必需 | UV4.exe 完整路径,例如 `D:\Tools\Keil\UV4\UV4.exe` | `keil_build.py`(`/keil-build`) | 不设即报错(exit 2);不从 C 盘旧路径猜测 | | `DTU_STLINK_CLI` | 复位/烧录/回读机必需 | `ST-LINK_CLI.exe` 完整路径,例如 `C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility\ST-LINK_CLI.exe` | `stlink_flash.py` | 未设时回退 PATH(`where ST-LINK_CLI.exe` 首个命中);已设但无效 = 硬报错 | | `DTU_PRECOMMIT_PYTHON` | 可选(覆盖用) | 含 `pre_commit` 模块的 `python.exe` 完整路径 | `ci_local.py` 静态门禁、CI `static-gates` | 未设时按 §1.1 探测(当前解释器 → `.venv` → PATH);已设但无效 = 硬报错 | | `DTU_GRAPHVIZ_DOT` | 可选(覆盖用) | `dot.exe` 完整路径 | `gen_refgraph.py` | 未设时只查 PATH;无 Graphviz 时降级为"生成 .dot/.json、跳过渲染" | diff --git a/docs/环境与部署/项目部署环境要求.md b/docs/环境与部署/项目部署环境要求.md index fb81d96e..5173ff0c 100644 --- a/docs/环境与部署/项目部署环境要求.md +++ b/docs/环境与部署/项目部署环境要求.md @@ -65,7 +65,7 @@ Graphviz / Cppcheck 的安装位置差异一律用 `DTU_*` 环境变量表达( | 变量 | 必需性 | 值的形态 | 使用方 | |---|---|---|---| -| `DTU_KEIL_UV4` | **必需**(MCU 构建机) | UV4.exe 完整路径 | `keil_build.py`(`@build`) | +| `DTU_KEIL_UV4` | **必需**(MCU 构建机) | UV4.exe 完整路径 | `keil_build.py`(`/keil-build`) | | `DTU_STLINK_CLI` | **必需**(用 ST-LINK CLI 的机器;已在 PATH 可不设) | ST-LINK_CLI.exe 完整路径 | `stlink_flash.py`(烧录/复位/回读) | | `DTU_PRECOMMIT_PYTHON` | 可选(pre-commit 不在 `.venv`/当前解释器时设;CI runner 建议必设) | 装有 pre-commit 的 python.exe 完整路径 | `scripts/ci_local.py`、CI 静态门禁 / 主机侧纯逻辑单测 | | `DTU_CPPCHECK` | 可选(开发机入 PATH 即可;无 PATH 的 CI runner 建议设) | cppcheck.exe 完整路径 | pre-commit cppcheck 钩子、CI 静态门禁 | @@ -94,7 +94,7 @@ Graphviz / Cppcheck 的安装位置差异一律用 `DTU_*` 环境变量表达( | 安装路径 | **任意目录** | `keil_build.py` / `stlink_flash.py` 经 `DTU_*` 环境变量解析工具路径(§2.1.1);新机器装完后设置 `DTU_KEIL_UV4` / `DTU_STLINK_CLI` 即可,**无需修改仓库脚本** | | 设备支持 | STM32F407ZGTx + CMSIS Flash 算法 | 工程引用 `STM32F4xx_1024.FLM`(1MB 片内 Flash,0x08000000),由 CMSIS STM32F4 设备包提供 | | STM32CubeMX | **6.17.0**(工程 `STM32F407-Demo.ioc` 记录的版本) | 修改外设/引脚/时钟配置并再生成 CubeMX 文件;固件包须 **STM32Cube FW_F4 V1.28.3** | -| 命令行构建 | `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`(即 `@build`) | 退出码 0 = 0 错 0 警;失败看 `MDK-ARM/build_log.txt` | +| 命令行构建 | `& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py`(即 `/keil-build`) | 退出码 0 = 0 错 0 警;失败看 `MDK-ARM/build_log.txt` | | 命令行烧录 | `& '.venv\Scripts\python.exe' .dsh\skills\stlink-flash\scripts\stlink_flash.py flash`(即 `@download`) | 烧后 `-Rst -Run`;判据 `Programming Complete` + 回读 `No difference found` | > ⚠ **版本一致性关键**:.ioc 记录 `MxCube.Version=6.17.0` + `FirmwarePackage=STM32Cube FW_F4 V1.28.3`, @@ -189,7 +189,7 @@ python tools/font_gen/extract_cn.py # 任意 python3: |---|---|---| | Python 串口助手 | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(仓库内置,.venv + pyserial) | 常驻 daemon + 薄 CLI + 全量日志文件;无需单独安装,`pip install -r requirements.txt` 即就绪 | | 调试串口 | USART1(PA9 TX / PA10 RX),115200 8N1 | **端口单一配置源 `.serialtest/config.json`**(本机 COM6);换端口只改这个文件 | -| 一键测试 | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`(即 `@test`,`& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py`) | 开串口 + 清日志 → 烧录 → wait-idle(静默 8s/超时 30s)→ 提取 → 标记校验;默认端口取 `.serialtest/config.json` / 115200,退出码 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法错误 | +| 一键测试 | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`(即 `/serial-boot-test`,`& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py`) | 开串口 + 清日志 → 烧录 → wait-idle(静默 8s/超时 30s)→ 提取 → 标记校验;默认端口取 `.serialtest/config.json` / 115200,退出码 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法错误 | > 采集 = 端口常开 + daemon 全量日志(`.dsh/skills/serial-assistant/scripts/state/serial.log`),无采集窗口/first-byte/EOF 重试仪式。 > 命令契约与陷阱见 `docs/测试说明/测试工具/串口助手设计说明.md`、`docs/测试说明/流程与规范/自动化测试流程.md`。 @@ -423,7 +423,7 @@ workflow 见 `.gitea/workflows/ci.yml`;覆盖边界与判定细节见 [`../测 | Actions 开关 | 实例 `[actions] ENABLED = true` | site admin 配置;未开启则 push 不触发任何 job | | runner OS | **Windows**(`runs-on: windows`) | labels 须含 `windows`;与开发机同平台,避免脚本语法分叉 | | runner 软件 | Python 3.x、cppcheck、pre-commit、**MSVC**(VS C++ 工具链 + Windows SDK) | 前三个缺 → `static-gates` **硬失败**(`::error::`);MSVC 缺 → `host-unit-tests` **降级跳过并告警**(不硬失败,但等于主机侧纯逻辑单测裸奔)。runner 以 SYSTEM 服务运行,**用户级 PATH/变量不可见**:自动发现失败时用**机器级** `DTU_PRECOMMIT_PYTHON` / `DTU_CPPCHECK` / `DTU_MSVC_CL` 注入(见 `docs/CI/远程CI完整指南.md` §4.1.3) | -| Keil | **不需要** | 刻意不跑 `@build`(需授权);`.map` 缺失时 `mem-numbers` 钩子自动跳过 | +| Keil | **不需要** | 刻意不跑 `/keil-build`(需授权);`.map` 缺失时 `mem-numbers` 钩子自动跳过 | | `mem-numbers` | 有 `.map` 就跑,无则 `SKIP` | Keil 构建产物 `.map` 已 gitignore;自建带 Keil 的 runner 会照常跑该钩子 | **runner 就绪前的过渡**:CI 只是"多一道网",**不能替代本地门禁**。提交前仍须在本地跑: @@ -501,13 +501,13 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级 | CH395F 以太网 | SPI2 | PB12 CS / PB13 SCK / PB14 SDO / PB15 SDI | PHY 强制 100M 全双工;SPI 分频系数必须为 8(PCB 设计约束,速度受限) | | GD5F2GQ5UE NAND(2 Gbit) | SPI1 | PE0 CS / PB3 SCK / PB4 MISO / PB5 MOSI / PB8 WP / PE1 HOLD | FatFS(FAT32)+ dhara FTL;上电无卷自动 `f_mkfs` | | TPAFE5160 ADC | 并行 16 位 | PG0~PG15 数据 / **PC6 FRSTDATA** / **PC7 BUSY(EXTI9_5)** / **PC8 RD** / **PC9 CONVST** / **PA8 OS2 · PA11 OS1 · PA12 OS0** | **编译期常驻**(无裁剪开关;原 `APP_ADC_ENABLE` 未接线占位宏已于 2026-10-22 删除) | -| USART1(调试串口) | UART | PA9 TX / PA10 RX | 115200 8N1;printf 日志 + `@test` 采集通道 | +| USART1(调试串口) | UART | PA9 TX / PA10 RX | 115200 8N1;printf 日志 + `/serial-boot-test` 采集通道 | | UART4(RS-485) | UART | **PC10 TX / PC11 RX / PA15 DE** | 半双工 115200;`app_main.c` 以 `rs485_init(&g_rs485, &huart4, ST_DIR4_GPIO_Port, ST_DIR4_Pin)` 绑定;**编译期常驻**(无裁剪开关;原 `APP_RS485_ENABLE` 占位宏已删) | | 状态 LED ×6 | GPIO | **PC5 / PB0 / PB1 / PB2 / PF11 / PF12**(LED1~LED6) | **低电平点亮**;网名↔实板丝印为 `LED(n+4)` 倒序(V0.1 网表关注点 V3),固件 LED 序表须按丝印对齐 | ### 3.3 资源预算(部署红线) -实测(**2026-10-22 `@build` 快照**)。实时权威值恒以 `docs/内存管理/RAM预算台账.md` 与 +实测(**2026-10-22 `/keil-build` 快照**)。实时权威值恒以 `docs/内存管理/RAM预算台账.md` 与 `docs/内存管理/RAM使用分析与优化方案.md` §2.1 受管快照为准,本节只是部署视角的摘要: | 资源 | 已用 | 总量 | 利用率 | 余量 | 红线说明 | @@ -582,7 +582,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级 | # | 项目 | 方法 | 通过标准 | |---|---|---|---| -| 1 | 上电自启动 | 断电上电 + 串口采集 | `@test` PASS:Required `App init OK;Network Init;net init: OK;PHY: 100M FULL`,无 `FAIL/ERROR/HardFault` | +| 1 | 上电自启动 | 断电上电 + 串口采集 | `/serial-boot-test` PASS:Required `App init OK;Network Init;net init: OK;PHY: 100M FULL`,无 `FAIL/ERROR/HardFault` | | 2 | 网络连通 | PC ping 192.168.1.100 | 通(PHY 100M FULL) | | 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 | | 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 | diff --git a/docs/硬件平台/ADC通道映射说明.md b/docs/硬件平台/ADC通道映射说明.md index 580c3c74..2db873b9 100644 --- a/docs/硬件平台/ADC通道映射说明.md +++ b/docs/硬件平台/ADC通道映射说明.md @@ -132,7 +132,7 @@ STM32(U17)为 LQFP144 脚号。 | STM32 ADC 外设 | **已生成(2026-10-09,CubeMX 6.17 + FW_F4)**:`Src/adc.c`/`Inc/adc.h` 已入库并挂进 `MDK-ARM/*.uvprojx`;`Inc/stm32f4xx_hal_conf.h` 的 `HAL_ADC_MODULE_ENABLED` 已生效;HAL `stm32f4xx_hal_adc.c/_ex.c` + `stm32f4xx_ll_adc.h` 已补入仓库(HAL 原本是裁剪子集);`Src/main.c` 里 `MX_ADC1_Init()` 排在 `MX_IWDG_Init()` 之后。生成配置核对无误:9 通道 / Rank 1~9 / 全部 144 cycles / `ScanConvMode=ENABLE` / `ContinuousConvMode=DISABLE` / `ExternalTrigConv=ADC_SOFTWARE_START` + `EDGE_NONE` / `EOCSelection=ADC_EOC_SINGLE_CONV` / `DataAlign=RIGHT` / 12 位;`HAL_ADC_MspInit()` 已把 PA0~PA7+PC4 配成 `GPIO_MODE_ANALOG` | 硬件通路已打通(`hadc1` 可用);**消费者已落地**——`App/algo/adc_input.c` 的 `stm32_take()`(「STM32 ADC1 启动与扫描」,v2.0,2026-10-10):ADC1 由 **TIM3 TRGO 硬件触发**一轮 9 通道 + **DMA2_Stream4 循环搬运**进乒乓双缓冲,任务侧只消费刚完成的半区(零忙等),9 通道一轮 ≈ 67 µs | | ADC1 上电自校准 | **已补(2026-10-10)**:`Src/adc.c` 的 `USER CODE BEGIN ADC1_Init 2` 调用 `adc1_calibrate()`(实现同文件 `USER CODE BEGIN 1`,按 RM0090 §13.3.6:清 ADON→等生效→置 ADON→置 CAL→等 CAL 由硬件清零)。⚠ **CubeMX 不生成这一步**,且本仓库裁剪版 HAL **没有** `HAL_ADCEx_Calibration_Start`、裁剪版 CMSIS 头**没有** `ADC_CR2_CAL`(bit3,代码里按 RM 自建 `ADC1_CAL_BIT`)→ 属 USER CODE 区手工定制,审计项 `adc1-calibration-call` / `adc1-calibration-impl` 兜底 | 实测有效:校准前后同一拍 STM32 侧 6 路**同向**变化 `UB 898→961`、`UC 978→1038`、`IB 906→922`、`IC 1061→1071`、`IN 1017→1055`、`T 790→846`(+10~+63 LSB ≈ 0.2~1.5 % FS);TPAFE 侧 `UA/UN/IA` 一字不变 → 变化确来自 ADC1 校准。缺这一步不报错,只是**读数带失调** | | ADC 时钟 | CubeMX 的 F4 ADC 面板**有** Clock Prescaler 一项(此前"面板没有该项、固定 DIV2"的判断有误),已选 **PCLK2/4 → ADCCLK = 21 MHz** ✓。面板里 **DIV2 是灰的、选不到**:DB 里 `FadcConstraint Max=36000000`,APB2 = 84 MHz → DIV2 = 42 MHz 超限,该候选值被 `Action="DISABLE"` 禁掉,超规值进不来 | **不设额外防护(2026-10-09 决定)**:约束已由 CubeMX 把关;`.ioc` 虽不保存 `ADC1.ClockPrescaler` 键,重生成也只会回落到约束允许的值。按 144+12 cycles 算 ≈ 7.4 µs/通道、9 通道一轮 ≈ 67 µs | -| `adc_task` | **已实现(B1,2026-10-10;语义取数与有效性层 v2.3;P1-5 事件驱动消费,2026-10-10)**:`App/task/adc_task.c` 每拍调 `adc_input_poll()`(`App/algo/adc_input.c`)= TPAFE `tpafe5160_snapshot()` 原子快照(8 通道)+ 消费一帧 STM32 ADC1 DMA 乒乓缓冲,**经 `adc_input_raw()` 按语义取数**(不再直接索引驱动缓冲);**消费唤醒由 TIM3 ISR 置位 `ADC_WAKE_FLAG`**(不再 1 ms 轮询);任务在 `Src/freertos.c` 的 `USER CODE` 区创建为 `adcTask`(512 words / **AboveNormal**) | 数据路径已通(台架 `@test`:`frames` 严格 1000 Hz 递增、**`idle=0`**、`adc1_rc=0` / `adc1_err=0`) | +| `adc_task` | **已实现(B1,2026-10-10;语义取数与有效性层 v2.3;P1-5 事件驱动消费,2026-10-10)**:`App/task/adc_task.c` 每拍调 `adc_input_poll()`(`App/algo/adc_input.c`)= TPAFE `tpafe5160_snapshot()` 原子快照(8 通道)+ 消费一帧 STM32 ADC1 DMA 乒乓缓冲,**经 `adc_input_raw()` 按语义取数**(不再直接索引驱动缓冲);**消费唤醒由 TIM3 ISR 置位 `ADC_WAKE_FLAG`**(不再 1 ms 轮询);任务在 `Src/freertos.c` 的 `USER CODE` 区创建为 `adcTask`(512 words / **AboveNormal**) | 数据路径已通(台架 `/serial-boot-test`:`frames` 严格 1000 Hz 递增、**`idle=0`**、`adc1_rc=0` / `adc1_err=0`) | | TPAFE 采样触发 | **周期 CONVST 已实现(2026-10-10)**:`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback` 在 **TIM3 更新事件(1 kHz)** 内调 `tpafe5160_start_conv_irq()`;`app_main_init()` 用 `HAL_TIM_Base_Start_IT(&htim3)` 启动节拍(在 `tpafe5160_init()` 之后)。同一 TIM3 更新事件还经 **TRGO** 硬件触发 ADC1 一轮 9 通道(DMA 搬运),并调 `adc_tick_irq_notify()` **唤醒消费者**(P1-5 方案 a,2026-10-10;内含"消费者就绪"门)→ 两片转换器由同一硬件事件同源对齐 | 连续波形(1 kHz);详见 `docs/子系统设计/ADC采样节拍设计说明.md` §4(DMA 乒乓搬运)与 §6(事件驱动唤醒) | | 前端偏置 | 每路 1k 串联 + 滤波 C 跨接差分输入;差分负端(`*_GND` 网)经 1k 拴 AGND。**不是对地分压**(2026-10-27 网表追线定死,拓扑图见 §7.1),**无中点偏置** | TPAFE 输入阻抗 **1 MΩ 恒定**(`../芯片手册/TPAFE5160.md` "Analog Input Impedance" 节),故端子对 AGND 的直流通路 = 1k + 1 MΩ + 1k ≈ **1.002 MΩ**,不是 1 kΩ。两侧各 1 kΩ 是手册要求的**源阻抗匹配**(AIN_nP / AIN_nGND 外接源阻抗相等以抵消失调),**不是分压**。STM32 侧只能测 **0~3.3 V 单极性**,交流量负半周被削顶;主板侧只做监视可接受,若要算 RMS/功率需硬件补偏置或明确算法口径 | | TPAFE 量程(**2026-10-25 量程口径冻结定死**) | 板上 RANGE 接 GND → **±5 V 双极性量程**(芯片手册输入量程表:RANGE = 0 → −5 ~ +5 V),16 位二进制补码,**±5 V 满量程 = 10 V 跨度**,LSB = 10 V / 65536 ≈ **152.59 µV/码(@ 转换器引脚)**。“±10 V” 是芯片的另一可选档(RANGE = HIGH,跨度 20 V、LSB ≈ 305.18 µV),**本板未用**——历史版本曾把本板误标为“±10 V 双极性”,与 §7 本行矛盾、且会让 TPAFE 侧系数差一倍,更正记录见 §9 | 用 `tpafe5160_to_voltage()` 换算;端到端口径见 §7.1 | diff --git a/docs/算法/继电保护功能复现规划.md b/docs/算法/继电保护功能复现规划.md index a8472c52..99f183a8 100644 --- a/docs/算法/继电保护功能复现规划.md +++ b/docs/算法/继电保护功能复现规划.md @@ -4,7 +4,7 @@ > **参考来源**:`D:\Code\DTU\参考资料\huaiyuan\huaiyuan\Scatter_P_HV03.01_SV01.01\项目分析文档\04-算法与采样专题\ADC采集至数据使用全流程详解`(同专题另有《项目详细分析-交流采样与有效值计算》《傅氏算法详解》《项目详细分析-阶段6-继电器保护单元分析》)。 > **阅读前提**:先读 `docs/算法/傅氏算法详解.md`(算法原理)与 `docs/硬件平台/ADC通道映射说明.md`(采样通道权威口径)。 > **相关文档**:`docs/产品需求/产品需求设计文档.md`、`docs/任务规划/README.md`、`docs/内存管理/RAM使用分析与优化方案.md`。 -> **状态**:**P0 采样与相量底座已台架闭环(2026-10-11)**——语义取数与有效性采集链路(B1,2026-10-10)、 采样有效性校验与 全周傅氏相量层(`sample_ring` + `phasor`,57 项主机单测 + 台架 `@test` 含 `phasor(first)/phasor(hb)` 标记闭环)均已落地;**P1/P2 保护元件与定值模型软件侧完成(2026-10-11,`prot_set` + `prot_elm`,57 项主机单测)**;**B5 板端接线完成(2026-10-12)**——`protectTask`(20 ms 判据节拍 + 「自检失败闭锁」采样故障闭锁 + 定值校验和巡检,IWDG `TASK_WD_PROTECT` bit8,栈 512 words 静态链 872 B / 余 57%),判据结果经 B4 IEC104 遥信(YX15~23/31)上送;**B7 定值持久化 + 全局软压板 + 竞态收口完成(2026-10-12,含台架腿验证)**;**出口记录(出口与记录)与标准源注入验收为下一步**(见 §7)。 +> **状态**:**P0 采样与相量底座已台架闭环(2026-10-11)**——语义取数与有效性采集链路(B1,2026-10-10)、 采样有效性校验与 全周傅氏相量层(`sample_ring` + `phasor`,57 项主机单测 + 台架 `/serial-boot-test` 含 `phasor(first)/phasor(hb)` 标记闭环)均已落地;**P1/P2 保护元件与定值模型软件侧完成(2026-10-11,`prot_set` + `prot_elm`,57 项主机单测)**;**B5 板端接线完成(2026-10-12)**——`protectTask`(20 ms 判据节拍 + 「自检失败闭锁」采样故障闭锁 + 定值校验和巡检,IWDG `TASK_WD_PROTECT` bit8,栈 512 words 静态链 872 B / 余 57%),判据结果经 B4 IEC104 遥信(YX15~23/31)上送;**B7 定值持久化 + 全局软压板 + 竞态收口完成(2026-10-12,含台架腿验证)**;**出口记录(出口与记录)与标准源注入验收为下一步**(见 §7)。 --- @@ -38,7 +38,7 @@ | 已实现子系统 | 网络(CH395F)、存储(GD5F2GQ5UE SPI NAND + dhara FTL + FatFs)、时间(SD2506 RTC + `sys_clock`)、配置(cfg_file)、日志、UI(远端显示)、看门狗(IWDG)、FTP | | 采样硬件 | TPAFE5160(16 位、8 通道**同步**、±5 V 双极性、并行 `GPIOG[15:0]`)+ STM32 内部 ADC1(12 位、9 通道**顺序**扫描、0~3.3 V 单极性) | | 采样软件 | `Drivers/BSP/TPAFE5160/tpafe5160.h` 驱动就绪(原子快照 API);`App/task/adc_task.c` **已实现真实消费者(B1,2026-10-10)**——`tpafe5160_snapshot()` 取数 + 帧统计;**采样节拍由 TIM3 更新事件(1 kHz)硬件触发**(2026-10-10;同一事件经 TRGO 触发 ADC1 + DMA);STM32 侧 **`App/algo/adc_input.c` 已实现语义映射 + ADC1 DMA 乒乓帧消费(「STM32 ADC1 启动与扫描」,v2.0)**,`HAL_ADC_Start_DMA` 已出现 | -| 保护算法层 | **全周傅氏相量层已实现并台架闭环**(`App/algo/sample_ring.c` + `phasor.c`:全周傅氏 5 实例、双区交换、每 1 ms 刷新,2026-10-11 `@test`);**保护判据元件框架 + 过流/过负荷/过压/欠压/零序过压 + 定值模型软件侧完成**(`prot_elm.c` + `prot_set.c`,2026-10-11,57 项主机单测);**出口 / SOE 记录仍缺失**(P4) | +| 保护算法层 | **全周傅氏相量层已实现并台架闭环**(`App/algo/sample_ring.c` + `phasor.c`:全周傅氏 5 实例、双区交换、每 1 ms 刷新,2026-10-11 `/serial-boot-test`);**保护判据元件框架 + 过流/过负荷/过压/欠压/零序过压 + 定值模型软件侧完成**(`prot_elm.c` + `prot_set.c`,2026-10-11,57 项主机单测);**出口 / SOE 记录仍缺失**(P4) | | 已知硬件缺陷 | 登记簿条目「双转换器无共同采样触发」:8 路电气量分属两片转换器 → V0.1 上禁止跨转换器做相位/功率/矢量运算 | ### 1.3 复现范围界定 @@ -65,9 +65,9 @@ | 依赖 | 说明 | 状态 | | --- | --- | --- | | **B0 产品定义** | 采集是否持续 / 采样率与通道 / 转发目标 / 保护元件清单——决定 B1 与保护层的**规模** | ✅ 已确认(2026-10-10,见 §1.5) | -| **B1 ADC 采集链路** | 周期 CONVST + `adc_task` 消费 `tpafe5160_snapshot()` + 建任务 + 栈预算 | ✅ 已实现并 `@test` 实机闭环(2026-10-10;`adcTask` 512 words,台架 1000 Hz) | +| **B1 ADC 采集链路** | 周期 CONVST + `adc_task` 消费 `tpafe5160_snapshot()` + 建任务 + 栈预算 | ✅ 已实现并 `/serial-boot-test` 实机闭环(2026-10-10;`adcTask` 512 words,台架 1000 Hz) | | **双转换器无共同采样触发(硬件缺陷)** | V0.1 跨转换器无同一时刻性 → 限定 V0.1 可实现的保护范围 | ⚠️ 硬件改版项(V0.2) | -| **栈预算** | 保护任务栈需入 `Src/freertos.c` 并经 `scripts/stack_report.py` 核算 | ✅ 两任务均已核算:`adcTask`(**「采样有效性校验」后:栈抬至 640 words,静态 1080 B / 台架实测峰值 952/2560 / 余 57%**);`protectTask`(B5,2026-10-12:**512 words,静态链 872 B / 余 1176 B / 57%**,台架水位待 `@test`) | +| **栈预算** | 保护任务栈需入 `Src/freertos.c` 并经 `scripts/stack_report.py` 核算 | ✅ 两任务均已核算:`adcTask`(**「采样有效性校验」后:栈抬至 640 words,静态 1080 B / 台架实测峰值 952/2560 / 余 57%**);`protectTask`(B5,2026-10-12:**512 words,静态链 872 B / 余 1176 B / 57%**,台架水位待 `/serial-boot-test`) | ### 1.5 B0 产品定义(2026-10-10 确认) @@ -238,12 +238,12 @@ | 功能名 | 关键参数 | 完成判据 | 状态 | | --- | --- | --- | --- | -| 周期 CONVST 重触发 | 1000 Hz(与保护节拍同源) | 示波器见 U3 `BUSY` 连续脉冲;`tpafe5160_frame_seq()` 持续递增 | ✅ 2026-10-10:**TIM3 更新事件(1 kHz)硬件触发**(ISR 内 `tpafe5160_start_conv_irq()`;方案 A,零引脚/零驱动改动。⚠ 节拍源**不能**是 TIM6/TIM7——F4 上 ADC1 收不到它们的 TRGO,见 `docs/子系统设计/ADC采样节拍设计说明.md` §3.2);台架 `@test` PASS——`seq` 每 10 s 精确 +10000 = 1000.0 Hz、`frst_err=0`(早期证据 `docs/测试说明/测试数据/boot_log_data/20261010_prod_adc_tim6_boot_log.txt`,节拍源当时为 TIM6) | -| TPAFE 快照消费 | 8 通道 / 帧 | `adc_task` 每拍调 `tpafe5160_snapshot()`,返回 1/0/-1 有处理 | ✅ B1 已实现并 `@test` 闭环(`adc_task.c`;台架 `idle=0`、`frst_err=0`) | -| STM32 ADC1 启动与扫描 | 9 通道、144 cycles、**TIM3 TRGO 硬件触发 + DMA** | 全库出现 `HAL_ADC_Start_DMA`;9 通道码值随注入变化 | ✅ 2026-10-10(v2.0):`App/algo/adc_input.c` 的 `stm32_take()`——ADC1 由 TIM3 更新事件经 TRGO 触发一轮 9 通道,`HAL_ADC_Start_DMA(&hadc1, buf, 18)` + DMA2_Stream4 循环模式把码值自动搬进**乒乓双缓冲**,任务侧只消费刚完成的半区(**零忙等**,实测单拍 `adcp` 122 µs → **8 µs**);台架 `@test` PASS——`adc1_seq` 每拍 +1、`adc1_err=0`、`adc1_hw=0`。⚠ 「9 通道码值随注入变化」已由 2026-10-27 台架已知电压注入直接证实(六项判据全 PASS,实测记录 `docs/硬件平台/ADC通道映射说明.md` §8.1);两点标定与端子/子板变比确认仍待做 | +| 周期 CONVST 重触发 | 1000 Hz(与保护节拍同源) | 示波器见 U3 `BUSY` 连续脉冲;`tpafe5160_frame_seq()` 持续递增 | ✅ 2026-10-10:**TIM3 更新事件(1 kHz)硬件触发**(ISR 内 `tpafe5160_start_conv_irq()`;方案 A,零引脚/零驱动改动。⚠ 节拍源**不能**是 TIM6/TIM7——F4 上 ADC1 收不到它们的 TRGO,见 `docs/子系统设计/ADC采样节拍设计说明.md` §3.2);台架 `/serial-boot-test` PASS——`seq` 每 10 s 精确 +10000 = 1000.0 Hz、`frst_err=0`(早期证据 `docs/测试说明/测试数据/boot_log_data/20261010_prod_adc_tim6_boot_log.txt`,节拍源当时为 TIM6) | +| TPAFE 快照消费 | 8 通道 / 帧 | `adc_task` 每拍调 `tpafe5160_snapshot()`,返回 1/0/-1 有处理 | ✅ B1 已实现并 `/serial-boot-test` 闭环(`adc_task.c`;台架 `idle=0`、`frst_err=0`) | +| STM32 ADC1 启动与扫描 | 9 通道、144 cycles、**TIM3 TRGO 硬件触发 + DMA** | 全库出现 `HAL_ADC_Start_DMA`;9 通道码值随注入变化 | ✅ 2026-10-10(v2.0):`App/algo/adc_input.c` 的 `stm32_take()`——ADC1 由 TIM3 更新事件经 TRGO 触发一轮 9 通道,`HAL_ADC_Start_DMA(&hadc1, buf, 18)` + DMA2_Stream4 循环模式把码值自动搬进**乒乓双缓冲**,任务侧只消费刚完成的半区(**零忙等**,实测单拍 `adcp` 122 µs → **8 µs**);台架 `/serial-boot-test` PASS——`adc1_seq` 每拍 +1、`adc1_err=0`、`adc1_hw=0`。⚠ 「9 通道码值随注入变化」已由 2026-10-27 台架已知电压注入直接证实(六项判据全 PASS,实测记录 `docs/硬件平台/ADC通道映射说明.md` §8.1);两点标定与端子/子板变比确认仍待做 | | 通道语义映射 | 见 `docs/硬件平台/ADC通道映射说明.md`(**按语义取数,不写死下标**) | `UA→buf[6]`、`UN→buf[5]`、`IA→buf[2]`;STM32 侧 `UB→ADC_CHANNEL_14` | ✅ 2026-10-10:`App/algo/adc_input.c` 的 `k_adc_map[]` 语义表(`UA/UN/IA`→TPAFE `buf[6]/[5]/[2]`,`UB/UC/IB/IC/IN/TEMP`→ADC1 `CH14/3/6/1/2/5`)+ `adc_input_raw()` 按语义取数;开机**映射自检**(`adc_input_init()` 校验 STM32 映射通道必落在扫描序列内 → 挡住"映射到不扫描的通道"静默错)通过(`ADC input layer init: OK`)。台架首帧日志 8 路 + 温度全部读出:`UA=11429 UN=11454 IA=11452`(与 TIM3 轮次一致)、`UB=1018 UC=1044 IB=943 IC=1069 IN=1052 T=850`(互异、非饱和;**两次开机读数不同** → 确为实时转换而非常量) | | 通道标定(一次系数 + 零漂) | 每通道 `k`、`b`;系数定点 `2^10` | 注入标准源,读数误差 ≤ 目标精度 | ⬜ | -| 采样有效性校验 | FRSTDATA 跳变、帧序号单调、丢帧计数 | 芯片静默/丢帧被检出并置告警 | ✅ 2026-10-11:新增 `App/algo/adc_health.c/h`(纯逻辑判定层:静默 100 ms 门限 / 丢帧边沿 / 序号倒退锁存 / FRSTDATA·HAL 错误·STM32 停摆计数增量)+ `adc_task.c` 告警编排(严重事件与故障上升沿 `DBG_ERROR` + 每类每次开机一次 `SYSLOG_SELFTEST`,恢复 `DBG_INFO`;gap 不告警只计数,心跳增 `tgap`);主机单测 `test/host/adc_health/` **31 用例全绿**(`adc-health-unit-test` 钩子);台架 `@test` PASS 零 `[ERR]`(健康不误报,`docs/测试说明/测试数据/boot_log_data/20261011_prod_adc_health_boot_log.txt`)。⚠ 抬栈:判定链 `adc_health_report`→`syslog_post` 回显尾使 adcTask 静态最坏链 832→**1080 B** → 栈 512→**640 words**(余 1480 B / 57%) | +| 采样有效性校验 | FRSTDATA 跳变、帧序号单调、丢帧计数 | 芯片静默/丢帧被检出并置告警 | ✅ 2026-10-11:新增 `App/algo/adc_health.c/h`(纯逻辑判定层:静默 100 ms 门限 / 丢帧边沿 / 序号倒退锁存 / FRSTDATA·HAL 错误·STM32 停摆计数增量)+ `adc_task.c` 告警编排(严重事件与故障上升沿 `DBG_ERROR` + 每类每次开机一次 `SYSLOG_SELFTEST`,恢复 `DBG_INFO`;gap 不告警只计数,心跳增 `tgap`);主机单测 `test/host/adc_health/` **31 用例全绿**(`adc-health-unit-test` 钩子);台架 `/serial-boot-test` PASS 零 `[ERR]`(健康不误报,`docs/测试说明/测试数据/boot_log_data/20261011_prod_adc_health_boot_log.txt`)。⚠ 抬栈:判定链 `adc_health_report`→`syslog_post` 回显尾使 adcTask 静态最坏链 832→**1080 B** → 栈 512→**640 words**(余 1480 B / 57%) | | 等周波采样(软件测频 + 调频闭环) | 20 点/周波;测频范围建议 **45~55 Hz**(参考 40.5~60.5) | 频率偏移 ±2 Hz 时幅值误差 ≤ 1% | ⬜ | | 零序通道接入(外部专用 / 自产切换) | 外部 `UN`/`IN`;自产 `3I0 = Ia+Ib+Ic`(V0.2 起) | 零序通道可切换来源,值正确 | ⬜ | | 二次谐波/五次谐波提取入口 | k=2(涌流)、k=5(选线) | 相量层可产出对应谐波相量 | ✅ 2026-10-11:`phasor.c` 实例表 `C04-IA-F2`(k=2)/ `C05-IA-F5`(k=5)随每拍重算产出,台架心跳日志 `phasor(hb) C04/C05` 行可见(`docs/测试说明/测试数据/boot_log_data/20261011_prod_phasor_boot_log.txt`);谐波**抑制/提取正确性**已由主机单测覆盖(`test/host/phasor/`) | @@ -267,7 +267,7 @@ > **「基波相量实例」实例数 4 → 5 的来历**:参考项目(`APP/CLS100_Dsp/InstCalCell.c`)为 **4 个实例**——C01 基波 I/U、C02 零序基波、C03 二次谐波、C04 五次谐波。本项目按 V0.1 **有效通道**(`UA`/`UN`/`IA`,见 §1.6:`UB/UC/IB/IC/IN` 无中点偏置 → 削顶不可用)展开:C01 的"I/U"拆为两行(`C01-IA-F1` 电流 / `C02-UA-F1` 电压),其余顺延为 `C03-UN-F1` 零序、`C04-IA-F2` 二次、`C05-IA-F5` 五次。实现见 `App/algo/phasor.c` 的 `k_inst[]`;判据按 **5** 个实例断言(见 `docs/测试说明/单元测试/相量层单元测试说明.md` §2.6)。 -> **本表状态列口径**(2026-10-11 更新):「全周傅氏系数表」~「相位输出」/「每 1 ms 滑动窗刷新」/「双区指针原子交换」已**实现 + 主机单测(57 用例)+ 台架 `@test` 闭环**——闭环命令 `run_boot_test.py --required "...;phasor(first);phasor(hb)" --require-order "App init OK>phasor(first)>phasor(hb)"`,证据 `docs/测试说明/测试数据/boot_log_data/20261011_prod_phasor_boot_log.txt`(`fw build` 身份戳匹配、回读一致、锚点 ×1、零 `[ERR]`)。⚠ **幅值/相位精度判据(标准源)在无信号源台架上不可达**(同 §7.1 无示波器口径),且「通道标定(一次系数 + 零漂)」标定未实现 → 现读数为**原始码值**,只证明链路通;精度复核排在标定之后。「线/负序/零序合成」随 V0.2(双转换器无共同采样触发),「遥测谐波轨(可选)」可选项未做。 +> **本表状态列口径**(2026-10-11 更新):「全周傅氏系数表」~「相位输出」/「每 1 ms 滑动窗刷新」/「双区指针原子交换」已**实现 + 主机单测(57 用例)+ 台架 `/serial-boot-test` 闭环**——闭环命令 `run_boot_test.py --required "...;phasor(first);phasor(hb)" --require-order "App init OK>phasor(first)>phasor(hb)"`,证据 `docs/测试说明/测试数据/boot_log_data/20261011_prod_phasor_boot_log.txt`(`fw build` 身份戳匹配、回读一致、锚点 ×1、零 `[ERR]`)。⚠ **幅值/相位精度判据(标准源)在无信号源台架上不可达**(同 §7.1 无示波器口径),且「通道标定(一次系数 + 零漂)」标定未实现 → 现读数为**原始码值**,只证明链路通;精度复核排在标定之后。「线/负序/零序合成」随 V0.2(双转换器无共同采样触发),「遥测谐波轨(可选)」可选项未做。 ### 3.3 保护判据元件 @@ -362,7 +362,7 @@ | 定值区 RAM | 元件数 × 定值项 × 字宽 | 可由 cfg_file 载入 | | 任务栈 | `adcTask` ✅ / `protectTask` ✅(B5:512 words,静态链 872 B / 余 57%) | 建任务前后各跑 `scripts/stack_report.py` | -> ⚠️ **改内存相关代码后必须刷新快照**:`@build` 出 `.map` 后跑 `scripts/check_mem_numbers.py --write`,同步 `docs/内存管理/RAM使用分析与优化方案.md` §2.1(`mem-numbers` 钩子会因体积变化 FAIL)。 +> ⚠️ **改内存相关代码后必须刷新快照**:`/keil-build` 出 `.map` 后跑 `scripts/check_mem_numbers.py --write`,同步 `docs/内存管理/RAM使用分析与优化方案.md` §2.1(`mem-numbers` 钩子会因体积变化 FAIL)。 ### 4.4 频率跟踪 @@ -483,7 +483,7 @@ | 阶段 | 交付物 | 验收判据 | | --- | --- | --- | -| P0 | `adcTask`/`algo` 模块(采集触发 + 语义映射 + 标定 + 全周傅氏 + 双区交换)+ 设计文档 + 主机侧纯逻辑单测 | TPAFE 三路(`UA`/`UN`/`IA`)标准源读数误差 ≤ 目标;相量每 1 ms 刷新;`@test` 通过 | +| P0 | `adcTask`/`algo` 模块(采集触发 + 语义映射 + 标定 + 全周傅氏 + 双区交换)+ 设计文档 + 主机侧纯逻辑单测 | TPAFE 三路(`UA`/`UN`/`IA`)标准源读数误差 ≤ 目标;相量每 1 ms 刷新;`/serial-boot-test` 通过 | | P1 | A 相过流 + A 相过负荷元件 + 定值模型 | 注入 1.2×/0.9× 定值,A 相正确动作/返回;返回系数正确 | | P2 | 过压/欠压 + 过频/低频 + 零序过压元件 | 越限动作时间符合定值延时 | | P3 | 自检闭锁 + 采样通道自检;三相类元件建接口 | 自检失败闭锁;未使能元件不误动 | diff --git a/docs/算法/采样有效性校验设计说明.md b/docs/算法/采样有效性校验设计说明.md index 4fba4417..32f6bee4 100644 --- a/docs/算法/采样有效性校验设计说明.md +++ b/docs/算法/采样有效性校验设计说明.md @@ -455,7 +455,7 @@ stateDiagram-v2 ## 11. 怎么验证 -**验证分工先说清**:台架 `@test` 只能证明「健康时不误报」(要求零 `[ERR]`), +**验证分工先说清**:台架 `/serial-boot-test` 只能证明「健康时不误报」(要求零 `[ERR]`), **证不了「故障时必报」**——静默与倒退在健康台架上不可复现(复现 = 拔芯片或改驱动)。 「必报」这一半完全由主机侧单测承担,这就是本层必须是纯逻辑的原因(§4.4)。 diff --git a/docs/算法/采集与算法层设计说明.md b/docs/算法/采集与算法层设计说明.md index 8a190d15..4869d8d6 100644 --- a/docs/算法/采集与算法层设计说明.md +++ b/docs/算法/采集与算法层设计说明.md @@ -500,7 +500,7 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分 ### 12.2 板端闭环 -`@build` 0 错误 0 警告 → `@test` 看 `[ADC_TASK]` 首拍与心跳:`phasor(...)` 行、`freq(...)` 行 +`/keil-build` 0 错误 0 警告 → `/serial-boot-test` 看 `[ADC_TASK]` 首拍与心跳:`phasor(...)` 行、`freq(...)` 行 (`f=5000 valid=1`,`×0.01` 定点,MicroLIB 无 `%f`)、`ring` / `skip` / `adc1_*` / `tgap` 计数;健康判据 = **零 `[ERR]`**(`adc_health_report()` 的告警全部以 `[ERR]` 开头 → 误报会被闭环 直接抓住)。浮空工况应为 `valid = 0` 且读数为保持值。 diff --git a/docs/门禁说明/bom-guard.md b/docs/门禁说明/bom-guard.md index 6f82fbb0..b1882684 100644 --- a/docs/门禁说明/bom-guard.md +++ b/docs/门禁说明/bom-guard.md @@ -15,7 +15,7 @@ HEAD 是 `EF BB BF` 而工作区不是 → FAIL 并列出文件与补救动作 ## 2. 为什么要它(拦下过什么真事) - **立项直接来自一天内 5 次踩坑**:2026-10-25 道E 在 task-15(UI 页面批次)里连踩 4 次, - 道B 在批 1 踩过 1 次。每次的症状都一样:改完 UI 源文件、`@build` 突然报一串语法错,而改动 + 道B 在批 1 踩过 1 次。每次的症状都一样:改完 UI 源文件、`/keil-build` 突然报一串语法错,而改动 本身是加注释或改常量;`MDK-ARM/build_log.txt` 里的行号指向中文注释所在的**上一行**, 照着报错去查会查不出任何东西。真因只是文件开头少了 3 个字节。 - **别的门禁和编译器都不会响**:MSVC(模拟器侧)带不带 BOM 都编得过、0 警告;`no-mojibake` @@ -61,7 +61,7 @@ HEAD 是 `EF BB BF` 而工作区不是 → FAIL 并列出文件与补救动作 "HEAD 无 BOM、工作区加了 BOM" **不报**。那是另一个方向的改动,可能是有意的(把某个文件纳入 BOM 名单),也可能是无意的;报红只会逼人 `--no-verify`,而**养成绕门禁的习惯比漏检一次贵得多**。 "新增文件没有 BOM" 同样不报:名单里的新文件(如新建 `ui_page_xxx.c`)由创建者负责加 BOM, -漏加会在 `@build` 时以真错误暴露——本门禁不假装自己能替代那一步。 +漏加会在 `/keil-build` 时以真错误暴露——本门禁不假装自己能替代那一步。 ### 6.2 名单三处互指(防漂移) AGENTS 铁律第 2 条的名单、钩子 `files` 正则、脚本内 `BOM_LIST` 必须一致;脚本每次运行都核一次, diff --git a/docs/门禁说明/cppcheck.md b/docs/门禁说明/cppcheck.md index 842f191a..be82aeaa 100644 --- a/docs/门禁说明/cppcheck.md +++ b/docs/门禁说明/cppcheck.md @@ -39,7 +39,7 @@ `Middlewares/`、`MDK-ARM/`、`test/`(`test/` 由第二个钩子负责)。 - UI 生成文件(`ui_font_cn_12`、`ui_menu_tab`)**纳入受检**——生成器按工程约定产出,不是第三方。 - 本机须装了 cppcheck 才有效(远端 CI 的 runner 同样按 `DTU_CPPCHECK` → PATH 探测,缺则 job 直接失败); - CI 不跑构建类门禁 `@build`(见 `docs/CI/README.md`)。 + CI 不跑构建类门禁 `/keil-build`(见 `docs/CI/README.md`)。 - 速度口径:**单文件约 1~2 秒、全库约 20 s**(2026-10-28 实测 `cppcheck` 20.5 s、`test-cppcheck` 38.1 s)—— 提交期不跑它们,全量由 CI 与 `ci_local.py` 的 manual 段承担。 diff --git a/docs/门禁说明/cubemx-survival.md b/docs/门禁说明/cubemx-survival.md index 3dad5274..b3f606e3 100644 --- a/docs/门禁说明/cubemx-survival.md +++ b/docs/门禁说明/cubemx-survival.md @@ -31,7 +31,7 @@ ## 3. 触发面 / 看不见什么 -- `MDK-ARM/*.uvprojx` **不参与漂移检测**(`no_baseline=True`):该文件每次 `@build` 后都被 UV4 +- `MDK-ARM/*.uvprojx` **不参与漂移检测**(`no_baseline=True`):该文件每次 `/keil-build` 后都被 UV4 自动改写(XML 归一化 + `PackID` 降到本机 DFP 版本),参与检测等于每次构建后提交都被拦; 对本项只保留 `must_contain` 断言("源文件仍挂载")。 - 只查清单里**列了的东西**:没写进清单的新定制 = 不受保护(这是清单式门禁的固有代价)。 diff --git a/docs/门禁说明/host-unit-tests.md b/docs/门禁说明/host-unit-tests.md index dae4386b..6df942de 100644 --- a/docs/门禁说明/host-unit-tests.md +++ b/docs/门禁说明/host-unit-tests.md @@ -82,7 +82,7 @@ ## 6. 限制与盲区 1. **宿主编译器 ≠ ARMCC**:ARMCC 的警告(`#870-D` 多字节字符、`#1-D` 之类)、结构体对齐、 - `enum` 取型、MicroLIB 行为差异,本层一概照不到 → 必须 `@build`。 + `enum` 取型、MicroLIB 行为差异,本层一概照不到 → 必须 `/keil-build`。 2. **桩就是第二份真相**:`stub/` 与被测代码依赖的真实头文件不同步时,会出现"单测全绿、真机 行为不同"。桩签名改了而测试没跟着改,编译器未必报错(尤其桩返回宽类型)。 3. `include_dirs` 与源文件清单是**手写**的:新依赖一个目录就会假失败(见 §4 第 2 类),反过来, diff --git a/docs/门禁说明/mem-budget.md b/docs/门禁说明/mem-budget.md index d8db3411..43e81318 100644 --- a/docs/门禁说明/mem-budget.md +++ b/docs/门禁说明/mem-budget.md @@ -17,7 +17,7 @@ 配套的两个入口(**不在提交门禁里**,属于日常): -- `--update`:每次 `@build` 后往 `scripts/ram_baseline.tsv` 追一行趋势; +- `--update`:每次 `/keil-build` 后往 `scripts/ram_baseline.tsv` 追一行趋势; - `--trend`:打印趋势并**高亮 `RW+ZI` 较上一行增长 > 1 KB 的对象**——把"ZI 增量超 1 KB 必须解释" 这条纪律机械化; - `--write`:同步台账受管区(区域水位 + 子系统配额对照)。 @@ -48,7 +48,7 @@ RAM 预算门禁 FAIL:RW_CCM 占用 92.3% > 92%(阈值 92%) `--trend -n 12` 看趋势。**`--gate` 不修改任何东西**,要同步台账用 `--write`。 - 报 FAIL 的正确动作是**回去减**(缩缓冲/挪区段/减日志调用点),不是抬阈值。 -- 两类**软提醒不拦提交**:`tsv` 末行落后于 `.map`(忘了跑 `--update`)、`.map` 早于最新源码(忘了 `@build`)。 +- 两类**软提醒不拦提交**:`tsv` 末行落后于 `.map`(忘了跑 `--update`)、`.map` 早于最新源码(忘了 `/keil-build`)。 - 退出码:0 = 通过或跳过 / 1 = 超阈值或台账漂移 / 2 = **解析失败**(`.map` 读不出结构,不等于"没问题")。 - `.map` 解析口径复用 `scripts/ram_report.py`,两个脚本不会各说各话。 - 为什么需要(背景):2026-09 体系评审提出的"超阈值即失败 + Top-10 附表"此前**没有任何机械检查** @@ -78,7 +78,7 @@ RAM 预算门禁 FAIL:RW_CCM 占用 92.3% > 92%(阈值 92%) **git 索引里的 blob**,所以「工作区有别人在飞、尚未入索引的改动」时指纹一定对不上,报「这份源码没有被 真实链接测量过」——**这不是预算问题,也不拦提交**(该脚本只挂在 `scripts/ci_local.py` 的体积门禁,不在 51 条 pre-commit 钩子内)。判据:报错里若同时列出「未入索引的文件」且那些文件正被别的任务改,就等它们入库后 - 统一 `@build` → `ram_baseline.py --update` 补凭证,**别为了把它调绿去 `git add` 别人在飞的改动**。本条写作时 + 统一 `/keil-build` → `ram_baseline.py --update` 补凭证,**别为了把它调绿去 `git add` 别人在飞的改动**。本条写作时 (2026-10-27 09:4x)凭证**仍是红的**:`App/protocol/iec104/iec104_comm.{c,h}` 正被通讯道改、未入索引; 回填动作发生在该卡入库之后,届时以 `--update` 的实际输出为准,本节不预告结果。 diff --git a/docs/门禁说明/mem-numbers.md b/docs/门禁说明/mem-numbers.md index e17963da..f02bc32b 100644 --- a/docs/门禁说明/mem-numbers.md +++ b/docs/门禁说明/mem-numbers.md @@ -23,7 +23,7 @@ ## 3. 触发面与"看不见"的地方 -- 事实源必须是**最新一次 `@build` 产出的 `.map`**;`.map` 不存在(这台机器没做 Keil 构建) +- 事实源必须是**最新一次 `/keil-build` 产出的 `.map`**;`.map` 不存在(这台机器没做 Keil 构建) → **自适应跳过**(与 `ram_baseline.py --gate` 同口径)。CI 明确不跑构建,这一门在远端曾长期 是盲区——**2026-10-25 已由同码凭证关闭为强制路径**(`scripts/check_size_gate.py`,口径正文见其 docstring;CI 复核同码性 + 按凭证数字复跑红线,不重算绝对值)。 @@ -51,11 +51,11 @@ 2. **散句只报不改**,且 `--scan` 靠正则认数字——写成"约 75 KB""七万五千字节"或换单位的句子 不在核对范围内(假阴性)。 3. `.map` 缺失即跳过 → **在没有 Keil 构建的机器上它是绿的**(自适应跳过 = 无信息,不是通过)。 -4. 依赖最新构建:`@build` 之后没跑 `--write` 与文档同步,就会红;反过来,**先 `--write` 再改代码** +4. 依赖最新构建:`/keil-build` 之后没跑 `--write` 与文档同步,就会红;反过来,**先 `--write` 再改代码** 会写出与将要提交的代码不一致的漂亮数字。 5. 只覆盖已登记的文档:新开一篇写满体积数字的文档,不在名单里就没人管。 6. 同码凭证(2026-10-25)不重算绝对数字,只复核"代码 == 测量时的代码"与红线复算;编译器选项 - (`.uvprojx`)不在指纹范围内——换优化等级不改指纹,靠本地 `@build` 纪律兜。 + (`.uvprojx`)不在指纹范围内——换优化等级不改指纹,靠本地 `/keil-build` 纪律兜。 7. 数字口径 = `.map` 的静态视图,**不含运行时水位**(栈峰值、堆低水位、碎片)——那些要另跑 `scripts/stack_report.py` 与串口 `[STACK]` 读数。 diff --git a/docs/门禁说明/nested-comment.md b/docs/门禁说明/nested-comment.md index dc1b7d13..0a58aa29 100644 --- a/docs/门禁说明/nested-comment.md +++ b/docs/门禁说明/nested-comment.md @@ -24,7 +24,7 @@ - **按包含者数量刷屏**:一处嵌套写在被广泛 include 的头里,每个包含者各打一行。 本仓实测一例扩散到 **16 条警告**,把同批 10 条**真**警告挤到看不见—— 等于把「能不能看清报错」这件事本身弄坏。 -- 本闸**不需要构建**:提交时就报,不必等 @build,也不必去猜是谁的编译单元。 +- 本闸**不需要构建**:提交时就报,不必等 /keil-build,也不必去猜是谁的编译单元。 ## 2.1 接线当日实测(都可复现) @@ -44,7 +44,7 @@ > ..\App\algo\run_par.c(267): warning: #9-D: nested comment is not allowed > ``` > -> ⚠ 该日志每次 @build 都被覆盖、且不入库(02:37 已被下一次构建清掉,我写完这段再去查就没了), +> ⚠ 该日志每次 /keil-build 都被覆盖、且不入库(02:37 已被下一次构建清掉,我写完这段再去查就没了), > 所以把原文抄在这里存档;当日那三处同类**没有一处在已提交历史里留下字节**(上表最后一行), > 本闸真正第一次拦住,会是「下一次有人在注释里写星号形态」。 @@ -87,7 +87,7 @@ 形态会算新发(这条有专门的自测项)。 4. 只覆盖 .c/.h。Python 生成器模板将来若写出 /**,本闸照得到**产物**(产物是 .c/.h), 但不会指出模板——那段链由 `generated-fresh` 保证「脚本与产物同一笔提交」。 -5. 编译器侧的真实警告仍以 `@build` 为准:本闸只拦「会让 ARMCC 报 9-D 的那一类」, +5. 编译器侧的真实警告仍以 `/keil-build` 为准:本闸只拦「会让 ARMCC 报 9-D 的那一类」, 编译日志里其他诊断一概不管。 6. **仓库根探测必须兼容 linked worktree**:脚本原先用「`.git` 是不是目录」定位仓库根,而 `git worktree` 出来的任务工作树里 `.git` 是**一个文件**(写着 `gitdir: …`)不是目录 → 本闸在任何任务树里恒 diff --git a/docs/问题记录/GD5F2GQ5UE_Trap_Records.md b/docs/问题记录/GD5F2GQ5UE_Trap_Records.md index 216898b3..b1fbf669 100644 --- a/docs/问题记录/GD5F2GQ5UE_Trap_Records.md +++ b/docs/问题记录/GD5F2GQ5UE_Trap_Records.md @@ -155,7 +155,7 @@ if (ok) { 1. 探针文件名改 `/fschk.tmp`(严格 8.3:名≤8、扩展≤3、不以点开头); 2. `FR_INVALID_NAME` 移出重格式化触发集合(名字语法错误与卷健康无关)。 -**验证**(台架日志代次 45~48,@test 连续复位): +**验证**(台架日志代次 45~48,/serial-boot-test 连续复位): `Map resumed: 197→198→199→200 sectors used`(卷随 boot 正常增量持久)、 无 reformat 行、`[CFG] load: OK (523B)`(config.ini 跨复位存活)、VERDICT PASS。 @@ -271,7 +271,7 @@ if (ok) { **验证**: -- `@build` 0 错 0 警;`@test` PASS,且**台架 journal 恢复点与修复前同轨**——连续四次启动 +- `/keil-build` 0 错 0 警;`/serial-boot-test` PASS,且**台架 journal 恢复点与修复前同轨**——连续四次启动 `sectors used` 恒 14,094、`Map head` 每轮 +128 单调推进(37,472 → 37,600 → 37,728 → 37,856)、 `epoch` 稳定 3 → 整页 `is_free` 未改变恢复语义、无双编程、无回退。 - 主机侧移植层单测 `test/host/nand_port/`(RAM 假芯片 + "旧算法只看页首 64 B"**对照用例**, diff --git a/scripts/check_cubemx_survival.py b/scripts/check_cubemx_survival.py index 6e8d0f58..297b4b54 100644 --- a/scripts/check_cubemx_survival.py +++ b/scripts/check_cubemx_survival.py @@ -35,7 +35,7 @@ CubeMX 重生成存活审计(check_cubemx_survival.py) 注意 ---- `MDK-ARM/*.uvprojx` **不参与漂移检测**(`no_baseline=True`):该文件每次 -`@build` 后被 Keil UV4 自动改写(XML 格式归一化 + `PackID` 降级到本机 DFP +`/keil-build` 后被 Keil UV4 自动改写(XML 格式归一化 + `PackID` 降级到本机 DFP 版本),内容每次构建都不同。若参与检测,则**每次构建后提交都会被拦**。 本项只保留 `must_contain` 断言("源文件仍挂载")。 """ @@ -242,7 +242,7 @@ CHECKS = [ kind="generated", path="MDK-ARM/STM32F407-Demo.uvprojx", must_contain=["task_watchdog.c"], - no_baseline=True, # 见下方说明:本文件每次 @build 后被 Keil 改写 + no_baseline=True, # 见下方说明:本文件每次 /keil-build 后被 Keil 改写 hint="task_watchdog.c 须挂在 MDK 工程 App 组;CubeMX 重生成可能重建 .uvprojx。", ), dict( @@ -490,7 +490,7 @@ def evaluate(): # ---- 基线快照 ---- # `no_baseline=True` 的项不参与漂移检测。理由:`MDK-ARM/*.uvprojx` - # 每次 `@build` 后被 Keil UV4 自动改写(XML 格式归一化 + PackID 降级到 + # 每次 `/keil-build` 后被 Keil UV4 自动改写(XML 格式归一化 + PackID 降级到 # 本机 DFP 版本),内容每次构建都不同 → 若参与漂移检测,则**每次构建后 # 提交都会被拦**。本项只保留 must_contain 断言("源文件仍挂载")。 if not c.get("no_baseline"): diff --git a/scripts/check_mem_numbers.py b/scripts/check_mem_numbers.py index 34291e8a..0920c2ca 100644 --- a/scripts/check_mem_numbers.py +++ b/scripts/check_mem_numbers.py @@ -336,10 +336,10 @@ def main(argv): map_path = pathlib.Path(args.map) if not map_path.exists(): print(f"[FAIL] 找不到 .map 事实源:{map_path}", file=sys.stderr) - print(" 先跑一次 @build(keil-build skill)生成 .map,再跑本门禁。", file=sys.stderr) + print(" 先跑一次 /keil-build(keil-build skill)生成 .map,再跑本门禁。", file=sys.stderr) print(" 这不是代码缺陷:.map 是**每棵工作树各自的构建产物**(不入版本库),", file=sys.stderr) - print(" 新开的工作树(task-worktree new)从未构建过时必然走到这里——先 @build", + print(" 新开的工作树(task-worktree new)从未构建过时必然走到这里——先 /keil-build", file=sys.stderr) print(" 一次即可;随后按需要跑 scripts/ram_baseline.py --update 回填台账。", file=sys.stderr) diff --git a/scripts/check_size_gate.py b/scripts/check_size_gate.py index 03b67590..d2817558 100644 --- a/scripts/check_size_gate.py +++ b/scripts/check_size_gate.py @@ -5,7 +5,7 @@ 要消除的盲区 ------------ `mem-numbers` / `mem-budget` 两根闸的**事实源都是 Keil 链接产物 `.map`** -(gitignore,只有真做过 `@build` 的机器上才有)。于是两处都是"没跑 = 通过": +(gitignore,只有真做过 `/keil-build` 的机器上才有)。于是两处都是"没跑 = 通过": · CI 侧:runner 没有 Keil,workflow 只能把这根钩子**跳过**,历史上只打一条 `::warning::`(上一轮的处置)——"CI 全绿"仍不等于"体积守住了"; @@ -34,7 +34,7 @@ ② 那次测量的 RAM 受管区是否 <=92%(用已提交的测量数字守红线)。 2. 本门禁**不能**判: · CI 不重算绝对体积——runner 无 Keil/无 `.map`,绝对数字的可信度来自本地那次 - `@build`;本脚本把盖戳卡在"新鲜 .map + 未超线"之后,凭证不是随手可写的。 + `/keil-build`;本脚本把盖戳卡在"新鲜 .map + 未超线"之后,凭证不是随手可写的。 · **编译器选项不在指纹范围内**:`MDK-ARM/STM32F407-Demo.uvprojx` 每次构建都被 UV4 改写且按约定不入库(keil-build skill),把它放进指纹会让门禁在"刚刚构建完" 的那一刻自己变红;代价是改优化等级/预处理宏不触发本门禁,这类改动必须人工 @@ -257,7 +257,7 @@ def cmd_stamp(args): """用新鲜 .map 盖同码戳;三道前置任一不过即拒绝(见模块 docstring)。""" map_path = pathlib.Path(args.map) if not map_path.exists(): - print("[FAIL] 没有 .map(%s)——先 @build 再盖戳" % map_path, file=sys.stderr) + print("[FAIL] 没有 .map(%s)——先 /keil-build 再盖戳" % map_path, file=sys.stderr) return 1 m = measure(map_path) if m is None: @@ -266,7 +266,7 @@ def cmd_stamp(args): map_mt = map_path.stat().st_mtime src_mt = newest_source_mtime() if src_mt > map_mt: - print("[FAIL] .map 早于最新受管源码——这份构建不含最新改动,重新 @build", + print("[FAIL] .map 早于最新受管源码——这份构建不含最新改动,重新 /keil-build", file=sys.stderr) print(" .map mtime=%d 最新源码 mtime=%d" % (int(map_mt), int(src_mt)), file=sys.stderr) @@ -290,7 +290,7 @@ def cmd_stamp(args): file=sys.stderr) print(" 修复:先 git add <这些文件>(凭证描述的就是你正要提交的那份),再盖戳;", file=sys.stderr) - print(" 或把它们还原。@build 与盖戳都应在同一份内容上做。") + print(" 或把它们还原。/keil-build 与盖戳都应在同一份内容上做。") return 1 fp, n = tree_fingerprint() write_attestation({ @@ -339,7 +339,7 @@ def _cmd_parity(args, ci_mode): if doc is None: return _report(args, ci_mode, [ "体积同码凭证不可用:" + err, - " 修复:本地 @build 之后跑 python scripts/ram_baseline.py --update", + " 修复:本地 /keil-build 之后跑 python scripts/ram_baseline.py --update", " (或单独跑 python scripts/check_size_gate.py --stamp)", ]) fp, n = tree_fingerprint() @@ -350,14 +350,14 @@ def _cmd_parity(args, ci_mode): "指纹范围内有 %d 处**未 staged** 的改动/未跟踪文件,凭证覆盖不到它们" % dirt, " " + "、".join(samples) + ("…" if dirt > len(samples) else ""), " 含义:这些内容既没进索引(凭证按索引生成),也就没被那次测量算进去", - " 修复:git add 之后 @build,再跑 python scripts/ram_baseline.py --update", + " 修复:git add 之后 /keil-build,再跑 python scripts/ram_baseline.py --update", ] if fp != doc["fingerprint"]: problems += [ "本次代码(指纹 %s…,%d 文件)与体积测量(指纹 %s…,%s)不同码" % ( fp[:12], n, str(doc["fingerprint"])[:12], doc.get("built_at", "?")), " 含义:这份源码没有被真实链接测量过(或测量完又改了代码/合入了别的改动)", - " 修复:@build 0/0 之后跑 python scripts/ram_baseline.py --update 再提交", + " 修复:/keil-build 0/0 之后跑 python scripts/ram_baseline.py --update 再提交", " (指纹取 git 索引 blob:同一份内容在任何工作树/runner 上算法一致," "不受 CRLF/LF 检出差异影响)", ] @@ -441,7 +441,7 @@ def main(argv=None): ap = argparse.ArgumentParser( description="体积门禁强制路径(同码凭证 + CI 红线复核)") g = ap.add_mutually_exclusive_group() - g.add_argument("--stamp", action="store_true", help="用新鲜 .map 盖同码戳(@build 后)") + g.add_argument("--stamp", action="store_true", help="用新鲜 .map 盖同码戳(/keil-build 后)") g.add_argument("--local", action="store_true", help="本地/ci_local 自查:工作树须与测量同码") # ⚠ help 字符串里 % 必须写成 %%(argparse 会对 help 做 %-插值;裸 % + 中文 # 如"92% 红"在**无参/非法参的 print_help 路径**上才炸:ValueError diff --git a/scripts/ci_local.py b/scripts/ci_local.py index d6ff9020..ac60d4dc 100644 --- a/scripts/ci_local.py +++ b/scripts/ci_local.py @@ -31,7 +31,7 @@ message 喂给钩子。 刻意**不含**(与 CI 同理由,见 workflow 文件头): - - `@build`(需 Keil 授权;CI 也不跑,真编译是开发者本地红线) + - `/keil-build`(需 Keil 授权;CI 也不跑,真编译是开发者本地红线) - `mem-numbers` / `mem-budget` 钩子(事实源 `.map` 是 Keil 产物——**本地若存在 .map 就照常跑**,与 CI 的自适应逻辑一致)。⚠ 这两根闸在"无 .map"时是 **SKIP 而不是 PASS**(等于这一趟什么都没查);那个缺口由上面的体积门禁(size)补住:同码凭证 @@ -508,7 +508,7 @@ def run_static_gates(): else: print("[INFO] 未找到 .map(未做 Keil 构建)-> 跳过 mem-numbers / " "mem-budget(与 CI 一致)") - print("[INFO] :warning: 体积门禁未执行——改代码后请本地 @build + " + print("[INFO] :warning: 体积门禁未执行——改代码后请本地 /keil-build + " "check_mem_numbers.py --scan + ram_baseline.py --gate 兜底") skip = env.get("SKIP", "") add = "mem-numbers,mem-budget" @@ -655,7 +655,7 @@ def run_size_gate(): return 2 rc = _run([PY, str(script), "--local"]) if rc == 1: - print(" (1 = 没有凭证,或代码与测量不同码:@build 0/0 之后跑 " + print(" (1 = 没有凭证,或代码与测量不同码:/keil-build 0/0 之后跑 " "python scripts/ram_baseline.py --update)") print("[SUMMARY] size-gate: rc={}".format(rc)) return rc diff --git a/scripts/ram_baseline.py b/scripts/ram_baseline.py index 48ca48c9..004c7cb0 100644 --- a/scripts/ram_baseline.py +++ b/scripts/ram_baseline.py @@ -10,7 +10,7 @@ RAM 预算门禁 + 趋势台账(登记簿条目「缩堆迁 CCM 后半(暂 无 `.map`(未做 Keil 构建)→ 自适应跳过(与 `mem-numbers` 同口径, CI 盲区口径见 `scripts/check_size_gate.py` 的 docstring)。 `--gate` 同时校验台账受管区与 `.map` 一致(防漂移,同 `mem-numbers` 思路)。 - 2. **趋势基线** `scripts/ram_baseline.tsv`(`--update` 每次 @build 后追加一行; + 2. **趋势基线** `scripts/ram_baseline.tsv`(`--update` 每次 /keil-build 后追加一行; `--trend` 看趋势并高亮 RW+ZI 较上一行增长 >1 KB 的对象——方案 §4.0.3 "ZI 增量 >1KB 必须解释"的机械化)。 3. **台账受管区**(`--write` 同步 `docs/内存管理/RAM预算台账.md` 的 @@ -136,7 +136,7 @@ def warn_unattributed(info): "「应用」(台账 §2:应用 = App/ 其余),合计 %d B:" % (len(missing), total)) for obj, nbytes in missing: print(" %-28s %6d B" % (_obj_base(obj), nbytes)) - print(" 修法:补 subsystem_of() 前缀 -> 重跑 @build -> --update / --write 回填台账") + print(" 修法:补 subsystem_of() 前缀 -> 重跑 /keil-build -> --update / --write 回填台账") return len(missing) @@ -232,7 +232,7 @@ def cmd_gate(args): print(" 强制兜底(无需 Keil,改代码后必然拦红):") print(" 本地 scripts/ci_local.py 的体积门禁步 / CI 的「体积门禁」步,") print(" 都跑 scripts/check_size_gate.py(同码凭证 + 92% 红线复核)。") - print(" 改过代码请先 @build,再 python scripts/ram_baseline.py --update。") + print(" 改过代码请先 /keil-build,再 python scripts/ram_baseline.py --update。") return 0 info = collect(map_path) if not info["regions"]: @@ -284,13 +284,13 @@ def cmd_gate(args): ir = info["regions"].get("RW_IRAM1") if rows and ir and int(rows[-1]["iram1"]) != ir["size"]: print("[WARN] ram_baseline.tsv 末行落后于当前 .map —— " - "@build 后请跑 `python scripts/ram_baseline.py --update`") + "/keil-build 后请跑 `python scripts/ram_baseline.py --update`") else: print("[WARN] ram_baseline.tsv 不存在 —— 跑 " "`python scripts/ram_baseline.py --update` 建档") newest = _newest_source_mtime() if newest and newest > map_path.stat().st_mtime: - print("[WARN] .map 早于最新源码——数字来自旧构建,建议先 @build 再提交") + print("[WARN] .map 早于最新源码——数字来自旧构建,建议先 /keil-build 再提交") warn_unattributed(info) @@ -303,7 +303,7 @@ def cmd_gate(args): def cmd_write(args): map_path = _map_or_skip(args) if map_path is None: - print("[FAIL] 找不到 .map 事实源,先 @build(.map 是每棵工作树各自的构建产物,不入版本库;", + print("[FAIL] 找不到 .map 事实源,先 /keil-build(.map 是每棵工作树各自的构建产物,不入版本库;", file=sys.stderr) print(" 新开的 task-worktree 工作树从未构建过时必然走到这里,不是代码缺陷)", file=sys.stderr) @@ -352,7 +352,7 @@ def _top_field(top): def cmd_update(args): map_path = _map_or_skip(args) if map_path is None: - print("[FAIL] 找不到 .map 事实源,先 @build(.map 是每棵工作树各自的构建产物,不入版本库;", + print("[FAIL] 找不到 .map 事实源,先 /keil-build(.map 是每棵工作树各自的构建产物,不入版本库;", file=sys.stderr) print(" 新开的 task-worktree 工作树从未构建过时必然走到这里,不是代码缺陷)", file=sys.stderr) @@ -388,7 +388,7 @@ def cmd_update(args): f"({region_pct(reg['RW_CCM']):.1f}%) → {TSV_PATH.relative_to(REPO_ROOT)}") # 强制路径(2026-10-25):同一次「真实测量」顺带刷新**体积同码凭证** - # scripts/size_attestation.json——这样「每次 @build 后跑 --update」这条**既有 + # scripts/size_attestation.json——这样「每次 /keil-build 后跑 --update」这条**既有 # 纪律**同时喂饱那条无需 Keil 也能判的强制门禁(check_size_gate --ci / # --local,即 ci_local 的体积门禁步与 CI 的体积门禁步),不新增人工步骤。 # 盖戳失败只告警、不改本命令返回值:凭证新鲜度由门禁自己 fail-closed 兜, @@ -471,7 +471,7 @@ def main(argv=None): g = ap.add_mutually_exclusive_group() g.add_argument("--gate", action="store_true", help="92% 阈值门禁(pre-commit 入口)") g.add_argument("--write", action="store_true", help="同步台账受管区") - g.add_argument("--update", action="store_true", help="追加 tsv 趋势行(@build 后跑)") + g.add_argument("--update", action="store_true", help="追加 tsv 趋势行(/keil-build 后跑)") g.add_argument("--trend", action="store_true", help="打印趋势 + 高亮 >1 KB 增长") ap.add_argument("-n", type=int, default=10, help="--trend 显示行数") ap.add_argument("--map", default=str(MAP_DEFAULT), help=".map 路径") diff --git a/scripts/rename_ids.py b/scripts/rename_ids.py index a9b45862..21a150d5 100644 --- a/scripts/rename_ids.py +++ b/scripts/rename_ids.py @@ -240,7 +240,7 @@ def main(): "已落盘,请接着登记注册表族并重跑 scripts/check_ids.py" if args.apply else "干跑结束,确认后加 --apply")) print("提醒:改名后必跑 check_ids.py + check_doc_links.py --all + ci_local.py;" - "改到 test/board 或 Drivers/ 还要 @build。") + "改到 test/board 或 Drivers/ 还要 /keil-build。") return 0 diff --git a/scripts/rename_ref.py b/scripts/rename_ref.py index ecd566b7..4f91d6ed 100644 --- a/scripts/rename_ref.py +++ b/scripts/rename_ref.py @@ -468,7 +468,7 @@ def main(argv=None): print("ℹ 引用关系图是生成物,未改写 → 重跑 & 'tools\\refgraph\\gen_refgraph.bat'") if old_abs.suffix.lower() in (".c", ".h") or new_abs.suffix.lower() in (".c", ".h"): print("ℹ 改了 .c/.h:已同步 uvprojx 与 CMakeLists;若**新增目录**还需把目录加进 " - "uvprojx 的 (见 new-source-file skill),随后跑 @build 验证") + "uvprojx 的 (见 new-source-file skill),随后跑 /keil-build 验证") print("ℹ 正文里的**裸文件名/昵称引用**(如「内存方案 §2.1」「app_main.c/h」)无法机械判定," "已尽量改写,仍建议 grep 一遍旧名:git grep -n '<旧文件名>'") diff --git a/scripts/v5_log_gap_report.py b/scripts/v5_log_gap_report.py index b6ccffc1..ef6de86d 100644 --- a/scripts/v5_log_gap_report.py +++ b/scripts/v5_log_gap_report.py @@ -14,7 +14,7 @@ 真停滞嫌疑段,见下。 输入契约(serial-assistant 落盘日志 = 纯文本完整行;两种时钟源自动择一): -- 首选采集相对钟:行首 [+NNNms](@test/采集管线相对时戳;相对钟回退 >5s、或出现 +- 首选采集相对钟:行首 [+NNNms](串口回归/采集管线相对时戳;相对钟回退 >5s、或出现 reset cause 锚点行 = 新会话边界——烧录/复位/clear 都会造成不连续); - 无相对钟时退化为设备绝对钟:行内 [YYYY-MM-DD HH:MM:SS](墙钟未就绪的 [----/--/-- --:--:--] 不算时戳;RTC 同步类向前跳变按会话边界处理、不计静默; diff --git a/test/README.md b/test/README.md index 6a13bd37..ca4ba5e8 100644 --- a/test/README.md +++ b/test/README.md @@ -15,7 +15,7 @@ ## 运行方式 ```bash -# @test 一键闭环(推荐) +# /serial-boot-test 一键闭环(推荐) & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py # 单独校验日志 @@ -41,7 +41,7 @@ ⚠️ 每轮要真机烧录 + 采集,4 套件约数分钟起;破坏性套件还会改设备持久状态。 **仅回归窗口用,绝不挂 pre-commit**。 -`--check-build` 则相反:**只改宏 + `@build`,不烧录不采集,无需任何硬件**,用于防止 +`--check-build` 则相反:**只改宏 + `/keil-build`,不烧录不采集,无需任何硬件**,用于防止 "测试套件长期编不过而无人知"(2026-08~09 CH395F 套件实例)。改动 `test/board/` 下测试 代码后建议先跑它——实测 4 套件均 `BUILD_OK: 0 errors, 0 warnings`。 @@ -57,6 +57,6 @@ ## 相关文档 - `docs/测试说明/流程与规范/测试开发标准流程.md` — 测试验证分层与门禁决策表 -- `docs/测试说明/流程与规范/自动化测试流程.md` — `@test` 细节与失败模式 +- `docs/测试说明/流程与规范/自动化测试流程.md` — `/serial-boot-test` 细节与失败模式 - 各模块测试指南:`docs/测试说明/板级测试/*_Test_Guide.md` - `docs/测试说明/专项验证/链路延迟测试说明.md` diff --git a/test/board/README.md b/test/board/README.md index 5b90d3ff..3ae34c31 100644 --- a/test/board/README.md +++ b/test/board/README.md @@ -36,7 +36,7 @@ 1. 在 `test_config.h` 加开关(并入互斥 `#error` 检查) 2. 新建 `<模块>_test_task.c/h` 3. **注册进 Keil 工程**(`..\test\board\xxx.c`) -4. 跑回归:`@test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`) +4. 跑回归:`/serial-boot-test`(`.dsh/skills/serial-boot-test/scripts/run_boot_test.py`) ## 相关文档 diff --git a/test/board/test_common.h b/test/board/test_common.h index 26b01da6..df352486 100644 --- a/test/board/test_common.h +++ b/test/board/test_common.h @@ -66,7 +66,7 @@ typedef struct { /* * 报告内核:套件运行结束时打印汇总行 * `=== : N/M PASSED (failed=0) ===` - * 该汇总行是 `@test` 回归的默认判定标记(见 测试开发标准流程.md §3.2/§6.2.1)。 + * 该汇总行是 `/serial-boot-test` 回归的默认判定标记(见 测试开发标准流程.md §3.2/§6.2.1)。 */ #define TEST_REPORT_IMPL(stats, name) do { \ DBG_INFO("=== %s: %u/%u PASSED (failed=%u) ===", \ diff --git a/test/host/README.md b/test/host/README.md index 0c660bb8..a6335cd8 100644 --- a/test/host/README.md +++ b/test/host/README.md @@ -8,7 +8,7 @@ | 脚本 | 作用 | |---|---| -| `run_boot_test.py` | **`@test` 一键闭环**:开串口+清日志 → 烧录 → wait-idle → `verify_log` 校验(退出码 0=PASS) | +| `run_boot_test.py` | **`/serial-boot-test` 一键闭环**:开串口+清日志 → 烧录 → wait-idle → `verify_log` 校验(退出码 0=PASS) | | `verify_log.py` | **日志标记校验**:Required / Forbidden 标记 → PASS / FAIL;可选 `--require-suite-summary` 校验套件汇总行(`=== <名称>: N/M PASSED (failed=0) ===` 且 `N>0`),抓"没跑到汇总行/断言数为 0"的盲区 | | `e02_probe.py` | 头阻塞专项探针 | @@ -98,7 +98,7 @@ ## 运行方式 ```bash -# @test 一键闭环(推荐,需真机) +# /serial-boot-test 一键闭环(推荐,需真机) & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py # 测试套件构建的回归:要求套件跑到汇总行且断言数 > 0 @@ -149,7 +149,7 @@ ## 相关文档 -- `docs/测试说明/流程与规范/自动化测试流程.md` — `@test` 细节与失败模式 +- `docs/测试说明/流程与规范/自动化测试流程.md` — `/serial-boot-test` 细节与失败模式 - `docs/测试说明/流程与规范/测试开发标准流程.md` — 测试分层与门禁 - `docs/测试说明/单元测试/看门狗单元测试说明.md` — 主机侧单测(用例 / 平台层 / 扩展方法) - `docs/测试说明/单元测试/系统时钟单元测试说明.md` — 系统时钟换算单测(用例 / 桩 / Zeller 缺陷记录) diff --git a/test/host/console_log/console_log_test.c b/test/host/console_log/console_log_test.c index 51c31498..7da4e0f2 100644 --- a/test/host/console_log/console_log_test.c +++ b/test/host/console_log/console_log_test.c @@ -812,7 +812,7 @@ static void tc_full_drop(void) con_dwt_step = 0U; writer_drain(); CHECK(tx_contains("[ERR] CONSOLE dropped 1 lines"), - "TC-CONLOG-060 the drain tail flushes [ERR] CONSOLE dropped 1 lines (loss is never silent, @test marks FAIL)"); + "TC-CONLOG-060 the drain tail flushes [ERR] CONSOLE dropped 1 lines (loss is never silent, serial-boot-test marks FAIL)"); CHECK(dbg_console_dropped() == 0U, "TC-CONLOG-061 the counter is cleared once flushed (next round does not repeat the warning)"); CHECK(used_now() == 0U, "TC-CONLOG-062 the warning goes out raw: not through the ring -> no con_append->drain recursion"); diff --git a/test/host/lftpd/stub/dbg_log.h b/test/host/lftpd/stub/dbg_log.h index 4fe48470..32025e71 100644 --- a/test/host/lftpd/stub/dbg_log.h +++ b/test/host/lftpd/stub/dbg_log.h @@ -9,7 +9,7 @@ * private/lftpd_log.h——桩目录赢不过它。所以正确做法是让真实 lftpd_log.h 照常参与 * 编译(它的分级语义也就一起被验证了),只把最底下的 DBG_* 换成计数。 * 顺带说明分级(真实头注释里写得很清楚):lftpd_log_peer 是 **info** 级不是 error, - * 因为"客户端关了个窗口"不该把 @test 的 Forbidden 标记 [ERR] 触发成 FAIL,也不该 + * 因为"客户端关了个窗口"不该把 /serial-boot-test 的 Forbidden 标记 [ERR] 触发成 FAIL,也不该 * 让任何人连 21 端口就走就能无限制造固件级 ERROR;而 DOWNLOAD/UPLOAD FAILED 仍记 * error(那是数据完整性事件)。 * diff --git a/test/run_all_suites.py b/test/run_all_suites.py index 2d1fcfa3..095bd134 100644 --- a/test/run_all_suites.py +++ b/test/run_all_suites.py @@ -8,9 +8,9 @@ 对每个套件: 1. 改 test_config.h 启用该套件(其它全注释) - 2. @build(0 错 0 警,否则跳过该套件) + 2. /keil-build(0 错 0 警,否则跳过该套件) 3. 烧录(stlink-flash skill:stlink_flash.py flash,Reset & Run) - 4. @test 采集 + 判读(.dsh/skills/serial-boot-test 的 verify_log.py,含套件汇总行校验) + 4. /serial-boot-test 采集 + 判读(.dsh/skills/serial-boot-test 的 verify_log.py,含套件汇总行校验) 结束后: 5. **无条件还原 test_config.h**(try/finally,含异常/KeyboardInterrupt 路径) @@ -35,7 +35,7 @@ & '.venv\\Scripts\\python.exe' test/run_all_suites.py --keep-config # 不还原(调试用,慎) --check-build: - 只做"改宏 + @build",不烧录不采集,故**不需要 MCU / 串口 / ST-LINK**。 + 只做"改宏 + /keil-build",不烧录不采集,故**不需要 MCU / 串口 / ST-LINK**。 用途:① 改动测试固件或 test_config.h 后自检 4 套件仍可编译; ② 防止"某套件长期编不过而无人知"(2026-08~09 CH395F 套件实例)。 注意 RAM 溢出会自然暴露为链接错误 L6406E——这正是想要的。 @@ -72,7 +72,7 @@ FORBIDDEN_SUITE = "HardFault;MEMFAULT;BUSFAULT;USAGEFAULT;assert;bring-up FAIL" # 但 MEM/BUS/USAGE 三种**平时完全靠 [ERR] 兜**。所以旧 GD5F 只写 `HardFault;assert` 其实 # 放过了后三种崩溃。覆盖集合一律显式列出四个 Fault 标签 + assert + 起网失败字样: # 豁免的是**设计内的守卫 [ERR]**,崩溃照旧判失败。 -# 产品 @test 判据(.serialtest/config.json 的 forbidden 含 [ERR])**不在这里动**—— +# 产品 /serial-boot-test 判据(.serialtest/config.json 的 forbidden 含 [ERR])**不在这里动**—— # 豁免只活在"套件回归"这条路上,渗不回产品路径。 # GD5F:TC-GD5F-704 故意用非 DMA 可达缓冲触发 `[ERR][NAND] RX DMA buffer not # DMA-reachable` 回落轮询(用例 PASS)→ 需豁免设计内 [ERR]。 @@ -151,7 +151,7 @@ def main(argv=None): ap.add_argument("--flash-timeout", type=int, default=300) ap.add_argument("--dry-run", action="store_true", help="只打印计划,不改配置不构建不烧录") ap.add_argument("--check-build", action="store_true", - help="只编译不烧录:逐套件改宏 + @build,验证 4 套件可编译性;" + help="只编译不烧录:逐套件改宏 + /keil-build,验证 4 套件可编译性;" "不需要 MCU / 串口 / ST-LINK,可用于 CI 或改动测试固件后自检") ap.add_argument("--keep-config", action="store_true", help="结束后不还原 test_config.h(调试用;⚠️ 会留下测试固件配置)") @@ -199,7 +199,7 @@ def main(argv=None): print("################ {} ################".format(suite)) _rewrite_config(macro) - # 1) 构建(@build = keil-build skill;脚本内超时先于 _run 超时触发, + # 1) 构建(keil-build skill 执行体;脚本内超时先于 _run 超时触发, # 超时会返回 rc=1 + BUILD_FAIL 文案,而不是被 _run 杀掉) rc, out = _run([py, str(KEIL_BUILD), "--timeout", str(args.build_timeout)], timeout=args.build_timeout + 60) @@ -224,7 +224,7 @@ def main(argv=None): print("[FLASH] rc={} elapsed={:.1f}s".format(frc, time.monotonic() - t0)) if frc != 0: print("\n".join(fout.strip().splitlines()[-6:])) - # 烧录失败不直接判死:板子可能已复位,交由采集+判读决定(与 @test 一致) + # 烧录失败不直接判死:板子可能已复位,交由采集+判读决定(与 /serial-boot-test 一致) print("[WARN] 烧录 rc={}(继续:板子可能已复位)".format(frc)) # 3) 采集 + 判读(含套件汇总行校验) @@ -265,7 +265,7 @@ def main(argv=None): # 5) 复位构建产物为产品态(2026-10-02):最后一轮构建留下的是**测试套件**的 # .map/.axf,会污染 mem-numbers 体积门禁(下次提交即 FAIL)。仅还原源码不够 # ——必须再编一次产品固件,使 .map 与 test_config.h 一起回到产品态。 - # --check-build 也要做:它每轮都 @build,同样会留下测试态 .map。 + # --check-build 也要做:它每轮都 /keil-build,同样会留下测试态 .map。 if restored and not args.keep_config: print("") print("[..] 复位构建产物为产品态(重建一次产品固件,避免 .map 停留在测试套件态)...") @@ -276,7 +276,7 @@ def main(argv=None): print("[OK] 产品固件已重建(.map/.axf 复位)") else: # 不判死:配置已还原,仅提示手工重建 - print("[WARN] 产品固件重建失败(rc={}),请手工跑 @build 复位 .map".format(rc)) + print("[WARN] 产品固件重建失败(rc={}),请手工跑 /keil-build 复位 .map".format(rc)) if tail: print(tail) diff --git a/tools/arch_diagram/gen_memory_svg.py b/tools/arch_diagram/gen_memory_svg.py index 90b3fb1d..25c3538e 100644 --- a/tools/arch_diagram/gen_memory_svg.py +++ b/tools/arch_diagram/gen_memory_svg.py @@ -17,7 +17,7 @@ import sys # Diagram data # --------------------------------------------------------------------------- -# ── RAM breakdown (2026-10-22 @build, map Grand Totals; data/ram_report_20261022.md) ── +# ── RAM breakdown (2026-10-22 构建, map Grand Totals; data/ram_report_20261022.md) ── # Region 1: main SRAM RW_IRAM1 @0x20000000 (128KB = 131,072 B, DMA-reachable) SRAM1_TOTAL = 131072 SRAM1_USED = 75264 @@ -45,7 +45,7 @@ RAM2_CATEGORIES = [ ("free", CCM_FREE, "#E0E0E0"), ] -# ── Flash breakdown (1MB = 1,048,576 B; 2026-10-23 @build) ────────────────── +# ── Flash breakdown (1MB = 1,048,576 B; 2026-10-23 构建) ────────────────── FLASH_TOTAL = 1048576 FLASH_CODE = 213676 FLASH_RO = 18860 @@ -76,7 +76,7 @@ FLASH_CODE_SUB = [ ("其他 (长尾)", 7585, "#E0E0E0"), # remainder(2026-10-23 的零散增量) ] -# ── Task stack budgets (2026-10-22 @build + 同日真机 [STACK] 实测; data/stack_report_20261022.md) ── +# ── Task stack budgets (2026-10-22 构建 + 同日真机 [STACK] 实测; data/stack_report_20261022.md) ── # (name, cfg, prio, worst, peak_used, margin, margin_pct, verdict, desc) # worst = max(静态最坏深度, 实测峰值); margin = cfg - worst # 9 行含 testTask(仅测试构建创建、无实测 → 取静态最坏链,峰位列填同值) @@ -178,7 +178,7 @@ def gen_ram_svg(): SCALE = (W - 80) / SRAM1_TOTAL # 128KB 占满宽;CCM (64KB) 占半宽 svg = svg_header(W, H) - svg += f'SRAM (192KB) 使用布局 — 2026-10-22 @build\n' + svg += f'SRAM (192KB) 使用布局 — 2026-10-22 构建\n' # Region 1: 主 SRAM y1 = 50 @@ -246,7 +246,7 @@ def gen_flash_svg(): SCALE = (W - 80) / FLASH_TOTAL svg = svg_header(W, H) - svg += f'Flash (1MB) 使用布局 — 2026-10-23 @build\n' + svg += f'Flash (1MB) 使用布局 — 2026-10-23 构建\n' x = BAR_X for label, size, color in FLASH_CATEGORIES: @@ -304,7 +304,7 @@ def gen_taskstack_svg(): svg = svg_header(W, H) svg += f'FreeRTOS 任务栈预算(9 任务;栈全部从 56KB FreeRTOS 堆分配,堆位于 CCM)\n' - svg += f'数据: 2026-10-22 @build + 同日真机 [STACK] 水位(data/stack_report_20261022.md);最坏 = max(静态最坏链, 实测峰值);判定线 = 余量 ≥ max(1024B ISR, 配置×30%)\n' + svg += f'数据: 2026-10-22 构建 + 同日真机 [STACK] 水位(data/stack_report_20261022.md);最坏 = max(静态最坏链, 实测峰值);判定线 = 余量 ≥ max(1024B ISR, 配置×30%)\n' svg += f'浅色条 = 分配栈 深色条 = 最坏占用(max(静态最坏, 实测峰值)) 下方文字 = 实测余量/判定\n' y = Y_START