merge: v2-on-ui into task/screenlink-bench-serial (wave + screenlink)
This commit is contained in:
@@ -34,5 +34,5 @@ python tools/font_gen/extract_cn.py # 扫描 UI 源码字符串 →
|
||||
- `requirements.txt` 必须**纯 ASCII**(Windows 下 pip 按 GBK 解码,中文注释 → UnicodeDecodeError)
|
||||
- 生僻字(宋体可能无字形)→ **写码前先手动验证字形**,避免"代码写完才发现是空白"
|
||||
- 生成成功后跑 `ui-verify` skill 全回归;黄金参考因字形变化而 FAIL 属预期 → `check_content_pages.py --update` 刷新
|
||||
- 字数变化时同步更新文档中的字符集计数(当前 218 汉字/符号 + ASCII 0x20-0x7E,共 313 字形;2026-10-28 实测。历史上 119/214 → 179/274 → 185/280 均为当时快照):`UI_中文字体生成与配置.md`、`UI_界面制作流程.md` C3、`UI_设计架构.md`、`simulator/README.md` 已知坑 #4
|
||||
- 字数变化时同步更新文档中的字符集计数(当前 **224** 汉字/符号 + ASCII 0x20-0x7E,共 **319** 字形;2026-10-29 实测:`ui_cn_chars.txt` 唯一码点 224、`ui_font_cn_12.c` 的 `.bitmap_index` 320 条含 notdef → 319 字形。历史上 119/214 → 179/274 → 185/280 → 218/313 均为当时快照):`UI_中文字体生成与配置.md`、`UI_界面制作流程.md` C3、`UI_设计架构.md`、`simulator/README.md` 已知坑 #4
|
||||
- **新增字低于既有最小码位**(如 Ⅰ U+2160)会下探稀疏 cmap 起点并吞掉单码位补回条目(实测 ▶ 箭头全变方框而旧版 coverage 全绿)——`make_font.py`(2026-10-25 起)按 LVGL 首命中语义插入/守卫/校验,扩字后模拟器像素回归**必跑**
|
||||
|
||||
@@ -7,12 +7,15 @@ version: 1.1.0
|
||||
|
||||
# 编号族清退成语义标识
|
||||
|
||||
**2026-10-29 用户裁定:全族一律停发**(含 `docs/编号规范.md` §4.1 那五个存量族)——本 playbook 的「停发」分支默认已成立,
|
||||
现在唯一还需要决策的是**要不要清退存量**(全仓改语义 + 销项删行 + 逐号换算表入档)。
|
||||
|
||||
**动手前先判这一族有没有「机器识别面」**——它决定计数口径的来源与落地去处,两条路的做法**正好相反**:
|
||||
|
||||
| 族形态 | 计数口径来源 | 改名手段 | `scripts/archive/id_gate/id_aliases.json` |
|
||||
|---|---|---|---|
|
||||
| 字母+数字,且在 `scripts/archive/id_gate/check_ids.py` 语料内(`id_registry.json` 有族、族 pattern 能匹配它) | 门禁自身计数(`ck.refs` / `ck.stats` / 报告行) | `scripts/rename_ids.py`(映射表 + 显式 scope + 默认干跑) | **要登记**:死号 `void`、转短语 `retired-to-phrase` |
|
||||
| 中文前缀(「卡」+ 数字)、纯散文里的号、圈号 ①②③ | **自建带标签正则扫描器**(门禁看不见它,2026-10-28 退役前的 id-check 全库报告与本族无关;现行 id-check-new 只查新增行) | 逐处替换 + 断言命中数(`rename_ids.py` 靠族 pattern 驱动,这里用不上) | **刻意不登记**:登记了机器也看不见,只多一张没人核的表;停发只靠规范 + 评审 |
|
||||
| 中文前缀(「卡」+ 数字)、纯散文里的号、圈号 ①②③ | **自建带标签正则扫描器**(门禁看不见它,2026-10-28 退役前的 id-check 全库报告与本族无关;两道门禁现都已退役(2026-10-28 / 2026-10-29),要普查形态就手工跑归档脚本 scripts/archive/id_gate/check_ids_new.py --range) | 逐处替换 + 断言命中数(`rename_ids.py` 靠族 pattern 驱动,这里用不上) | **刻意不登记**:登记了机器也看不见,只多一张没人核的表;停发只靠规范 + 评审 |
|
||||
|
||||
怎么判(三条,任一为否即无识别面):① `grep -nE '"name"|"pattern"' scripts/archive/id_gate/id_registry.json`,没有任何族 pattern 能匹配该形态;② `./.venv/Scripts/python.exe scripts/archive/id_gate/check_ids.py --top 0` 的任何校验里找不到它;③ registry 的 `blind_spots` 已明写不判(圈号与正常列举无法机械区分、裸形同字母多义只在属主登记处判)。实测:「卡」+ 数字**没有族**(中文形态只有 `malformed` 里 `陷阱 ?[0-9]` 那条禁令,管的是陷阱编号不是卡号),该族真实站点 288 处 / 56 文件,而门禁对它贡献 0。
|
||||
|
||||
@@ -48,9 +51,9 @@ version: 1.1.0
|
||||
14. 裁定「不做」也要写成可执行硬口径入档
|
||||
> 本节全文(逐字搬移、未改写,含命令与实测数字)→ [steps-alpha-numeric-family.md](reference/steps-alpha-numeric-family.md)。动手前先读那篇。
|
||||
|
||||
## Steps(中文前缀族:门禁咬不到的那一路)
|
||||
## Steps(中文前缀族:连归档扫描工具都咬不到的那一路)
|
||||
|
||||
1. 先确认门禁咬不到,并把这句话写进交付
|
||||
1. 先确认连归档扫描工具也咬不到(两道编号门禁 2026-10-28 / 29 已退役,"咬不咬"从此只是工具层面的事),并把这句话写进交付
|
||||
2. 销项删行前先核证据属主
|
||||
3. 自建带标签正则扫描脚本
|
||||
4. 该族与形似噪声号分开计数,各自给分母
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本文由 id-family-semantic-migration 的 SKILL.md「Steps(字母+数字族:门禁看得见的那一路)」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。
|
||||
|
||||
1. **确认范围与定调**:先按开头的表判识别面(无识别面 → 走下一节);再读 `docs/编号规范.md` §4.1(族总表,穷举)、§4.1.1(已停发栏),存量与批次见 `scripts/archive/id_gate/编号治理历史.md` §9,明确本族属主登记处(`owner_docs_glob`)与判据「有脚本按字面量匹配它」。清退 = 全仓改语义;停发 = 游标冻结、只减不增。
|
||||
1. **确认范围与定调**:先按开头的表判识别面(无识别面 → 走下一节);再读 `docs/编号规范.md` §4.1(族总表,穷举)、§4.1.1(已停发栏),存量与批次见 `scripts/archive/id_gate/编号治理历史.md` §9,明确本族属主登记处(`owner_docs_glob`);旧判据「有脚本按字面量匹配它」自 2026-10-29 起作废——全族一律停发。清退 = 全仓改语义;停发 = 在规范表里写死不再新增引用(`id_registry.json` 的游标冻结只是裁定留痕,2026-10-29 起无机器读者)。
|
||||
2. **按族实测三口径(字面量 / 站点 / 文件),不用 grep 估数**。`Checker.refs` 是 **dict(字面量 → 文件集合)不是方法**,`ck.refs(fam)` 这种写法会 `TypeError`;站点数门禁不直接给,用族正则重扫它自己的语料:
|
||||
```python
|
||||
import sys; sys.path.insert(0, "scripts")
|
||||
@@ -38,7 +38,7 @@
|
||||
p.write_text(t, encoding='utf-8')
|
||||
```
|
||||
**token 取最小形态**:文档里被反引号/加粗包住的号,替换单位取反引号形(```T4```)而**不含 `**` 标记**(第 225 行是 `**裸形 `T4`**`,按「裸形 `T4`」替就命中 0)。讲编号的文档改成**形态描述**(如「字母 T 紧跟数字 4」)能把不可判定绝对数还回余量:实测 4 处 → 982 降到 978,且 lesson 不丢。
|
||||
8. **同步更新门禁侧配置**:`check_ids` 的 `excluded_paths` 与噪声表按第 3 步的分类结果改,使剩余命中不再是「假编号」;新测试套件发号的悬空引用要先定**定义位**:
|
||||
8. **同步更新归档扫描器的配置**:`check_ids` 的 `excluded_paths` 与噪声表按第 3 步的分类结果改,使剩余命中不再是「假编号」;新测试套件发号的悬空引用要先定**定义位**:
|
||||
- 定义位**只能落一处**——注册表族(`scripts/archive/id_gate/id_registry.json`)或该套件说明文档,**两处并存比没有定义位更坏**;文档真写出来了(含判据与上板清单)就以文档为准、**撤族**;
|
||||
- 要建族就照现有同类族克隆(`pattern` / `digit_width` / `owner_docs_glob` / `scope` / `cursor`),别自造字段;
|
||||
- **抬游标前先确认游标是字符串形态**(`'cursor': 'TC-UIDATA-026'`)——整型比较会静默不生效(实测踩坑),用字符串解析或正则取序号再比。
|
||||
@@ -58,9 +58,9 @@
|
||||
./.venv/Scripts/python.exe scripts/archive/id_gate/check_ids.py --strict 2>&1 | tail -1 # [OK] 校验 1~6 无错误
|
||||
```
|
||||
报告「字面量 8787→8299→7828」「不可判定绝对数 1161→984→982→978(上限 1166)」「可判定比例 88.14%(基线 86.0%,容差 0.1pp)」。**绝对数与比例两根线都要报**:把可判定引用改成散文会稀释比值,所以另有不可判定绝对数上限。涨点归因见「净室归因」一节。
|
||||
13. **回写 `docs/编号规范.md`**:§4.1 族表(停发/待清栏)、§4.1.1 已停发栏、§9 存量表与批次结果表(只减不增)、§9 清点数字按最终实测同步;表格若保留动手前的清点数,**明确标注「不是当前存量,是动手前基线」**,否则就是拿旧数当现状。**用户裁定原文与理由直接写进对应节**(§4.1.1 停发行 + 该族的批次结论段落)——2026-10-28 起不再往变更记录写流水(`docs/文档规范.md` §5.1)。
|
||||
13. **回写 `docs/编号规范.md`**:§4.1 族表(停发/待清栏)、§4.1.1 已停发栏;存量表与批次结果表在 `scripts/archive/id_gate/编号治理历史.md` §9(只减不增、清点数字按最终实测同步);表格若保留动手前的清点数,**明确标注「不是当前存量,是动手前基线」**,否则就是拿旧数当现状。**用户裁定原文与理由直接写进对应节**(§4.1.1 停发行 + 该族的批次结论段落)——2026-10-28 起不再往变更记录写流水(`docs/文档规范.md` §5.1)。
|
||||
14. **裁定「不做」也要写成可执行硬口径入档**(不是只划掉一行——「不做」最容易被读成「那还可以继续发号」):
|
||||
- §4.1.1「已停发」栏新增该族一行:**游标冻结、现存号只减不增、新用例改用语义化测试函数名 + 文档表格行名**,并写清与「允许继续发号」栏为何不打脸;
|
||||
- §9.1 批次段落写明裁定原文与理由(该族是唯一真被脚本按字面量匹配的族:C 断言标签 + 测试函数名 + 文档用例表三处同一号),并给出**将来真要清退的前置条件**:追溯器(`check_test_traceability.py`)先同时认新旧两种形态、并行核对通过,**不许先改文档再改代码**;
|
||||
- §4.1.1「已停发」栏新增该族一行:**游标冻结、现存号只减不增、新用例改用语义化测试函数名 + 文档表格行名**,并写清与 §4.1 存量族栏的关系(那五族 2026-10-29 起也一律停发);
|
||||
- 批次段落写明裁定原文与理由(归档 `scripts/archive/id_gate/编号治理历史.md` §9.1——§9 一族的事实源已在归档篇,规范正文只留口径)(该族是唯一真被脚本按字面量匹配的族:C 断言标签 + 测试函数名 + 文档用例表三处同一号),并给出**将来真要清退的前置条件**:追溯器(`check_test_traceability.py`)先同时认新旧两种形态、并行核对通过,**不许先改文档再改代码**;
|
||||
- 全文核「等你裁定 / 需你裁定」悬空表述残留数归 0;
|
||||
- 无机器识别面的族落地面不同(§4.1.1 一行 + §9.3 换算表 + 叙述同步三处),见下一节第 7 步。
|
||||
- 无机器识别面的族落地面不同(`docs/编号规范.md` §4.1.1 一行 + 归档 `编号治理历史.md` §9.3 换算表 + 叙述同步三处),见下一节第 7 步。
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Steps(中文前缀族:门禁咬不到的那一路)(自 SKILL.md 外置)
|
||||
# Steps(中文前缀族:连归档扫描工具都咬不到的那一路)(自 SKILL.md 外置)
|
||||
|
||||
> 本文由 id-family-semantic-migration 的 SKILL.md「Steps(中文前缀族:门禁咬不到的那一路)」一节逐字外置而来(渐进展开:主文件只留索引与指针)。步骤号沿用原文,未重编。
|
||||
|
||||
1. **先确认门禁咬不到,并把这句话写进交付**:`scripts/archive/id_gate/check_ids.py` 的族 pattern 是拉丁形态、中文前缀不进语料 → **id-check 的 refs / 可判定比例 / 悬空引用与本族无关**;报告开头写「本族不在 check_ids 语料内,口径 = 自建扫描器」。
|
||||
1. **先确认连归档扫描工具也咬不到,并把这句话写进交付**(两道编号门禁已退役,机器面整体消失):`scripts/archive/id_gate/check_ids.py` 的族 pattern 是拉丁形态、中文前缀不进语料 → **id-check 的 refs / 可判定比例 / 悬空引用与本族无关**;报告开头写「本族不在 check_ids 语料内,口径 = 自建扫描器」。
|
||||
2. **销项删行前先核证据属主**(这是删行的唯一许可证):
|
||||
- 先取历史口径:`git log --oneline -20 -- <文档>`、`git show <rev>:<文档> | sed -n '/^## 5/,/^## 6/p'` 找回被删的卡表与当初「完成即销项删行 / 标 ✅ 保留行」的理由;
|
||||
- 核对判据与实测记录**已完整沉淀进属主章节**(实测:「已知电压注入验证」六项判据全 PASS、斜率 6544.2 码/V、偏差 −0.14 % 全在 `docs/硬件平台/ADC通道映射说明.md` §8.1,执行侧在 `docs/测试说明/ADC前端注入验证流程.md`);
|
||||
@@ -48,8 +48,8 @@
|
||||
5. **派单前写一份 spec 文件**(`ci-artifacts/card_retire_spec.md`),作为各道唯一口径来源,含五块:① 背景与**用户裁定原文**;② 逐号映射表(旧号形态 → 语义短语 → 属主出处);③ 措辞规则(补短语时括号怎么降级、动不动字符串字面量、生成器归属、悬空引用怎么判);④ 硬约束(BOM 原样补回、行尾由 `.gitattributes` 写死、冻结历史行与原始证据不回改、代码头注释引用的设计约束章节保留、带 BOM 文件名单);⑤ 自检与交付格式(逐文件命中数、删了哪几行、残留数、门禁复跑结果)。`send_message` 只发路径 + 关键裁定,长中文正文不要整段塞进消息(一次串多条长消息会撑爆 120s,发出 1 条后 abort,剩余未发)。
|
||||
6. **逐处替换断言命中数**(同第 7 步的 `rep(tag, old, new, want)`),且 `old` 串**从原文取、不凭记忆敲**:本仓中文引号与 ASCII 双引号混用(实测某文档里的「中文引号」其实是 ASCII `"`),凭印象写 `old` 必 `got=0`;断言抛错后先 `git status --porcelain=v1 -- <文件>` 自证文件未被别道改动,再回读该行真身重跑。删行类改动单独记 tag 并打印删了几行、行号与原行长度。多道并行时同一文件常被别道改过:`edit` 报 `file changed since it was read` → 重新 `read` 再改;报 `ReplaceFileW EIO (Win32 1175)` → 先自证文件字节完好(BOM / CRLF / `EF BF BD` 三项)再重试同一笔,不要顺手改内容。
|
||||
7. **落地位置写死(这一族只有这几条兜)**:
|
||||
- `docs/编号规范.md` **§4.1.1「已停发的族」表加一行**,三列齐全,第三列直写「⚠ **机器面空白**:中文前缀不进 `scripts/archive/id_gate/check_ids.py` 语料 → 门禁咬不到,只有本表 + §9.3 + 评审三条兜;`id_aliases.json` 刻意不登记(登记了机器也看不见,只多一张没人核的表)」;
|
||||
- `docs/编号规范.md` **§9.3 逐号换算表**(旧号形态 → 语义短语 → 现行口径出处;本族实测 14 行 = 0、0b、1~12)+ §9 批次段落(裁定原文与逐号结论都落在正文),**写明旧号永久作废、不得重新取走**;
|
||||
- `docs/编号规范.md` **§4.1.1「已停发的族」表加一行**,三列齐全,第三列直写「⚠ **这一族从来没有机器面**:中文前缀不进任何编号语料(族 pattern 全是拉丁形态),而两道编号门禁已于 2026-10-28 / 29 退役 → 只有本表 + 归档换算表 + 评审三条兜;`id_aliases.json` 刻意不登记(没有机器读者,登记等于多一张没人核的表)」;
|
||||
- `scripts/archive/id_gate/编号治理历史.md` **§9.3 逐号换算表**(旧号形态 → 语义短语 → 现行口径出处;本族实测 14 行 = 0、0b、1~12)+ 同篇 §9 批次段落(裁定原文与逐号结论都落在归档正文),**写明旧号永久作废、不得重新取走**;
|
||||
- 表内**把形态写成「卡」+ 数字而不复述字面量**:规范本身是门禁语料,复述字面量会把「讲规则的文档」变成自己的引用站点;
|
||||
- **同步叙述**:`AGENTS.md`「文档与图形」的销项条目、`docs/README.md`(例:`数据可信.md = 三项阻塞任务` → 两项)、`docs/任务规划/README.md`、`docs/任务规划/登记簿.md`——「剩余三项」这类旧口径实测归零;
|
||||
- **与有识别面族的判别写进同一张表**:能被脚本按字面量匹配 → 走 `rename_ids.py` + 别名登记;匹配不到(中文前缀、纯散文号、圈号)→ 走规范换算表,**不登记别名**;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: keil-build
|
||||
description: Keil MDK-ARM 命令行编译闭环(自带独立脚本 keil_build.py,纯标准库,拷到任何 Keil 项目即用):UV4 -b 构建 → "0 错误 0 警告"判定 → build_log 定位报错。封装入口约定(PowerShell 与 bash/ops_bash 两种调用形态及各自的 exit 2 归属)、退出码判读、UV4 改写工程文件的入库陷阱、构建副产物「%SystemDrive% 字面量垃圾目录」的定性与清理/入库排除。
|
||||
whenToUse: 需要编译 Keil MDK 工程、跑 `@build` 验证代码改动是否 0 错误 0 警告、解读 UV4 构建日志与退出码 2/4,或在 bash / `ops_bash` 里执行 `@build` 报语法错(syntax error near unexpected token),或构建后 `git status` 冒出 `%SystemDrive%` 目录时。
|
||||
description: "Keil MDK-ARM 命令行编译闭环(自带独立脚本 keil_build.py,纯标准库,拷到任何 Keil 项目即用),覆盖 UV4 -b 构建、「0 错误 0 警告」判定、build_log 定位报错、PowerShell 与 bash 两种调用形态及各自的 exit 2 归属、退出码与 build 锁持有者名判读(含插件树的 pw: 前缀)、exit 2 时 UV4.exe 的候选路径探测与单次调用 env 注入闭环、UV4 改写工程文件的入库陷阱、构建副产物「%SystemDrive% 字面量垃圾目录」的取证、清理与入库排除。"
|
||||
whenToUse: "需要编译 Keil MDK 工程、跑 @build 验证代码改动(只改 .c 注释也算改动)是否「0 错误 0 警告」、@build 报 BUILD_ENV_ERROR 与 UV4.exe not found(退出码 2;DSH 会话看不见用户级 DTU_KEIL_UV4)、解读 UV4 构建日志、退出码 2/4 或 build 锁持有者名(含 pw: 前缀),在 bash / ops_bash 里执行 @build 报 syntax error near unexpected token,或构建后 git status 冒出 %SystemDrive% 目录时。"
|
||||
---
|
||||
|
||||
# keil-build — Keil 命令行编译(独立可运行 skill)
|
||||
@@ -15,6 +15,14 @@ whenToUse: 需要编译 Keil MDK 工程、跑 `@build` 验证代码改动是否
|
||||
# 覆盖:--project X.uvprojx --uv4 UV4.exe --target 名字 --log 路径 --timeout 600
|
||||
```
|
||||
|
||||
⚠ **改本文件 frontmatter 要么整值加双引号,要么一个 ASCII 冒号都别写**(2026-10-30 这份就因此整份加载失败)。
|
||||
DSH 用 `yaml` 包的 `parse()` 读 frontmatter(`@deepseek-ai/dsh-skill-filesystem` 的 `parseFrontmatter()`),
|
||||
plain scalar 里出现 **ASCII「冒号 + 空格」**(例如把报错原文 `BUILD_ENV_ERROR: UV4.exe not found` 或
|
||||
`pw: 前缀` 直接写进 description)就报 `Nested mappings are not allowed in compact mappings at line 2,
|
||||
column 14` → **技能整份不加载**(`skill()` 读不到、也不进技能目录)。**`js-yaml` 能吃下去** ——
|
||||
拿它自检会假绿,必须用 DSH 自带的那个解析器验。另 — 值首字符不能是 `%`(directive indicator)或 `&`
|
||||
(anchor),双引号里的 `%SystemDrive%` 之类不受影响。落盘后**让 DSH 真加载一次**再收口。
|
||||
|
||||
## 调用形态:先认执行器是哪个 shell(2026-10-26 实测)
|
||||
|
||||
- 同一个 @build,PowerShell 与 bash 是两种写法:bash 侧要去掉前导 & 并把路径改正斜杠(照抄 AGENTS 的 PowerShell 形态进 ops_bash 会语法错秒退 exit 2)
|
||||
@@ -22,8 +30,63 @@ whenToUse: 需要编译 Keil MDK 工程、跑 `@build` 验证代码改动是否
|
||||
- 后台跑(run_in_background: true)要自己收码,且别把脚本接进管道:实测 $? 取到的是 tail 的码(恒 0);要看报错行就单独 grep MDK-ARM/build_log.txt
|
||||
- 解析顺序:命令行 > 项目档案 .serialtest/config.json > 环境 > 探测;UV4 显式设了但路径无效 = 立即报错,不静默回退
|
||||
- 退出码 0=PASS(0错0警) / 1=构建失败 / 2=环境错误 / 3=用法 / 4=build 锁被占用;2026-10-12 修正过 2 与 3 的归属,该篇末尾记了前后口径
|
||||
- **bash / `ops_bash` 侧「构建到底过没过」的一次自查**(2026-10-26 实测口径):先 `cd` 到仓库根(或该 worktree 根),跑 `.venv/Scripts/python.exe .dsh/skills/keil-build/scripts/keil_build.py; echo BUILD_RC=$?` —— 正斜杠、**无前导 `&`**,退出码归 python、不归 bash;再单独读日志计数 `grep -a -E "Error\(s\)|error:|Warning\(s\)" MDK-ARM/build_log.txt`。报结论要三样齐 —— **`BUILD_RC=0` + `UV4_RC: 0` + `BUILD_OK: 0 errors, 0 warnings`**(脚本自己判「无 `error:` 行 且 尾行含 `0 Error(s), 0 Warning(s)`」);bash 吐的 `syntax error near unexpected token` **既不是构建通过也不是构建失败**,那是写法问题,别拿它当构建结论报。
|
||||
> 本节全文(逐字搬移、未改写,含命令与实测数字)→ [executor-shell-forms.md](reference/executor-shell-forms.md)。动手前先读那篇。
|
||||
|
||||
## exit 2 实战闭环:`UV4.exe not found` 的探测 → 注入 → 复核(2026-10-30 实测)
|
||||
|
||||
**先定性,别动源码**:脚本报
|
||||
`BUILD_ENV_ERROR: UV4.exe not found (--uv4 / config 'uv4' / $DSH_KEIL_UV4 / $DTU_KEIL_UV4 / PATH)`
|
||||
且**退出码 2** = **环境错,不是构建失败**——本次 **UV4 根本没启动、`MDK-ARM/build_log.txt` 也没被改写**,
|
||||
日志里看到的是上一次的。此时去改 `.c`/`.h` 或怀疑代码,就是把环境问题当编译问题排。
|
||||
(会话为什么看不见:DSH 会话的 shell 由常驻服务派生,**服务启动之后**设的用户级变量不会自动进来;
|
||||
`run_code` 起的 Node 进程 `process.env` 干脆是空的——AGENTS.md「会话不继承用户级变量」同记此坑。
|
||||
所以**变量"已设但本会话看不见"是常态**,不是用户没设。)
|
||||
|
||||
1. **探测候选安装路径**(逐个 `fs.existsSync` / `test -f`,命中即取绝对路径)——
|
||||
`C:/Keil_v5/UV4/UV4.exe`、`C:/Keil/UV4/UV4.exe`、`D:/Keil_v5/UV4/UV4.exe`、`D:/Keil/UV4/UV4.exe`、
|
||||
`C:/Program Files/Keil_v5/UV4/UV4.exe`、`D:/Program Files/Keil_v5/UV4/UV4.exe`
|
||||
(2026-10-30 实测只命中 `C:/Keil_v5/UV4/UV4.exe`,其余五个都不存在)。
|
||||
```bash
|
||||
for p in 'C:/Keil_v5/UV4/UV4.exe' 'C:/Keil/UV4/UV4.exe' 'D:/Keil_v5/UV4/UV4.exe' \
|
||||
'D:/Keil/UV4/UV4.exe' 'C:/Program Files/Keil_v5/UV4/UV4.exe' 'D:/Program Files/Keil_v5/UV4/UV4.exe'; do
|
||||
[ -f "$p" ] && echo "HIT $p"; done
|
||||
```
|
||||
2. **读用户级来源确认"值本来就对"**(同一轮里顺手做掉,别只靠猜路径):
|
||||
bash / Node 侧 `reg query HKCU\Environment`(整表打出来再找 `DTU_KEIL_UV4` / `DSH_KEIL_UV4`),
|
||||
PowerShell 侧 `[Environment]::GetEnvironmentVariable('DTU_KEIL_UV4','User')`。
|
||||
有值 → 直接用它;没值/值指向旧目录 → 用第 1 步的命中路径。
|
||||
⚠ **不替用户改全局**:不要 `setx`、不要 `SetEnvironmentVariable(...,'User')`——那是用户自己在真终端里
|
||||
做的事(`docs/环境与部署/本地工具路径环境变量方案.md`);本闭环只解决"这一次构建怎么跑起来",
|
||||
报告里也必须这么写。
|
||||
3. **只给这一次调用注入**再重跑(两种等价形态,二选一):
|
||||
```js
|
||||
// run_code:options.env 是**整体替换**,所以要连 SystemRoot / Path / USERPROFILE 一起给
|
||||
const { execSync } = await import('node:child_process');
|
||||
execSync('".venv/Scripts/python.exe" .dsh/skills/keil-build/scripts/keil_build.py', {
|
||||
cwd: MAIN, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
|
||||
env: { SystemRoot: 'C:\\Windows', Path: 'C:\\Windows;C:\\Windows\\System32',
|
||||
USERPROFILE: 'C:\\Users\\<你>', DTU_KEIL_UV4: 'C:\\Keil_v5\\UV4\\UV4.exe' } });
|
||||
```
|
||||
- bash / `ops_bash` 侧的等价写法 = 显式参数 `--uv4 'C:\Keil_v5\UV4\UV4.exe'`(值含空格要整体加引号)。
|
||||
- `run_code` 的 `timeoutMs` 给足:注入后这次构建实测 **10 s** 跑完,但首次冷构建更长(我给的 500000)。
|
||||
- **别顺手加 `--no-lock`**:注入重跑时锁链照旧(`LOCK: 已获取 build 锁(task=build-cli)` → `已释放锁`)。
|
||||
4. **复核口径**(三样齐了才算真跑过,缺一样就是没构建):脚本回显
|
||||
`UV4: <绝对路径> (<来源>)`(来源随注入方式变:`$DTU_KEIL_UV4` 或 `--uv4`)→
|
||||
`UV4_RC: 0` → `BUILD_OK: 0 errors, 0 warnings`,且**外层退出码 0**(自己包 execSync 就显式把码打出来,
|
||||
别接管道,见「调用形态」)。报告里写清"本次注入 + 来源",**不得声称已永久设置系统/用户环境变量**。
|
||||
|
||||
- ⚠ 注入的若是**注册表里那个陈旧值**,会撞同族的另一种 2:
|
||||
`BUILD_ENV_ERROR: $DTU_KEIL_UV4 is set but not a file: … (fix the value; no silent fallback)`
|
||||
—— 值在但指向旧盘符/旧目录名(本仓踩过 `D:\Code\DTU 程序\…` 搬迁)。脚本**刻意不静默回退**,
|
||||
回到第 1 步用探测命中的路径覆盖它,别去猜目录、更别改脚本让它"猜 C 盘"(设计上明确禁止从旧路径猜)。
|
||||
- ⚠ 本闭环只回答"UV4 在哪儿",**不回答"调用写法对不对"**:从 bash 侧照抄 PowerShell 的 `&` 也是 exit 2,
|
||||
但 stderr 是 `syntax error near unexpected token`(见「调用形态」)。**先看 stderr 再决定排障方向**,
|
||||
两种 2 的处置完全不同。
|
||||
- **改 `.c` 注释(哪怕只改一行中文注释)也必须跑完这道构建验证**:2026-10-30 的实测就是
|
||||
「改注释 → `@build` → 吃 rc=2 → 探测+注入 → 10 s 通过」。注释类坑(BOM 被剥 → `#870-D`、
|
||||
注释里写 `**` 路径 → 9-D)只在**重编译那个 TU 时**才暴露,"以前 0 警告"不代表安全。
|
||||
|
||||
## build 锁:脚本自持自释(2026-10-12)
|
||||
|
||||
**UV4 不能两个实例同时 `-b`,但构建锁不该由调用方"记得释放"**——那正是泄漏源:
|
||||
@@ -33,7 +96,7 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
|
||||
| 开关 | 作用 |
|
||||
|---|---|
|
||||
| `--lock-task <ID>` | 持有者名。默认 `$DTU_TASK_ID` → 从 cwd 的 `_wt/<ID>-<slug>/` 反推 `<ID>` → `build-cli` |
|
||||
| `--lock-task <ID>` | 持有者名。默认 `$DTU_TASK_ID` → 从 cwd 反推:`_wt/<ID>-<slug>/` → `<ID>`(`@task` 那批);`.dsh-worktrees/worktree/<name>/` → `pw:<name>`(DSH worktree **插件**建的树,`plugin_tree_owner()`)→ 都没有才落 `build-cli` |
|
||||
| `--lock-wait <N>` | 锁被占时等待 N 秒(默认 0 = 立即失败,退出码 4) |
|
||||
| `--no-lock` | 完全不加锁(危险:并发 `-b` 互相干扰) |
|
||||
| `DTU_LOCK_HELD=build` | 环境变量:**调用方已持锁**时设它,本脚本跳过自持自释(`task-worktree done` 的门禁即如此用) |
|
||||
@@ -45,6 +108,11 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
并告警**(保持本 skill 可独立搬运)。
|
||||
- ⚠ 从 `_wt/<ID>-<slug>/` 反推持有者名,意味着"手工 `lock acquire build --task <ID>`
|
||||
后再跑 `@build`"仍**幂等**(同名即视为自己持有),不会自我死锁。
|
||||
- **退出码 4 的持有者名带 `pw:`(如 `pw:refactor-logging`)= 插件在 `.dsh-worktrees/worktree/<name>/` 里跑的构建占着 UV4,不是某个 `@task` 任务 ID**:
|
||||
插件树**没有台账**,别拿它去 `lock release <ID>`(释放不掉别人的锁)、更别去杀 UV4 进程。正解 =
|
||||
等那次构建自己 `finally` 释放(自持自释;崩溃则按自报 TTL 兜底),或本次加 `--lock-wait <N>` 排队;
|
||||
想知道是哪棵树的谁:`"$PY" "$TW" plugin list`(缺联接再 `plugin adopt <name>`)→ 细节归
|
||||
`task-worktree` skill F 节(插件桥)。
|
||||
- **只 build 锁自动;`bench` 锁仍须 Agent 手工持**(它是**会话作用域**,跨
|
||||
烧录→`@test`→探针多条命令,逐命令自动释放会让别的任务插进来扰动设备)。
|
||||
|
||||
@@ -54,6 +122,9 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
- 根因(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
|
||||
- **取证在前,处置在后**(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` 零命中;**绝不提交**(把本机缓存塞进历史)
|
||||
> 本节全文(逐字搬移、未改写,含命令与实测数字)→ [systemdrive-junk-dir.md](reference/systemdrive-junk-dir.md)。动手前先读那篇。
|
||||
|
||||
## 本仓库
|
||||
@@ -65,12 +136,13 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
判定口径(与原 `MDK-ARM/build.bat` 同款;该文件已于 2026-10-10 删除,本 skill 即 `@build`):
|
||||
先删旧日志 → `-b -j0 -o` → 无 `error:` 且含 `0 Error(s), 0 Warning(s)`。
|
||||
本 skill 另有超时保护、UV4 多级解析链(含 PATH 兜底)、失败只打报错行。
|
||||
环境变量 `DTU_KEIL_UV4`(用户级,指向 UV4.exe)。
|
||||
环境变量 `DTU_KEIL_UV4`(用户级,指向 UV4.exe)——注意**它设了不等于本会话能看见**,仍报退出码 2 时走「exit 2 实战闭环」那一节,别先去找用户。
|
||||
|
||||
## 铁律与陷阱(实测教训)
|
||||
|
||||
- **任何代码改动后必须先编译验证(0 错误 0 警告)再谈烧录/提交**——编译不过就进
|
||||
硬件闭环只会把编译错误伪装成运行问题。
|
||||
硬件闭环只会把编译错误伪装成运行问题。**只改 `.c` 里一行中文注释也算改动**(2026-10-30 实测:
|
||||
改注释 → `@build` → 环境错 → 探测注入 → 10 s 通过):BOM/嵌套注释这类坑只在重编译那个 TU 时才暴露。
|
||||
- **警告也算失败**:判 PASS 只认退出码 0,别只看"有没有 error:"(取退出码的管道陷阱见「调用形态」)。
|
||||
- **`MDK-ARM/*.uvprojx` 每次构建后被 UV4 自动改写,改动一律不入库**:UV4 会把工程
|
||||
XML 归一化成本机风格(`<Tag />` → `<Tag></Tag>`、XML 声明加 `standalone="no"`)并把
|
||||
@@ -83,17 +155,18 @@ Agent 手工 `lock acquire build` 后忘了 `release`,锁要挂到 TTL 才被
|
||||
`rm -rf -- '%SystemDrive%'`(bash 引号形态)删掉即可,**严禁 `rd /s /q %SystemDrive%`——cmd 会把它
|
||||
展开成 `C:`**;`.gitignore` 已兜住 + 脚本已加 `ENV_SPARSE`/`JUNK_DIR` 守卫 → 见「构建副产物」一节。
|
||||
- 报错定位读日志别只看终端回显:本仓库 = `MDK-ARM\build_log.txt`。
|
||||
- **exit 2 先分三种再动手**(2026-10-10 / 2026-10-26 实测):① **bash 语法错**——stderr 有
|
||||
- **exit 2 先分三种再动手**(2026-10-10 / 2026-10-26 / 2026-10-30 实测):① **bash 语法错**——stderr 有
|
||||
`syntax error near unexpected token`,是把 PowerShell 形态丢进了 `ops_bash`,换写法即可;
|
||||
② **真没设** `DTU_KEIL_UV4`;③ **会话没继承**——agent 的 shell 由常驻服务派生,
|
||||
服务启动**之后**设置的用户级环境变量不会自动进来。查
|
||||
`[Environment]::GetEnvironmentVariable('DTU_KEIL_UV4','User')`,有值就
|
||||
`$env:DTU_KEIL_UV4 = <该值>` 注入本会话再跑,别急着让用户去设。
|
||||
② **真没设** `DTU_KEIL_UV4`;③ **已设但本会话看不见**(agent 的 shell 由常驻服务派生,服务启动**之后**
|
||||
设的用户级变量不会进来;`run_code` 的 `process.env` 直接是空的)。
|
||||
②③ 一律走 **「exit 2 实战闭环」** 那一节:探测候选路径 + 读 `HKCU\Environment`,
|
||||
再**只往那一次调用**的 env / `--uv4` 里注入命中路径重跑——别急着让用户去设,也别替他改全局。
|
||||
- 编码类报错(`#870-D: invalid multibyte character sequence` 等)先怀疑源文件
|
||||
BOM/编码被改写,而不是代码本身(见仓库 AGENTS.md 编码铁律)。
|
||||
|
||||
## 与其它 skill 的衔接
|
||||
|
||||
- **build 锁的对面是谁**(`BUILD_LOCK_BUSY`、退出码 4、`pw:<name>` 插件树、`@task plugin list` / `plugin adopt`、`_wt/` 台账与合并回收)归 `task-worktree` skill(F 节 = DSH worktree 插件桥);换机/新克隆时 `DTU_*` 工具路径变量的设立与「会话不继承」坑 = `new-machine-setup` skill。
|
||||
- 烧录/回读 = `stlink-flash` skill;串口闭环 = `serial-boot-test` skill
|
||||
(其档案 `flash_cmd`/`readback_cmd` 即 stlink-flash 的入口命令)。
|
||||
- 收尾固定链(2026-10-26 实证):`@build` → `scripts/ram_baseline.py --update` →
|
||||
|
||||
@@ -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`(文本断言 + 60 张黄金参考 + 往返池)→ `@build` → `coding-standard`。
|
||||
5 步门禁 = 构建 → `check_menu_rows.py`(菜单 8 状态像素 + 池基线)→ `check_content_pages.py`(文本断言 + 66 张黄金参考 + 往返池)→ `@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。
|
||||
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
---
|
||||
name: stale-doc-count-sweep
|
||||
description: 全仓陈旧计数同步 playbook——某个事实数(黄金参考页数/张数、菜单树叶数与已绑叶数、字库字数与字形数、提交钩子数等)变了之后:每个数先剔伪命中再双源互证定真值 → 全形态全命中面扫描(含 .pre-commit 钩子显示名与 .dsh/skills 正文)→ 逐条分类(当前态改 / 历史行冻结(2026-10-28 起只剩保留变更记录的 5 篇) / 同数不同义不动 / 带日期戳的测量只加「当时」/ 数字对但口径描述错另起一笔)→ 每处先读现文再字面替换并断言命中数 → 全文通读核对各小节 → 提交后再按全部旧形态复扫归零 → 跑门禁分笔提交。
|
||||
description: 全仓陈旧计数同步 playbook——某个事实数(黄金参考页数/张数、菜单树叶数与已绑叶数、字库字数与字形数、提交钩子数等)变了之后:每个数先剔伪命中再双源互证定真值 → 全形态全命中面扫描(含 .pre-commit 钩子显示名与 .dsh/skills 正文)→ 逐条分类(当前态改 / 历史行冻结(只有保留变更记录的 5 篇真台账与 scripts/archive/ 归档) / 同数不同义不动 / 带日期戳的测量只加「当时」/ 数字对但口径描述错另起一笔)→ 每处先读现文再字面替换并断言命中数 → 全文通读核对各小节 → 提交后再按全部旧形态复扫归零 → 跑门禁分笔提交。
|
||||
whenToUse: 某个计数/数字变了要跨 md/yaml/py/txt 同步、用户说「同步一下 / 全仓改一遍 / 这个页数(张数、叶数、字形数、钩子数)不对」、怀疑某个数是伪命中或口径错了(含 notdef 吗、含 ASCII 吗、是张数还是页数)、发现文档数字与实测不符、或同一份文档两节数字/顺序对不上要先核口径时。
|
||||
---
|
||||
|
||||
# 陈旧计数全仓同步
|
||||
|
||||
适用:**一个事实数变了**(黄金参考 27→40→47→53→60、已绑叶数 22→28、字库 185/280→218/313、钩子 51→52→51…),仓库里多处引用它的当前态表述要跟着变,而历史记录、同数异义、带日期戳的测量**不能**跟着变。
|
||||
适用:**一个事实数变了**(黄金参考 27→40→47→53→60→66、已绑叶数 22→28→30、主机单测套数 28→33→41→44、无专属钩子套数 18→19→22、字库 185/280→218/313→224/319、菜单节点行数 42→43、钩子 51→52→51→52…),仓库里多处引用它的当前态表述要跟着变,而历史记录、同数异义、带日期戳的测量**不能**跟着变。
|
||||
|
||||
本仓已跑过四轮,照序列复跑、别临场重新发明分类:`b24b2b5f`(27→40,13 文件)、`31ca4a1f`(40→47,11 文件)、UI 补齐批(`94b0d790` 落地 → `cbb4fdb8` 产品需求同步 → `11d60057` 架构总览补漏 → `6daa01ae` 字体**口径**更正)、钩子一增一减(`1668842d`,52→51)。
|
||||
本仓已跑过四轮,照序列复跑、别临场重新发明分类:`b24b2b5f`(27→40,13 文件)、`31ca4a1f`(40→47,11 文件)、UI 补齐批(`94b0d790` 落地 → `cbb4fdb8` 产品需求同步 → `11d60057` 架构总览补漏 → `6daa01ae` 字体**口径**更正)、钩子一增一减(`1668842d`,52→51)、**第五轮 2026-10-29 录波落地**(套数 41→44、无专属钩子 19→22、黄金 60→66、菜单 42→43 节点 / 33→34 叶、已绑 28→30、字库 218/313→224/319;改动面含 `.pre-commit-config.yaml` 显示名与 `docs/门禁说明/README.md` 成对、三个 skill 正文)。
|
||||
|
||||
## 各计数的权威口径速查(本仓实测值,动手前先对表)
|
||||
|
||||
| 计数 | 真值来源(**至少两处互证**) | 已知伪命中 / 换算 |
|
||||
|---|---|---|
|
||||
| 内容页黄金参考**张数** | `simulator/tests/ref/` 下 `shot_*.bin` 实数(实测 60)= `simulator/tests/check_content_pages.py` 的 `PAGES` 条目数(实测 60) | 分页页要**单独再拍一张**(id 加 `_p2`,如 `runreport_p2`)→ **张数 = 页面个数 + 分页多出的张数**;两个数分开报,不许互相冒充(53 张时代的一个 12 行分页页会让 55 页变 56 张这类差 1) |
|
||||
| 已绑内容页**叶数** | `Drivers/BSP/UI/ui_page_registry.h` 的 `#define UI_PAGE_MAP_COUNT 28u`(由 `tools/menu_gen/gen_menu.py` 生成) | `grep -c '#page=' ui_menu_tree.txt` 得 **29**:第 5 行是菜单文件**头部注释里的示例行** → 剔掉注释/模板行后的 28 才是真值 |
|
||||
| 内容页黄金参考**张数** | `simulator/tests/ref/` 下 `shot_*.bin` 实数(2026-10-29 实测 **66**)= `simulator/tests/check_content_pages.py` 的 `PAGES` 条目数(实测 **66**,其中 2 个 `_p2` 分页 → 页面 64) | 分页页要**单独再拍一张**(id 加 `_p2`,如 `runreport_p2`)→ **张数 = 页面个数 + 分页多出的张数**;两个数分开报,不许互相冒充(53 张时代的一个 12 行分页页会让 55 页变 56 张这类差 1) |
|
||||
| 已绑内容页**叶数** | `Drivers/BSP/UI/ui_page_registry.h` 的 `#define UI_PAGE_MAP_COUNT 30u`(由 `tools/menu_gen/gen_menu.py` 生成;2026-10-29 录波道绑两叶后 28→30) | `grep -c '#page=' ui_menu_tree.txt` 得 **31**:第 5 行是菜单文件**头部注释里的示例行** → 剔掉注释/模板行后的 30 才是真值 |
|
||||
| 提交**钩子数** | YAML 解析后的条目数(实测 **51**):自查命令见 `docs/门禁说明/README.md` §2 首条;与 AGENTS.md「共 N 个钩子」、该目录 §2 标题**三数同源** | `grep -c` 配置里的声明行 = 52(多算一条**被注释掉的** clang-format 候选);在配置**注释里复述那个 grep 字面量**会让文本计数再虚高 1 |
|
||||
| 字库**唯一字数** | `ui_cn_chars.txt` 唯一码点数(实测 218,其中 ASCII 区 **0** 个) | `make_font.py` 的 `-r 0x20-0x7E` 是 ASCII 子集参数,**不算进字数** |
|
||||
| **字形数** | `Drivers/BSP/UI/ui_font_cn_12.c` 里 `.bitmap_index` 出现次数 **314 条含 notdef → 313 字形**;换算式 = 218 汉字/符号 + ASCII 95 = 313 | 直接报 314 就是把 notdef 当字形;文档必须写出「不含 ASCII」「含 notdef 共 314 条」这类口径句 |
|
||||
| 菜单**节点数 / 叶子数** | `ui_menu_tree.txt` 头部规则:缩进即层级(每级 2 空格)、`#` 开头为注释、空行忽略;叶子 = **不存在更深缩进后继行**的行 | 42 是节点行数、33 才是叶子;`未绑 = 待做 + 不做` 要与分类表逐项闭合 |
|
||||
| 字库**唯一字数** | `ui_cn_chars.txt` 唯一码点数(2026-10-29 实测 **224**,其中 ASCII 区 **0** 个) | `make_font.py` 的 `-r 0x20-0x7E` 是 ASCII 子集参数,**不算进字数** |
|
||||
| **字形数** | `Drivers/BSP/UI/ui_font_cn_12.c` 里 `.bitmap_index` 出现次数 **320 条含 notdef → 319 字形**;换算式 = 224 汉字/符号 + ASCII 95 = 319 | 直接报 314 就是把 notdef 当字形;文档必须写出「不含 ASCII」「含 notdef 共 314 条」这类口径句 |
|
||||
| 变更记录**保留篇数** | 变体标题感知的普查(`grep -rn "^## " docs scripts` 再看哪些标题是变更记录 / 修订记录);当前态清单以 `docs/文档规范.md` §5.1 那行为准 | 只 grep 裸 `^## 变更记录` 会**漏算带编号与中文序号的标题**(实测 5 篇真台账里 2 篇是 `## 6.` / `## 九、`,另有 `## 修订记录` 变体)——2026-10-29 就差点把 5 改成 3 |
|
||||
| 菜单**节点数 / 叶子数** | `ui_menu_tree.txt` 头部规则:缩进即层级(每级 2 空格)、`#` 开头为注释、空行忽略;叶子 = **不存在更深缩进后继行**的行 | 43 是节点行数、34 才是叶子(2026-10-29 现值;42 / 33 是此前时点);`未绑 = 待做 + 不做` 要与分类表逐项闭合 |
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -61,12 +62,12 @@ whenToUse: 某个计数/数字变了要跨 md/yaml/py/txt 同步、用户说「
|
||||
提交前逐条打勾;回报时**逐处列文件 + 命中数断言**,不要笼统说「已全部更新」。
|
||||
|
||||
- [ ] 每个数都写了**两个独立来源**且数值一致(`ref/shot_*.bin` 实数 = `PAGES` 条目数;`UI_PAGE_MAP_COUNT` = 剔伪命中后的 `#page=` 计数),并在结论里贴出该数字与伪命中取舍理由
|
||||
- [ ] 字形数说明**是否剔除 notdef**并给出条目数→字形数的换算(314 − 1 = 313);字数说明**是否含 ASCII**,且该口径句与实测一致
|
||||
- [ ] 字形数说明**是否剔除 notdef**并给出条目数→字形数的换算(条目数 − 1 = 字形数,2026-10-29 现值 320 − 1 = 319);字数说明**是否含 ASCII**,且该口径句与实测一致
|
||||
- [ ] 分类表逐条列出:改了哪些当前态、哪些历史行保留、哪些误报不动(附理由)、哪些只加「当时」、哪些属**口径错**并已另起一笔
|
||||
- [ ] 替换脚本打印了每处 `old -> new xN` 且每处 `assert` 通过(无半落地;报错时已 `git diff --stat` 确认零写入)
|
||||
- [ ] 命中面覆盖 `.pre-commit-config.yaml` 钩子 `name:`、AGENTS.md、`docs/**`、`scripts/**`、`.dsh/skills/*/SKILL.md`,逐条在改动清单里出现
|
||||
- [ ] 被改文件已全文通读,同文档各小节与配套 README/清单的顺序、计数、完成状态一致;口径闭合(如 `未绑 = 待做 + 不做`)
|
||||
- [ ] 变更记录:**保留的 5 篇**历史行原文未动;其余文档本轮**零新增流水行**(当前态改完即止);钩子一增一减这类过程细节写进提交正文,不写进文档
|
||||
- [ ] 变更记录:**保留的 5 篇真台账**(清单 → `docs/文档规范.md` §5.1)历史行原文未动;其余文档本轮**零新增流水行**(当前态改完即止);钩子一增一减这类过程细节写进提交正文,不写进文档
|
||||
- [ ] **提交后**又按全部旧形态复扫一次,当前态残留 = **0**,每条剩余命中都能归类(历史行 / 误报 / 已加「当时」);发现的漏网当前态已单独补一笔
|
||||
- [ ] `check_doc_links.py --all --sections-strict` 打印「文档路径检查通过(N 个文档)」;`ci_local.py` / `pre-commit` 全绿(RC=0、`failed=0`);BOM 与原文件一致、无 `EF BF BD`
|
||||
- [ ] 分笔提交完成,回报 commit 号 + 每笔改了哪些文件 + 没动的那几类及理由
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
- **权威脚本乱码就臆读或退回抄文档**:Windows 代码页下 `font-charset` 的整行输出退化成 U+FFFD 替换字符时,改数源文件符号出现次数,别猜、别抄旧文档。
|
||||
- **钩子数用 `grep` 声明行**:多算一条注释掉的候选钩子(51 变 52);反过来在配置注释里复述那个 grep 字面量会让文本计数再虚高 1。口径只认 YAML 解析后的条目数,且要与 AGENTS.md、`docs/门禁说明/README.md` §2 标题、`ci_local.py` 实跑 passed+failed+skipped 三数同源。
|
||||
- **张数与页数互相冒充**:一个 12 行分页页第 2 页要单独拍一张,于是「+2 页」可能等于「+3 张」。两个数分别报、分别换算,别互相顶替。
|
||||
- **数篇数只 grep 裸标题**:`^## 变更记录` 漏掉 `## 6. 变更记录`、`## 九、变更记录`、`## 修订记录` 三种写法 → 篇数直接少数一半,拿这个错数去"同步"全仓就是把对的改成错的(2026-10-29 实测:5 篇真台账只数出 3 篇,改到第 10 处才发现)。普查一律 `grep -rn "^## " docs scripts` 再看标题,或直接读 `docs/文档规范.md` §5.1 那行的例外清单。
|
||||
- **只扫 docs/**:漏 `.pre-commit-config.yaml` 钩子 `name:` 显示名里的页数、AGENTS.md 钩子清单、`.dsh/skills/*/SKILL.md` 自带的计数与脚本路径(skill 正文也是命中面:font-regen 的 179/274 本窗最后才改)。
|
||||
- **提交完就收工**:第 7 步的提交后复扫才是把漏网当前态(架构总览 §3)揪出来的那一步;漏了它,仓库里就留下一行「当前态写旧数」直到别人再扫。
|
||||
- **去已删掉变更记录的文档里「补一行流水」**:2026-10-28 起全仓只有 **5 篇**保留变更记录表(清单 → `docs/文档规范.md` §5.1),计数同步**只改当前态**、过程交给 `git log`,别再到别的文档里找或加变更记录行。真要往那 5 篇的台账里加行,历史坑照旧:它们的表按日期**升序** → 新行**追加为末行**,插表顶会把首行「初版」挤成第二行。
|
||||
- **历史行被当陈旧值改掉**:「47 → 53 页」是当天事实,改了就是伪造历史;正确动作是**只改当前态那几处**、历史行一个字不动(保留变更记录的 5 篇要写台账也只追加、不回改)。**历史行被写坏**时按无反引号兜底写法整行重写,别用同样写法再改一遍。
|
||||
- **去已删掉变更记录的文档里「补一行流水」**:2026-10-29 起全仓只剩 **5 篇**真台账保留变更记录表(清单 → `docs/文档规范.md` §5.1),外加 `scripts/archive/` 归档;其余文档**没有这一节**。计数同步**只改当前态**、过程交给 `git log`,别再到别的文档里找或加变更记录行。真要往那 5 篇的台账里加行,历史坑照旧:它们的表按日期**升序** → 新行**追加为末行**,插表顶会把首行「初版」挤成第二行。
|
||||
- **历史行被当陈旧值改掉**:「47 → 53 页」是当天事实,改了就是伪造历史;正确动作是**只改当前态那几处**、历史行一个字不动(保留变更记录的 5 篇真台账要写台账也只追加、不回改)。**历史行被写坏**时按无反引号兜底写法整行重写,别用同样写法再改一遍。
|
||||
- **照抄用户给的数**:用户说「从 40 扩到 47」也要自己数 `ref/*.bin`;数出来不一定是 47。
|
||||
- **把节点行当叶子数**:42 是 `ui_menu_tree.txt` 的节点行数、33 才是叶子;不读文件头规则就数错(实测把错数写进两份文档,下一轮才自查出来)。
|
||||
- **整文件正则批处理**:第一次尝试的正则没编译过、整批零落地。改法 = 逐条字面替换 + 每处 `assert count == expect`。
|
||||
@@ -20,4 +21,4 @@
|
||||
- **误报跟着改**:「< 40KB」是绘制缓冲红线,与页数无关。
|
||||
- **带日期戳的测量臆改新数**:「2026-10-26 实跑 …覆盖 40 页」改成 47 就是把历史测量冒充现值;只加「当时」。
|
||||
- **用截断的 grep 输出判定**:`cut -c` 截掉的后半句常含日期戳/口径,据此分类必错。
|
||||
- **新写文本撞编号门禁**:新增的「单字母+数字」裸形(如 `SD2506`、`CP56Time2a`)会被 `id-check-new`(只查 `git diff` 新增行、零语料)拦下;旧的八项全库校验 `id-check` 已于 2026-10-28 退役进 `scripts/archive/id_gate/`。撞到就把自己这批文本逐处归因或改形态描述,别去放宽白名单。
|
||||
- **新写文本别再造编号**:两道编号门禁都已退役(全库八项校验 2026-10-28、只查新增行 2026-10-29,脚本与白名单在 `scripts/archive/id_gate/`),**撞号不再有机器报警**,只剩规范与评审——所以这批文本的形态自查要自己做(`docs/编号规范.md` §1 三句;要机器帮忙就手工跑 `scripts/archive/id_gate/check_ids_new.py --worktree`)。
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
| 类别 | 判定 | 动作 |
|
||||
|---|---|---|
|
||||
| 当前态陈述 | 描述「现在是多少」 | 改成实测真值 |
|
||||
| 变更记录历史行 | 形如「47 → 53 页」的当日事实 | **原样冻结不回改**——但这条**只适用于保留变更记录的 5 篇**(台账 / 被别处当唯一证据出处的文档,清单 → `docs/文档规范.md` §5.1);其余文档的变更记录节已于 2026-10-28 整节删除,**不再追加任何流水行**,数字同步只改**当前态** |
|
||||
| 变更记录历史行 | 形如「47 → 53 页」的当日事实 | **原样冻结不回改**——但这条**只适用于保留变更记录的 5 篇真台账**(台账 / 被别处当唯一证据出处的文档,清单 → `docs/文档规范.md` §5.1)与 `scripts/archive/` 归档;其余文档没有变更记录节,**不再追加任何流水行**,数字同步只改**当前态** |
|
||||
| 同数不同义 | `最坏路径 < 40KB` 这类内存/绘制缓冲红线 | **不动**,并在结论里写明「为什么不动」 |
|
||||
| 带日期戳的测量 | 「2026-10-26 实跑 …覆盖当时 40 页中的」 | **不臆改新数**,只补「当时」两字锁口径;新数留待下次实跑回填 |
|
||||
| 数字对但**口径描述错** | 数没错、句子把口径讲错了(实测:写「218 字(含 ASCII/符号,共 313 字形)」,而 `ui_cn_chars.txt` 的 218 个码点**全为非 ASCII**) | **另起一笔只改口径的提交**(`6daa01ae`),不与数字同步混笔;这类命中在任何数字 diff 里都看不见,只能把口径句逐句读一遍 |
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
```bash
|
||||
& '.venv\Scripts\python.exe' scripts/check_doc_links.py --all --sections-strict # 期望「文档路径检查通过(N 个文档)」
|
||||
& '.venv\Scripts\python.exe' scripts/check_ids_new.py --self-test # 新增行编号门禁(旧 check_ids.py 已退役进 scripts/archive/id_gate/)
|
||||
& '\.venv\Scripts\python.exe' scripts/archive/id_gate/check_ids_new.py --range A..B # 编号形态普查(两道门禁 2026-10-28 / 2026-10-29 都已退役,不再是提交钩子)
|
||||
& '.venv\Scripts\python.exe' scripts/ci_local.py # 或 pre-commit run --files <被改文件>
|
||||
```
|
||||
|
||||
|
||||
+233
-209
@@ -1,265 +1,289 @@
|
||||
---
|
||||
name: task-worktree
|
||||
description: >-
|
||||
多任务并行开发的空间管理(git worktree 生命周期):`@task new` 开工作树+任务分支、目录联接共享 .venv 与模拟器 SDK、活跃上限 8 与腾位(A 节)、build/bench 两类独占锁与非登记持有者「缺台账」释放不掉(B 节)、`done` 是合并点不是查看器(C 节)、主工作树被别的并发会话弄脏时的手工合并路径(C2 节,详情已外置)、门禁指标异常增长时用净室工作树逐文件归因(D 节,外置)、回收 `@task list` 看不到的工作树并清悬挂台账(E 节,外置)、与 DSH worktree 插件 `/worktree`、`worktree_create` 共存(F 节,外置)、任务树内跑门禁与 `simulator/SDL2` 联接假红、树内提交/合并信息的禁词自检(G 节,外置)、插件树两条共享联接的分级(`simulator/SDL2` 是硬需求、`.venv` 有解释器逃生门,F 节,外置)、并行上限数字的唯一事实源(`MAX_CONCURRENT`)。规格见 docs/环境与部署/并行任务工作树方案.md;标「外置」的深规程全文在本技能目录 reference/ 下,动手前先读对应那篇。
|
||||
并行开发的空间管理(git worktree 生命周期,短命令 `@task`):开任务树与分支、联接共享 `.venv` 与模拟器 SDK、build/bench 独占锁、并发上限与腾位(A)、放不掉的锁(B)、`done` 是合并点不是查看器与补绿链(C)、主树被别的会话弄脏时的手工合并(C2)、门禁指标异常的净室归因(D)、回收非登记树与悬挂台账(E)、与 DSH worktree 插件共存及联接分级(F)、树内跑门禁与消息禁词(G)。规格 = docs/环境与部署/并行任务工作树方案.md;深规程全文在 reference/。
|
||||
whenToUse: >-
|
||||
要同时推进多个任务、担心同分支同文档互相冲突;要为新任务开隔离空间;要把任务合并回主干;或遇到 `new` 报「已有 8 个活跃任务(上限 8)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿」或「主工作树不干净」而那份未提交改动是别的会话/插件留下的、两个任务抢 COM6 / UV4、"改了文档被覆盖"、并集合并残留;或要在任务树里跑文档/审计门禁(往 `ci-artifacts/` 写输出报 ENOENT、`--all` 的问题全在 `simulator/SDL2` 联接下);或门禁指标异常增长(如不可判定数 +16)却查不出是哪个文件造成的;或要回收 `@task list` 里根本看不到的工作树、清台账里 branch/path 都已不存在的悬挂条目;或任务是在任务树里改文档再合主干、要写提交/合并信息(被清退的主题不得在消息里复现);或想确认插件工作树里 `.venv` 与 `simulator/SDL2` 到底哪个必须补;或要改并行上限这个数字并同步全仓口径。
|
||||
要为新任务开隔离空间、同时推进多个任务、把任务合回主干或回收工作树;或撞到 `new` 报「已有 8 个活跃任务(上限 8)」、`lock release` 报「任务 X 不存在(缺台账)」、`done` 报「门禁未全绿」或「主工作树不干净」而那份脏改动是别的会话/插件留的、两个任务抢 COM6 或 UV4、并集合并残留;或在任务树里跑门禁写输出报 ENOENT、`--all` 的问题全在 `simulator/SDL2` 联接下;或门禁指标异常增长查不出哪个文件造成;或要回收 `@task list` 看不到的树、清 branch/path 都已不存在的悬挂台账;或要确认插件树里 `.venv` 与 `simulator/SDL2` 哪个必须补;或要改并行上限这个数字并同步全仓口径。
|
||||
---
|
||||
# task-worktree — 多任务并行开发空间(短命令 `@task`)
|
||||
|
||||
**执行体 = 本 skill 自带脚本** `.dsh/skills/task-worktree/scripts/task_worktree.py`
|
||||
(纯标准库,任意 python3;拷整个 skill 目录到别的项目即可用)。
|
||||
- **执行体** = `.dsh/skills/task-worktree/scripts/task_worktree.py`(纯标准库;拷整个 skill 目录即可换项目用)。
|
||||
- **深规程全文在 reference/ 下:做 C2 / D / E / F / G 任何一条之前先把对应那篇读完**——本文只留判得出来「要不要读」的判据。
|
||||
- 为什么这么拆:本文件受 dsh-mnemon 写侧上限 **65536 字节**(`skill-size` 钩子 49152 告警;实测 72,601 字节的技能照常可读可提交,只是自进化每次「生成草案」被拒、永久卡在「生成中」)→ **新增深规程进 reference/,本文只加判据**。瘦身前 frontmatter 原文 → [frontmatter-history.md](reference/frontmatter-history.md)。
|
||||
|
||||
**本文档受 dsh-mnemon 写侧上限 65536 字节约束**(实测 2026-10-29:正文涨到 72,601 字节后,技能自进化每次「生成草案」都被写侧拒绝、
|
||||
那一行永久卡在「生成中」)。所以**新增的深规程写进 reference/<篇名>.md**,本文只留可执行判据的摘要 + 指针;瘦身前的 frontmatter
|
||||
原文逐字留在 [reference/frontmatter-history.md](reference/frontmatter-history.md)。**动手做某条深规程前,先把对应那篇 reference 读完**(摘要只用来判定要不要读)。
|
||||
## 症状 → 去哪一节
|
||||
|
||||
@T3@powershell
|
||||
# cwd = 仓库根(或任意子目录)
|
||||
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" <子命令> [参数]
|
||||
@T3@
|
||||
|
||||
bash(`ops_bash`)里固定两个变量,下文用 `$TW` 代指脚本:
|
||||
|
||||
@T3@bash
|
||||
PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktree.py"
|
||||
"$PY" "$TW" list
|
||||
@T3@
|
||||
|
||||
退出码:`0`=成功 / `1`=操作失败 / `2`=前置或环境错误 / `3`=用法。
|
||||
**⚠ 这几个码只有「不接管道」时才读得到**——原因与对策见「读输出」一节。
|
||||
| 现场 / 目标 | 去 |
|
||||
|---|---|
|
||||
| 开新树 / 看全景 / 追平主干 / 放弃任务 | 固定命令、典型流程 |
|
||||
| `new` 报「已有 8 个活跃任务(上限 8)」 | **A** |
|
||||
| `lock release` 报「任务 X 不存在(缺台账)」,锁照旧挂着 | **B** |
|
||||
| `done` 做了什么、红了怎么补绿、能不能反悔、怎么算「真完成」 | **C** |
|
||||
| `done` 报「主工作树不干净」,而那份脏是别的会话/插件留的 | **C2** → [merge-dirty-main-tree.md](reference/merge-dirty-main-tree.md) |
|
||||
| 门禁指标异常增长要逐文件归因(主树全程只读) | **D** → [clean-room-measure.md](reference/clean-room-measure.md) |
|
||||
| `@task list` 里没有的树要回收 / detached / 有在飞内容 / 悬挂台账 | **E** → [reclaim-unregistered.md](reference/reclaim-unregistered.md) |
|
||||
| 插件树缺联接、锁归属 `pw:<name>`、「有插件为何还自持生命周期」 | **F** → [dsh-plugin-bridge.md](reference/dsh-plugin-bridge.md) |
|
||||
| 树内写门禁输出 ENOENT、`--all` 假红、写提交/合并信息、字节体检 | **G** → [gates-in-tree.md](reference/gates-in-tree.md) |
|
||||
| 退出码读到 0 其实是 2、`tail` 之后误判「有冲突」 | 读输出 |
|
||||
| 谁不许跑 `done`/`abort`、清场前必做什么、任务期哪些文件不许改 | 铁律 1–9 |
|
||||
| 收工前最后一遍自查(各节该留什么证据)/全部实测坑 | [verification.md](reference/verification.md) / [pitfalls.md](reference/pitfalls.md) |
|
||||
|
||||
## 固定命令
|
||||
|
||||
```bash
|
||||
PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktree.py"
|
||||
"$PY" "$TW" <子命令> [参数] # PowerShell:& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" <子命令>(cwd = 仓库根或任意子目录)
|
||||
```
|
||||
|
||||
| 命令 | 作用 |
|
||||
|---|---|
|
||||
| `new <ID> <slug>` | 建分支 `task/<ID>-<slug>` + 工作树 + 共享联接 + 台账;打印新空间路径 |
|
||||
| `list` | 所有任务一览:分支 / 落后领先 / 脏文件数 / 台架状态 + 两个锁的归属 |
|
||||
| `status <ID>` | 单任务详情(未提交清单、相对基线漂移、台架记录、已建联接) |
|
||||
| `sync <ID>` | 在任务工作树里 `rebase` 追平主干,把冲突控制在任务分支上 |
|
||||
| `lock acquire\|release\|status <build\|bench>` | 独占资源锁 |
|
||||
| `plugin list\|adopt\|unadopt [插件树名]` | 与 DSH worktree 插件的桥:看清单 / 补共享联接 / 摘联接(**不删树**)→ F 节 |
|
||||
| `done <ID>` | 预演 → 合入主干 → 并集残留审计 → 重生成产物 → 门禁 → 清场(主树被别的会话弄脏时改走 **C2** 手工合并) |
|
||||
| `abort <ID>` | 放弃任务(工作树 + 分支一起删,**永久丢弃**) |
|
||||
| `new <ID> <slug>` | 建分支 `task/<ID>-<slug>` + 工作树 + 共享联接 + 台账,打印新空间路径 |
|
||||
| `list` | 分支 / 落后领先 / 脏文件数 / 台架状态 + 两把锁归属。**只遍历台账** → 非登记树看不见(E 节) |
|
||||
| `status <ID>` | 未提交清单、相对基线漂移、台架记录、已建联接 |
|
||||
| `sync <ID>` | 在任务树里 `rebase` 追平主干,把冲突留在任务分支上 |
|
||||
| `lock acquire|release|status <build\|bench>` | 独占资源锁(→ 铁律 3、B 节) |
|
||||
| `plugin list|adopt|unadopt [插件树名]` | 插件桥:看清单 / 补共享联接 / 摘联接(**不删树**)→ F 节 |
|
||||
| `done <ID>` | 预演 → 合入主干 → 并集残留审计 → 重生成产物 → 门禁 → 清场 |
|
||||
| `abort <ID>` | 工作树 + 分支一起删,**永久丢弃** |
|
||||
|
||||
`new` 常用开关:`--base <分支>`(默认 `v2-on-ui`)· `--needs-bench`(无台架 PASS 则 `done` 拒合)·
|
||||
`--lean`(跳过 Git LFS smudge)· `--no-bootstrap`(不建联接)·
|
||||
`--force`(**仅跳过并发上限 8 的检查**,不解决「谁该让位」,见 A 节)。
|
||||
|
||||
`done` 常用开关:`--dry-run`(只预演,零改动)· `--skip-build` · `--skip-gates` · `--force`(清场时强制移除工作树)。
|
||||
|
||||
`lock` 开关:`--task <ID>`(acquire 必填)· `--wait <秒>` · `--ttl <小时>`(自报窗口)· `--note <文本>` ·
|
||||
`--bench-pass`(release 时给台账记 PASS)· `--force`。
|
||||
⚠ `--note` 只在 **acquire** 时写进锁 payload,**release 时被忽略**——别指望它留下验收证据(证据写进主干提交说明或文档)。
|
||||
|
||||
**环境变量**(高级):`DTU_WT_ROOT`(任务容器位置,默认仓库同级 `_wt/`)·
|
||||
`DTU_TASK_BASE`(默认基线分支,默认 `v2-on-ui`)·
|
||||
`DTU_INTEGRATION_WORKTREE`(集成点,默认主工作树——用于隔离演练/测试)。
|
||||
- **退出码** `0` 成功 / `1` 操作失败 / `2` 前置或环境错误 / `3` 用法——**只有不接管道才读得到**(→ 读输出)。
|
||||
- `new`:`--base`(默认 `v2-on-ui`)· `--needs-bench`(无台架 PASS 则 `done` 拒合)· `--lean`(跳 LFS smudge,只省磁盘不省时间)· `--no-bootstrap`(不建联接)· `--force`(**只跳过并发上限检查**,不解决谁让位 → A)
|
||||
- `done`:`--dry-run` · `--skip-build` · `--skip-gates` · `--force`。⚠ `--dry-run` **只保护预演段、不保护清场段**(实测:对已合入的分支它照样摘联接、删分支、清台账;且它的前置也要抢 `build` 锁)→ 用完复验 `git worktree list`,细节 → 陷阱清单。
|
||||
- `lock`:`--task`(acquire 必填)· `--wait <秒>` · `--ttl <小时>`(默认 `build`=1h / `bench`=6h,超期视为陈旧、下次 acquire 自动接管)· `--note` · `--bench-pass` · `--force`。⚠ `--note` 只在 **acquire** 写进 payload,**release 时被忽略** → 验收证据写主干提交说明或文档。
|
||||
- 环境变量:`DTU_WT_ROOT`(默认仓库同级 `_wt/`)· `DTU_TASK_BASE`(默认基线)· `DTU_INTEGRATION_WORKTREE`(集成点,用于隔离演练)。
|
||||
|
||||
## 典型流程
|
||||
|
||||
@T3@powershell
|
||||
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" new N21 dhara-isfree --needs-bench
|
||||
# → 把新会话的工作目录切到打印出来的 _wt/N21-dhara-isfree/,在那里干活
|
||||
|
||||
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" lock acquire bench --task N21
|
||||
# 烧录 / @test / 板级套件……
|
||||
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" lock release bench --task N21 --bench-pass
|
||||
|
||||
# 回到主工作树执行回收
|
||||
& $PY ".dsh\skills\task-worktree\scripts\task_worktree.py" done N21
|
||||
@T3@
|
||||
```bash
|
||||
"$PY" "$TW" new N21 dhara-isfree --needs-bench # 把新会话工作目录切到打印出来的 _wt/N21-dhara-isfree/
|
||||
"$PY" "$TW" lock acquire bench --task N21 # 烧录 / @test / 板级套件
|
||||
"$PY" "$TW" lock release bench --task N21 --bench-pass
|
||||
"$PY" "$TW" done N21 # 回主工作树(铁律 1);先 --dry-run 读全量
|
||||
```
|
||||
|
||||
## 读输出:管道与截断(两起误判的共同根因)
|
||||
|
||||
1. **`... 2>&1 | tail -N` 的退出码是 `tail` 的,恒为 0** → 脚本的 `1` / `2` 全被吞。实测三条印着
|
||||
「rc=0」的输出其实都是 exit 2:`new` 撞活跃上限、`lock release` 缺台账两次。
|
||||
所以**判据先看文本**(`[ok]` / `!!` / `前置/环境错误:` / `失败:`),要真码就**直接执行不接管道**或补一句
|
||||
`echo RC=$?`;坚持接管道就在 bash 里取 `${PIPESTATUS[0]}`。
|
||||
2. **`done` / `done --dry-run` 的输出不要用 `tail -2` 截**:接管道时 python 的 stdout 是块缓冲、git
|
||||
子进程的 stderr 不缓冲,两股流错位混排,`tail -2` 只剩「--dry-run:到此为止…」加上 stderr 里带
|
||||
`conflict` 字样的片段 → 据此误判「有冲突」。实测:`tail -2` 误读成 conflict,放宽到 `tail -10` 才看到
|
||||
`[ok] 无冲突,预演结果树 <sha>`。dry-run 全文只有 5~8 行,直接读全量。
|
||||
1. **`| tail -N` 之后的退出码是 `tail` 的,恒 0** → 脚本的 1/2 全被吞(实测三条「rc=0」真码都是 2)。判据先看文本(`[ok]` / `!!` / `前置/环境错误:` / `失败:`),要真码就**不接管道**或取 `${PIPESTATUS[0]}`。
|
||||
2. **`done` 的输出别 `tail -2`**:管道下 python stdout 块缓冲、git 子进程 stderr 不缓冲,两股流错位 → 只剩尾部那句「--dry-run:到此为止…」加带 `conflict` 字样的 stderr 片段,据此误判「有冲突」。dry-run 全文 5~8 行,**读全量**。
|
||||
|
||||
## 分步操作(八条运行时机制)
|
||||
## 分步操作(节号 A–G/C2、各节步骤号与铁律编号被 reference/ 与 docs 引用,**不许重排**)
|
||||
|
||||
### A. `new` 被拒「已有 8 个活跃任务(上限 8)」——怎么腾位
|
||||
### A. `new` 撞并发上限 → 腾位
|
||||
|
||||
1. 读原文:`前置/环境错误:已有 8 个活跃任务(上限 8);先 done/abort,或加 --force`(exit 2)。
|
||||
上限是脚本常量 `MAX_CONCURRENT = 8`;**「活跃」的判据 = `_wt/.state/<ID>.json` 台账存在,且台账里
|
||||
`path` 指向的目录还在**(手工删了工作树目录就不占名额,但会留下悬挂台账,见第 5 步与 **E 第 6 步**)。
|
||||
2. `$TW list` 看全景(分支 / 落后领先 / 脏文件数)决定谁让位:**已完成的先 `done`**(合并回收,一步不省)。
|
||||
3. 没人认领、零提交、尚未 claim 的空闲树 → `abort <ID>` 腾位(实测就是 abort 掉排在后面的空闲树换出名额)。
|
||||
`abort` = 工作树 + 分支一起删、**永久丢弃**;工作树有未提交改动或已有提交时脚本会拒绝,确认可弃再加 `--force`。
|
||||
4. 确实要同时开第 9 棵才 `new <ID> <slug> --force`:磁盘代价约 **83 MB/棵**(`--lean` 约 45 MB),
|
||||
并行度上去了、**冲突面与台架排队也一起上去**——不是免费的。
|
||||
5. **别用手工 `git worktree remove` 给「登记任务」腾位**:台账不会被删(下次 `new` 同 ID 会报「任务 <ID> 已存在」),
|
||||
还绕过了「先摘联接」(铁律 5),有让 git 递归进联接把主工作树 `.venv` 删掉的风险。要回收的若是**非登记树**
|
||||
(`$TW list` 里根本没有它:`new` 出来又被手工删掉只剩悬挂台账的残骸 / D 节 `--detach` 净室 / 别的会话手工建的)
|
||||
→ 腾位与回收一起走 **E 节**,那里有「怎么证明这棵树零联接所以可以 remove」的可执行判据。
|
||||
6. 腾位后复验:`$TW list` 的条目数与 `ls _wt` 的目录数一致,再 `new`。
|
||||
1. 认原文 `前置/环境错误:已有 8 个活跃任务(上限 8);先 done/abort,或加 --force`(exit 2)。「活跃」= `_wt/.state/<ID>.json` 在且台账 `path` 目录还在 → **手工删目录不占名额、但留悬挂台账**(E 第 6 步)。
|
||||
2. `$TW list` 定谁让位:**已完成的先 `done`**(合并回收一步不省)。
|
||||
3. 无人认领、零提交、未 claim 的空闲树 → `abort <ID>`;有未提交或有提交时脚本会拒,确认可弃才 `--force`。
|
||||
4. 真要第 9 棵才 `--force`:并行度上去,**冲突面与台架排队一起上去**(每棵磁盘代价见铁律 6)。
|
||||
5. **别用手工 `git worktree remove` 给登记任务腾位**:台账不删(同 ID 报「任务已存在」)、又绕过「先摘联接」(铁律 5),有让 git 递归进联接删掉主树 `.venv` 的风险。要回收的是**非登记树**(`list` 里没有它)→ **E 节**。
|
||||
6. 复验:`list` 条目数 == `ls _wt` 目录数,再 `new`。
|
||||
|
||||
### B. 非登记持有者占的锁:`lock release --bench-pass` 曾报「任务 X 不存在(缺台账)」
|
||||
### B. 非登记持有者的锁放不掉
|
||||
|
||||
**症状**(lead 在主工作树跑台架验收窗,用临时名占锁 `lock acquire bench --task acceptwin`):
|
||||
症状:`lock release bench --task acceptwin --bench-pass`(带 `--force` 同)报 `前置/环境错误:任务 acceptwin 不存在(缺台账 _wt/.state/acceptwin.json)`,**锁照旧挂着**。根因:`release` 带 `--bench-pass` 就先 `load_state(task)`,而 `acquire` 侧不查台账 → 「释放」被「记账」挡死;锁没放,`done` 的 `release_stale_locks()` 对未到 TTL 的活锁判失败 → **一个没登记的名字挡住所有任务收口**。
|
||||
|
||||
@T3@bash
|
||||
"$PY" "$TW" lock release bench --task acceptwin --bench-pass
|
||||
前置/环境错误:任务 acceptwin 不存在(缺台账 _wt/.state/acceptwin.json) # 锁照旧挂着
|
||||
"$PY" "$TW" lock release bench --task acceptwin --force --bench-pass # 同一句错误,锁仍在
|
||||
@T3@
|
||||
**2026-10-29 已修**:无台账降级为 `[warn] …跳过 PASS 记录,仅释放锁`,**不再需要手写占位台账**。
|
||||
1. `$TW lock status` 认现场(持有者 / pid / 已持有 / TTL)——别对同一句报错反复重试。
|
||||
2. `$TW lock release <build|bench> --task <NAME> [--bench-pass]` 直接放;PASS 记不上会 warn,锁照放。
|
||||
3. **PASS 后面还要用**(`done` 只认台账 `bench_pass`)→ 从一开始就用已 `new` 的 ID 占锁,或把结论写进主干提交/文档。插件树 `pw:<name>` 天然无台账 → F 节。
|
||||
4. 最后手段:锁 = `_wt/.state/locks/<name>.lock` 的单行 JSON(`name/task/pid/host/epoch/at/note/ttl`),`release` 就是 unlink 它——手工删 = 不记 PASS 的强制释放,前面各步都不可用才做。**早期为绕坑手写的占位台账现在有害**(既占上限名额又被 `list` 当任务)→ E 第 6 步备份后删。
|
||||
|
||||
**根因**:`release` 分支里只要带 `--bench-pass` 就先 `load_state(task)` 去台账写 PASS,而 `acquire` 侧
|
||||
**不查台账**——这个不对称让「释放」被「记账」挡死(`--force` 只覆盖持有者不符与陈旧接管,管不到台账)。
|
||||
后果比报错本身大:锁没放,`done` 的前置 `release_stale_locks()` 对未到 TTL 的活锁直接判失败 →
|
||||
**一个名字没登记,就挡住所有任务的收口**。
|
||||
### C. `done <ID>`:它是合并点,不是查看器
|
||||
|
||||
**2026-10-29 起已修**(`task_worktree.py`:无台账降级为 `[warn] …跳过 PASS 记录,仅释放锁` 并照常释放)。
|
||||
现在的正解只有三步,**不再需要手写占位台账**:
|
||||
|
||||
1. `$TW lock status` 认现场(持有者 / pid / 已持有时长 / TTL)——别对同一句报错反复重试 release。
|
||||
2. `$TW lock release <build|bench> --task <NAME> [--bench-pass]` 直接放;PASS 记不上会打印 warn,锁照放。
|
||||
3. **PASS 后面还要用**(`done` 只认台账里的 `bench_pass`)→ 从一开始就用已 `new` 出来的任务 ID 占锁,
|
||||
或把验收结论写进主干提交说明 / 文档。**插件树 `pw:<name>` 天然没有台账**,别指望它留 PASS → F 节。
|
||||
|
||||
最后手段(脚本本身都走不通时):锁就是 `_wt/.state/locks/<name>.lock` 里的单行 JSON
|
||||
(`name/task/pid/host/epoch/at/note/ttl`),`release` 的实现就是 unlink 它——手工删这个文件等价于
|
||||
「不记 PASS 的强制释放」,只在前面几步都不可用时才用。**历史遗留的占位台账**(早期为绕这个坑手写的
|
||||
`_wt/.state/<NAME>.json`)现在已无必要:它活着时既占 `MAX_CONCURRENT = 8` 名额又被 `list` 当任务列出,
|
||||
按 E 第 6 步备份后删掉即可。
|
||||
|
||||
### C. `done <ID>` 的正确用法(它是合并点,不是查看器)
|
||||
|
||||
1. **合并前确认任务属主已把全部改动提交完**。**`$TW status <ID>`** 看未提交清单与领先提交数;跨会话任务
|
||||
再问一句属主(`send_message`)。实测教训:属主还剩「2/2」没提交就 `done`,把只含 1/2 的分支合进了主干,
|
||||
只能另开一棵树补后半并向对方说明(是自己的时序错,不是分支问题)。
|
||||
2. **先 `--dry-run`,并读全量输出**(别 `tail -2`,见「读输出」)。判据两行:
|
||||
`[ok] 前置检查通过(领先 <base> 共 N 个提交)` + `[ok] 无冲突,预演结果树 <sha>`;
|
||||
**N 必须等于属主应交的提交数**,对不上回到第 1 步。dry-run 零改动。
|
||||
1. **合并前确认属主提交完**:`$TW status <ID>` 看未提交与领先数,跨会话再问属主一句。实测教训:属主还剩「2/2」没提交就 `done` → 只含 1/2 的分支进了主干,只能另开一棵树补后半(自己的时序错,不是分支问题)。
|
||||
2. 先 `--dry-run` 且**读全量**。判据两行:`[ok] 前置检查通过(领先 <base> 共 N 个提交)` + `[ok] 无冲突,预演结果树 <sha>`;**N 必须等于属主应交的提交数**,对不上回第 1 步。
|
||||
3. 正式 `$TW done <ID>`——**必须在主工作树**(铁律 1)。
|
||||
4. 输出 `!! 门禁未全绿:工作树与分支**保留**,未清场` 时,紧跟的 `主干已含合并提交 <sha>` 已经说明
|
||||
**合并落进主干了**。
|
||||
- ✅ 走提示里的 **a) 在主工作树修正后追加提交**;
|
||||
- ❌ **绝不用 b) 的 `git -C "<主树>" reset --hard <sha>`**——那是丢弃已入库的合并提交,而
|
||||
`reset --hard` 本身是项目级 Never(会连未提交工作一起丢)。提示 b 只是列出的可能性,不是建议。
|
||||
- 先定位是**哪条钩子**红:门禁输出里的 `[SUMMARY] static: pre-commit hooks -- passed=N failed=M`,
|
||||
不同根因处置不同(见陷阱「`done` 的门禁 = `pre-commit run --all-files`」)。最常态是 `.map` 滞后 → 第 5 步。
|
||||
5. 补绿固定链(2026-10-25 实测;顺序即因果,全在**主工作树**跑,逐条不接管道看退出码):
|
||||
4. 见 `!! 门禁未全绿:工作树与分支**保留**,未清场` 时,紧跟的 `主干已含合并提交 <sha>` 说明**合并已落主干**;先定位哪条钩子红(`[SUMMARY] static: pre-commit hooks -- passed=N failed=M`),最常态是 `.map` 滞后 → 第 5 步。
|
||||
- ✅ 走提示 a):主工作树修正后追加提交。
|
||||
- ❌ **绝不用 b) 的 `reset --hard <sha>`**——那是丢弃已入库的合并提交,且 `reset --hard` 是项目级 Never(连未提交工作一起丢)。提示 b 是列出的可能性,不是建议。
|
||||
5. 补绿固定链(顺序即因果,全在主工作树,逐条不接管道看退出码):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
"$PY" scripts/check_mem_numbers.py --write # 台账受管区 ↔ 新 .map 对齐
|
||||
"$PY" scripts/ram_baseline.py --write # RAM 快照 / 92% 预算台账
|
||||
"$PY" scripts/ram_baseline.py --update # 趋势基线追加一行
|
||||
"$PY" scripts/check_test_coverage.py --update-baseline # 覆盖率基线(新单测 / 新套件时必刷)
|
||||
"$PY" scripts/check_doc_links.py --all # 文档链接与章节引用
|
||||
git add <上面几条刷出来的基线/快照文件> # 显式列文件,别 -A 把别人在飞的改动一起提交
|
||||
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md 边界),按用户指示再做
|
||||
@T3@
|
||||
git add <上面刷出来的基线/快照文件> # 显式列文件,别 -A 把别人在飞的改动一起提交
|
||||
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md 边界)
|
||||
```
|
||||
|
||||
为什么必然要补,见陷阱「`done` 的门禁跑在 `@build` 之前」。
|
||||
6. **重跑 `done` 之前主工作树必须干净**——否则前置检查直接
|
||||
`失败:主工作树不干净(…)——合并会把未提交改动卷进来`,所以第 5 步的产物必须**先提交**。重跑时若分支
|
||||
已并入,会打印 `[i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场` → 摘联接 → `完成(分支此前已合入)`。
|
||||
7. 清场完成后再敲一次 `done <ID>` 会得到 `前置/环境错误:任务 <ID> 不存在(缺台账 …json)`(exit 2)——
|
||||
**这不是失败**:清场的最后一步 unlink 了台账。判完成以 `git log` 为准。
|
||||
为什么必然要补 → 陷阱「`done` 的门禁跑在 `@build` 之前」。合并解过冲突时,**合并提交期 pre-commit 只查冲突文件**(→ G 节第 3 条)→ 落主干后必须另跑全量。
|
||||
6. **重跑 `done` 前主树必须干净**,否则 `失败:主工作树不干净(…)——合并会把未提交改动卷进来` → 第 5 步产物先提交。分支已并时重跑打印 `[i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场`。主树脏且那份脏不是你的 → **C2**。
|
||||
7. 清场后再敲一次 `done <ID>` 得 `前置/环境错误:任务 <ID> 不存在(缺台账 …json)`(exit 2)——**不是失败**:清场最后一步 unlink 了台账。**判完成以 `git log` 为准**。
|
||||
|
||||
### C2. `done` 被「主工作树不干净」挡住,而那份脏改动是**别的并发会话**留下的
|
||||
### C2. `done` 被「主工作树不干净」挡住,而那份脏是**别的并发会话**留的
|
||||
|
||||
- **不许**对那份外部脏改动做 stash / checkout / reset / 提交 / 删除(项目级 Never,你也不知道它写没写完);
|
||||
`done` 的前置要求主工作树干净,于是**合并与回收一起被挡**——这是挡给你看的保护,不是工具坏了。
|
||||
- **正解链(六步)**:取证(mtime + diff 定性有无交集,顺手 `worktree list` 看有没有别人中途新建的树)→
|
||||
`git merge-tree --write-tree <base> <task 分支>` 预演(判据:打出结果树 sha 且 RC=0)→ 主树
|
||||
`git merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt <task 分支>` → 合并后把
|
||||
`git hash-object <外部文件>` 与 `git diff --stat` 和**合并前存下的值逐项对账**(实测同为 `369380ca…`、2+/2−
|
||||
→ 证明 pre-commit 自己的 stash 往返没动别人的工作)→ 台账先 `cp` 成 `<ID>.json.state-bak` 再让 `done`(或 E 节
|
||||
手工路径)自己摘 .venv / simulator/SDL2 联接拆树 → `worktree list` + `lock status` 复验收工。
|
||||
- **报告里要写明绕开 `done` 的理由并逐项补回它的证据**:预演 sha + RC=0、blob id 对账、合并后在
|
||||
**主树**跑全库 `check_doc_links.py --all`(见 G 节)、两把锁空闲;并且点名那份外部脏文件与中途冒出来的第二棵树
|
||||
(谁的、怎么定性、你没动它的证据)。**blob id 变了 = stash pop 半冲突**:立刻停止收工,两份 blob id 与两份 diff
|
||||
交给该文件属主,绝不 `checkout` 抹平。
|
||||
- **认得出它**:本仓这份外部脏改动两次都是 `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`(2+/2−,别的会话/插件正在写它)——别把它当成自己漏提交的文件去提交或清掉。同一次窗口里 `git -C "$MAIN" worktree list` 还会冒出**别人中途新建的第二棵树**(实测 `data-trust-restructure`),那是并发会话的硬证据:点名写进报告,并且不是你的树就别顺手回收(→ E 节)。
|
||||
- **合并信息也过 G 节的禁词自检**:清退/删主题类任务,`merge:` 那行与正文里被清退主题的关键词计数必须是 0。
|
||||
- **不许**对它做 stash / checkout / reset / 提交 / 删除(项目级 Never,你不知它写没写完)。`done` 要求主树干净 → 合并与回收一起被挡,**那是保护不是工具坏了**。
|
||||
- 正解六步,编号与 [reference/merge-dirty-main-tree.md](reference/merge-dirty-main-tree.md) 第 1~6 步一一对应(每步的确切命令与判据在那篇):**取证**(mtime + diff 定性有无交集,顺带 `worktree list` 看有无别人中途新建的树)→ **预演** `git merge-tree --write-tree <base> <task>`(打出结果树 sha 且 RC=0)→ **主树手工合并** `git merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt <task>` → **对账**(合并后 blob id 与 stat 同合并前存值逐项一致)→ **备份台账** `cp <ID>.json <ID>.json.state-bak` 后让 `done` 自己摘联接拆树 → **复验** `worktree list` + `lock status`。
|
||||
- **blob id 变了 = stash pop 半冲突**:立刻停止收工,把两份 blob id 与 diff 交给该文件属主,绝不 `checkout` 抹平。
|
||||
- **报告要点名**:绕开 `done` 的理由 + 补回它的证据(预演 sha 与 RC、blob 对账、主树全库 `check_doc_links.py --all`、两把锁空闲)+ 那份外部脏文件与中途冒出来的第二棵树(谁的、你没动它的证据)。合并信息也过 G 的禁词自检;「这份脏通常是谁」见陷阱清单。
|
||||
|
||||
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/merge-dirty-main-tree.md](reference/merge-dirty-main-tree.md) —— 主树被别的并发会话弄脏时的手工合并路径(取证 → `merge-tree` 预演 → 主树 `merge --no-ff -F` → 外部改动 blob id / `diff --stat` 前后对账)。
|
||||
### D. 门禁指标异常增长 → 净室逐文件归因(主树全程只读)
|
||||
|
||||
### D. 门禁指标异常增长 → 逐文件归因(把净室工作树当**测量仪**,主树全程只读)
|
||||
三条判据:仓外 detached 树当净室、**一次只注入一份在飞文件**、与 HEAD 基线逐文件差分;主树前后 `git status --porcelain` 逐行一致才算「全程只读」。全文 → [reference/clean-room-measure.md](reference/clean-room-measure.md)。
|
||||
|
||||
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/clean-room-measure.md](reference/clean-room-measure.md) —— 把仓外 detached 工作树当净室测量仪:一次只注入一份在飞文件、与 HEAD 基线差分做逐文件归因、主树全程只读。
|
||||
### E. 回收**非登记**工作树(`list` 为空 / detached / 树里有在飞或未跟踪内容)
|
||||
|
||||
### E. 回收**非登记**工作树(`$TW list` 为空 / detached HEAD / 树里有在飞或未跟踪内容)
|
||||
三条不可跳过:① 逐文件内容**三桶归类**(与主线工作区/HEAD 比对;「很久没动」不是判据)② 动手前跑**只读 reparse 预扫**,拿到 `REPARSE: none` 才许 `git worktree remove`(铁律 5)③ 唯一未合并残留先备份再 `--force`,拆完复验 + 清悬挂台账。**绝不跑 `done`/`abort`**(铁律 9)。全文(第 1~6 步 + 两份探针脚本)→ [reference/reclaim-unregistered.md](reference/reclaim-unregistered.md)。
|
||||
|
||||
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/reclaim-unregistered.md](reference/reclaim-unregistered.md) —— 回收 `@task list` 看不到的工作树:逐文件内容三桶归类、reparse/联接只读预扫、唯一残留先备份再 `--force`、悬挂台账清理复验。
|
||||
### F. 与 DSH worktree 插件(`/worktree`、`worktree_create`)共存
|
||||
|
||||
### F. 与 DSH worktree 插件(`/worktree`、模型工具 `worktree_create`)共存
|
||||
| 联接 | 级别 | 依据 | 缺了会怎样 | 逃生门 |
|
||||
|---|---|---|---|---|
|
||||
| `simulator/SDL2` | **硬需求** | `simulator/CMakeLists.txt:12` 的 `set(SDL2_DIR …)` **非 `CACHE`**,include / `lib/x64` / `SDL2.dll` 全从它派生 | `build_sim.bat` 当场失败 | **无** |
|
||||
| `.venv` | 软需求 | `scripts/ci_local.py` 有 `DTU_PRECOMMIT_PYTHON` + 五级解释器候选 | **静默**落机器级解释器(缺 fontTools → `generated-fresh` 假红) | 环境变量 |
|
||||
|
||||
- **两条共享联接不同级**(别答成「两个都必须」):`simulator/SDL2` = **硬需求**——`simulator/CMakeLists.txt:12` 是普通 `set(SDL2_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SDL2/SDL2-2.32.10")` 而**非 `CACHE`**,既没有环境变量也没有 `-DSDL2_DIR=` 逃生门,include / `lib/x64` / `SDL2.dll` 三条路径全从它派生(`:34` `:70` `:71` `:180` `:207`)→ 缺它 `build_sim.bat` 当场失败。`.venv` = **软需求**——`scripts/ci_local.py` 有 `DTU_PRECOMMIT_PYTHON` 显式逃生门 + 五级解释器候选(显式 var → `sys.executable` → 仓库 `.venv` → `which python` → 机器级常见位置),绕开联接只是**静默**落到机器级解释器(缺 fontTools → `generated-fresh` 假红)。`plugin adopt` 两条都补是因为便宜,不是两条都硬。
|
||||
- **「有插件了为什么还自持 Python 生命周期」别凭印象答**:先实读插件包(`npm root -g` → `node_modules/@deepseek-ai/dsh/` 下定位插件、读 `package.json` 记版本),再 grep 三个概念名并报命中数——独占锁 `lock`、共享目录联接(`.venv` / `simulator`-SDL2)、联接状态三态显示;插件侧零命中就是自持的理由。重叠面只有开/收两个动作(两侧底层都是 `git worktree add`,没重造「建树」);锁、联接、台账与 `done` 的「预演 → 合并 → 审计 → 门禁 → 清场」编排才是插件没有的东西(本仓资源各只有一份:一块 ST-LINK、一套 Keil 授权、一个 COM6,SDL2 又无逃生门 → 必须串行 + 必须共享)。
|
||||
- `plugin adopt` 两条都补只是因为便宜,不代表两条都硬。插件树占锁记 `pw:<name>`、**回收仍归 /worktree**(本 skill 不删插件树,插件也不删 `_wt/`)。「有插件为何还自持生命周期」=先读插件包(`npm root -g`)再 grep 三个概念名报命中数,插件侧零命中就是理由——取证姿势与答案边界 → [reference/dsh-plugin-bridge.md](reference/dsh-plugin-bridge.md) F-2。
|
||||
|
||||
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/dsh-plugin-bridge.md](reference/dsh-plugin-bridge.md) —— 与 DSH worktree 插件(`/worktree`、`worktree_create`)共存:`plugin adopt` 补共享联接(两条联接的分级:SDL2 硬 / `.venv` 软)、锁归属 `pw:<name>`、回收仍归 /worktree、以及「为什么不用插件而自持生命周期」的取证姿势(F-2)。
|
||||
### G. 任务树内跑门禁:落点、`--all` 假红、消息禁词
|
||||
|
||||
### G. 任务树内跑门禁与审计:输出落点(ci-artifacts)与 SDL2 联接假红
|
||||
|
||||
- **新建的任务树里没有 `ci-artifacts/`**(被 `.gitignore` 忽略 → 不会被检出):重定向输出或
|
||||
`fs.writeFileSync` 直接 `ENOENT`,**整段程序零执行**。落盘前先单独一条 `mkdir -p ci-artifacts`;树内输出
|
||||
一律用树内相对路径(`cd` 进树后 `../_wt/x.txt` 会落到 `_wt/_wt/`,实测就是这句报错)。
|
||||
- **文档门禁在树内按单文件跑,全库 `--all` 基线留到合并后在主树跑**:树内的 `simulator/SDL2` 是指向
|
||||
主树同一份的目录联接,`--all` 会把这份「第二份仓库副本」一起扫:实测树内打 222 个文档 / 35 处问题、RC=1
|
||||
而 **35 处全在联接下**、要改的文档一条没有 = 假红;合并后主树同一条命令 = 2,597 处 / 194 个文档 / rc=0,
|
||||
这才是有效基线(单文件实测 161 处/98%/rc=0、16 处/88%/rc=0)。
|
||||
- 确要在树内跑 `--all`:判红固定三步——① 主树同命令拿基线 ② 过滤 `simulator/SDL2` 联接下的问题行
|
||||
③ 剩下的逐行比。只做一步(见 RC=1 就报「门禁挂了」/ 过滤后为空就报「全绿」)都是误判。
|
||||
- **跑门禁前先做一次字节体检**(文档改动在树内提交前的固定项):`lines / bytes / BOM / CR / U+FFFD / 三连续空行 / 行尾空格 / 末行单个 LF` + 自写 Markdown 表格每行列数一致性检查。报出来的坏行**未必是本次改动**(实测 2,665 行的需求文档里两处坏表格与本次编辑无关)——先定位归属再决定动不动手,别顺手改别人的区域。这些面 `doc-links` / `no-mojibake` 只覆盖编码那半,表格断列与多出的空行要自己数。
|
||||
- **提交/合并信息一律 `-F` 读文件**(UTF-8 无 BOM、标题 ≤100 字符、正文每行 ≤80,文件放 `ci-artifacts/`),清退类任务另加一条**禁词自检**:`git log -1 --pretty=%B | grep -c '<被清退主题的关键词>'` 必须是 **0**(标题与正文都不许再出现那个关键词,改用「其它串口通道对照内容」这类语义短语;合并信息同样查一遍)。提交前自查消息里的行数/统计有没有算错;**未推送前**发现错了 → `git commit --amend -F <改好的文件>` 就地修,别追加一笔「更正」提交(那等于把禁词又写回历史)。
|
||||
|
||||
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/gates-in-tree.md](reference/gates-in-tree.md) —— 任务树内跑门禁与审计:`<tree>/ci-artifacts/` 先 `mkdir -p`、文档链接按单文件在树内跑、全库 `--all` 合并后在主树跑、`simulator/SDL2` 联接假红、提交/合并信息禁词自检、改完文档的字节与表格体检。
|
||||
- **新树没有 `ci-artifacts/`**(被 ignore → 不检出):重定向/写文件直接 `ENOENT` 且**整段程序零执行** → 落盘前先单独一条 `mkdir -p ci-artifacts`;树内输出一律树内相对路径(`cd` 进树后 `../_wt/x.txt` 落到 `_wt/_wt/`)。一次性产物**不进仓库根**。
|
||||
- **文档门禁树内按单文件跑;全库 `--all` 留到合并后在主树跑**——树内 `simulator/SDL2` 是同一份仓库的联接,`--all` 把它一起扫成假红(实测 RC=1 而问题全在联接下、要改的文档一条没有)。非要树内跑:① 主树同命令拿基线 ② 过滤联接下的问题行 ③ 剩下的逐行比。**只做一步就是误判。**
|
||||
- **合并提交期 pre-commit 只查冲突文件**(日志首行 `[INFO] Checking merge-conflict files only.`,其余全 `(no files to check) Skipped`)→ 合并落主干后**必须另跑全量** `scripts/ci_local.py`。
|
||||
- **跨层改动拆不成「按层分笔」**:钩子看到的世界 = HEAD + 本笔 staged(未暂存被 stash;未跟踪文件不被 stash 但会拖红同一棵树上别人的提交)→ 登记文件 / 生成物 / 互锁引用的文档分到他笔,本笔必被整仓门禁拦(`doc-links`/`registration-check`/`generated-fresh`/`mem-numbers`)。判据:**一笔提交必须自洽**。
|
||||
- **跑门禁前字节体检**:`lines / bytes / BOM / CR / U+FFFD / 三连续空行 / 行尾空格 / 末行单个 LF` + Markdown 表格每行列数一致性(单元格内竖线写成反斜杠竖线,否则被当分隔符断列)。坏行**未必是本次改动**,先定归属。`doc-links`/`no-mojibake` 只覆盖编码那半。
|
||||
- **提交/合并信息一律 `-F` 读文件**(UTF-8 无 BOM、标题 ≤100、正文每行 ≤80,放 `ci-artifacts/`);清退类任务加**禁词自检**:`git log -1 --pretty=%B | grep -c '<关键词>'` 必须是 **0**。未推送前发现消息写错 → `--amend -F` 就地修,别追加「更正」提交(等于把禁词写回历史)。
|
||||
- 全文(第 1~7 条,含 stash 机制与四道整仓门禁现场)→ [reference/gates-in-tree.md](reference/gates-in-tree.md)。
|
||||
|
||||
## 铁律
|
||||
|
||||
1. **`done` 只能在主工作树执行**——主干分支检出在主工作树,git 不允许同一分支在两处检出。
|
||||
2. **`done` 不 push**。它结束于本地合入 + 门禁全绿;推送需用户明确许可(项目既有纪律)。
|
||||
3. **`build` 与 `bench` 都必须串行**:UV4 不能两个实例同时 `-b`;串口 COM6、ST-LINK、
|
||||
设备 IP `192.168.1.100` 各只有一份。**构建与烧录都不可并发。**
|
||||
- `build` 锁由 **`@build` 自持自释**,调用方无需手工 acquire/release;
|
||||
- `bench` 锁**必须手工**:`lock acquire bench --task <ID>` → 烧录/`@test`/探针 →
|
||||
`lock release bench --task <ID> --bench-pass`。非登记持有者(lead 的验收窗、插件树 `pw:<name>`)占了锁
|
||||
怎么放 → B 节(2026-10-29 起无台账也只 warn、照常释放);插件树整体口径 → F 节。
|
||||
4. **单写者文档**:`docs/任务规划/`(整个文件夹)、`AGENTS.md` 的任务表块、`MDK-ARM/*.uvprojx`、
|
||||
`.pre-commit-config.yaml`、内存快照 —— 任务期**不在任务工作树里改**,留到 `done` 统一处理。
|
||||
5. **清场必须先摘联接**:脚本会先 `os.rmdir` 掉 `.venv` / `simulator/SDL2` 联接再
|
||||
`git worktree remove`。**手工清场时千万别直接 `git worktree remove`**——git 可能递归进联接,
|
||||
把主工作树的 `.venv` 一起删掉。手工拆任何一棵树之前先跑 **E 第 3 步**的预扫,拿到 `REPARSE: none` 才动手。
|
||||
6. **活跃任务硬上限 8**(脚本常量 `MAX_CONCURRENT = 8`,每棵约 83 MB 检出——du 口径,内容字节和 84.0 MiB → `docs/环境与部署/并行任务工作树方案.md` §4)。满了 `new` 直接 exit 2,
|
||||
腾位顺序与 `--force` 的代价 → A 节;悬挂台账的清理与复验 → **E 第 6 步**。
|
||||
**改上限 = 只改 `task_worktree.py` 里那一行常量**(`MAX_CONCURRENT`,:30):报错文案「已有 N 个活跃任务(上限 8)」
|
||||
与 `new` 打的「另有 N 个 DSH 插件工作树(`.dsh-worktrees/`,不计入上限 8)」提示都是 `.format(…, MAX_CONCURRENT)`
|
||||
插值该常量(实测 `task_worktree.py:764-766` / `:768-771`)——AGENTS.md、方案文档、本技能 frontmatter 与 A 节里的
|
||||
数字**全是引用同步**,不是第二处事实源。全形态扫描(`最多 N` / `N 棵` / `三棵` / `第 N+1 棵` / `白占 N 个名额` /
|
||||
`MAX_CONCURRENT = N` / 硬上限)与「上限真生效」的隔离小仓验证(`REPO_ROOT` 由 `__file__` 反推 → 脚本必须按同一
|
||||
目录深度放,才能测到同级 `_wt/`)→ `stale-doc-count-sweep` skill;每棵树的磁盘代价按实测口径重述,别照抄旧数。
|
||||
7. **`@task` 的工作树放仓库同级 `_wt/`,绝不在仓库内部**——否则污染 `git status`、`pre-commit --all-files`
|
||||
与全库搜索。唯一例外 = DSH worktree 插件写死的仓内 `.dsh-worktrees/`(改不了;已用「预置 .gitignore +
|
||||
三个全库扫描门禁排除」兜住,见 **F 节**)。自己新建的树一律走 `_wt/`。
|
||||
8. **`done` 是合并点**:属主没提交完就别 `done`;合并提交一旦进了主干,只用「主工作树补提交」收尾,
|
||||
**不用 `reset --hard` 反悔** → C 节。
|
||||
9. **非登记 / detached 的树绝不跑 `done` / `abort`**:两者都以台账为前置,无台账直接 exit 2;带着残留台账时
|
||||
`abort --force` 会绕过内容保全把别人的在飞改动连树一起删。回收走 **E 节**(全景 → 分桶 → 联接预扫 → 备份再拆 →
|
||||
复验 + 清悬挂台账)。
|
||||
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 节。
|
||||
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 步。
|
||||
`MAX_CONCURRENT`
|
||||
(
|
||||
`task_worktree.py:30`
|
||||
)——唯一事实源**:报错文案与
|
||||
`new`
|
||||
的插件树提示都插值它,AGENTS.md / 方案文档 / frontmatter / A 节的数字全是引用同步。改这个数字 → 全形态扫描走
|
||||
`stale-doc-count-sweep`
|
||||
skill,并另建隔离小仓验证上限真生效;每棵磁盘代价按实测口径重述(du 约 83 MB /
|
||||
`--lean`
|
||||
约 45 MB,方案文档 §4)别照抄旧数。满了
|
||||
`new`
|
||||
直接 exit 2 → A 节;悬挂台账 → E 第 6 步。
|
||||
7. **工作树放仓库同级
|
||||
`_wt/`
|
||||
,绝不在仓库内部**(否则污染
|
||||
`git status`
|
||||
、
|
||||
`--all-files`
|
||||
与全库搜索)。唯一例外 = 插件写死的仓内
|
||||
`.dsh-worktrees/`
|
||||
(改不了,已用预置 .gitignore + 三个全库扫描排除兜住 → F 节)。
|
||||
8. **
|
||||
`done`
|
||||
是合并点**:属主没提交完就别跑;合并提交进了主干就只用「主树补提交」收尾,**不用
|
||||
`reset --hard`
|
||||
** 反悔 → C 节。
|
||||
9. **非登记 / detached 的树绝不跑
|
||||
`done`
|
||||
/
|
||||
`abort`
|
||||
**:两者都以台账为前置(无台账 exit 2),带残留台账时
|
||||
`abort --force`
|
||||
会绕过内容保全把别人在飞的改动连树一起删 → E 节。
|
||||
|
||||
## 陷阱(实测)
|
||||
## 陷阱与验证(收工前各扫一遍)
|
||||
|
||||
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/pitfalls.md](reference/pitfalls.md) —— 「陷阱(实测)」逐条清单原文。
|
||||
|
||||
## 验证
|
||||
|
||||
> 详情已外置(本文件须留头量给自进化:dsh-mnemon 写侧上限 65536 字节,超限的技能再也生不成草案)。全文见 [reference/verification.md](reference/verification.md) —— 「验证」清单原文。
|
||||
- [reference/pitfalls.md](reference/pitfalls.md):
|
||||
`done`
|
||||
的门禁跑在
|
||||
`@build`
|
||||
之前 · 门禁 =
|
||||
`pre-commit run --all-files`
|
||||
- [reference/pitfalls.md](reference/pitfalls.md):全部实测坑逐条(含 `--dry-run` 不保护清场段、门禁跑在 `@build` 之前、未跟踪文件不留痕、并集合并残留、新树从未构建 → 体积类门禁必然红)。收工前扫一眼。
|
||||
`git log`
|
||||
与合并里都不留痕 · 并集合并残留 · 新树从未构建 → 体积类门禁必然红 ·
|
||||
`--force`
|
||||
只覆盖四类检查 · 联接不是 symlink(
|
||||
`core.symlinks=false`
|
||||
)· 锁不绑进程存活。
|
||||
- [reference/verification.md](reference/verification.md):每节要留的证据(预演 sha +
|
||||
- [reference/verification.md](reference/verification.md):每节要留的证据(预演 sha + `MT_RC=0`、blob 对账、两把锁空闲、禁词计数 0、`REPARSE: none`、改上限数字后的全形态扫描)。
|
||||
、blob id 对账、两把锁空闲、禁词计数 0、
|
||||
`REPARSE: none`
|
||||
…)。
|
||||
|
||||
## 与其它 skill 的衔接
|
||||
|
||||
- 构建 = `keil-build` skill(`@build`);烧录/回读 = `stlink-flash` skill;串口闭环 = `serial-boot-test` skill
|
||||
- 烧录产物的真实 hex 路径以 `.serialtest/config.json` 的 hex 键为准(实测
|
||||
`MDK-ARM/Objects/STM32F407-Demo.hex` 这个路径并不存在),拷错等于烧了旧镜像 → `serial-boot-test` skill
|
||||
- 新 `.c` 文件 = `new-source-file` skill(`uvprojx` 与 `CMakeLists.txt` 双端注册)
|
||||
- **DSH worktree 插件**(人类命令 `/worktree`、模型工具 `worktree_create`,树在仓内 `.dsh-worktrees/`)
|
||||
与本 skill 的分工、桥接命令与锁归属 → F 节(本 skill 不删插件树,插件也不删 `_wt/`)
|
||||
- `.map` 口径 / RAM 台账与趋势 → `mem-budget` skill;UI 回归 → `ui-verify` skill
|
||||
- 完整机制与目录分配 → `docs/环境与部署/并行任务工作树方案.md`
|
||||
- 构建 = `keil-build`(`@build`)· 烧录/回读 = `stlink-flash` · 串口闭环 = `serial-boot-test`(真实 hex 路径以 `.serialtest/config.json` 的 hex 键为准,拷错 = 烧旧镜像)
|
||||
`keil-build`
|
||||
(
|
||||
`@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`
|
||||
|
||||
@@ -10,14 +10,14 @@ detached HEAD 的干净工作树当净室,一次只往里注入一份在飞文
|
||||
1. **建净室 + 先落基线数**(路径必须**在仓库目录之外**;铁律 7 对临时树同样成立——放仓内就污染 `git status` /
|
||||
`pre-commit --all-files` / 全库搜索):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
MAIN="D:/Code/DTU Code/STM32F4-Base" # 主工作树绝对路径(含空格 → 变量整体加引号)
|
||||
WT="$MAIN/../_wt/und-attr" # 仓外同级 _wt/ 下;一次测量固定用同一个名字
|
||||
rm -rf "$WT" 2>/dev/null
|
||||
git -C "$MAIN" worktree add --detach "$WT" HEAD >/dev/null 2>&1 || { echo WT_FAIL; exit 9; }
|
||||
git -C "$WT" status --porcelain # 应为空
|
||||
"$PY" "$WT/scripts/archive/id_gate/check_ids.py" 2>&1 | tail -25 # ← 口径:id-check 钩子已于 2026-10-28 退役(脚本移入 scripts/archive/id_gate/),按该脚本自身默认参数跑;基线与注入用**同一条命令**
|
||||
@T3@
|
||||
```
|
||||
|
||||
- `--detach`(**不要 `-b`**):净室没有分支、不写 `_wt/.state/<ID>.json` 台账 → **不占 `MAX_CONCURRENT = 8`
|
||||
名额、不出现在 `$TW list`**,也没人会被误指去对它跑 `done`(建与拆都不走 `$TW new` / `$TW done`)。
|
||||
@@ -27,14 +27,14 @@ git -C "$WT" status --porcelain # 应为空
|
||||
看清会删什么,只挪走多出来的那些再测;语料被污染出来的数字与 CI 不可比。
|
||||
2. **一次只注入一份在飞文件 → 跑同一个扫描器 → 与基线差分**(得到的是每个文件的**独立**贡献,不是合计):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
for f in "App/log/sys_log_mcu.c" "test/host/sys_log_mcu/sys_log_mcu_test.c" "docs/子系统设计/运行与自检记录设计说明.md"; do
|
||||
mkdir -p "$WT/$(dirname "$f")"
|
||||
cp "$MAIN/$f" "$WT/$f" # 拷进净室;主树那份只读、一字节不动
|
||||
echo "== $f =="; "$PY" "$WT/scripts/archive/id_gate/check_ids.py" 2>&1 | tail -25 # ← 与第 1 步同一条命令
|
||||
git -C "$WT" checkout -- "$f" # 只还原净室里自己刚拷进来的那份
|
||||
done
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测:`run_par.c` / `board_ui_nav.py` / `check_nested_comment.py` / keil `SKILL.md` **各 +0**,
|
||||
而 log 道那三个文件 **+6 / +8 / +2**,合计**正好 +16**。**Σ(逐文件增量) ≠ 观察到的涨幅 → 还有没被注入的
|
||||
@@ -45,11 +45,11 @@ done
|
||||
全部落进 `skip_bare_unmanaged` —— 到这一步结论才配发给属主。
|
||||
4. **闭合 + 复验主树一个字节没被污染**:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" worktree remove --force "$WT" # 净室里只有自己的拷贝、没有原件,--force 安全
|
||||
git -C "$MAIN" worktree list # 不该再有 und-attr
|
||||
git -C "$MAIN" status --porcelain | sort # 与测量前**逐行一致**(测量前先存一份基线清单)
|
||||
@T3@
|
||||
```
|
||||
|
||||
⚠ **上面第一行的前提是有条件的**:树里混进了别人的在飞 / 未跟踪内容时「只有自己的拷贝」就不成立了,
|
||||
`--force` 直接拆等于替别人把唯一版本删掉 → 先走 **E 节**(全景 → 逐文件分桶 → 联接预扫 → 唯一残留先备份),
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
|
||||
三步接上(`<name>` 可带 `worktree/` 前缀,也可多段如 `refactor/logging`):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
"$PY" "$TW" plugin list # 一览:分支 / 脏 / 缺哪个联接(缺项另起一行说后果)
|
||||
"$PY" "$TW" plugin adopt N21-dhara-isfree # 只补缺失的 .venv 与 simulator/SDL2 联接
|
||||
"$PY" "$TW" plugin unadopt N21-dhara-isfree # 只摘联接(os.rmdir),绝不动目标内容与树里文件
|
||||
@T3@
|
||||
```
|
||||
|
||||
- **两条联接坏了的样子完全不同**:缺 `.venv` → `ci_local.py` 的四级解释器回退**静默**落到机器级
|
||||
python(无 fontTools = `generated-fresh` 假红的老根因,AGENTS.md 记着);缺 `simulator/SDL2` →
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
(扫描根就是那一个文件,联接目录不在面内)。主树本身没有 SDL2 时不产生这类假红(联接如实镜像主树,
|
||||
F 节那棵树连 SDL2 都没得联),但**别据此认为 `--all` 在树内可信**。
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
F="docs/<子目录>/<本次改动的文件>.md" # 本次改动的那一个文件
|
||||
"$PY" scripts/check_doc_links.py "$F" > ci-artifacts/dl-<slug>.txt 2>&1; echo RC=$?
|
||||
cat ci-artifacts/dl-<slug>.txt
|
||||
@T3@
|
||||
```
|
||||
|
||||
逐文件跑的实测读数:需求文档 161 处章节引用 / 98% 机器可判定 / rc=0;方案文档 16 处 / 88% / rc=0。
|
||||
确实要在树内跑 `--all` 时(例如怀疑主干基线本身),判红顺序**固定三步**,缺一步都不许下结论:
|
||||
@@ -47,13 +47,15 @@ cat ci-artifacts/dl-<slug>.txt
|
||||
`ci-artifacts/`(被忽略 → 不污染脏行数),UTF-8 无 BOM、CR=0、标题 ≤100 字符、正文每行 ≤80(实测标题 31 / 32 字符、
|
||||
正文最长行 50 / 55)——`-m` 会把中文按终端代码页转码搞坏,长中文标题还容易撞 gitlint。硬自检:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git log -1 --pretty=%B | grep -c '485' # 被清退主题的关键词:必须是 0
|
||||
git log -1 --pretty=%B | grep -ci '\bDE\b' # 同主题的次要关键词:同样 0
|
||||
@T3@
|
||||
```
|
||||
|
||||
关键词不许出现在**标题、正文、合并信息**任何一处(实测标题改写成「删除与本方案无关的其它串口通道对照内容」这种
|
||||
语义短语替掉了它)——否则「别让我看到 X」这条要求在 `git log` 里当场破产。合并信息是另一份文件:
|
||||
`ci-artifacts/merge-msg-<slug>.txt` 落盘后**同样 grep 一遍**再 `git merge --no-ff --no-edit -F`(C2 第 3 步)。
|
||||
自审发现消息里的行数 / 统计算错而**尚未推送** → `git commit --amend -F <改好的文件>` 就地修(实测就这么修掉一次
|
||||
off-by-one);**别追加一笔「更正」提交**——那等于把禁词和被更正的错数一起写回历史。
|
||||
6. **合并提交期 pre-commit 只查冲突文件(2026-10-29 实测)**:合并那一次提交的钩子日志首行是 `[INFO] Checking merge-conflict files only.`,其余钩子全打 `(no files to check) Skipped` —— 于是「合并提交通过了 30 项钩子」这句话**只覆盖冲突文件**,别拿它当全量绿。正解:合并落主干后立刻在主工作树跑全量 `scripts/ci_local.py`(静态 + 提交信息 + 主机侧单测 + manual 段),实测全量这一步才会抓到「表格断列」「章节引用悬空」这类跨文件问题。合并解冲突时若上游把 Markdown 表格重排过,注意**单元格里的竖线必须写成反斜杠竖线**(先例 `docs/子系统设计/运行与自检记录设计说明.md` 的 `FA_WRITE\|FA_SEEKEND`),否则 `FA_WRITE|FA_OPEN_APPEND` 会被当列分隔符打断,而 `doc-links` 不查表格列数——要自己数(第 4 条)。
|
||||
7. **跨层改动拆不成「按层分笔」提交(2026-10-29 实测,四笔改一笔)**:钩子看到的世界 = HEAD + 本笔 staged——pre-commit 会 stash 已跟踪文件的未暂存改动,而**未跟踪文件不在 `git diff-index` 里、不会被藏**(`git add -N` 也救不了,pre-commit 会把它撤下索引、文件仍在盘上)。后果:把双端登记(`MDK-ARM/*.uvprojx` / `simulator/CMakeLists.txt`)、生成器产物、互锁引用的文档分到别的笔里,本笔必被 `doc-links`(引用了还没入册的小节)、`registration-check`(源文件登记在另一笔)、`generated-fresh`(`#page=` 找不到页面描述符、字库清单与源不一致)、`mem-numbers`(受管区还是旧 .map 的数)四道**整仓**门禁一起拦。判据:**一笔提交必须自洽**——互锁的改动(代码 + 登记 + 生成物 + 文档)走同一笔;机制与三条处置结论(半成品要么先落地要么先挪出工作树)→ `docs/代码规范/pre-commit使用说明.md` §2「stash 覆盖面」。
|
||||
|
||||
@@ -11,7 +11,7 @@ stash / checkout / reset / 删除(项目级 Never:丢弃别人的工作)
|
||||
1. **先取证再动手**(别一看见脏就下「谁弄坏的」结论)。记下它的 mtime 与 diff 内容,判断与本合并有无交集,
|
||||
并把**合并前**的 blob id 与 `diff --stat` 存进 `ci-artifacts/`(第 4 步拿它对账):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
MAIN="D:/Code/DTU Code/STM32F4-Base"
|
||||
EXT=".dsh/skills/serial-boot-test/scripts/run_boot_test.py" # 外部脏文件的仓库相对路径
|
||||
mkdir -p ci-artifacts
|
||||
@@ -20,7 +20,7 @@ stat -c "%y %s %n" "$MAIN/$EXT" # mtime + 字节数:定性是
|
||||
git -C "$MAIN" diff -- "$EXT" | cat # 内容性质;与本次合并有无同文件同区域
|
||||
git -C "$MAIN" hash-object "$MAIN/$EXT" | tee ci-artifacts/ext-blob-before.txt # ← 存下这个 blob id
|
||||
git -C "$MAIN" diff --stat -- "$EXT" | tee ci-artifacts/ext-stat-before.txt # ← 以及这份统计(几加几减)
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测:mtime 落在另一会话的活跃时段内,diff 是「docstring 引号改写」2+/2−,与要合的文档毫无交集 →
|
||||
定性为**与本合并无交集、不丢弃**。顺手 `git -C "$MAIN" worktree list` 看一眼有没有**别人中途新建的任务树**
|
||||
@@ -28,9 +28,9 @@ git -C "$MAIN" diff --stat -- "$EXT" | tee ci-artifacts/ext-stat-before.txt
|
||||
2. **`merge-tree` 预演:先证明能干净合并,再碰主干**。`$TW done --dry-run` 在这条路上同样被前置挡住,
|
||||
预演只能自己敲(git 2.38+ 的新语法,打印的是**结果树 sha**):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" merge-tree --write-tree v2-on-ui task/<ID>-<slug>; echo MT_RC=$?
|
||||
@T3@
|
||||
```
|
||||
|
||||
判据 = **打印出一个 tree sha 且 RC=0**(实测打出 `4220906b…` + `MT_RC=0` 才敢往下走)。⚠ 本机 git 太旧没有
|
||||
这个子命令时退回 `$TW done <ID> --dry-run`——它的前置也是「主树干净」,可用环境变量 `DTU_INTEGRATION_WORKTREE`
|
||||
@@ -39,9 +39,9 @@ git -C "$MAIN" merge-tree --write-tree v2-on-ui task/<ID>-<slug>; echo MT_RC=$?
|
||||
3. **主工作树里手工 `--no-ff` 合并**(合并信息走 `-F` 文件,放在被忽略的 `ci-artifacts/`;编码口径同 C 第 5 步:
|
||||
UTF-8 无 BOM、正文每行 ≤80 字符;标题格式 `merge: task/<ID>-<slug> 合入 <base>`):
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt task/<ID>-<slug>
|
||||
@T3@
|
||||
```
|
||||
|
||||
pre-commit 会**自己**把那份未提交改动 stash 起来、跑完钩子再放回(输出里能看到 `[INFO] Stashing unstaged files to …`)
|
||||
——**这条不是让你去手工 stash**,正好相反:钩子的 stash 往返让它自己走完,你只需在第 4 步验证它把东西原样还回来了。
|
||||
@@ -52,12 +52,12 @@ git -C "$MAIN" merge --no-ff --no-edit -F ci-artifacts/merge-msg-<slug>.txt task
|
||||
4. **自证那份外部改动原样还在**(本节唯一的硬证据,两次实测都靠它结案):合并后**再跑一次**同一对命令,
|
||||
与第 1 步存的两个值**逐项相同**:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" hash-object "$MAIN/$EXT" | tee ci-artifacts/ext-blob-after.txt
|
||||
git -C "$MAIN" diff --stat -- "$EXT" | tee ci-artifacts/ext-stat-after.txt
|
||||
diff ci-artifacts/ext-blob-before.txt ci-artifacts/ext-blob-after.txt # 空输出才算过
|
||||
git -C "$MAIN" status --porcelain # 该文件仍是 M:没被顺手提交,也没被清成干净
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测:合并前后 blob id 同为 `369380ca8eb024b593d872455b6a020797abcd8d`、`diff --stat` 同为 2+/2−
|
||||
→ 证明 pre-commit 的 stash 往返没动别人的工作;合进主干的那份文档另按 G 节复核(零残留 + 全库基线)。
|
||||
@@ -66,10 +66,10 @@ git -C "$MAIN" status --porcelain # 该文件仍是 M:没被顺
|
||||
5. **收工仍走技能自身的代码路径,别自己手写 rm**:先把台账**备份成 `*.state-bak`**(后缀与 E 第 6 步统一),
|
||||
再让脚本自己摘联接:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
cp "$WTROOT/.state/<ID>.json" "$MAIN/ci-artifacts/<ID>.json.state-bak" # 先备份再动
|
||||
"$PY" "$TW" done <ID> # 打「已合入 <base>,跳过合并,直接清场」→ 先摘 .venv 与 simulator/SDL2 联接 → 删工作树 → 删分支 → unlink 台账
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测这条路径能走完(日志:`state backup -> ci-artifacts\screenlink-doc-history.json.state-bak` +
|
||||
`[ok] 已摘除联接 .venv` + `[ok] 已摘除联接 simulator/SDL2`)。**若它仍拿「主工作树不干净」挡在合并前**(没走到
|
||||
|
||||
@@ -115,3 +115,4 @@
|
||||
进入 acquire、`finally` 无条件 release,所以"Agent 忘了 release、锁一直挂到 TTL"这条在构建路径上已消除。
|
||||
`done` 的门禁自己持锁并给子进程设 `DTU_LOCK_HELD=build` 告知跳过(新旧脚本都兼容)。
|
||||
**`bench` 锁仍须 Agent 手工持**(会话作用域:跨烧录→`@test`→探针,见 `keil-build` SKILL.md)。
|
||||
- **⚠ `done --dry-run` 只保护预演段、不保护清场段**(2026-10-29 实测):`done <ID> --dry-run --skip-build --skip-gates` 在「分支已合入主干」这条路上**照样执行清场**——摘 `.venv` / `simulator/SDL2` 联接、删分支、清台账,然后打「完成(分支此前已合入)」。想真·零改动就只跑不带 `--skip-gates` 的 `--dry-run` 并停在预演输出;用完一律 `git worktree list` 复验(判完成以 `git log` 为准,不是以有没有再敲一次 `done` 为准)。另:`--dry-run` 的前置检查同样要抢 `build` 锁——被别的任务占着时它会直接报「独占资源 build 仍被 X 占用,先 release」,此时加 `--skip-build` 或先 release,**别为了预演去抢别人的锁**。
|
||||
|
||||
@@ -14,7 +14,7 @@ HEAD 是 detached(`git rev-parse --abbrev-ref HEAD` 打 `HEAD`);或树里
|
||||
1. **建全景(全程只读)**:先看 `git worktree list` 的全集,再**逐棵**记三件事(脏行数含未跟踪 / 树顶提交 / 有无分支)
|
||||
加一条台账判定,同时存下主树的拆前基线清单:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
MAIN="D:/Code/DTU Code/STM32F4-Base"; WTROOT="$MAIN/../_wt"
|
||||
git -c core.quotePath=false worktree list # 全集:登记树 + 非登记树都在这份里
|
||||
MAIN_SHA=$(git -C "$MAIN" rev-parse --short HEAD) # 下面所有比对都以这一版为主线尺子
|
||||
@@ -28,7 +28,7 @@ for t in "$WTROOT"/*/; do
|
||||
git -C "$t" rev-parse --abbrev-ref HEAD # 打 HEAD = detached,压根没分支可 done
|
||||
ls "$WTROOT/.state/$(basename "$t").json" 2>/dev/null || echo "NO-LEDGER" # 台账位置随 DTU_WT_ROOT
|
||||
done
|
||||
@T3@
|
||||
```
|
||||
|
||||
- **`-uall` 不是可选**:默认 `status --porcelain` 会把未跟踪目录折叠成一行 `dir/`,`-uall` 才展开到文件级;少了它
|
||||
第 2 步的分桶就漏项(实测 `und-attr` 唯一那份就是未跟踪 `?? scripts/check_nested_comment.py`)。
|
||||
@@ -39,7 +39,7 @@ done
|
||||
内容,拆 = 丢)/ `OLDER-DRAFT`(同路径主线已有更新版本)。落成独立只读探针跑(别用 heredoc 现写现改,
|
||||
见陷阱「别用 heredoc 打补丁造探针」):
|
||||
|
||||
@T3@python
|
||||
```python
|
||||
# ci-artifacts/wt_bucket_scan.py — 只读:逐文件与主线工作区/HEAD 比对,三桶归类
|
||||
import os, subprocess, tempfile
|
||||
MAIN = r"D:\Code\DTU Code\STM32F4-Base"
|
||||
@@ -87,12 +87,12 @@ for name in TREES:
|
||||
for b in ("UNIQUE-UNMERGED", "OLDER-DRAFT", "OTHER"):
|
||||
for rel, why in tally.get(b, []):
|
||||
print(f" {b}: {rel} # {why}")
|
||||
@T3@
|
||||
```
|
||||
|
||||
报告里必须**逐桶给计数 + 判定证据**(实测:`verify-task16` IN-MAINLINE=6 / UNIQUE=0;`verify-batch` IN-MAINLINE=35 /
|
||||
UNIQUE=1;`und-attr` IN-MAINLINE=0 / UNIQUE=1)。两个非空桶再各钉一条命令把证据坐实:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
# 「唯一未合并」是真更新还是更旧草稿?blob 级 diff + 逐行看方向
|
||||
git -c core.quotePath=false -C "$MAIN" diff --stat "$MAIN_SHA" -- simulator/tests/README.md
|
||||
diff <(git -C "$MAIN" show "$MAIN_SHA":simulator/tests/README.md) "$WTROOT/verify-batch/simulator/tests/README.md"
|
||||
@@ -100,7 +100,7 @@ diff <(git -C "$MAIN" show "$MAIN_SHA":simulator/tests/README.md) "$WTROOT/verif
|
||||
for c in 77779d49 1d4b6a17; do
|
||||
git -C "$MAIN" merge-base --is-ancestor "$c" HEAD && echo "$c MERGED" || echo "$c NOT-merged"
|
||||
done
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测:那份 `simulator/tests/README.md` 树内写「27 页 × 3200 B」、主线 HEAD 已是「60 张 × 3200 B」→ **更旧草稿**(被取代的
|
||||
早先版本),不是未合并成果;`bench-21-77779d49` / `bench-23-1d4b6a17` 两棵 `merge-base --is-ancestor` 判 **MERGED** 且脏数 = 0
|
||||
@@ -109,7 +109,7 @@ done
|
||||
还得看未跟踪那一层。`UNIQUE-UNMERGED` 非空又确实要留 → 它进第 4 步的备份,**不许靠 `--force` 顺手带走**。
|
||||
3. **拆前扫联接 / reparse point(物理安全性 = 铁律 5 的可执行判据)**。存成 `ci-artifacts/wt_junction_scan.py` 再跑:
|
||||
|
||||
@T3@python
|
||||
```python
|
||||
# 只读预扫:一个路径是不是 reparse point(Windows 目录联接在这里现形);发现即停止深入,绝不 walk 进联接
|
||||
import os, ctypes
|
||||
FILE_ATTRIBUTE_REPARSE_POINT = 0x400
|
||||
@@ -131,7 +131,7 @@ for name in ["verify-task16", "verify-batch", "und-attr", "bench-21-77779d49", "
|
||||
print(" REPARSE: none → 可直接 git worktree remove --force"
|
||||
if not hits else " REPARSE:\n " + "
|
||||
".join(hits))
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测输出 `== verify-task16 ... REPARSE: none`(每棵的体积 / 条目数 + 零联接就是证据),而 `bench-*` 两棵各挂一个 `.venv`
|
||||
联接 → 判 **KEEP**(本节不拆它们;将来真要拆,先 `rmdir` 摘掉联接再 remove)。
|
||||
@@ -144,7 +144,7 @@ for name in ["verify-task16", "verify-batch", "und-attr", "bench-21-77779d49", "
|
||||
`ci-artifacts/wt-leftover-backup/`(`ci-artifacts/` 已被 `.gitignore` 忽略 → **备份不会改变主树脏行数**;仓库根不放一次性产物),
|
||||
**文件名 + 字节数必须打进输出**当证据:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
BK="$MAIN/ci-artifacts/wt-leftover-backup"; mkdir -p "$BK"
|
||||
# 命名约定:<树名>__<原路径里的 / 换成 _>,反向可定位;台账备份统一后缀 .state-bak
|
||||
cp "$WTROOT/verify-batch/simulator/tests/README.md" "$BK/verify-batch__simulator_tests_README.md"
|
||||
@@ -154,25 +154,25 @@ for t in verify-task16 verify-batch und-attr; do
|
||||
git -C "$MAIN" worktree remove --force "$WTROOT/$t"; echo "$t remove_rc=$?" # 不接管道才读得到码(见「读输出」)
|
||||
done
|
||||
git -C "$MAIN" worktree prune # 顺带清 .git/worktrees 里的残籍
|
||||
@T3@
|
||||
```
|
||||
|
||||
自检:**备份条目数 = 第 2 步里判「要留档」的条目数**,且每个都带字节数。这一步就是本节相对 D 第 4 步的增量——
|
||||
D 的前提是「净室里只有自己的拷贝」,本节的树里混着别人在飞的东西,`--force` 是一次不可逆删除,**先备份才谈得上安全**。
|
||||
5. **复验:主树一个字节没动 + 树确实没了**:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
git -C "$MAIN" status --porcelain -uall > ci-artifacts/wt-main-after.txt
|
||||
wc -l < ci-artifacts/wt-main-before.txt; wc -l < ci-artifacts/wt-main-after.txt # 两个数必须相同(实测 0 → 0)
|
||||
diff ci-artifacts/wt-main-before.txt ci-artifacts/wt-main-after.txt # 空输出 = 逐行一致
|
||||
git -c core.quotePath=false -C "$MAIN" worktree list # 已拆路径逐条消失;主树与判「保留」的树原样在列
|
||||
@T3@
|
||||
```
|
||||
|
||||
行数不同 = 备份或摘除碰到了主线(多半是备份写进了未被忽略的目录,或漏摘联接)→ 立刻定位,别报「回收完成」。
|
||||
第 2 步判「保留不动」的树还在 `worktree list` 里是**预期结果**,在报告里点名解释为什么留(附 `MERGED` 判据)。
|
||||
6. **顺手清悬挂台账**(branch / path 都已不存在的 `_wt/.state/<ID>.json`;它会让 `$TW list` 打出一行怪条目、并让下次
|
||||
`new` 同 ID 报「任务 <ID> 已存在」)。先给证据、后动手:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
for j in "$WTROOT"/.state/*.json; do # 台账位置随 DTU_WT_ROOT
|
||||
id=$(basename "$j" .json)
|
||||
br=$("$PY" -c "import json,sys;print(json.load(open(sys.argv[1],encoding='utf-8')).get('branch',''))" "$j")
|
||||
@@ -181,19 +181,19 @@ for j in "$WTROOT"/.state/*.json; do # 台账位置随 DTU_WT_ROOT
|
||||
[ -d "$pp" ] && p=path-OK || p=path-MISSING
|
||||
echo "$id $b $p <- $j"
|
||||
done
|
||||
@T3@
|
||||
```
|
||||
|
||||
实测这条判据打出的正是 `v5tool2 branch-MISSING path-MISSING`:`git rev-parse task/v5tool2-v11` 报
|
||||
`fatal: ambiguous argument 'task/v5tool2-v11': unknown revision ...`,台账 `path` 指的目录已无,`git worktree list` 里也没有这棵树。
|
||||
**三条齐(rev-parse fatal + path 目录不存在 + worktree list 无此树)才叫悬挂**;缺任何一条都回到第 1~2 步查是不是真有东西要保。
|
||||
然后**备份 → 删 → 复跑两条 list 复验**:
|
||||
|
||||
@T3@bash
|
||||
```bash
|
||||
cp "$WTROOT/.state/v5tool2.json" "$BK/v5tool2.json.state-bak" && rm "$WTROOT/.state/v5tool2.json"
|
||||
"$PY" "$TW" list # 该条目消失(全空时打「(没有任务空间。用 new <ID> <slug> 建一个)」),echo RC=$? 应为 0
|
||||
"$PY" "$TW" lock status # build 空闲(TTL 1.0h)/ bench 空闲(TTL 6.0h)—— 证明清台账没牵连两把锁
|
||||
ls "$WTROOT"/.state/locks/ 2>/dev/null # 若该 ID 名下还有 .lock,先按 B 节释放,再删台账
|
||||
@T3@
|
||||
```
|
||||
|
||||
- **必须 `cp` 先备份再 `rm`**:台账里存着 base / junctions / needs_bench / bench_pass,是真出纠纷时唯一的现场记录。
|
||||
- 悬挂条目按定义 `path` 已不存在 → 通常**不占** `MAX_CONCURRENT = 8` 名额(A 第 1 步的「活跃」判据要求 path 目录还在),
|
||||
|
||||
+11
-17
@@ -649,7 +649,7 @@ repos:
|
||||
# 疑似 MSBuild 节点争用;此后 3 次干净树连跑均绿。判据:单次失败先复跑一次再看,
|
||||
# 连续两次失败才当真(不许直接 --no-verify 绕过)。
|
||||
- id: ui-golden
|
||||
name: UI 像素回归(菜单 8 状态 + 内容页 60 张 黄金参考)
|
||||
name: UI 像素回归(菜单 8 状态 + 内容页 66 张 黄金参考)
|
||||
entry: python scripts/check_ui_golden.py
|
||||
language: system
|
||||
files: ^(Drivers/BSP/UI/|App/task/ui_task\.(c|h)$|simulator/(tests|sim|src)/|simulator/main\.c$|simulator/CMakeLists\.txt$|simulator/lv_conf\.h$|scripts/check_ui_golden\.py$|\.pre-commit-config\.yaml$)
|
||||
@@ -680,20 +680,12 @@ repos:
|
||||
# 61 笔被编号体系牵连),脚本与语料整体移入 `scripts/archive/id_gate/`
|
||||
# (保留可复现性,**不再进提交门禁**)。退役路线与判据替换见规范 §8.5。
|
||||
|
||||
# 14) 编号门禁(2026-10-28 起是**唯一**的编号门禁):只查**新增行**,零语料
|
||||
# 背景:语义标识已是默认形态,全库语料审计的成本高过收益(见上条历史)。
|
||||
# 判据:看 git diff 的新增行里有没有**未登记形态**的「字母+数字」标识
|
||||
# (自造前缀直接 FAIL;已停发族只 WARN);白名单在脚本里,按实测普查枚举、几乎不增长。
|
||||
# 自检:`python scripts/check_ids_new.py --self-test`;范围自查用 `--range A..B`。
|
||||
# ⚠ 计数口径(2026-10-28,一增一减写成同日的两个阶段,净零):本条落地使 YAML 解析条目
|
||||
# 51 → 52,同日摘掉退役的旧语料审计钩子后 52 → **51**。口径与自查命令、「声明 / 文档 /
|
||||
# 实跑」三数同源核对 → docs/门禁说明/README.md §2 首条(本文件注释与 AGENTS 必须同批改)。
|
||||
- id: id-check-new
|
||||
name: 编号门禁·新增行(零语料,拦截新造编号)
|
||||
entry: python scripts/check_ids_new.py
|
||||
language: system
|
||||
files: \.(md|c|h|py|ya?ml|txt)$|^\.pre-commit-config\.yaml$
|
||||
pass_filenames: false
|
||||
# 14) 编号门禁(2026-10-28 上线只查新增行那一道;2026-10-29 用户裁定**整体退役**,钩子已摘)
|
||||
# 退役后编号形态没有机器面:判据回到 docs/编号规范.md §1 三句自问 + §4.1 族总表 + 评审。
|
||||
# 脚本移入 scripts/archive/id_gate/check_ids_new.py(手工可跑,--self-test 保留),留档说明
|
||||
# → docs/门禁说明/id-check-new.md;手工普查与恢复机器面 → docs/编号规范.md §8.5。
|
||||
# ⚠ 计数口径:本条摘除使 YAML 解析条目 52 → **51**,须与 AGENTS.md 的「共 N 个钩子」、
|
||||
# docs/门禁说明/README.md §2 标题三数同源同批改(自查命令见该条;别在注释里写数钩子的字面量)。
|
||||
|
||||
# 14) 分层方向门禁(2026-10-22 加,结构债的"防扩大"那一半)
|
||||
# 背景:AGENTS.md 与 项目架构总览.md 早就写着"六层单向依赖",但**从来没有机械检查**
|
||||
@@ -735,7 +727,8 @@ repos:
|
||||
# P1 新增的 12 套不在任何提交钩子里。原先"再加一份全量插桩等于同一件事做两遍"
|
||||
# 这条理由到现在已经**反了**(现状是有一遍没做),所以必须挂;缺口此前如实登记在
|
||||
# docs/测试说明/主机单测覆盖率口径.md §8。
|
||||
# 为什么挂这一根而不是补 12 个 per-module 钩子:本钩子跑**全部 28 套**,并且守 §5 的
|
||||
# 为什么挂这一根而不是补 12 个 per-module 钩子:本钩子当时跑**全部 28 套**(现值 44 套,台账见
|
||||
# docs/门禁说明/host-unit-tests.md §6 第 4 条),并且守 §5 的
|
||||
# 回退台账(某套被删、或某文件的已覆盖行变少 → [REGRESS] 直接红)。这是 per-module
|
||||
# 钩子做不到的——删掉一套测试,它那根钩子只会因为"没有文件要检查"而静默跳过。
|
||||
# ⚠ FAIL 语义(用户裁定 2026-10-24):找不到 mscoverage 时脚本退出码 2 → 本钩子**失败**,
|
||||
@@ -745,7 +738,8 @@ repos:
|
||||
# 口径 / 分母 / 基线 / 盲区详解都在 docs/测试说明/主机单测覆盖率口径.md,本处只留接线事实。
|
||||
# 触发面**含门禁自身**(.pre-commit-config.yaml):不带自身就等于谁改坏这根闸的 files:
|
||||
# 模式,它就再也不跑且无人察觉(同 lv-conf-sync / bsp-err 的既有做法)。
|
||||
# ⚠ stages: [manual](2026-10-28 用户裁定):本钩子跑光全部 41 套单测(实测 ~105 s,
|
||||
# ⚠ stages: [manual](2026-10-28 用户裁定):本钩子跑光全部 44 套单测(41 套时实测 ~105 s,套件数
|
||||
# 涨了耗时就涨,44 套这轮未重测耗时;
|
||||
# 占一次带代码提交耗时的三分之二)→ 不压在提交期,由**远端 CI** 强制
|
||||
# (.gitea/workflows/ci.yml 的「手动阶段门禁(覆盖率总闸 + 全量 cppcheck)」步),本地等价入口 =
|
||||
# scripts/ci_local.py(manual 段);手工复跑:
|
||||
|
||||
+93
-3
@@ -18,11 +18,15 @@
|
||||
| `freq.h` / `.c` | 全周傅氏相量的派生量 | 频率测量:相邻两拍基波相量共轭乘积取角 + 自适应窗滑动平均 + 假值钳制 + 幅值门(UA→IA 源回退) | `freq_run()` `freq_get()` `freq_valid()` | 「过频 / 低频」测量侧(测频范围 → `docs/算法/继电保护功能复现规划.md` §4.2) |
|
||||
| `prot_set.h` / `.c` | 保护判据输入 | 保护定值模型:Q10 定点 RAM 表 + 范围校验 + 校验和封存 / 自检 / 闭锁 + 持久化 blob 纯逻辑 | `prot_set_load_raw()` `prot_set_get_f()` `prot_set_selfcheck()` | 「定值校验和」载体 |
|
||||
| `prot_elm.h` / `.c` | 保护判据 | 保护元件框架:通用"越限 + 返回系数滞回 + 定时限"核 + 数据驱动实例表(过流三段 / 过负荷 / 过压 / 欠压 / 零序过压 / 过频 / 低频) | `prot_elm_run()` `prot_elm_eval()` `prot_elm_active_mask()` | 「三段式相间过流」/「过负荷」/「过压 / 欠压」/「过频 / 低频」/「零序过压」(软件侧) |
|
||||
| `wave_ring.h` / `.c` | 故障录波数据面 | 录波环与冻结状态机:int16 × 320 点 × 8 路按通道分列,触发即冻结写指针、后触发计满 160 点封段、落盘回执才解冻、冷却期只加溢出计数 | `wave_ring_push()` `wave_ring_tick()` `wave_ring_request()` `wave_ring_sealed()` `wave_ring_read_ch()` | 「故障录波」数据面(环 + 触发冻结语义) |
|
||||
| `wave_store.h` / `.c` | 故障录波段格式 | 段文件字节层的编解码与分次搬运游标:段头 + 通道表 + 两处 CRC-16/MODBUS + 段名轮转与反解 + 整段自检;不碰文件系统 | `wave_store_crc()` `wave_store_head_build()` `wave_store_head_parse()` `wave_store_seg_check()` `wave_store_pack_next()` `wave_store_seg_path()` | 「故障录波」段格式(布局 + 自检) |
|
||||
|
||||
依赖(自下而上、无环):`adc_input.h`(纯枚举 + 声明)← `adc_cal`(另依赖 `cfg_file.h` 接口,
|
||||
纯头文件级)← `sample_ring` ← `phasor` ← `freq`;`meas` ← `sample_ring` + `phasor`
|
||||
(仅 `phasor_calc` 纯核;频率/品质由调用方注入,**不链** `freq`/`adc_cal`);
|
||||
`prot_set` **独立**;`prot_elm` ← `prot_set` + `phasor` + `freq`。
|
||||
`wave_store` **独立**(只用 `stdint.h` 与 `stddef.h`,编码与解码共用同一个字段校验函数);`wave_ring` ← `wave_store`(只取段头类型与返回码;**不链** `adc_cal`——封存瞬间的系数由
|
||||
adcTask 现取现填后交棒,本层不复制第二个事实源)。两份录波文件同样零平台依赖、不碰文件系统。
|
||||
`adc_health` 只吃调用方喂进来的计数 / 码值,不反向依赖 语义取数与有效性实现(「采样通道自检」贴轨门限由调用方
|
||||
从 `adc_cal_rail_ths()` 取,本层仍零算法依赖)。
|
||||
|
||||
@@ -38,6 +42,11 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
||||
`prot_elm_run()`;**每 200 ms** `meas_tick()` 发布遥测快照供 iec104Task / uiTask 读,即「数据可信」遥测快照发布)。节拍 = TIM3 1 kHz 更新事件,两片转换器同源触发。
|
||||
完整数据流图与初始化落点 → `docs/算法/采集与算法层设计说明.md` §2。
|
||||
|
||||
录波支路(与上面这条相量主线**并行**、同拍只多走两步,互不阻塞):`adc_input_raw()` 取 8 路 →
|
||||
`wave_ring_push()` → `wave_ring_tick()`(触发即冻结写指针,后触发计满 160 点封段)→ defaultTask
|
||||
只见 `wave_ring_sealed()` 与已封存区读口 → 游标分次搬运落盘 → `wave_ring_write_done()` 才解冻。
|
||||
→ docs/子系统设计/故障录波设计说明.md §4.2 与 §5.1。
|
||||
|
||||
## 3. 逐文件要点(只留"改代码时必须知道"的;机制与理由见设计文档)
|
||||
|
||||
> **改本目录前先记五条红线**:① 取数只经 `adc_input_raw()`,不写死下标;② **禁止跨转换器**做
|
||||
@@ -124,11 +133,67 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
||||
(本层**不** include 协议层头——分层方向)。
|
||||
- 104 死区迁移 / 频率点绑定 = 104 改读快照那一波(道D 后续分支);读数与注入源比对 = **待台架实测**。
|
||||
|
||||
### 3.10 `wave_ring.c` / `wave_ring.h` — 录波环与冻结状态机(「故障录波」数据面)
|
||||
- **形态三条钉死**(动它之前读 docs/子系统设计/故障录波设计说明.md §6.2 与 §7):只存 `int16`
|
||||
原始码值(不做折算、不许 `float`)、**按通道分列**(不按帧交错——回放与搬运都是整列取)、
|
||||
`ch_count` 固定 8(TEMP 本波不录:未折算的非线性量进录波只多占字节不增信息)。环本体 5120 B,
|
||||
本层全部可写静态 5298 B,落 SRAM1(scatter 的 `RW_IRAM1` 收 `.ANY (+RW +ZI)`,代码里**没有**
|
||||
任何 section 属性;CCM 只放 FreeRTOS 堆,那 5 KB 既塞不进也不该塞)。
|
||||
- **冻结 ≠ 停采集**:触发冻住的是覆盖窗口(写指针停走),丢的只是录波样本,`drop_cnt` 的单位是
|
||||
「点」(一拍 8 路 = 8 点);`sample_ring` / 相量 / 保护在同拍照常跑,本层不碰它们。
|
||||
- **点序**:触发那一拍的样本落在段内点 159(预触发窗最后一点),点 160 起是触发后第一拍,两半
|
||||
各 160 点、不重叠;回放页横轴与 PC 脚本都按这条对齐(设计说明未钉死 ±1,本层按不重叠实现)。
|
||||
- **封存握手(顺序不许换,单测逐拍钉死)**:`wave_ring_tick()` 计满那一拍返回
|
||||
`WAVE_RING_EVT_SEALED` → adcTask 才去取标定事实并 `wave_ring_latch_meta()` → `wave_ring_sealed()`
|
||||
要求「状态 SEALED **且**已 latch」才返 1(两个标志并成一个,落盘道不可能拿半份快照落盘)→
|
||||
defaultTask 只读锁副本 → `wave_ring_write_done(0 / 非 0)` 是唯一解冻入口:成功才推进轮转号与段
|
||||
计数,失败只计 err 且**保持同号**(下一段重写同一个文件,盘上不留半截)。
|
||||
- **两条不许把环焊死的兜底**:SEALED 滞留超过 `WAVE_SEAL_TIMEOUT_MS`(2000 拍,待台架核)自弃本段
|
||||
并计 err;冷却窗 `WAVE_RING_COOL_MS`(500 拍,待台架核)**只在回执之后**计时——「落完解冻」用的是
|
||||
状态不是时间,回执前任何新触发只加 `ovf_cnt` 不重录。
|
||||
- **上下文归属直接长在函数命名上**(跨任务安全不靠约定):`wave_ring_init` / `push` / `tick` /
|
||||
`latch_meta` 只能在 adcTask(⚠ 严禁挂到 TIM3 回调:那是 ISR 且比调度器早约 100 ms,本层没有
|
||||
消费者就绪门);`wave_ring_write_done` 只能在 defaultTask;`request` / `state` / `sealed` /
|
||||
`sealed_meta` / `read_ch` / `read_cb` / `get_stat` / `col` 任意上下文可调。**本层不提供任何读活
|
||||
缓冲的口子**(docs/子系统设计/故障录波设计说明.md §5.2 第一条陷阱:`sample_ring_window()` 无临界区,本波不复用)。
|
||||
- `seg_idx` 只是本次上电的本地计数;盘上的下一个写号归落盘道自持(开机扫目录用
|
||||
`wave_store_idx_from_name()` 重建、写成功后 `wave_store_next_idx()` 推进)。
|
||||
|
||||
### 3.11 `wave_store.c` / `wave_store.h` — 段文件字节层(「故障录波」段格式)
|
||||
- **只做字节**:文件落点、路径拼法、标定系数、数据本体全部由调用方注入(meta 由 adcTask 填、
|
||||
数据经 `wave_reader_fn` 回调取、路径由落盘道拼)→ 两份文件零平台依赖,主机单测免桩 `#include`
|
||||
原样纳入;`wave_store_meta_init()` 只填格式侧静态事实(并把通道表 `sem` 钉成下标、按归属填
|
||||
`conv` / `bits`),`avail` / `k_q16` / `b_codes` 一律留 0 等封存瞬间现取现填——不复制第二个事实源。
|
||||
- **尺寸等式**(单测钉死,落盘道拿它比对写回字节数):整段 = 32 + 12×通道数 + 2×点数×通道数 + 2;
|
||||
当前口径(8 路 320 点)= 头 128 + 数据 5120 + 尾 CRC 2 = **5250 字节**(这条等式是给落盘道比对写回
|
||||
字节数用的):FatFs 该移植在簇分配失败时会返回 FR_OK + 短写且无错误码,判成功必须校验字节数
|
||||
(故障录波设计说明「四条已知陷阱」第二条)。
|
||||
- **CRC-16/MODBUS 自带无表实现**(反射多项式 0xA001、初值 0xFFFF、异或输出 0,自检锚点字符串
|
||||
"123456789" = 0x4B37)。设计说明 §6.2 原写「与屏板链路同一张表」经全仓实测**不成立**(这张表不
|
||||
存在:crc.c 与 crc.h 已于 2026-10-02 删除,屏板侧 `remo_crc()` 是单字节异或)——文档等文档道回改,
|
||||
代码不擅自改口径,也不再新造第三张表。
|
||||
- **编解码同一把尺**:`wave_store_head_build()` 与 `wave_store_head_parse()` 走同一个字段校验函数,
|
||||
判据与 PC 侧读段脚本的拒绝清单逐条对齐(零长度、每周波点数不足 2、每通道点数不够一周波、
|
||||
预触发点数超段长、通道表项语义号不等于下标、版本号与魔数第三字节不一致)——防的就是「板端
|
||||
写得出、PC 读不了」。反过来**不设**任何品质侧不变式:`avail` 同时带有效与削顶是合法事实
|
||||
(lead 裁定 2026-10-29),编码器如实透传、解码器不许据此判废。
|
||||
- **游标分次搬运**(256 B 缓冲够用,整段绝不驻留 RAM;数据 CRC 走增量中间值):
|
||||
`wave_store_pack_next()` 头那一步要求缓冲容量不小于整头 128,否则返 `WAVE_ST_E_PARAM` 且**不推进
|
||||
游标**;没有数据源却进数据区 → `WAVE_ST_E_STATE` 且**不产空字节**(产空字节等于写出「长度对、
|
||||
内容全 0」的段 = docs/子系统设计/故障录波设计说明.md §8.1 明令禁止的伪造证据)。
|
||||
- 段名严格 8.3(该移植 `FF_USE_LFN=0`):`wave_store_seg_path()` 当前布局恒返回 16 个字符(不含结束
|
||||
符),**调用方缓冲至少 17 字节**(`WAVE_ST_PATH_MAX`,原写 16 会让结束符写到缓冲外一字节);
|
||||
100 个号轮转,`wave_store_next_idx()` 回绕,`wave_store_idx_from_name()` 只认 basename 上的段名。
|
||||
- `wave_store_head_build()` / `wave_ring_read_ch()` 这类函数**成功时返回实际字节数 / 点数**(不是
|
||||
`WAVE_ST_OK`),负值才是错误码——写断言前先读头文件的「返 回 值」行(本层单测两次踩在这上面)。
|
||||
|
||||
## 4. 构建与测试
|
||||
|
||||
### 4.1 挂载与模拟器边界
|
||||
- 九个 `.c` 已登记在 `MDK-ARM/STM32F407-Demo.uvprojx`;`../App/algo` 已在 `<IncludePath>` 内。
|
||||
- 本层**不进** `simulator/CMakeLists.txt`(UI 模拟器不跑采集链路):九个文件全部登记在
|
||||
- 十一个 `.c` 已登记在 `MDK-ARM/STM32F407-Demo.uvprojx`;`../App/algo` 已在 `<IncludePath>` 内。
|
||||
- 模拟器侧分两类(Lead 裁定 2026-10-29):`wave_ring.c` 与 `wave_store.c` **真实参与**
|
||||
`simulator/CMakeLists.txt`——回放页要在模拟器里出真波形,两份又零平台依赖,与「双端共享核心、不造
|
||||
第二真相」同一条口径,故**不进** `SIM_EXEMPT`;其余九个采集链文件(UI 模拟器不跑采集链路)仍登记在
|
||||
`scripts/check_registration.py` 的 `SIM_EXEMPT`(附理由),`registration-check` 钩子反向校验(登记簿条目「rs485_task 白名单缺口」)。
|
||||
|
||||
### 4.2 主机侧纯逻辑单测——生产源文件以 `#include` 原样纳入
|
||||
@@ -143,10 +208,20 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
||||
| 频率测量 | `& '.venv\Scripts\python.exe' test/host/freq/run_unit_test.py` | `freq-unit-test` |
|
||||
| 通道标定(含真实 cfg_file 引擎集成) | `& '.venv\Scripts\python.exe' test/host/adc_cal/run_unit_test.py` | `adc-cal-unit-test` |
|
||||
| 遥测快照(布局钉死/双区无撕裂/品质汇总) | `& '.venv\Scripts\python.exe' test/host/meas/run_unit_test.py` | `meas-unit-test` |
|
||||
| 录波环(几何与点序 / 冻结与丢样 / 封存握手 / 读窗夹取 / 自弃与冷却) | `& '.venv\Scripts\python.exe' test/host/wave_ring/run_unit_test.py` | 无专属钩子:靠 `test-coverage` 总闸真跑 |
|
||||
| 录波段格式(尺寸等式 / CRC 锚点 / 编解码往返与拒绝清单 / 搬运游标 / 段名轮转) | `& '.venv\Scripts\python.exe' test/host/wave_store/run_unit_test.py` | 同上 |
|
||||
| 录波触发与全链路(三条来路 / 同拍多请求 / 已封存区不变 / 字节逐列对环) | `& '.venv\Scripts\python.exe' test/host/wave_trig/run_unit_test.py` | 同上 |
|
||||
|
||||
改本目录任一 `.c/.h`,提交时对应钩子自动跑。**用例清单与判据数字的属主**是各
|
||||
`test/host/<模块>/` 与 `docs/测试说明/` 下的单测说明,本文不复制(三处抄数字必然打架)。
|
||||
|
||||
录波三套**刻意不新增专属钩子**(Lead 裁定 2026-10-27:钩子口径冻结,新增套数由 `test-coverage`
|
||||
总闸全量真跑并守基线回退,失败照样拦,只是归因落在总闸输出上)。两套录波单测之间刻意留了
|
||||
一份**分工**:环那套管语义、段格式那套管字节、`wave_trig` 管「接起来之后的语义」(含把封存区
|
||||
逐列读出来与打包游标产出的字节逐字节对齐)。两套合成段落在临时目录并把绝对路径打进日志(日志里
|
||||
同时标明是哪一份形态),要跟 PC 侧读段脚本互验时把当次产物拷进 `ci-artifacts/wave_interop/` 再报
|
||||
路径——**别拿旧副本当本次产物**(踩过:拷过一次之后没刷,别人读到的是上一轮的字节)。
|
||||
|
||||
### 4.3 板端
|
||||
`@build` 0 错误 0 警告 → `@test` 看 `[ADC_TASK]` 首拍 / 心跳(`phasor(...)`、`freq(...)`、
|
||||
`cal: ua_mv=… vl=0x00 cl=0xE6 rail=…`、`ring` / `skip` / `adc1_*` / `tgap`);
|
||||
@@ -157,12 +232,24 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
||||
## 5. 新增一个算法模块的固定动作
|
||||
|
||||
1. 在本目录建 `xxx.c/h`,文件头写模块注释块(规范 V1.0),**尽量零平台依赖**以便免桩单测。
|
||||
需要 `NULL` 的 `.c` 自己 `#include` 一个 `stddef.h`:ARMCC 的 `stdint.h` 不提供 `NULL`,而主机侧
|
||||
MSVC / gcc 会传递性把它拉进来,**主机单测照不出这个分叉**(2026-10-29 台基线构建实测报 22 条
|
||||
identifier "NULL" 未定义,根因即此;与 `sample_ring.c` / `freq.c` 同口径,别塞进公共头)。
|
||||
2. 用 `new-source-file` skill 注册到 `MDK-ARM/STM32F407-Demo.uvprojx`(新建子目录还要补 `<IncludePath>`)。
|
||||
模拟器侧二选一:有消费路径 → 加 `simulator/CMakeLists.txt`;纯算法无消费路径 → 加
|
||||
`scripts/check_registration.py` 的 `SIM_EXEMPT`(照本目录既有七条写理由),否则 `registration-check` 拦。
|
||||
3. 加 `test/host/<模块>/` 主机单测 + `.pre-commit-config.yaml` 钩子(照 `phasor-unit-test` 抄)。
|
||||
3. 加 `test/host/<模块>/` 主机单测(照 `test/host/meas` 或 `test/host/soe_store` 抄目录形态与
|
||||
`run_unit_test.py`)。**要不要补 `.pre-commit-config.yaml` 专属钩子按 Lead 裁定 2026-10-27:默认不补**,
|
||||
由 `test-coverage` 总闸全量真跑;确有秒级归因需求再单独申请。用例标题写「它在验证什么」这类语义
|
||||
短语,**不要发字母紧跟数字的新编号**(`docs/编号规范.md` §4.1「已停发」栏:停发不等于可以新发)。
|
||||
4. 消费者写在 `App/task/`,本层只出"数";跨转换器禁令见 §3 开头四条红线。
|
||||
5. 在本 README §1 清单加一行,并在 `docs/算法/采集与算法层设计说明.md` 里写清该模块的契约与理由。
|
||||
6. **单测产出的互验字节(要被别人的脚本读的那些):跑测之后立刻刷新副本并记下 sha256**。三套录波单测把合成段落
|
||||
在临时目录、并把**绝对路径 + 是哪一份形态**打进日志,所以「跑完顺手拷一次、以后再没刷」必然留下旧副本——
|
||||
本仓这类坑不止一次(旧副本被当本次证据引用过:一次是别人拿上一轮的字节下了「字节级已闭合」的结论)。
|
||||
判据:引用方只认**当次运行日志里那条路径 + 当次 sha256**;产物有第二种形态(例如本波真值形态与「已标定后
|
||||
仍削顶」的构造形态)就在文件名或日志里把形态写明,别让读者靠猜;拷进 `ci-artifacts/wave_interop/` 之类的
|
||||
共享目录时,同一笔操作里就把新 sha 一起报给对岸,别隔一轮。
|
||||
|
||||
## 6. 相关文档
|
||||
|
||||
@@ -171,5 +258,8 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
||||
- 分层与功能项清单:`docs/算法/继电保护功能复现规划.md`
|
||||
- 傅氏算法与有效值口径:`docs/算法/傅氏算法详解.md`
|
||||
- 采样节拍(TIM3 1 kHz + TRGO 触发 ADC1 / DMA):`docs/子系统设计/ADC采样节拍设计说明.md`
|
||||
- **故障录波**(数据面 / 段格式 / 现场回放契约):docs/子系统设计/故障录波设计说明.md §6.2 是段文件
|
||||
字节布局的属主,同一篇的 docs/子系统设计/故障录波设计说明.md §6.3 是字段口径裁定——两节合起来是板端
|
||||
编码器与 PC 读段脚本两份独立实现的共同依据;卡片判据与顺序 → docs/任务规划/故障录波.md §2
|
||||
- 相量层 / ADC 输入层单测细则:`docs/测试说明/相量层单元测试说明.md`、`docs/测试说明/ADC输入层单元测试说明.md`
|
||||
- RAM 与栈台账:`docs/内存管理/RAM使用分析与优化方案.md`
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
* DTU_BENCH 旁路同时收成全仓唯一一处;此前该判据在
|
||||
* protect_task.c 与 ui_data_feed.c 各写一份,两处一旦
|
||||
* 漂移,屏上「未标定」与判据实际停走就不是同一件事
|
||||
* v1.3 2026-10-29 DTU 项目组 故障录波 task-2:新增 adc_cal_k_q16() int32 原值直读口
|
||||
* (段头 k 字段 LSB 精确,float 反推在 5.28e7 掉尾数,
|
||||
* 口径见 docs/子系统设计/故障录波设计说明.md §6.3)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -336,6 +339,25 @@ float adc_cal_k_uv(adc_semantic_t sem) {
|
||||
return ((float)k) / 65536.0f;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读通道当前生效系数的 int32 Q16 原值(adc_cal_k_uv 孪生,见 .h 声明)
|
||||
* 入口参数:sem - 语义通道 adc_semantic_t
|
||||
* 返 回 值:k[Q16];TEMP/越界返 0 int32_t
|
||||
* 限定条件:无(任意上下文,纯读)
|
||||
* 函数说明:与 adc_cal_k_uv() 共用 s_eff_kb 生效视图(越带回退 ROM 口径一致),
|
||||
* 差别只在不做浮点还原——录波段头按 LSB 精确记账必须走本口(§6.3)
|
||||
*/
|
||||
int32_t adc_cal_k_q16(adc_semantic_t sem) {
|
||||
int32_t k;
|
||||
int32_t b;
|
||||
|
||||
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
|
||||
return 0;
|
||||
}
|
||||
(void)s_eff_kb((unsigned)sem, &k, &b);
|
||||
return k;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读通道当前生效零点偏移(码值,浮点)——adc_cal_k_uv 孪生入口
|
||||
* 入口参数:sem - 语义通道 adc_semantic_t
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
* 三路 VALID 是否齐」这份判据停走条件从
|
||||
* protect_task.c 与 ui_data_feed.c 的两份重复实现收成本层
|
||||
* 单点(DTU_BENCH 旁路同时收成一处)
|
||||
* v1.3 2026-10-29 DTU 项目组 故障录波 task-2:新增 adc_cal_k_q16()——k 的 int32
|
||||
* Q16 原值直读口(adc_cal_k_uv 的孪生)。录波段头 k 字段
|
||||
* 是 LSB 精确记账,float 在 5.28e7 码字上掉尾数,
|
||||
* 禁止经浮点反推(docs/子系统设计/故障录波设计说明.md §6.3)
|
||||
*/
|
||||
|
||||
#ifndef ADC_CAL_H
|
||||
@@ -149,6 +153,18 @@ float adc_cal_k_uv(adc_semantic_t sem);
|
||||
*/
|
||||
float adc_cal_b_codes(adc_semantic_t sem);
|
||||
|
||||
/*
|
||||
* 函数功能:读通道当前生效系数的 **int32 Q16 原值**(µV/码 × 65536)——
|
||||
* adc_cal_k_uv() 的孪生入口,给「必须 LSB 精确」的消费者用
|
||||
* 入口参数:sem - 语义通道 adc_semantic_t
|
||||
* 返 回 值:k[Q16 原值](与折算同一生效视图:越带回退 ROM、b 不参与);TEMP/越界返 0 int32_t
|
||||
* 限定条件:无(任意上下文,纯读)
|
||||
* 函数说明:故障录波段头的 k 字段是 int32 LSB 精确记账(docs/子系统设计/故障录波设计说明.md
|
||||
* §6.3 裁定)——float 还原后在 5.28e7 码字上掉尾数,**禁止**拿
|
||||
* adc_cal_k_uv() 反乘 65536 回算落盘,故开本原值口。
|
||||
*/
|
||||
int32_t adc_cal_k_q16(adc_semantic_t sem);
|
||||
|
||||
/*
|
||||
* 函数功能:整定入口——写通道系数与零点,并置位 VALID(「数据可信」标定落盘与整定入口)
|
||||
* 入口参数:sem - 语义通道(电气 8 路) adc_semantic_t
|
||||
|
||||
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* 模块名称:录波环(故障录波 · 数据面纯逻辑层:滚动环 + 触发冻结状态机 + 已封存区读口)
|
||||
* 模块功能:见 App/algo/wave_ring.h 的文件头注释块——所有权矩阵、封存握手、跨任务安全、
|
||||
* 形态钉死与预算全部写在那里,本文件不重抄一遍,只在实现处补「为什么这么写」。
|
||||
* 本文件是纯逻辑:只 include App/algo/wave_ring.h(它带 wave_store.h),
|
||||
* 不 include HAL / CMSIS / RTOS 头、不调 FatFs、不建任何 RTOS 对象、不打日志。
|
||||
* 临界区也不需要:写者只有 adcTask 一个,读者只碰 SEALED 期间冻结的已封存区,
|
||||
* §5.2 第一条陷阱(无临界区的活缓冲窗口)在本层根本不成立,因为没有那种入口。
|
||||
* 适用平台:STM32F407ZGTx(Cortex-M4,小端);主机侧单测 test/host/wave_ring/
|
||||
* 与 test/host/wave_trig/
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29(故障录波 task-1 · 数据面纯逻辑层)
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建
|
||||
* v1.1 2026-10-29 DTU 项目组 触发挂号改单字节载荷编码(lead 评审:三段写在
|
||||
* adcTask 抢占下会把段头的触发源 / 元件号录成上一段的旧值,溯源字段作废)
|
||||
*/
|
||||
|
||||
#include "wave_ring.h"
|
||||
|
||||
/* ARMCC 的 stdint.h 不提供 NULL,而主机侧 gcc / MSVC 会传递性把它拉进来 ——
|
||||
* 这个差别主机单测照不出来(2026-10-29 台基线实跑 @build 才暴露:本层
|
||||
* wave_ring.c 报 22 条 identifier "NULL" 未定义)。与 App/algo/sample_ring.c、App/algo/freq.c
|
||||
* 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */
|
||||
#include <stddef.h>
|
||||
|
||||
/* 触发挂号的载荷编码(v1.1):挂号量必须**一次单字节存写**才原子,所以把载荷本身编进
|
||||
* 那一个字节 —— 0 = 无请求,非 0 = (触发源 << 位移) | 元件号。触发源取值 1 到 3 占高 3 位、
|
||||
* 元件号取 prot_elm 的 picked 位号(uint32 位图 → 0 到 31)占低 5 位,合起来 8 位够用。
|
||||
* 本层双端编译(板端 ARMCC 与模拟器),不能引 FreeRTOS / CMSIS 的临界区内在函数,
|
||||
* 所以「把三个字节压成一个字节」是唯一既原子又不依赖平台的写法。 */
|
||||
#define WAVE_RG_REQ_SHIFT (5u) /* 载荷里触发源的低位置 */
|
||||
#define WAVE_RG_REQ_ELM_MSK (0x1Fu) /* 载荷里元件号的位掩码(低 5 位) */
|
||||
#define WAVE_RG_REQ_ELM_MAX (32u) /* 元件号合法上界(不含),picked 位图只有 32 位 */
|
||||
|
||||
/* ===== 一、静态对象(全部文件作用域 static,禁堆分配;落位与字节数见头注第七节) ===== */
|
||||
|
||||
/* 数据区:按通道分列,8 列 × 320 点 × 2 字节 = 5120 字节(§7 预算的大头)。
|
||||
* 不许改成 float、不许按帧交错(§12:形态错一次 RAM 翻倍 + 跨步取数)。
|
||||
* 不放在 CCM:DMA 看不见它,且 .sct 里 RW_CCM 只收 heap_4.o,普通 .bss 天然落 SRAM1 */
|
||||
static int16_t s_ring[WAVE_RING_CH][WAVE_RING_PTS];
|
||||
|
||||
/* 已封存段的段头:latch 之前只有环侧字段有效,latch 之后才是完整段头。
|
||||
* 这是 defaultTask 与 uiTask 能看到的唯一「段头视图」——内部指针一律不外露 */
|
||||
static wave_seg_meta_t s_sealed;
|
||||
|
||||
/* 观测计数(§9 周期行的 seg= / ovf= / drop= / err= 全从这里取,落盘道不数第二份账) */
|
||||
static uint32_t s_seg_total; /* 成功 ACK 的段数 */
|
||||
static uint32_t s_ovf_cnt; /* 被忽略的触发数 */
|
||||
static uint32_t s_drop_cnt; /* SEALED 窗口丢弃的点数(单位 = 点,一次丢一拍 = 加 8) */
|
||||
static uint32_t s_err_cnt; /* 落盘失败 + 滞留超时自弃 */
|
||||
static uint32_t s_last_t0_ms; /* 最近一段段首毫秒 */
|
||||
static uint32_t s_last_seq; /* 最近一段段首帧序号 */
|
||||
|
||||
/* 状态机与索引 */
|
||||
static uint16_t s_head; /* 下一个写入点(滚动位置) */
|
||||
static uint16_t s_base; /* 已封存 / 正在录的段首在环内的下标(段内点 0) */
|
||||
static uint16_t s_post_left; /* 后触发还差多少点封段 */
|
||||
static uint16_t s_cool_left; /* 冷却剩余拍数 */
|
||||
static uint16_t s_seal_hold; /* SEALED 已滞留拍数 */
|
||||
static uint8_t s_state; /* wave_ring_state_t */
|
||||
/* 触发请求挂号:0 = 无请求;非 0 = (触发源 << WAVE_RG_REQ_SHIFT) | 元件号。
|
||||
* 刻意只用这一个字节承载「是谁要录波」:旧实现分三个字节写(先写挂号位、后写载荷),
|
||||
* adcTask 抢在两条指令之间就会把上一段的旧触发源 / 旧元件号抄进本段 —— 而这两个字段是
|
||||
* 这段记录唯一的溯源信息(现场靠它与 /log/start.log 对表),错一次事后无从发现。
|
||||
* 单字节存写在 Cortex-M4 上原子,写序不再有意义,所以不需要任何顺序假设或屏障。 */
|
||||
static uint8_t s_req;
|
||||
static uint8_t s_trig_src; /* 本段实际用的触发源(钉基址时从请求抄过来) */
|
||||
static uint8_t s_trig_elm; /* 本段实际用的元件序号 */
|
||||
static uint8_t s_meta_ready; /* 1 = 段头已 latch */
|
||||
static uint8_t s_seg_idx; /* 本次上电的轮转下标(跨上电接号由落盘道自持,见头注) */
|
||||
|
||||
/* 语义号 → 列号(本波恒等,但把这条恒等式收在一处:二期带 TEMP 时只改这张表,
|
||||
* 写死下标的调用点会静默错位,走本函数的不会) */
|
||||
static const int8_t s_col_of_sem[(uint8_t)WAVE_ST_CH_MAX] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, -1
|
||||
};
|
||||
|
||||
/* ===== 二、内部小工具 ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:环内下标 +n 后取模
|
||||
* 入口参数:a_idx 当前下标;a_step 步长(小于环长)
|
||||
* 返 回 值:取模后的下标 uint16_t
|
||||
* 限定条件:仅本文件;a_idx 与 a_step 均在环长量级,无溢出风险
|
||||
* 函数说明:环长 320 是 2 的幂吗?不是(320 = 64×5),所以老老实实取模,
|
||||
* 不用位与——写错一次就是「看起来能跑、偶尔串段」的那种 bug。
|
||||
*/
|
||||
static uint16_t wave_rg_wrap(uint16_t a_idx, uint16_t a_step) {
|
||||
uint16_t v = (uint16_t)(a_idx + a_step);
|
||||
|
||||
if (v >= (uint16_t)WAVE_RING_PTS) {
|
||||
v = (uint16_t)(v - (uint16_t)WAVE_RING_PTS);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:把已封存段的「环侧字段」初始化(封存瞬间调,标定字段一律清零待 latch)
|
||||
* 入口参数:a_now_ms 段首毫秒;a_frame_seq 段首帧序号
|
||||
* 返 回 值:无 void
|
||||
* 限定条件:仅本文件;只在钉基址那一拍调一次
|
||||
* 函数说明:先把 ch[] 全部清成「sem = 下标、其余 0」,是为了让**没有 latch** 的段
|
||||
* 不可能带着上一段的系数被写进文件:sealed() 在未 latch 时本就是 0,
|
||||
* 但清零让「万一有人绕过握手直接读 s_sealed」也读不出旧真值。
|
||||
*/
|
||||
static void wave_rg_seal_reset(uint32_t a_now_ms, uint32_t a_frame_seq) {
|
||||
uint8_t i;
|
||||
|
||||
s_sealed.fs_hz = (uint16_t)WAVE_ST_FS_DEF;
|
||||
s_sealed.pts_per_cycle = (uint8_t)WAVE_ST_PPC_DEF;
|
||||
s_sealed.ch_count = (uint8_t)WAVE_RING_CH;
|
||||
s_sealed.pts_per_ch = (uint16_t)WAVE_RING_PTS;
|
||||
s_sealed.pre_pts = (uint16_t)WAVE_RING_PRE;
|
||||
s_sealed.trig_src = s_trig_src;
|
||||
s_sealed.trig_elm = s_trig_elm;
|
||||
s_sealed.t0_ms = a_now_ms;
|
||||
s_sealed.wall = 0u;
|
||||
s_sealed.eng = (uint8_t)WAVE_ENG_PIN;
|
||||
s_sealed.flags_seq = 0u;
|
||||
s_sealed.clip_mask = 0u;
|
||||
s_sealed.frame_seq = a_frame_seq;
|
||||
for (i = 0u; i < (uint8_t)WAVE_ST_CH_MAX; i++) {
|
||||
s_sealed.ch[i].sem = i;
|
||||
s_sealed.ch[i].conv = 0u;
|
||||
s_sealed.ch[i].bits = 0u;
|
||||
s_sealed.ch[i].avail = 0u;
|
||||
s_sealed.ch[i].k_q16 = 0;
|
||||
s_sealed.ch[i].b_codes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 三、初始化与压环 ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:复位环与全部状态
|
||||
* 入口参数:无
|
||||
* 返 回 值:无 void
|
||||
* 限定条件:只能在 adcTask 调,且在开始压环之前(见头文件同函数注释)
|
||||
* 函数说明:数据区显式清零而不是依赖 .bss 清零:本函数也可能在运行中被重新调
|
||||
* (显式重配 / 单测复用),留旧数据就等于留下「上一段的尾巴混进下一段」的口子。
|
||||
*/
|
||||
void wave_ring_init(void) {
|
||||
uint8_t c;
|
||||
uint16_t i;
|
||||
|
||||
for (c = 0u; c < (uint8_t)WAVE_RING_CH; c++) {
|
||||
for (i = 0u; i < (uint16_t)WAVE_RING_PTS; i++) {
|
||||
s_ring[c][i] = 0;
|
||||
}
|
||||
}
|
||||
s_head = 0u;
|
||||
s_base = 0u;
|
||||
s_post_left = 0u;
|
||||
s_cool_left = 0u;
|
||||
s_seal_hold = 0u;
|
||||
s_state = (uint8_t)WAVE_RING_ST_IDLE;
|
||||
s_req = 0u;
|
||||
s_trig_src = (uint8_t)WAVE_TRIG_NONE;
|
||||
s_trig_elm = 0u;
|
||||
s_meta_ready = 0u;
|
||||
s_seg_idx = 0u;
|
||||
s_seg_total = 0u;
|
||||
s_ovf_cnt = 0u;
|
||||
s_drop_cnt = 0u;
|
||||
s_err_cnt = 0u;
|
||||
s_last_t0_ms = 0u;
|
||||
s_last_seq = 0u;
|
||||
wave_rg_seal_reset(0u, 0u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:压入一拍的 8 路原始码值
|
||||
* 入口参数:p_codes 语义序数组(前 8 项);a_n 必须等于 WAVE_RING_CH
|
||||
* 返 回 值:WAVE_ST_OK 已写入;WAVE_ST_DONE 本拍被丢弃(SEALED 窗口);WAVE_ST_E_PARAM 参数错 int32_t
|
||||
* 限定条件:只能在 adcTask 调,每拍一次
|
||||
* 函数说明:写的是「列优先」的 8 个分散地址(每列同一行)——这就是 §6.2 分列形态的代价,
|
||||
* 换来的是落盘与回放都能按列连续搬,不用跨步长取数。
|
||||
*/
|
||||
int32_t wave_ring_push(const int16_t *p_codes, uint8_t a_n) {
|
||||
uint8_t c;
|
||||
|
||||
if ((p_codes == NULL) || (a_n != (uint8_t)WAVE_RING_CH)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if (s_state == (uint8_t)WAVE_RING_ST_SEALED) {
|
||||
/* 冻结窗口丢样本(§4.2):计数单位是点,一次丢一整拍 = WAVE_RING_CH 点 */
|
||||
s_drop_cnt += (uint32_t)a_n;
|
||||
return WAVE_ST_DONE;
|
||||
}
|
||||
for (c = 0u; c < (uint8_t)WAVE_RING_CH; c++) {
|
||||
s_ring[c][s_head] = p_codes[c];
|
||||
}
|
||||
s_head = wave_rg_wrap(s_head, 1u);
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/* ===== 四、触发、推进、交棒、ACK ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:投递一次录波请求
|
||||
* 入口参数:a_src 触发源(1 手动 / 2 保护 / 3 bench);a_elm 元件序号(0 到 31)
|
||||
* 返 回 值:WAVE_ST_OK 受理;WAVE_ST_DONE 被忽略(ovf 已加一);WAVE_ST_E_PARAM 触发源
|
||||
* 非法或元件号越界 int32_t
|
||||
* 限定条件:任意上下文(**只写一个字节**,理由见 s_req 声明处)
|
||||
* 函数说明:这里判「非 IDLE 就拒」而不是「非 IDLE 就排队」——本波不做多段排队(已定裁定),
|
||||
* 而且排队会把「最近一次故障」降级成「上上次故障」。
|
||||
* s_req 已挂号时也拒:同一拍内多个元件同时 picked 只该录一段。
|
||||
* 两条入参校验排在状态判断之前:非法调用是调用方的错,不该记进 ovf 账 ——
|
||||
* ovf 的语义是「真来了第二次触发」。
|
||||
* 元件号上界写死在这里:今天没有调用方会越界(picked 是 uint32 位图),这条是给
|
||||
* 二期加元件的人留的牙 —— 载荷只有 5 位,越界必须当场拒,不许静默截断。
|
||||
*/
|
||||
int32_t wave_ring_request(uint8_t a_src, uint8_t a_elm) {
|
||||
if ((a_src < (uint8_t)WAVE_TRIG_MANUAL) || (a_src > (uint8_t)WAVE_TRIG_BENCH)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if (a_elm >= (uint8_t)WAVE_RG_REQ_ELM_MAX) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if ((s_state != (uint8_t)WAVE_RING_ST_IDLE) || (s_req != 0u)) {
|
||||
s_ovf_cnt++;
|
||||
return WAVE_ST_DONE;
|
||||
}
|
||||
/* 一次存写:触发源与元件号同字节落地。别的上下文要么看到「没请求」、要么看到
|
||||
* 「完整的这次请求」,不存在读到半份载荷的中间态 */
|
||||
s_req = (uint8_t)(((uint32_t)a_src << (uint32_t)WAVE_RG_REQ_SHIFT) | (uint32_t)a_elm);
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:推进一拍状态机
|
||||
* 入口参数:a_now_ms 当前相对上电毫秒;a_frame_seq 当前帧序号
|
||||
* 返 回 值:wave_ring_evt_t(本拍刚封段返回 WAVE_RING_EVT_SEALED) int32_t
|
||||
* 限定条件:只能在 adcTask 调,每拍在 wave_ring_push 之后一次
|
||||
* 函数说明:五个分支各管一件事,互不重叠:
|
||||
* IDLE —— 消费挂号、钉段首基址、转 FREEZE
|
||||
* FREEZE —— 后触发计点,计满转 SEALED
|
||||
* SEALED —— 只计滞留,超时自弃(绝不让环永久焊死)
|
||||
* COOL —— 递减冷却,到 0 回 IDLE
|
||||
* 段首基址 = 触发那一拍写入点的「往前 160 点」,于是段内点 0 是最老的预触发点、
|
||||
* 点 160 正好是触发后第一点;后触发的 160 点落在环的另一半(本来就即将被覆盖的
|
||||
* 最老那 160 槽),所以「冻结」在物理上是**停止滚动覆盖**,不是停止写入。
|
||||
*/
|
||||
int32_t wave_ring_tick(uint32_t a_now_ms, uint32_t a_frame_seq) {
|
||||
int32_t evt = (int32_t)WAVE_RING_EVT_NONE;
|
||||
|
||||
switch (s_state) {
|
||||
case (uint8_t)WAVE_RING_ST_IDLE:
|
||||
s_cool_left = 0u;
|
||||
s_seal_hold = 0u;
|
||||
{
|
||||
/* 只读一次快照再解码:分两次读就会把两次请求的字段拼成一段的身份 */
|
||||
uint8_t req = s_req;
|
||||
|
||||
if (req != 0u) {
|
||||
/* 钉基址:本拍的样本已写进 (head-1),段首 = head - 预触发点数 */
|
||||
s_trig_src = (uint8_t)((uint32_t)req >> (uint32_t)WAVE_RG_REQ_SHIFT);
|
||||
s_trig_elm = (uint8_t)((uint32_t)req & (uint32_t)WAVE_RG_REQ_ELM_MSK);
|
||||
s_req = 0u;
|
||||
s_base = wave_rg_wrap(s_head, (uint16_t)((uint32_t)WAVE_RING_PTS - (uint32_t)WAVE_RING_PRE));
|
||||
s_post_left = (uint16_t)WAVE_RING_POST;
|
||||
s_meta_ready = 0u;
|
||||
s_last_t0_ms = a_now_ms;
|
||||
s_last_seq = a_frame_seq;
|
||||
wave_rg_seal_reset(a_now_ms, a_frame_seq);
|
||||
s_state = (uint8_t)WAVE_RING_ST_FREEZE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case (uint8_t)WAVE_RING_ST_FREEZE:
|
||||
if (s_post_left > 0u) {
|
||||
s_post_left--;
|
||||
}
|
||||
if (s_post_left == 0u) {
|
||||
/* 封段:此刻起 s_head 正好回到 s_base,再写就覆盖段内点 0 —— 所以 push 开始丢 */
|
||||
s_state = (uint8_t)WAVE_RING_ST_SEALED;
|
||||
s_seal_hold = 0u;
|
||||
evt = (int32_t)WAVE_RING_EVT_SEALED;
|
||||
}
|
||||
break;
|
||||
|
||||
case (uint8_t)WAVE_RING_ST_SEALED:
|
||||
/* 兜底:defaultTask 挂住 / 卷没挂载 / 落盘道被更高优先级的事挡住,都不能让环
|
||||
* 永久冻结——下一次真故障无波可录是比丢一段更贵的代价(lead 裁定) */
|
||||
s_seal_hold++;
|
||||
if (s_seal_hold >= (uint16_t)WAVE_SEAL_TIMEOUT_MS) {
|
||||
s_err_cnt++;
|
||||
s_meta_ready = 0u;
|
||||
s_seal_hold = 0u;
|
||||
s_cool_left = (uint16_t)WAVE_RING_COOL_MS;
|
||||
s_state = (uint8_t)WAVE_RING_ST_COOL;
|
||||
}
|
||||
break;
|
||||
|
||||
case (uint8_t)WAVE_RING_ST_COOL:
|
||||
/* 冷却只在 ACK(或自弃)之后计时:它是二次抑制窗,不是冻结窗 */
|
||||
if (s_cool_left > 0u) {
|
||||
s_cool_left--;
|
||||
}
|
||||
if (s_cool_left == 0u) {
|
||||
s_state = (uint8_t)WAVE_RING_ST_IDLE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* 状态字节被写坏(跑飞 / 越界写)时回到 IDLE 而不是卡死:录波宁可多录也不能停 */
|
||||
s_state = (uint8_t)WAVE_RING_ST_IDLE;
|
||||
break;
|
||||
}
|
||||
return evt;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:封存交棒——把封存瞬间事实锁进段头
|
||||
* 入口参数:p_cal 封存瞬间事实(不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK 已锁存;WAVE_ST_E_STATE 不在待交棒态;WAVE_ST_E_PARAM 参数错 int32_t
|
||||
* 限定条件:只能在 adcTask 调,只在 EVT_SEALED 那一拍调一次
|
||||
* 函数说明:只 copy 封存瞬间的事实,不动环侧字段(点数 / 采样率 / 预触发这些
|
||||
* 在封段那刻已按 §6.2 定死)。重复调用直接拒,不覆盖已有锁存:
|
||||
* 半路换系数会让落盘道写进去的头与它已经在搬的数据不同源。
|
||||
*/
|
||||
int32_t wave_ring_latch_meta(const wave_seal_cal_t *p_cal) {
|
||||
uint8_t i;
|
||||
|
||||
if (p_cal == NULL) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if (p_cal->eng > (uint8_t)WAVE_ENG_PRI) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if ((s_state != (uint8_t)WAVE_RING_ST_SEALED) || (s_meta_ready != 0u)) {
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
s_sealed.eng = p_cal->eng;
|
||||
s_sealed.flags_seq = p_cal->flags_seq;
|
||||
s_sealed.clip_mask = p_cal->clip_mask;
|
||||
s_sealed.wall = p_cal->wall;
|
||||
for (i = 0u; i < s_sealed.ch_count; i++) {
|
||||
s_sealed.ch[i].conv = p_cal->ch[i].conv;
|
||||
s_sealed.ch[i].bits = p_cal->ch[i].bits;
|
||||
s_sealed.ch[i].avail = p_cal->ch[i].avail;
|
||||
s_sealed.ch[i].k_q16 = p_cal->ch[i].k_q16;
|
||||
s_sealed.ch[i].b_codes = p_cal->ch[i].b_codes;
|
||||
}
|
||||
s_meta_ready = 1u;
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:落盘 ACK
|
||||
* 入口参数:a_result 0 = 成功;非 0 = 失败(短写 / 打开失败 / CRC 自弃)
|
||||
* 返 回 值:WAVE_ST_OK 已解冻并起冷却;WAVE_ST_E_STATE 当前不在 SEALED int32_t
|
||||
* 限定条件:只能由 defaultTask 调,一段一次
|
||||
* 函数说明:无论成败都解冻 + 起冷却:错误只计数不冻结(环冻着的代价是下一次故障无波可录)。
|
||||
* 成功才推进轮转号与段计数(失败段留同号,下次重写覆盖,不留半截文件)。
|
||||
*/
|
||||
int32_t wave_ring_write_done(int32_t a_result) {
|
||||
if (s_state != (uint8_t)WAVE_RING_ST_SEALED) {
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
if (s_meta_ready == 0u) {
|
||||
/* 没 latch 就没有「段」可 ACK:这时候段头从未就绪,落盘道根本拿不到
|
||||
* wave_ring_sealed_meta(),绝不可能真写出文件。若在这里放过 ACK,
|
||||
* 就会白推进轮转号(号 +1 而盘上没那个文件 = 凭空吃掉一个号)并把
|
||||
* seg_total 报成一个没发生过的数(串口周期行的 seg=NN 从此对不上账)。
|
||||
* 拒绝而不是记账,把「没交棒就 ACK」变成调用方看得见的错误。
|
||||
* 本段的出口只有一个:滞留到 WAVE_SEAL_TIMEOUT_MS 自弃(那一路照实计 err)。 */
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
if (a_result == 0) {
|
||||
s_seg_idx = wave_store_next_idx(s_seg_idx);
|
||||
s_seg_total++;
|
||||
} else {
|
||||
s_err_cnt++;
|
||||
}
|
||||
s_meta_ready = 0u;
|
||||
s_seal_hold = 0u;
|
||||
s_cool_left = (uint16_t)WAVE_RING_COOL_MS;
|
||||
s_state = (uint8_t)WAVE_RING_ST_COOL;
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/* ===== 五、状态查询与已封存区读口 ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:查当前状态
|
||||
* 入口参数:无
|
||||
* 返 回 值:wave_ring_state_t 取值 uint8_t
|
||||
* 限定条件:任意上下文(只读单字节)
|
||||
*/
|
||||
uint8_t wave_ring_state(void) {
|
||||
return s_state;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查「有已封存段可读」——跨任务唯一握手量
|
||||
* 入口参数:无
|
||||
* 返 回 值:1 = SEALED 且段头已 latch;0 = 不可用 uint8_t
|
||||
* 限定条件:任意上下文(只读单字节)
|
||||
*/
|
||||
uint8_t wave_ring_sealed(void) {
|
||||
if ((s_state == (uint8_t)WAVE_RING_ST_SEALED) && (s_meta_ready != 0u)) {
|
||||
return 1u;
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取已封存段的段头快照
|
||||
* 入口参数:p_out 输出(整份拷出)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_STATE 无就绪段 int32_t
|
||||
* 限定条件:任意上下文(defaultTask / uiTask 只读)
|
||||
*/
|
||||
int32_t wave_ring_sealed_meta(wave_seg_meta_t *p_out) {
|
||||
uint16_t i;
|
||||
const uint8_t *p_src;
|
||||
uint8_t *p_dst;
|
||||
|
||||
if (p_out == NULL) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if (wave_ring_sealed() == 0u) {
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
/* 逐字节拷出而不是 *p_out = s_sealed:结构体赋值在 ARMCC 上会调 __aeabi_memcpy,
|
||||
* 这里没必要为一个赋值多拉一层依赖,量也就 136 字节 */
|
||||
p_src = (const uint8_t *)&s_sealed;
|
||||
p_dst = (uint8_t *)p_out;
|
||||
for (i = 0u; i < (uint16_t)sizeof(wave_seg_meta_t); i++) {
|
||||
p_dst[i] = p_src[i];
|
||||
}
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按通道读已封存区的连续点窗
|
||||
* 入口参数:a_ch 列号;a_from 段内起点;a_cnt 期望点数(超出段尾自动夹);p_dst 输出
|
||||
* 返 回 值:实际点数;WAVE_ST_E_PARAM / WAVE_ST_E_RANGE / WAVE_ST_E_STATE int32_t
|
||||
* 限定条件:任意上下文,只读已封存区(本层没有任何读活缓冲的入口)
|
||||
* 函数说明:按 s_base 取模展开,读者看到的是 320 点的线性数组。
|
||||
*/
|
||||
int32_t wave_ring_read_ch(uint8_t a_ch, uint16_t a_from, uint16_t a_cnt, int16_t *p_dst) {
|
||||
uint16_t left;
|
||||
uint16_t n;
|
||||
uint16_t idx;
|
||||
uint16_t i;
|
||||
|
||||
if ((p_dst == NULL) || (a_cnt == 0u)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if ((a_ch >= (uint8_t)WAVE_RING_CH) || (a_from >= (uint16_t)WAVE_RING_PTS)) {
|
||||
return WAVE_ST_E_RANGE;
|
||||
}
|
||||
if (wave_ring_sealed() == 0u) {
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
left = (uint16_t)((uint16_t)WAVE_RING_PTS - a_from);
|
||||
n = (a_cnt < left) ? a_cnt : left;
|
||||
idx = wave_rg_wrap(s_base, a_from);
|
||||
for (i = 0u; i < n; i++) {
|
||||
p_dst[i] = s_ring[a_ch][idx];
|
||||
idx = wave_rg_wrap(idx, 1u);
|
||||
}
|
||||
return (int32_t)n;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读口的 wave_reader_fn 形态适配(落盘道直接把它交给打包游标)
|
||||
* 入口参数:p_user 未用;其余同 wave_ring_read_ch(点数与起点是 uint32 形态)
|
||||
* 返 回 值:实际点数;负 = WAVE_ST_E_* int32_t
|
||||
* 限定条件:任意上下文,只读已封存区
|
||||
* 函数说明:32 位入参先夹到 16 位形态再转调,越界一律报错不夹成 0 ——
|
||||
* 夹成 0 会让落盘道以为「这一列读完了」而写出一个短段。
|
||||
*/
|
||||
int32_t wave_ring_read_cb(void *p_user, uint8_t a_ch, uint32_t a_from,
|
||||
uint32_t a_cnt, int16_t *p_dst) {
|
||||
(void)p_user;
|
||||
|
||||
if (a_cnt > (uint32_t)WAVE_RING_PTS) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if (a_from > (uint32_t)WAVE_RING_PTS) {
|
||||
return WAVE_ST_E_RANGE;
|
||||
}
|
||||
return wave_ring_read_ch(a_ch, (uint16_t)a_from, (uint16_t)a_cnt, p_dst);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取观测计数快照
|
||||
* 入口参数:p_out 输出(不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int32_t
|
||||
* 限定条件:任意上下文(只读)
|
||||
*/
|
||||
int32_t wave_ring_get_stat(wave_ring_stat_t *p_out) {
|
||||
if (p_out == NULL) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
p_out->state = s_state;
|
||||
p_out->trig_src = s_trig_src;
|
||||
p_out->trig_elm = s_trig_elm;
|
||||
p_out->meta_ready = s_meta_ready;
|
||||
p_out->seg_idx = s_seg_idx;
|
||||
/* 对外字段是 8 位:本波后触发最多 160 点,不会溢出;真溢出说明环长被改过,
|
||||
* 饱和在 255 让它在日志里显眼,而不是绕回 0 假装快封段了 */
|
||||
p_out->post_left = (s_post_left > 255u) ? 255u : (uint8_t)s_post_left;
|
||||
p_out->rsv1 = 0u;
|
||||
p_out->rsv2 = 0u;
|
||||
p_out->cool_left = s_cool_left;
|
||||
p_out->seal_hold = s_seal_hold;
|
||||
p_out->seg_total = s_seg_total;
|
||||
p_out->ovf_cnt = s_ovf_cnt;
|
||||
p_out->drop_cnt = s_drop_cnt;
|
||||
p_out->err_cnt = s_err_cnt;
|
||||
p_out->last_t0_ms = s_last_t0_ms;
|
||||
p_out->last_seq = s_last_seq;
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:语义通道号 → 本环列号
|
||||
* 入口参数:a_sem 语义号
|
||||
* 返 回 值:列号;WAVE_ST_E_RANGE = 该语义不录 int32_t
|
||||
* 限定条件:任意上下文(纯计算)
|
||||
*/
|
||||
int32_t wave_ring_col(uint8_t a_sem) {
|
||||
if (a_sem >= (uint8_t)WAVE_ST_CH_MAX) {
|
||||
return WAVE_ST_E_RANGE;
|
||||
}
|
||||
if (s_col_of_sem[a_sem] < 0) {
|
||||
return WAVE_ST_E_RANGE;
|
||||
}
|
||||
return (int32_t)s_col_of_sem[a_sem];
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
/*
|
||||
* 模块名称:录波环形缓冲层(故障录波 · 8 路 × 320 点原始码值环 + 触发冻结状态机)
|
||||
* 模块功能:按拍把 8 路电气量的原始码值压进环形缓冲,在「预触发 + 后触发」窗口计满后
|
||||
* 把这一段冻结成只读的已封存区,等落盘道取走并明确 ACK 之后才解冻。口径属主 =
|
||||
* docs/子系统设计/故障录波设计说明.md §4.2(段长与冻结语义)、§5.1(任务所有权)、
|
||||
* §5.2(三条陷阱)、§6.2 与 §6.3(段格式;本层的已封存区就是数据区的字节形态)、
|
||||
* §7(预算与落位)、§12(风险表)。本文件是它的机器可执行形态;冲突以设计文档
|
||||
* 为准并回报 lead,不在代码里单方面「修正」。
|
||||
* 纯逻辑层纪律:不 include HAL / CMSIS / RTOS 头、不建任务、不打日志、不碰文件系统,
|
||||
* 也不链 App/algo/adc_cal —— 折算档位、有效性位图、削顶位图、epoch 秒与每路
|
||||
* k_q16 / b_codes 由 adcTask 在「刚封存那一拍」作为参数交棒(见 wave_ring_latch_meta
|
||||
* 与下面的「封存握手顺序」),本层只负责把它们随段头一起锁存。因此本层可被
|
||||
* 主机侧单测 test/host/wave_ring/ 与 test/host/wave_trig/ 原样纳入断言、零桩。
|
||||
* 与 sample_ring 的关系(为什么另起一个环):App/algo/sample_ring.c 是相量与保护的
|
||||
* 上游、float 形态、深度 256,且它的 sample_ring_window() 没有临界区
|
||||
* (§5.2 第一条陷阱)。录波要的是 int16 原始码值、320 点、跨任务只读,复用那个环
|
||||
* 要么改它的形态与深度(动相量链),要么在跨任务读上继续裸奔 —— 两条都比另起
|
||||
* 一个环贵,故本波解耦(§2.1 第 3 条结构约束)。
|
||||
*
|
||||
* 形态钉死(§7 预算 + §12 风险表,动这里之前先读):
|
||||
* - 元素类型 int16,不许 float:float 让环从 5120 字节变 10240 字节,直接顶穿 §7 的
|
||||
* 约 5.9 KB 静态增量目标;
|
||||
* - 按通道分列(一路连续 320 点),不按帧交错:回放页只画一路、PC 重算全周傅氏也逐路
|
||||
* 独立,交错布局两种用法都要跨步长取数(§6.2 末段「为什么按通道分列」);
|
||||
* - 通道数固定 8,TEMP 不录:温度是片内 ADC 的 12 位单极性原始值、非线性且未参与线性
|
||||
* 标定(docs/任务规划/数据可信.md §7 已定案),进录波只多占 640 字节,并让回放页多一
|
||||
* 格会被误读成电气量的通道;带 TEMP 属二期,且必须连 flags_seq 与 clip_mask 一起升
|
||||
* 版本(§6.2 那两行的括注);
|
||||
* - 落位 SRAM1:本层全部是普通文件级静态,链接脚本 MDK-ARM/STM32F407-Demo/
|
||||
* STM32F407-Demo.sct 把 RW_IRAM1 的 .ANY (+RW +ZI) 收在 0x20000000,CCM 只收
|
||||
* heap_4.o 与 .noinit → 不加任何段属性就落在 SRAM1。CCM 只剩 7904 字节没有余量,
|
||||
* 而本环不是 DMA 可见对象,也没有非进 CCM 不可的理由。
|
||||
*
|
||||
* 封存握手顺序(唯一合法次序,写死在这里,接线道与后续改动都必须照此):
|
||||
* 1) adcTask 每拍 wave_ring_push() 然后 wave_ring_tick();
|
||||
* 2) tick 计满后触发点返回 WAVE_RING_EVT_SEALED —— 此刻状态进 WAVE_RING_ST_SEALED 但
|
||||
* **尚未就绪**,wave_ring_sealed() 仍返 0;
|
||||
* 3) adcTask 在**同一拍**现取封存瞬间事实(adc_cal_k_q16 / adc_cal_flags / sys_clock /
|
||||
* 削顶位图)并调 wave_ring_latch_meta() 一次 —— 只在第 2 步那一拍取一次,
|
||||
* 既不每拍读 adc_cal(1 kHz 下 8 路白读),也不低频刷新(低频刷新等于快照可能
|
||||
* 来自标定之前的旧值,正是 §12「标定落地后老文件被解释错」的入口);
|
||||
* 4) 此后 wave_ring_sealed() 才返 1,defaultTask 才准开文件落盘;meta 未就绪时
|
||||
* defaultTask 必须把本段当「不可用」处理并计 err,**不许拿半份快照落盘**;
|
||||
* 5) defaultTask 搬完(成功或失败都算搬完)调 wave_ring_write_done(result) 明确 ACK;
|
||||
* ACK 之前冻结一直有效、新触发一律只加 ovf;ACK 之后才起冷却抑制窗。
|
||||
* 为什么必须用状态而不是用时间来挡(lead 裁定 2026-10-29):若解冻靠计时,会出现
|
||||
* 「冷却走完、adcTask 重新推进写指针,而 defaultTask 还在读那份已封存区」→
|
||||
* 半新半旧的段落进文件且没有任何错误码 —— §5.2 第一条陷阱的翻版。
|
||||
* 兜底:WAVE_RING_ST_SEALED 滞留超过 WAVE_SEAL_TIMEOUT_MS(写者没来取:卷未挂载、
|
||||
* 落盘链卡死都算)→ 自弃本段、err 计数加一、走冷却解冻。**绝不许让录波把环永久焊死**,
|
||||
* 那会让下一次真故障无波可录。
|
||||
*
|
||||
* 跨任务安全(§5.1 与 §5.2 第一条;本层的唯一并发契约):
|
||||
* 写者只有 adcTask,读者是 defaultTask 与 uiTask,因此只暴露「已封存」标志与已封存区
|
||||
* 读口,不给任何跨任务读活缓冲的路径:
|
||||
* 只能在 adcTask 调:wave_ring_init、wave_ring_push、wave_ring_tick、
|
||||
* wave_ring_latch_meta;
|
||||
* 只能由文件系统唯一写者 defaultTask 调:wave_ring_write_done;
|
||||
* 任意上下文可调:wave_ring_request(只投一个字节,protectTask / uiTask /
|
||||
* bench 都走它)、wave_ring_state、wave_ring_sealed、
|
||||
* wave_ring_sealed_meta、wave_ring_read_ch、wave_ring_read_cb、
|
||||
* wave_ring_get_stat、wave_ring_col。
|
||||
* 为什么这样就够、不需要临界区:单字节状态量与请求量在 Cortex-M4 上单字读写天然原子;
|
||||
* sealed 为 1 期间已封存区对 adcTask 是只读不写(写指针被冻在窗口之外),读者看到的数据
|
||||
* 不会在脚下变;请求走「单字投递 + 下一拍由 adcTask 自己消费」,与 App/algo/prot_elm.c
|
||||
* 的 prot_elm_request_reset() 同一模式。
|
||||
* 「单字」是硬要求不是措辞(lead 评审 2026-10-29):投递方是 protectTask(Normal)与
|
||||
* uiTask(BelowNormal),消费者是 adcTask(AboveNormal,会**抢占**它们)。旧实现先写挂号位
|
||||
* 再写触发源与元件号(三个字节、两条指令的窗口),adcTask 抢在中间就会把上一段的旧触发源
|
||||
* 与旧元件号抄进本段 —— 波形没错,错的恰好是这段记录唯一的溯源字段(现场靠它与
|
||||
* /log/start.log 对表),一次故障只录一段、错过事后无从发现。现在载荷本身编进那一个字节
|
||||
* (0 = 无请求,非 0 = 触发源占高位 | 元件号占低 5 位),一次存写要么看不见、要么看见完整
|
||||
* 一份,不需要顺序假设,也不需要任何平台相关的屏障或临界区(本层双端编译,禁止引
|
||||
* FreeRTOS / CMSIS 内在函数)。
|
||||
*
|
||||
* 观测计数的已知容差(**不修、只入档**,lead 裁定 2026-10-29):ovf 与 err 两个计数是跨上下文
|
||||
* 的读-改-自增(ovf 由任意上下文的拒绝路径加、err 由 adcTask 的超时自弃与 defaultTask 的落盘
|
||||
* 失败分别加),本层不设临界区,所以在并发拒绝 / 并发失败时理论上可能少计一次。定性成
|
||||
* **观测计数的已知容差**:它们只用于趋势与判读,不作为任何判据的唯一依据 —— 要坐实「冷却期
|
||||
* 内确实来了第二次触发」,同时看 armed 位与 /log/start.log 的时刻,别只盯 ovf 数字。
|
||||
*
|
||||
* 触发与冻结语义(§4.2,严格按此实现,别「顺手优化」):
|
||||
* IDLE →(触发)FREEZE:段首基址当拍钉死(预触发区从此不再被覆盖,后触发区继续写入),
|
||||
* 后触发计满 160 点 → SEALED:整段只读,新样本一律丢弃并计 drop,等 meta latch 与落盘
|
||||
* ACK → (ACK)COOL:恢复正常滚动写入,但本窗口内新触发只加 ovf 不重录 → IDLE。
|
||||
* 冻结期间丢的是录波样本:sample_ring、相量、保护照常跑,本层不碰它们、也无权影响它们。
|
||||
*
|
||||
* 适用平台:STM32F407ZGTx(Cortex-M4,小端);主机侧单测 test/host/wave_ring/、
|
||||
* test/host/wave_trig/
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29(故障录波 task-1 · 数据面纯逻辑层)
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建:8 路 × 320 点 int16 分列环、请求/冻结/封段/ACK/
|
||||
* 冷却状态机(含 SEALED 滞留超时自弃)、段头锁存与
|
||||
* 已封存区按通道读口
|
||||
*/
|
||||
|
||||
#ifndef WAVE_RING_H
|
||||
#define WAVE_RING_H
|
||||
|
||||
/* 本头对外只暴露 stdint 的类型;文件里出现的 NULL 全在注释内(「不可为 NULL」这类
|
||||
* 约束说明),所以这里刻意不 include stddef.h —— 需要 NULL 的是包含本头的 .c,
|
||||
* 它们自己 include(台基线实测:ARMCC 的 stdint.h 不带 NULL)。 */
|
||||
#include <stdint.h>
|
||||
#include "wave_store.h" /* 同层配对头:wave_seg_meta_t / wave_ch_desc_t / wave_reader_fn /
|
||||
WAVE_ST_* 返回码(本层复用同一套 int 返回码通道,不另发明) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ===== 一、环几何(数值属主 = docs/子系统设计/故障录波设计说明.md §4.2 与 §7) ===== */
|
||||
|
||||
/* 每通道点数 = 预触发 8 周波 + 后触发 8 周波 = 160 + 160 = 320 点(1 kHz 下 320 ms) */
|
||||
#define WAVE_RING_PTS (320u)
|
||||
/* 预触发点数:触发点之前保留 8 周波;回放画触发光标的位点就是这个数(也是段内触发点下标) */
|
||||
#define WAVE_RING_PRE (160u)
|
||||
/* 后触发点数:触发点之后采满 8 周波才封段 */
|
||||
#define WAVE_RING_POST (160u)
|
||||
/* 通道数 = 8(TEMP 不录,理由见本文件头注);必须与 WAVE_ST_CH_NUM 相等,单测里钉住 */
|
||||
#define WAVE_RING_CH (8u)
|
||||
/* 冷却抑制窗:ACK 之后这么多拍内新触发只加 ovf 不重录(§4.2「只记第一次」)。
|
||||
* 500 拍 = 500 ms @1 kHz。注意:「覆盖一轮落盘窗口」这件事由 ACK 保证,本数值只是
|
||||
* ACK 之后的二次抑制(防同一次故障的抖动重复触发),待台架核。 */
|
||||
#define WAVE_RING_COOL_MS (500u)
|
||||
/* 已封存滞留上限:SEALED 状态超过这么多拍仍没人来取(卷未挂载、落盘链卡死都算)
|
||||
* → 自弃本段 + err 计数加一 + 走冷却解冻。2000 拍 = 2 s @1 kHz,待台架核。
|
||||
* 判据是 tick 的推进次数(也就是 adcTask 的存活),不是墙上时间:adcTask 自己挂了
|
||||
* 就轮到看门狗处理,本层不越权。 */
|
||||
#define WAVE_SEAL_TIMEOUT_MS (2000u)
|
||||
|
||||
/* 环数据区字节数:int16 × 320 × 8 = 5120(§7 预算里最大的一项;动它等于动预算) */
|
||||
#define WAVE_RING_BYTES (WAVE_RING_PTS * WAVE_RING_CH * (uint32_t)sizeof(int16_t))
|
||||
|
||||
/* ===== 二、类型 ===== */
|
||||
|
||||
/* wave_ring_tick 的事件:返回值语义是「本拍发生了什么」,不是错误 */
|
||||
typedef enum {
|
||||
WAVE_RING_EVT_NONE = 0, /* 正常推进,无事件 */
|
||||
WAVE_RING_EVT_SEALED = 1 /* 本拍刚计满后触发:**adcTask 必须在本拍调
|
||||
* wave_ring_latch_meta() 交棒封存瞬间事实**;
|
||||
* 不调则本段永不就绪,最终由 WAVE_SEAL_TIMEOUT_MS
|
||||
* 自弃(err 加一),可从串口周期行看到 */
|
||||
} wave_ring_evt_t;
|
||||
|
||||
/* 环状态。迁移只发生在 wave_ring_tick 与 wave_ring_write_done 里(单写者) */
|
||||
typedef enum {
|
||||
WAVE_RING_ST_IDLE = 0, /* 滚动:新样本覆盖最旧点,接受触发 */
|
||||
WAVE_RING_ST_FREEZE = 1, /* 已触发:段首基址已钉死(预触发区不再被覆盖),
|
||||
后触发区继续写入,直到计满 WAVE_RING_POST 点 */
|
||||
WAVE_RING_ST_SEALED = 2, /* 已封存:整段只读,新样本一律丢弃并计 drop;
|
||||
等 meta latch、等 defaultTask 落盘并 ACK */
|
||||
WAVE_RING_ST_COOL = 3 /* 冷却:已 ACK 解冻、恢复正常滚动,但新触发只加 ovf */
|
||||
} wave_ring_state_t;
|
||||
|
||||
/* 封存瞬间必须由 adcTask 交棒的事实(本层不链 App/algo/adc_cal,全靠这份入参)。
|
||||
* 段长 / 采样率 / 每周波点数 / 预触发点数 / 触发源与序号 / t0_ms / frame_seq 由本层自己填
|
||||
* (环就是这些数的物理实现,让调用方填只会多一个「忘了填」的失败面)。 */
|
||||
typedef struct {
|
||||
uint8_t eng; /* 折算档位(wave_eng_t:0 引脚域 / 1 二次值 / 2 一次值) */
|
||||
uint8_t flags_seq; /* bit s = 语义通道 s 的 ADC_CAL_F_VALID(§6.3) */
|
||||
uint16_t clip_mask; /* bit s = 语义通道 s 的 ADC_CAL_F_CLIPPED(V0.1 恒 0xE6) */
|
||||
uint32_t wall; /* 对时后的 epoch 秒(未对时 0;未对时就写 0,不许拿上电毫秒冒充) */
|
||||
wave_ch_desc_t ch[WAVE_ST_CH_MAX]; /* 通道表前 8 项的 avail / k_q16 / b_codes:
|
||||
必须取 adc_cal_k_q16() 的 int32 原值,
|
||||
不许经 adc_cal_k_uv() 的 float 回算(§6.3:
|
||||
float 在 5.28e7 量级掉尾数);
|
||||
conv 与 bits 也在这里给(本层无从知道两片
|
||||
转换器的归属);sem 由本层按「表内语义号 == 下标」
|
||||
自己钉,传进来的 sem 被忽略 */
|
||||
} wave_seal_cal_t;
|
||||
|
||||
/* 观测快照(给 §9 那条周期行 wave: 与主机单测;字段序 ≈ 日志序,别随手换) */
|
||||
typedef struct {
|
||||
uint8_t state; /* wave_ring_state_t */
|
||||
uint8_t trig_src; /* 当前/最近一段的触发源(wave_trig_src_t;未触发为 0) */
|
||||
uint8_t trig_elm; /* 触发元件序号(prot_elm 实例下标;手动与 bench 为 0) */
|
||||
uint8_t meta_ready; /* 1 = 段头已 latch 可落盘;SEALED 期间为 0 就是「本段不可用」 */
|
||||
uint8_t seg_idx; /* 下一个要写的段号 0..99(轮转号由本层持有,defaultTask 照它取名) */
|
||||
uint8_t post_left; /* 后触发还差多少点封段(FREEZE 期间有意义;>255 时饱和在 255) */
|
||||
uint8_t rsv1; /* 对齐补齐 */
|
||||
uint8_t rsv2; /* 对齐补齐 */
|
||||
uint16_t cool_left; /* 冷却剩余拍数 */
|
||||
uint16_t seal_hold; /* 本段已在 SEALED 滞留多少拍(与 WAVE_SEAL_TIMEOUT_MS 对照) */
|
||||
uint32_t seg_total; /* 累计成功 ACK 的段数(给日志 seg=NN 与对账) */
|
||||
uint32_t ovf_cnt; /* 被丢弃的触发数:ACK 前重复触发 + 冷却期内触发(§9 ovf=) */
|
||||
uint32_t drop_cnt; /* 冻结窗口丢弃的录波点数,单位 = 点不是拍(§9 drop=) */
|
||||
uint32_t err_cnt; /* 落盘失败(write_done 非 0,含短写与 CRC 自弃)
|
||||
+ SEALED 滞留超时自弃 的次数(§9 err=) */
|
||||
uint32_t last_t0_ms; /* 最近一段段首相对上电毫秒 */
|
||||
uint32_t last_seq; /* 最近一段段首帧序号(与串口周期行的 frames 对账) */
|
||||
} wave_ring_stat_t;
|
||||
|
||||
/* ===== 三、生命周期(写者侧) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:复位环与全部状态(清 5120 字节数据区 + 索引 + 状态机 + 观测计数 + 轮转号)
|
||||
* 入口参数:无
|
||||
* 返 回 值:无 void
|
||||
* 限定条件:**只能在 adcTask 调**,且只能在开始压环之前调(启动期或显式重配)。
|
||||
* 内核启动前调用也安全:本函数不调任何 RTOS API、不建对象,纯数组清零
|
||||
* (平台约束「osKernelStart 之前禁止创建类 API」对本层不适用)
|
||||
* 函数说明:观测计数(seg_total / ovf_cnt / drop_cnt / err_cnt)与轮转号一并归零:
|
||||
* 重启录波就是重新起账,不留旧账,免得串口周期行的累计数与段文件对不上。
|
||||
* 轮转号归零意味着**本层的 seg_idx 只是本次上电的本地计数**(供日志与首轮参考),
|
||||
* 不是盘上真值:跨上电的权威下标必须由落盘道自持——启动扫目录用
|
||||
* wave_store_idx_from_name 重建、每次写成功后用 wave_store_next_idx 推进。
|
||||
* 若照本层归零后的 0 直接取名,重启后第一笔覆盖的就是上次上电的**最新**段,
|
||||
* 与轮转「新证据优先」的初衷正相反(§6.2 轮转语义)。
|
||||
*/
|
||||
void wave_ring_init(void);
|
||||
|
||||
/*
|
||||
* 函数功能:压入一拍的 8 路原始码值(数据面的唯一写入路径)
|
||||
* 入口参数:p_codes 8 路码值数组,顺序 = adc_semantic_t 的前 8 项
|
||||
* (UA UB UC UN IA IB IC IN,即语义号升序,与段文件通道表列序
|
||||
* 同一口径,§6.3)
|
||||
* a_n 数组长度,必须等于 WAVE_RING_CH
|
||||
* 返 回 值:WAVE_ST_OK = 已写入;WAVE_ST_DONE = 本拍样本被丢弃(SEALED 窗口,drop 已加一);
|
||||
* WAVE_ST_E_PARAM = 空指针或长度不对 int32_t
|
||||
* 限定条件:**只能在 adcTask 调**,每拍一次(1 kHz);不得从其它任务或 ISR 调。
|
||||
* 尤其禁止图省事挂在 Src/freertos.c 的 TIM3 回调里(adc_tick_irq_notify()
|
||||
* 旁边):那是 ISR 上下文,且 ISR 比调度器早约 100 ms 启动
|
||||
* (docs/子系统设计/看门狗设计说明.md §10.1),本层没有「消费者就绪」门,
|
||||
* 挂上去就是拿 ISR 改状态机。
|
||||
* 函数说明:IDLE、FREEZE、COOL 三态都写入;SEALED 一律丢弃并计数 —— 这就是 §4.2
|
||||
* 「冻结期间丢的是录波样本」,不是丢采样链路:sample_ring、相量、保护照常被
|
||||
* adcTask 喂,本层无权也不该影响它们。
|
||||
* 码值是转换器原始输出:TPAFE 为 16 位补码双极性、片内 ADC 为 0..4095 原样
|
||||
* 正数,**不做中点偏置、不做折算**(§6.3「样本编码」行:加偏置等于伪造双极性)。
|
||||
*/
|
||||
int32_t wave_ring_push(const int16_t *p_codes, uint8_t a_n);
|
||||
|
||||
/* ===== 四、触发请求、每拍推进、封存交棒、落盘 ACK ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:投递一次录波请求(单字投递,不碰环、不碰索引)
|
||||
* 入口参数:a_src 触发源(wave_trig_src_t 的 1 / 2 / 3;0 与其它值按参数错拒绝)
|
||||
* a_elm 触发元件序号(prot_elm 的 picked 位号,**必须小于 32**;
|
||||
* 手动与 bench 定时传 0。越界当场拒,不许静默截断 —— 载荷只有 5 位)
|
||||
* 返 回 值:WAVE_ST_OK = 已受理(下一拍由 adcTask 自己钉段首基址);
|
||||
* WAVE_ST_DONE = 被忽略(处于 FREEZE 或 SEALED 或 COOL,或请求还没被消费;
|
||||
* ovf 计数已加一,§9 的 ovf=);
|
||||
* WAVE_ST_E_PARAM = 触发源非法或元件号越界(参数错**不记 ovf 账**) int32_t
|
||||
* 限定条件:**任意上下文可调**(protectTask 的 picked 上升沿、uiTask 的手动确认、
|
||||
* bench 定周期、adcTask 自己),它只写**一个字节**(载荷编码见文件头
|
||||
* 「跨任务安全」段),与 App/algo/prot_elm.c 的 prot_elm_request_reset()
|
||||
* 同一模式(§5.1)
|
||||
* 函数说明:本函数不产生「立刻冻结」的效果 —— 段首基址由 adcTask 在下一拍
|
||||
* wave_ring_tick 里钉,避免跨上下文改环索引。最坏延迟一拍 = 1 ms,
|
||||
* 相对 160 点的预触发窗可以忽略。
|
||||
* **ACK 前一律只加 ovf**(lead 裁定):SEALED 期间再来触发不重录、不排队,
|
||||
* 本波不做多段排队。
|
||||
* **返回 WAVE_ST_E_PARAM 的两条路径不触碰任何计数**(ovf / err / drop 一个都不加,
|
||||
* 也不留挂号):那是调用方传错参数,与「第二次真触发」是两回事。台架不得拿 ovf
|
||||
* 当「谁误用了接口」的证据——误用只在返回值里体现(lead 裁定 2026-10-29)。
|
||||
*/
|
||||
int32_t wave_ring_request(uint8_t a_src, uint8_t a_elm);
|
||||
|
||||
/*
|
||||
* 函数功能:推进一拍状态机(消费请求钉基址、计后触发点数、到点进 SEALED、
|
||||
* 冷却递减、SEALED 滞留超时自弃)
|
||||
* 入口参数:a_now_ms 当前相对上电毫秒(填段头 t0_ms;用 adcTask 已有的时基即可)
|
||||
* a_frame_seq 当前帧序号(填段头 frame_seq,与串口周期行的 frames 对账)
|
||||
* 返 回 值:wave_ring_evt_t(WAVE_RING_EVT_NONE / WAVE_RING_EVT_SEALED) int32_t
|
||||
* 限定条件:**只能在 adcTask 调**,且每拍在 wave_ring_push 之后调一次
|
||||
* (先写样本再算点数,后触发才正好 160 个点)
|
||||
* 函数说明:FREEZE 期间写入行为与滚动无异,只是段首基址已钉死、预触发区不再被覆盖
|
||||
* —— 这就是 §4.2「触发即冻结写指针」的准确含义:冻住的是覆盖窗口,
|
||||
* 不是停止采集。计满 WAVE_RING_POST 点的本拍返回 WAVE_RING_EVT_SEALED。
|
||||
* SEALED 期间本函数只做「滞留计时 + 超时自弃」,不动数据、不动基址。
|
||||
*/
|
||||
int32_t wave_ring_tick(uint32_t a_now_ms, uint32_t a_frame_seq);
|
||||
|
||||
/*
|
||||
* 函数功能:封存交棒 —— 把「环已知字段 + adcTask 现取的封存瞬间事实」拼成完整段头并锁存
|
||||
* 入口参数:p_cal 封存瞬间事实(折算档位 / 有效性位图 / 削顶位图 / epoch 秒 / 通道表的
|
||||
* conv、bits、avail、k_q16、b_codes;不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK = 已锁存,wave_ring_sealed() 从此为 1;
|
||||
* WAVE_ST_E_STATE = 不在「刚封段待交棒」态(含已 latch 过第二次、非 SEALED);
|
||||
* WAVE_ST_E_PARAM = 空指针或折算档位非法 int32_t
|
||||
* 限定条件:**只能在 adcTask 调**,且只在 wave_ring_tick 返回 WAVE_RING_EVT_SEALED 的
|
||||
* 那一拍调一次(重复调返回 WAVE_ST_E_STATE 且不覆盖已有锁存值 —— 半路覆盖
|
||||
* 会让落盘道读到的头与数据不同源)
|
||||
* 函数说明:为什么系数要调用方传、而不让本层自己读 adc_cal —— 本层要能被主机单测零桩
|
||||
* 纳入;更根本的是 §12 的要求:标定一旦落地,老文件必须还能按**当时**的系数
|
||||
* 解释,所以 eng / k / b / clip 属于段头、不属于当前固件状态。
|
||||
* 取值只能在第 3 步那一拍做一次(不每拍读、不低频刷),理由见本文件头注。
|
||||
*/
|
||||
int32_t wave_ring_latch_meta(const wave_seal_cal_t *p_cal);
|
||||
|
||||
/*
|
||||
* 函数功能:落盘 ACK —— defaultTask 明确告知本段已搬完(成功或失败),据此解冻并起冷却
|
||||
* 入口参数:a_result 0 = 落盘成功(打开、写入字节数相符、目录可用);
|
||||
* 非 0 = 失败(含 f_write 短写、打开/建目录失败、CRC 自弃)——
|
||||
* 失败时段已丢,但环必须解冻,错误只计数不冻结
|
||||
* 返 回 值:WAVE_ST_OK = 已 ACK 并转入冷却;WAVE_ST_E_STATE = 当前不在 SEALED
|
||||
* (重复 ACK 或没有段可 ACK —— 不静默改状态,防落盘道把上一段的 ACK 打到新段),
|
||||
* 或段头还没 latch(第 3 步没做):这时候落盘道拿不到 sealed_meta,根本没可能
|
||||
* 写出文件,放过 ACK 等于白吃一个轮转号并把 seg=NN 报成没发生过的数,
|
||||
* 故一律拒绝。这种段唯一的出口是滞留到 WAVE_SEAL_TIMEOUT_MS 自弃并计 err int32_t
|
||||
* 限定条件:**只能由文件系统唯一写者 defaultTask 调**,一段一次,调之前先看
|
||||
* wave_ring_sealed()(它就是「SEALED 且已 latch」的合并判据)
|
||||
* 函数说明:失败不推进轮转号(下一段仍用同号重写,同号文件按 FA_CREATE_ALWAYS 截断,
|
||||
* 不留半截文件骗过 PC 脚本);成功才推进 seg_idx 与 seg_total。
|
||||
* 本函数不删文件、不碰 FatFs:那些都在落盘道(§5.1 所有权)。
|
||||
*/
|
||||
int32_t wave_ring_write_done(int32_t a_result);
|
||||
|
||||
/* ===== 五、状态查询与已封存区读口(跨任务安全的唯一入口) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:查当前状态
|
||||
* 入口参数:无
|
||||
* 返 回 值:wave_ring_state_t 的取值 uint8_t
|
||||
* 限定条件:任意上下文(只读单字节)
|
||||
*/
|
||||
uint8_t wave_ring_state(void);
|
||||
|
||||
/*
|
||||
* 函数功能:查「有已封存段可读」标志 —— 跨任务的唯一握手量(§5.2 第一条陷阱)
|
||||
* 入口参数:无
|
||||
* 返 回 值:1 = 状态为 SEALED **且段头已 latch**,此刻起 wave_ring_sealed_meta 与
|
||||
* wave_ring_read_ch 可用;0 = 不可用 uint8_t
|
||||
* 限定条件:任意上下文(只读单字节)
|
||||
* 函数说明:为什么把「已 latch」并进来而不是单开一个查询:省得 defaultTask 自己
|
||||
* 拼两个标志、拼错就是拿半份快照落盘(第 4 步明写的禁忌)。
|
||||
* 为 1 期间已封存区对 adcTask 只读不写,读者可以慢慢分次取、不需要临界区;
|
||||
* 为 0 时所有读口一律返 WAVE_ST_E_STATE 而**不是返回旧数据** ——
|
||||
* 把上一段的字节当这一段,等于伪造证据。
|
||||
*/
|
||||
uint8_t wave_ring_sealed(void);
|
||||
|
||||
/*
|
||||
* 函数功能:取已封存段的段头快照(落盘道编头、回放页画标题都用它)
|
||||
* 入口参数:p_out 输出(整份拷出,约 136 字节;不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_STATE 无就绪段 int32_t
|
||||
* 限定条件:任意上下文(defaultTask / uiTask 只读)。整份拷贝、不暴露内部指针,
|
||||
* 免得调用方攥着指针跨到下一段去看
|
||||
* 函数说明:这里的 fs_hz / pts_per_cycle / pre_pts 是**数据**,回放页与 PC 脚本必须
|
||||
* 从这里读,不许当编译期常量(§2.1 第 1 条)。
|
||||
*/
|
||||
int32_t wave_ring_sealed_meta(wave_seg_meta_t *p_out);
|
||||
|
||||
/*
|
||||
* 函数功能:按通道读已封存区的连续点窗(回放页画一路、落盘道搬一路都走它)
|
||||
* 入口参数:a_ch 列号 = 通道表下标 = 语义号(0 到 WAVE_RING_CH-1)
|
||||
* a_from 段内起始点下标(0 = 段首 = 最早的一点。点序口径:触发那一拍的
|
||||
* 样本落在点 WAVE_RING_PRE-1(预触发窗的最后一点),段内点
|
||||
* WAVE_RING_PRE 起是触发之后的第一拍——预触发 8 周波与后触发
|
||||
* 8 周波各 160 点,故障时刻正好是两半的分界(§4.2)
|
||||
* a_cnt 点数(1 起;超出段尾时函数内部夹到剩余点数,不报错)
|
||||
* p_dst 输出 int16 数组(不可为 NULL;画窗 40 点或搬运 128 点按调用方给的容量)
|
||||
* 返 回 值:不小于 0 = 实际读出的点数(到段尾会少于 a_cnt);
|
||||
* WAVE_ST_E_PARAM 空指针或 a_cnt 为 0;WAVE_ST_E_RANGE 列号或起始点越界;
|
||||
* WAVE_ST_E_STATE 无就绪段 int32_t
|
||||
* 限定条件:任意上下文,但**只读已封存区**:本层不提供任何读活缓冲的接口,
|
||||
* IDLE / FREEZE / COOL 下一律 WAVE_ST_E_STATE(§5.2 第一条:不给跨任务读活环的路径)
|
||||
* 函数说明:内部按段首基址取模展开,对读者来说已封存区就是一段 320 点的线性数组。
|
||||
* 回放页画 2 周波 = 40 点(§8.1)一次调用即可;字节形态 = 段文件数据区的
|
||||
* 字节形态(int16 小端),所以落盘道能直接把它交给 wave_store 的打包游标。
|
||||
*/
|
||||
int32_t wave_ring_read_ch(uint8_t a_ch, uint16_t a_from, uint16_t a_cnt, int16_t *p_dst);
|
||||
|
||||
/*
|
||||
* 函数功能:同上,形态适配 App/algo/wave_store.h 的 wave_reader_fn(落盘道直接传它)
|
||||
* 入口参数:p_user 上下文(本层是单例,忽略,传 NULL 即可)
|
||||
* a_ch 列号
|
||||
* a_from 段内起始点下标
|
||||
* a_cnt 点数
|
||||
* p_dst 输出 int16 数组
|
||||
* 返 回 值:实际读出的点数(不小于 0);负 = WAVE_ST_E_*(同上三条) int32_t
|
||||
* 限定条件:任意上下文;只读已封存区(同上)
|
||||
* 函数说明:存在的唯一理由是让 wave_store_pack_start 的 p_reader 直接指过来,
|
||||
* 省掉接线索里一个只为转发的包装函数。
|
||||
*/
|
||||
int32_t wave_ring_read_cb(void *p_user, uint8_t a_ch, uint32_t a_from,
|
||||
uint32_t a_cnt, int16_t *p_dst);
|
||||
|
||||
/*
|
||||
* 函数功能:取观测计数快照(给 §9 的周期行与主机单测)
|
||||
* 入口参数:p_out 输出(不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int32_t
|
||||
* 限定条件:任意上下文(只读;一次拷约 44 字节,个别字段可能与 adcTask 的推进错开一拍,
|
||||
* 这是观测量的固有精度,不构成判据错误)
|
||||
* 函数说明:err_cnt 的两条来源都在本层收口(write_done 非 0、SEALED 滞留超时自弃),
|
||||
* 所以 §9 日志行的 err= 直接取它即可 —— 落盘道不需要自己数第二份账。
|
||||
*/
|
||||
int32_t wave_ring_get_stat(wave_ring_stat_t *p_out);
|
||||
|
||||
/*
|
||||
* 函数功能:把「语义通道号」换算成本环的列号(列序 = 语义号升序,§6.3)
|
||||
* 入口参数:a_sem 语义号(adc_semantic_t 值)
|
||||
* 返 回 值:0 到 WAVE_RING_CH-1 = 列号;WAVE_ST_E_RANGE = 该语义不录(TEMP 与越界值) int32_t
|
||||
* 限定条件:任意上下文(纯计算)
|
||||
* 函数说明:本波只录前 8 个语义通道,所以「列号 == 语义号」这条恒等式成立;
|
||||
* 但调用方必须走这个函数而不是自己写死下标 —— 二期带 TEMP 时列号与语义号
|
||||
* 就会分叉,写死下标的地方会静默错位。
|
||||
*/
|
||||
int32_t wave_ring_col(uint8_t a_sem);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WAVE_RING_H */
|
||||
@@ -0,0 +1,981 @@
|
||||
/*
|
||||
* 模块名称:录波段格式层实现(故障录波 · 段文件字节布局编解码 + 打包游标 + 段名轮转)
|
||||
* 模块功能:见 App/algo/wave_store.h 的文件头注释块——口径属主、职责边界、跨任务安全、
|
||||
* 二期不返工的约束全部写在那里,本文件不重复一遍。
|
||||
* 本文件是纯逻辑:只 include App/algo/wave_store.h 与 C 标准库的 stdint/stddef,
|
||||
* 不 include HAL / CMSIS / RTOS 头、不调 FatFs、不碰文件系统、不打日志。
|
||||
* 适用平台:STM32F407ZGTx(Cortex-M4,小端);主机侧单测 test/host/wave_store/
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29(故障录波 task-1 · 数据面纯逻辑层)
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建
|
||||
*/
|
||||
|
||||
#include "wave_store.h"
|
||||
|
||||
/* ARMCC 的 stdint.h 不提供 NULL,而主机侧 gcc / MSVC 会传递性把它拉进来 ——
|
||||
* 这个差别主机单测照不出来(2026-10-29 台基线实跑 @build 才暴露:本层
|
||||
* wave_store.c 报 22 条 identifier "NULL" 未定义)。与 App/algo/sample_ring.c、App/algo/freq.c
|
||||
* 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */
|
||||
#include <stddef.h>
|
||||
|
||||
/* 打包游标一次最多从数据源取多少个点(128 字节栈上临时区,落盘道在 defaultTask 上跑,
|
||||
* 不在 1 kHz 采集链上;取批是为了把「每点一次回调」压成「每 64 点一次回调」) */
|
||||
#define WAVE_ST_BATCH (64u)
|
||||
/* 数据区点下标非法时的内部哨兵(对外一律走 wave_store_data_off 的 0xFFFFFFFFu) */
|
||||
#define WAVE_ST_BAD_OFF (0xFFFFFFFFu)
|
||||
|
||||
/* ===== 内部:小端读写(显式移位拼装,不依赖宿主字节序,板端与 PC 侧同一判据) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:写 16 位小端
|
||||
* 入口参数:p 目标位置(至少 2 字节);v 值
|
||||
* 返 回 值:无 void
|
||||
* 限定条件:调用方保证 p 可写 2 字节
|
||||
*/
|
||||
static void wave_st_put16(uint8_t *p, uint16_t v) {
|
||||
p[0] = (uint8_t)(v & 0xFFu);
|
||||
p[1] = (uint8_t)((v >> 8) & 0xFFu);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:写 32 位小端
|
||||
* 入口参数:p 目标位置(至少 4 字节);v 值
|
||||
* 返 回 值:无 void
|
||||
* 限定条件:调用方保证 p 可写 4 字节
|
||||
*/
|
||||
static void wave_st_put32(uint8_t *p, uint32_t v) {
|
||||
p[0] = (uint8_t)(v & 0xFFu);
|
||||
p[1] = (uint8_t)((v >> 8) & 0xFFu);
|
||||
p[2] = (uint8_t)((v >> 16) & 0xFFu);
|
||||
p[3] = (uint8_t)((v >> 24) & 0xFFu);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读 16 位小端
|
||||
* 入口参数:p 源位置(至少 2 字节)
|
||||
* 返 回 值:值 uint16_t
|
||||
* 限定条件:调用方保证 p 可读 2 字节
|
||||
*/
|
||||
static uint16_t wave_st_get16(const uint8_t *p) {
|
||||
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读 32 位小端
|
||||
* 入口参数:p 源位置(至少 4 字节)
|
||||
* 返 回 值:值 uint32_t
|
||||
* 限定条件:调用方保证 p 可读 4 字节
|
||||
*/
|
||||
static uint32_t wave_st_get32(const uint8_t *p) {
|
||||
return ((uint32_t)p[0]) | (((uint32_t)p[1]) << 8) |
|
||||
(((uint32_t)p[2]) << 16) | (((uint32_t)p[3]) << 24);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:把 int16 写成小端两字节
|
||||
* 入口参数:p 目标位置;v 码值
|
||||
* 返 回 值:无 void
|
||||
* 限定条件:调用方保证 p 可写 2 字节
|
||||
* 函数说明:先转 uint16 再移位——负数右移是实现定义行为,转成无符号后行为确定。
|
||||
*/
|
||||
static void wave_st_put_code(uint8_t *p, int16_t v) {
|
||||
wave_st_put16(p, (uint16_t)v);
|
||||
}
|
||||
|
||||
/* ===== 内部:段头字段一致性自检(编码前与解码后各跑一次,同一把尺子) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:校验段头元数据的**几何与表序**是否自相矛盾
|
||||
* 入口参数:p_meta 段头元数据(非空)
|
||||
* 返 回 值:WAVE_ST_OK = 合法;WAVE_ST_E_SIZE 长度类不成立(零通道 / 零点数 / 预触发超段长);
|
||||
* WAVE_ST_E_FORMAT 档位 / 触发源 / 表序非法 int
|
||||
* 限定条件:p_meta 由调用方保证非空
|
||||
* 函数说明:为什么编码侧也要跑:写一个「看起来像样的坏段文件」比拒写糟得多——落盘道会
|
||||
* 把 CRC 与长度都算自洽的垃圾写进 NAND,等到回放页或 PC 脚本才发现,那时
|
||||
* 现场已经没了(§8.1「画成一条直线等于伪造证据」是同一条忌讳的另一半)。
|
||||
*/
|
||||
static int wave_st_meta_check(const wave_seg_meta_t *p_meta) {
|
||||
uint8_t i;
|
||||
|
||||
/* 零通道 / 零点数:这一段连长度都不成立(§6.2 末行「长度为 0 判废」) */
|
||||
if ((p_meta->ch_count == 0u) || (p_meta->ch_count > (uint8_t)WAVE_ST_CH_MAX)) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
if (p_meta->pts_per_ch == 0u) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
/* 预触发点数不能大于一段的点数;后触发 = pts_per_ch - pre_pts,允许为 0(手动触发
|
||||
* 撞上落盘窗口等极端情形),但不允许负数——那是把段长算错 */
|
||||
if (p_meta->pre_pts > p_meta->pts_per_ch) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
/* 采样率与每周波点数是数据,但为 0 的数据没法解释(回放与 PC 都要拿它做除数) */
|
||||
if ((p_meta->fs_hz == 0u) || (p_meta->pts_per_cycle == 0u)) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
/* 与 PC 侧 reader 的拒绝清单对齐(谁都不能产出一段「自己写得出、对面读不了」的文件):
|
||||
* 每周波不足 2 点连一个周期都描不出来;段长不足一个整周期则基波算法无从取窗。 */
|
||||
if (p_meta->pts_per_cycle < 2u) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
if (p_meta->pts_per_ch < (uint32_t)p_meta->pts_per_cycle) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
if (p_meta->eng > (uint8_t)WAVE_ENG_PRI) {
|
||||
return WAVE_ST_E_FORMAT;
|
||||
}
|
||||
if (p_meta->trig_src > (uint8_t)WAVE_TRIG_BENCH) {
|
||||
return WAVE_ST_E_FORMAT;
|
||||
}
|
||||
/* 表序:第 i 项的语义号必须等于下标(§6.3「通道表序与列序」行)——
|
||||
* 数据区第 i 列归第 i 项解释,错一位就是拿 IB 的波形配 IC 的系数 */
|
||||
for (i = 0u; i < p_meta->ch_count; i++) {
|
||||
if (p_meta->ch[i].sem != i) {
|
||||
return WAVE_ST_E_FORMAT;
|
||||
}
|
||||
if (p_meta->ch[i].conv > (uint8_t)1u) {
|
||||
return WAVE_ST_E_FORMAT;
|
||||
}
|
||||
/* 位宽留个合理域:本波只有 16(TPAFE)与 12(片内 ADC),但格式层不写死这两个值,
|
||||
* 只挡明显不成立的(0 位、超过 32 位)——二期换转换器不必改这里 */
|
||||
if ((p_meta->ch[i].bits == 0u) || (p_meta->ch[i].bits > 32u)) {
|
||||
return WAVE_ST_E_FORMAT;
|
||||
}
|
||||
}
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/* ===== CRC-16/MODBUS ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:CRC-16/MODBUS 增量计算
|
||||
* 入口参数:a_crc 上一次的中间值(首次传 WAVE_ST_CRC_INIT)
|
||||
* p_data 数据(NULL 视为空操作)
|
||||
* a_len 字节数
|
||||
* 返 回 值:新的中间值 uint16_t
|
||||
* 限定条件:任意上下文;纯计算
|
||||
* 函数说明:反射形态 = 右移、异或 0xA001、初值 0xFFFF、异或输出 0;
|
||||
* 写进文件时低字节在前(§6.3)。无表实现的理由见 wave_store.h 头注。
|
||||
*/
|
||||
uint16_t wave_store_crc_update(uint16_t a_crc, const uint8_t *p_data, uint32_t a_len) {
|
||||
uint16_t crc = a_crc;
|
||||
uint32_t i;
|
||||
|
||||
if (p_data == NULL) {
|
||||
return crc;
|
||||
}
|
||||
for (i = 0u; i < a_len; i++) {
|
||||
uint8_t b = p_data[i]; /* 身兼两职:本路字节 + 下面内层 8 次的计数器 */
|
||||
|
||||
crc = (uint16_t)(crc ^ (uint16_t)b);
|
||||
for (b = 0u; b < 8u; b++) {
|
||||
if ((crc & 0x0001u) != 0u) {
|
||||
crc = (uint16_t)((crc >> 1) ^ WAVE_ST_CRC_POLY);
|
||||
} else {
|
||||
crc = (uint16_t)(crc >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 异或输出恒 0:写成宏引用,换异或输出时只改一处 */
|
||||
return (uint16_t)(crc ^ WAVE_ST_CRC_XOROUT);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:CRC-16/MODBUS 一次算完
|
||||
* 入口参数:p_data 数据;a_len 字节数
|
||||
* 返 回 值:CRC;空输入返回初值定义 uint16_t
|
||||
* 限定条件:任意上下文。零长度的判废由调用方做(见 wave_store.h 同函数注释)
|
||||
*/
|
||||
uint16_t wave_store_crc(const uint8_t *p_data, uint32_t a_len) {
|
||||
return wave_store_crc_update(WAVE_ST_CRC_INIT, p_data, a_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:CRC 实现自检(对规范锚点,不是自算自验)
|
||||
* 入口参数:无
|
||||
* 返 回 值:WAVE_ST_OK 相符;WAVE_ST_E_FORMAT 不符 int
|
||||
* 限定条件:任意上下文;开机或首次落盘前一次,不在每拍路径上
|
||||
* 函数说明:锚点串「123456789」的 CRC-16/MODBUS = 0x4B37(§6.3)。字面量必须纯 ASCII,
|
||||
* 所以这里用显式字节数组而不是把中文塞进字符串。
|
||||
*/
|
||||
int wave_store_crc_selftest(void) {
|
||||
static const uint8_t s_anchor[9] = {
|
||||
(uint8_t)0x31u, (uint8_t)0x32u, (uint8_t)0x33u,
|
||||
(uint8_t)0x34u, (uint8_t)0x35u, (uint8_t)0x36u,
|
||||
(uint8_t)0x37u, (uint8_t)0x38u, (uint8_t)0x39u
|
||||
};
|
||||
uint16_t got;
|
||||
|
||||
got = wave_store_crc(s_anchor, (uint32_t)sizeof(s_anchor));
|
||||
if (got != WAVE_ST_CRC_CHECK) {
|
||||
return WAVE_ST_E_FORMAT;
|
||||
}
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/* ===== 段头编解码 ===== */
|
||||
|
||||
/* 语义通道的转换器归属与位宽(0 = TPAFE5160,1 = 片内 ADC1;口径属主是
|
||||
* docs/硬件平台/ADC通道映射说明.md,本表是其抄本,改硬件接线时两处一起改)。
|
||||
* 第 9 项 TEMP 本波不录入段文件(ch_count 恒 8),仍按实际归属填,
|
||||
* 免得将来放开 TEMP 时这里是个 0。 */
|
||||
static const uint8_t s_sem_conv[(uint8_t)WAVE_ST_CH_MAX] = {
|
||||
(uint8_t)0u, (uint8_t)1u, (uint8_t)1u,
|
||||
(uint8_t)0u, (uint8_t)0u, (uint8_t)1u, (uint8_t)1u, (uint8_t)1u, (uint8_t)1u
|
||||
};
|
||||
static const uint8_t s_sem_bits[(uint8_t)WAVE_ST_CH_MAX] = {
|
||||
16u, 12u, 12u,
|
||||
16u, 16u, 12u, 12u, 12u, 12u
|
||||
};
|
||||
|
||||
/*
|
||||
* 函数功能:把段头元数据填成本波默认值(只填格式侧静态事实)
|
||||
* 入口参数:p_meta 输出
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:填三样格式侧的静态事实:语义号 = 下标(§6.3 表序口径,也是 PC 侧判据)、
|
||||
* conv / bits 按两片转换器的归属(下面那张表的口径属主是
|
||||
* docs/硬件平台/ADC通道映射说明.md 的抄本,本层刻意不带 adc_input.h,
|
||||
* 这样格式层在主机侧不需要任何假件就能编译;单测里 0xE6 的算术交叉验证
|
||||
* 会同时钉住这套顺序,抄错就会红)。
|
||||
* 不填 avail / k_q16 / b_codes:那些是封存瞬间的事实,必须由调用方现取现填
|
||||
* (adc_cal_k_q16 / adc_cal_flags / sys_clock),本层不链它们;
|
||||
* 调用方忘填 k_q16 与 avail 不会让编码失败(0 是合法整数),
|
||||
* 但会让 PC 侧把波形解释成一条 0 线——所以单测里有一条专门检查
|
||||
* 「填了系数的往返」与「没填系数时段的形状仍然正确、只是幅值归零」。
|
||||
*/
|
||||
int wave_store_meta_init(wave_seg_meta_t *p_meta) {
|
||||
uint8_t i;
|
||||
|
||||
if (p_meta == NULL) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
p_meta->fs_hz = (uint16_t)WAVE_ST_FS_DEF;
|
||||
p_meta->pts_per_cycle = (uint8_t)WAVE_ST_PPC_DEF;
|
||||
p_meta->ch_count = (uint8_t)WAVE_ST_CH_NUM;
|
||||
p_meta->pts_per_ch = (uint16_t)WAVE_ST_PTS_DEF;
|
||||
p_meta->pre_pts = (uint16_t)WAVE_ST_PRE_DEF;
|
||||
p_meta->trig_src = (uint8_t)WAVE_TRIG_NONE;
|
||||
p_meta->trig_elm = 0u;
|
||||
p_meta->t0_ms = 0u;
|
||||
p_meta->wall = 0u;
|
||||
p_meta->eng = (uint8_t)WAVE_ENG_PIN;
|
||||
p_meta->flags_seq = 0u;
|
||||
p_meta->clip_mask = 0u;
|
||||
p_meta->frame_seq = 0u;
|
||||
for (i = 0u; i < (uint8_t)WAVE_ST_CH_MAX; i++) {
|
||||
p_meta->ch[i].sem = i;
|
||||
p_meta->ch[i].conv = s_sem_conv[i];
|
||||
p_meta->ch[i].bits = s_sem_bits[i];
|
||||
p_meta->ch[i].avail = 0u;
|
||||
p_meta->ch[i].k_q16 = 0;
|
||||
p_meta->ch[i].b_codes = 0;
|
||||
}
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:内部——把固定头 + 通道表写进缓冲(不含头 CRC)
|
||||
* 入口参数:p_meta 段头;p_out 缓冲(容量须 >= 头 CRC 覆盖区)
|
||||
* 返 回 值:写入字节数(= 头 CRC 覆盖区长度) uint32_t
|
||||
* 限定条件:p_meta 已过 wave_st_meta_check、p_out 容量已核过
|
||||
*/
|
||||
static uint32_t wave_st_head_raw(const wave_seg_meta_t *p_meta, uint8_t *p_out) {
|
||||
uint8_t i;
|
||||
const uint32_t tbl_n = (uint32_t)p_meta->ch_count * WAVE_ST_CH_BYTES;
|
||||
|
||||
p_out[WAVE_ST_OFF_MAGIC] = (uint8_t)WAVE_ST_MAGIC0;
|
||||
p_out[WAVE_ST_OFF_MAGIC + 1u] = (uint8_t)WAVE_ST_MAGIC1;
|
||||
/* 魔数第三字节 = 版本号:与 ver 字段同源同值(§6.2 第 0 行 + §6.3 判废口径) */
|
||||
p_out[WAVE_ST_OFF_MAGIC + 2u] = (uint8_t)WAVE_ST_VER;
|
||||
p_out[WAVE_ST_OFF_VER] = (uint8_t)WAVE_ST_VER;
|
||||
wave_st_put16(&p_out[WAVE_ST_OFF_FS_HZ], p_meta->fs_hz);
|
||||
p_out[WAVE_ST_OFF_PTS_CYC] = p_meta->pts_per_cycle;
|
||||
p_out[WAVE_ST_OFF_CH_CNT] = p_meta->ch_count;
|
||||
wave_st_put16(&p_out[WAVE_ST_OFF_PTS_CH], p_meta->pts_per_ch);
|
||||
wave_st_put16(&p_out[WAVE_ST_OFF_PRE_PTS], p_meta->pre_pts);
|
||||
p_out[WAVE_ST_OFF_TRIG_SRC] = p_meta->trig_src;
|
||||
p_out[WAVE_ST_OFF_TRIG_ELM] = p_meta->trig_elm;
|
||||
wave_st_put32(&p_out[WAVE_ST_OFF_T0_MS], p_meta->t0_ms);
|
||||
wave_st_put32(&p_out[WAVE_ST_OFF_WALL], p_meta->wall);
|
||||
p_out[WAVE_ST_OFF_ENG] = p_meta->eng;
|
||||
p_out[WAVE_ST_OFF_FLAGS] = p_meta->flags_seq;
|
||||
wave_st_put16(&p_out[WAVE_ST_OFF_CLIP], p_meta->clip_mask);
|
||||
wave_st_put32(&p_out[WAVE_ST_OFF_SEQ], p_meta->frame_seq);
|
||||
|
||||
for (i = 0u; i < p_meta->ch_count; i++) {
|
||||
uint32_t pos = (uint32_t)WAVE_ST_OFF_TBL + ((uint32_t)i * WAVE_ST_CH_BYTES);
|
||||
|
||||
p_out[pos + WAVE_ST_CH_OFF_SEM] = p_meta->ch[i].sem;
|
||||
p_out[pos + WAVE_ST_CH_OFF_CONV] = p_meta->ch[i].conv;
|
||||
p_out[pos + WAVE_ST_CH_OFF_BITS] = p_meta->ch[i].bits;
|
||||
p_out[pos + WAVE_ST_CH_OFF_AVAIL] = p_meta->ch[i].avail;
|
||||
/* int32 系数按小端原样落 4 字节:右移到 24 位前把符号扩展掉,
|
||||
* 保证板端 ARMCC 与主机 CRT 写出同一串字节 */
|
||||
wave_st_put32(&p_out[pos + WAVE_ST_CH_OFF_KQ16], (uint32_t)p_meta->ch[i].k_q16);
|
||||
wave_st_put32(&p_out[pos + WAVE_ST_CH_OFF_BCODE], (uint32_t)p_meta->ch[i].b_codes);
|
||||
}
|
||||
return (uint32_t)WAVE_ST_OFF_TBL + tbl_n;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:编码段头(固定头 + 通道表 + 头 CRC)
|
||||
* 入口参数:p_meta 段头元数据;p_out 输出缓冲;a_cap 容量
|
||||
* 返 回 值:>= 0 写入字节数;负 = WAVE_ST_E_* int
|
||||
* 限定条件:任意上下文;无静态缓冲、可重入
|
||||
* 函数说明:头 CRC 覆盖 [0, 30+12×ch_count)、自身紧随其后、低字节在前(§6.3)。
|
||||
*/
|
||||
int wave_store_head_build(const wave_seg_meta_t *p_meta, uint8_t *p_out, uint32_t a_cap) {
|
||||
uint32_t crc_pos;
|
||||
uint16_t crc;
|
||||
int rc;
|
||||
|
||||
if ((p_meta == NULL) || (p_out == NULL)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
rc = wave_st_meta_check(p_meta);
|
||||
if (rc != WAVE_ST_OK) {
|
||||
return rc;
|
||||
}
|
||||
crc_pos = WAVE_ST_HEAD_CRC_POS(p_meta->ch_count);
|
||||
if (a_cap < (crc_pos + 2u)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
(void)wave_st_head_raw(p_meta, p_out);
|
||||
crc = wave_store_crc(p_out, crc_pos);
|
||||
/* 低字节在前(§6.3 首行):put16 本来就是小端,这里不额外翻转 */
|
||||
wave_st_put16(&p_out[crc_pos], crc);
|
||||
return (int)(crc_pos + 2u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:解码 + 全套判废(魔数 / 版本 / 缓冲长度 / 几何 / 表序 / 头 CRC)
|
||||
* 入口参数:p_buf 头字节流;a_len 可读字节数;p_meta 输出
|
||||
* 返 回 值:WAVE_ST_OK 或 WAVE_ST_E_*(见 wave_store.h) int
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:顺序刻意是「先验魔数与版本,再算整头长度,再验 CRC,最后验语义」——
|
||||
* 前两步决定我们**有没有资格**按这份布局去数偏移;反过来先按 ch_count 算长度,
|
||||
* 一个被写坏的 ch_count 就能让我们读到缓冲外面去。
|
||||
*/
|
||||
int wave_store_head_parse(const uint8_t *p_buf, uint32_t a_len, wave_seg_meta_t *p_meta) {
|
||||
uint8_t ch_count;
|
||||
uint32_t crc_pos;
|
||||
uint32_t head_sz;
|
||||
uint16_t want;
|
||||
uint16_t got;
|
||||
int rc;
|
||||
wave_seg_meta_t tmp;
|
||||
|
||||
if ((p_buf == NULL) || (p_meta == NULL)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
/* 固定头是 30 + 1 字节通道数才能算出整头长度,至少得读到偏移 7 */
|
||||
if (a_len < ((uint32_t)WAVE_ST_OFF_CH_CNT + 1u)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if ((p_buf[WAVE_ST_OFF_MAGIC] != (uint8_t)WAVE_ST_MAGIC0) ||
|
||||
(p_buf[WAVE_ST_OFF_MAGIC + 1u] != (uint8_t)WAVE_ST_MAGIC1)) {
|
||||
return WAVE_ST_E_FORMAT;
|
||||
}
|
||||
/* ver 必须等于魔数第三字节、且等于本层认识的版本(§6.2 末行) */
|
||||
if ((p_buf[WAVE_ST_OFF_VER] != p_buf[WAVE_ST_OFF_MAGIC + 2u]) ||
|
||||
(p_buf[WAVE_ST_OFF_VER] != (uint8_t)WAVE_ST_VER)) {
|
||||
return WAVE_ST_E_FORMAT;
|
||||
}
|
||||
ch_count = p_buf[WAVE_ST_OFF_CH_CNT];
|
||||
head_sz = wave_store_head_bytes(ch_count);
|
||||
if (head_sz == 0u) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
if (a_len < head_sz) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
crc_pos = WAVE_ST_HEAD_CRC_POS(ch_count);
|
||||
want = wave_st_get16(&p_buf[crc_pos]);
|
||||
got = wave_store_crc(p_buf, crc_pos);
|
||||
if (want != got) {
|
||||
return WAVE_ST_E_CRC;
|
||||
}
|
||||
|
||||
/* 头 CRC 过了才开始解释字段:下面填的是一份可交回调用方的完整元数据 */
|
||||
tmp.fs_hz = wave_st_get16(&p_buf[WAVE_ST_OFF_FS_HZ]);
|
||||
tmp.pts_per_cycle = p_buf[WAVE_ST_OFF_PTS_CYC];
|
||||
tmp.ch_count = ch_count;
|
||||
tmp.pts_per_ch = wave_st_get16(&p_buf[WAVE_ST_OFF_PTS_CH]);
|
||||
tmp.pre_pts = wave_st_get16(&p_buf[WAVE_ST_OFF_PRE_PTS]);
|
||||
tmp.trig_src = p_buf[WAVE_ST_OFF_TRIG_SRC];
|
||||
tmp.trig_elm = p_buf[WAVE_ST_OFF_TRIG_ELM];
|
||||
tmp.t0_ms = wave_st_get32(&p_buf[WAVE_ST_OFF_T0_MS]);
|
||||
tmp.wall = wave_st_get32(&p_buf[WAVE_ST_OFF_WALL]);
|
||||
tmp.eng = p_buf[WAVE_ST_OFF_ENG];
|
||||
tmp.flags_seq = p_buf[WAVE_ST_OFF_FLAGS];
|
||||
tmp.clip_mask = wave_st_get16(&p_buf[WAVE_ST_OFF_CLIP]);
|
||||
tmp.frame_seq = wave_st_get32(&p_buf[WAVE_ST_OFF_SEQ]);
|
||||
for (ch_count = 0u; ch_count < (uint8_t)WAVE_ST_CH_MAX; ch_count++) {
|
||||
tmp.ch[ch_count].sem = 0u;
|
||||
tmp.ch[ch_count].conv = 0u;
|
||||
tmp.ch[ch_count].bits = 0u;
|
||||
tmp.ch[ch_count].avail = 0u;
|
||||
tmp.ch[ch_count].k_q16 = 0;
|
||||
tmp.ch[ch_count].b_codes = 0;
|
||||
}
|
||||
for (ch_count = 0u; ch_count < p_buf[WAVE_ST_OFF_CH_CNT]; ch_count++) {
|
||||
uint32_t pos = (uint32_t)WAVE_ST_OFF_TBL + ((uint32_t)ch_count * WAVE_ST_CH_BYTES);
|
||||
tmp.ch[ch_count].sem = p_buf[pos + WAVE_ST_CH_OFF_SEM];
|
||||
tmp.ch[ch_count].conv = p_buf[pos + WAVE_ST_CH_OFF_CONV];
|
||||
tmp.ch[ch_count].bits = p_buf[pos + WAVE_ST_CH_OFF_BITS];
|
||||
tmp.ch[ch_count].avail = p_buf[pos + WAVE_ST_CH_OFF_AVAIL];
|
||||
tmp.ch[ch_count].k_q16 = (int32_t)wave_st_get32(&p_buf[pos + WAVE_ST_CH_OFF_KQ16]);
|
||||
tmp.ch[ch_count].b_codes = (int32_t)wave_st_get32(&p_buf[pos + WAVE_ST_CH_OFF_BCODE]);
|
||||
}
|
||||
rc = wave_st_meta_check(&tmp);
|
||||
if (rc != WAVE_ST_OK) {
|
||||
return rc;
|
||||
}
|
||||
*p_meta = tmp;
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/* ===== 长度 / 偏移换算 ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:整头字节数
|
||||
* 入口参数:a_ch_count 通道数
|
||||
* 返 回 值:字节数;通道数非法返回 0 uint32_t
|
||||
* 限定条件:任意上下文
|
||||
*/
|
||||
uint32_t wave_store_head_bytes(uint8_t a_ch_count) {
|
||||
if ((a_ch_count == 0u) || (a_ch_count > (uint8_t)WAVE_ST_CH_MAX)) {
|
||||
return 0u;
|
||||
}
|
||||
return WAVE_ST_HEAD_BYTES(a_ch_count);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按头部字段算整段应有字节数
|
||||
* 入口参数:p_meta 段头
|
||||
* 返 回 值:期望字节数;字段非法返回 0 uint32_t
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:等式 = 32 + 12×ch_count + pts_per_ch×ch_count×2 + 2(§6.3;
|
||||
* 8 路 320 点 = 5250)。落盘道拿它比对 f_write 写回字节数与 f_size。
|
||||
*/
|
||||
uint32_t wave_store_seg_size(const wave_seg_meta_t *p_meta) {
|
||||
if (p_meta == NULL) {
|
||||
return 0u;
|
||||
}
|
||||
if ((p_meta->ch_count == 0u) || (p_meta->ch_count > (uint8_t)WAVE_ST_CH_MAX)) {
|
||||
return 0u;
|
||||
}
|
||||
if (p_meta->pts_per_ch == 0u) {
|
||||
return 0u;
|
||||
}
|
||||
return WAVE_ST_SEG_TOTAL(p_meta->ch_count, p_meta->pts_per_ch);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:数据区某点的文件绝对偏移
|
||||
* 入口参数:a_ch_count 通道数;a_pts_ch 每通道点数;a_ch 列号;a_pt 段内点下标
|
||||
* 返 回 值:偏移;非法返回 0xFFFFFFFFu 哨兵 uint32_t
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:按通道分列 → 偏移 = 整头长 + (列 × 点数 + 点下标) × 2。单通道整列是连续的,
|
||||
* 这正是 §6.2「只读那一路」能一次连续读的原因。
|
||||
*/
|
||||
uint32_t wave_store_data_off(uint8_t a_ch_count, uint16_t a_pts_ch,
|
||||
uint8_t a_ch, uint32_t a_pt) {
|
||||
uint32_t head_sz = wave_store_head_bytes(a_ch_count);
|
||||
|
||||
if ((head_sz == 0u) || (a_pts_ch == 0u) || (a_ch >= a_ch_count)) {
|
||||
return WAVE_ST_BAD_OFF;
|
||||
}
|
||||
if (a_pt >= (uint32_t)a_pts_ch) {
|
||||
return WAVE_ST_BAD_OFF;
|
||||
}
|
||||
return head_sz + (((uint32_t)a_ch * (uint32_t)a_pts_ch) + a_pt) * 2u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:从某点起当前列还能连续读多少字节
|
||||
* 入口参数:a_pts_ch 每通道点数;a_pt 起始点下标;a_want 想读点数
|
||||
* 返 回 值:可连续读字节数(参数非法返回 0) uint32_t
|
||||
* 限定条件:任意上下文
|
||||
*/
|
||||
uint32_t wave_store_data_bytes_avail(uint16_t a_pts_ch, uint32_t a_pt, uint32_t a_want) {
|
||||
uint32_t left;
|
||||
uint32_t n;
|
||||
|
||||
if ((a_pts_ch == 0u) || (a_want == 0u)) {
|
||||
return 0u;
|
||||
}
|
||||
if (a_pt >= (uint32_t)a_pts_ch) {
|
||||
return 0u;
|
||||
}
|
||||
left = (uint32_t)a_pts_ch - a_pt;
|
||||
n = (a_want < left) ? a_want : left;
|
||||
return n * 2u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:小端字节 → int16 点序列
|
||||
* 入口参数:p_raw 原始字节;a_len 字节数;p_out 输出;a_max_pts 容量;p_pts 实际点数(可空)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 / 奇数字节 / 容量不足 int
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:奇数字节按参数错处理而不是「丢掉尾巴」——半截样本被静默吞掉,
|
||||
* 回放页画出来的波形会比真的少最后一个点,而这种误差正好最难查。
|
||||
*/
|
||||
int wave_store_le_decode(const uint8_t *p_raw, uint32_t a_len,
|
||||
int16_t *p_out, uint32_t a_max_pts, uint32_t *p_pts) {
|
||||
uint32_t n;
|
||||
uint32_t i;
|
||||
|
||||
if ((p_raw == NULL) || (p_out == NULL)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if (((a_len & 0x00000001u) != 0u) || (a_len > (a_max_pts * 2u))) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
n = a_len / 2u;
|
||||
for (i = 0u; i < n; i++) {
|
||||
/* 先攒成 uint16 再转 int16:补码回绕在 C 里是实现定义但两大编译器一致,
|
||||
* 而这里需要的正是「按补码位型解释」——TPAFE 的 16 位补码就是这么存的 */
|
||||
p_out[i] = (int16_t)wave_st_get16(&p_raw[i * 2u]);
|
||||
}
|
||||
if (p_pts != NULL) {
|
||||
*p_pts = n;
|
||||
}
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:码值 → 定点毫单位(mV)
|
||||
* 入口参数:p_desc 通道表项(必须来自段文件头);a_code 码值;p_milli 输出 mV
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||||
* 限定条件:任意上下文(只在回放页与单测用,不在 1 kHz 采集链上;用 int64 中间量)
|
||||
* 函数说明:模型 eng = (code + b) × k × 1e-6(§6.3,与 adc_cal_apply() 同式),
|
||||
* k = k_q16 / 65536 µV/码 → mV = (code + b) × k_q16 / 65536000,四舍五入。
|
||||
* 中间量最大约 (32767 + 8192) × 5.28e7 ≈ 2.2e12,int64 宽裕。
|
||||
*/
|
||||
int wave_store_code_to_milli(const wave_ch_desc_t *p_desc, int16_t a_code,
|
||||
int32_t *p_milli) {
|
||||
int64_t num;
|
||||
int64_t den = 65536000;
|
||||
int64_t v;
|
||||
|
||||
if ((p_desc == NULL) || (p_milli == NULL)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
num = ((int64_t)a_code + (int64_t)p_desc->b_codes) * (int64_t)p_desc->k_q16;
|
||||
if (num >= 0) {
|
||||
v = (num + (den / 2)) / den;
|
||||
} else {
|
||||
v = -(((-num) + (den / 2)) / den);
|
||||
}
|
||||
*p_milli = (int32_t)v;
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/* ===== 整段校验 ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:整段镜像自检(头 + 数据区 + 数据 CRC)
|
||||
* 入口参数:p_img 镜像;a_len 长度;p_meta 输出段头(可空)
|
||||
* 返 回 值:WAVE_ST_OK 或 WAVE_ST_E_*(见 wave_store.h) int
|
||||
* 限定条件:任意上下文;整段驻留时用(本波 5250 字节)
|
||||
* 函数说明:三条判废一条都不能省:长度 0 或不等、头 CRC、数据 CRC。
|
||||
* 期望总长用**头里的字段**算,不用编译期常量——文件是自己说它多长,
|
||||
* 与镜像实际长度不一致就是坏段(§6.2 末行)。
|
||||
*/
|
||||
int wave_store_seg_check(const uint8_t *p_img, uint32_t a_len, wave_seg_meta_t *p_meta) {
|
||||
wave_seg_meta_t meta;
|
||||
uint32_t want_total;
|
||||
uint32_t data_off;
|
||||
uint32_t data_len;
|
||||
uint16_t want;
|
||||
uint16_t got;
|
||||
int rc;
|
||||
|
||||
if (p_img == NULL) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if (a_len == 0u) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
rc = wave_store_head_parse(p_img, a_len, &meta);
|
||||
if (rc != WAVE_ST_OK) {
|
||||
return rc;
|
||||
}
|
||||
want_total = wave_store_seg_size(&meta);
|
||||
if (want_total == 0u) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
if (a_len != want_total) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
data_off = wave_store_head_bytes(meta.ch_count); /* 数据区起点 = 整头字节数 */
|
||||
data_len = a_len - 2u - data_off;
|
||||
if (data_len == 0u) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
want = wave_st_get16(&p_img[a_len - 2u]);
|
||||
got = wave_store_crc(&p_img[data_off], data_len);
|
||||
if (want != got) {
|
||||
return WAVE_ST_E_CRC;
|
||||
}
|
||||
if (p_meta != NULL) {
|
||||
*p_meta = meta;
|
||||
}
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/* ===== 打包游标 ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:初始化打包游标
|
||||
* 入口参数:p_ctx 游标;p_meta 段头;p_reader 数据源(可空 = 只产头);p_user 上下文
|
||||
* 返 回 值:WAVE_ST_OK 或 WAVE_ST_E_* int
|
||||
* 限定条件:任意上下文(纯计算);一段一个游标
|
||||
* 函数说明:段头整份拷进游标——搬运窗口几十毫秒,期间调用方完全可以去改它自己那份
|
||||
* (比如 adcTask 又跑了几拍),本段的字节不能受影响。
|
||||
*/
|
||||
int wave_store_pack_start(wave_pack_ctx_t *p_ctx, const wave_seg_meta_t *p_meta,
|
||||
wave_reader_fn p_reader, void *p_user) {
|
||||
int rc;
|
||||
|
||||
if ((p_ctx == NULL) || (p_meta == NULL)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
rc = wave_st_meta_check(p_meta);
|
||||
if (rc != WAVE_ST_OK) {
|
||||
p_ctx->phase = (uint8_t)WAVE_PACK_IDLE;
|
||||
return rc;
|
||||
}
|
||||
p_ctx->total = wave_store_seg_size(p_meta);
|
||||
if (p_ctx->total == 0u) {
|
||||
p_ctx->phase = (uint8_t)WAVE_PACK_IDLE;
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
p_ctx->phase = (uint8_t)WAVE_PACK_HEAD;
|
||||
p_ctx->ch = 0u;
|
||||
p_ctx->head_done = 0u;
|
||||
p_ctx->ch_done = 0u;
|
||||
p_ctx->tail_done = 0u;
|
||||
p_ctx->rsv = 0u;
|
||||
p_ctx->crc_data = WAVE_ST_CRC_INIT;
|
||||
p_ctx->sent = 0u;
|
||||
p_ctx->p_reader = p_reader;
|
||||
p_ctx->p_user = p_user;
|
||||
p_ctx->meta = *p_meta;
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取整段字节流的下一片
|
||||
* 入口参数:p_ctx 游标;p_out 缓冲;a_cap 容量;p_len 实际产出字节数
|
||||
* 返 回 值:WAVE_ST_OK 还有后续;WAVE_ST_DONE 整段产完;负 = WAVE_ST_E_* int
|
||||
* 限定条件:任意上下文,同一游标不得并发使用
|
||||
* 函数说明:三段推进——头(一次给完,要求容量 >= 整头)、数据区(按列连续、整样本切,
|
||||
* 绝不切半个 int16,也不跨列,便于回调按列读)、尾 CRC 两字节。
|
||||
* 数据 CRC 边产边算(增量中间值),所以全程不需要整段驻留 RAM。
|
||||
*/
|
||||
int wave_store_pack_next(wave_pack_ctx_t *p_ctx, uint8_t *p_out,
|
||||
uint32_t a_cap, uint32_t *p_len) {
|
||||
uint32_t n = 0u;
|
||||
|
||||
if ((p_ctx == NULL) || (p_out == NULL) || (p_len == NULL)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
*p_len = 0u;
|
||||
if ((p_ctx->phase == (uint8_t)WAVE_PACK_IDLE) || (a_cap == 0u)) {
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
if (p_ctx->phase == (uint8_t)WAVE_PACK_DONE) {
|
||||
return WAVE_ST_DONE;
|
||||
}
|
||||
|
||||
/* --- 头:整头一次产完(接线索按 §7 给 256 字节缓冲,足够 128 字节整头) --- */
|
||||
if (p_ctx->phase == (uint8_t)WAVE_PACK_HEAD) {
|
||||
uint32_t head_sz;
|
||||
int rc;
|
||||
|
||||
head_sz = wave_store_head_bytes(p_ctx->meta.ch_count);
|
||||
if (head_sz == 0u) {
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
if (a_cap < head_sz) {
|
||||
/* 不拆着头给:半截头意味着游标要存 128 字节中间态,纯浪费 */
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
rc = wave_store_head_build(&p_ctx->meta, p_out, a_cap);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
n = (uint32_t)rc;
|
||||
p_ctx->head_done = (uint16_t)n;
|
||||
p_ctx->sent += n;
|
||||
*p_len = n;
|
||||
p_ctx->phase = (uint8_t)WAVE_PACK_DATA;
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
/* --- 数据区:按列推进,一次最多一批 --- */
|
||||
if (p_ctx->phase == (uint8_t)WAVE_PACK_DATA) {
|
||||
int16_t batch[WAVE_ST_BATCH];
|
||||
|
||||
if (p_ctx->p_reader == NULL) {
|
||||
/* 没有数据源却进了数据区:宁可报错也不产空字节。
|
||||
* 产空字节会写出「长度对、内容是 0」的段文件,回放页会画出一条直线——
|
||||
* 那正是 §8.1 明令禁止的伪造证据形态。 */
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
while ((n < a_cap) && (p_ctx->ch < p_ctx->meta.ch_count)) {
|
||||
uint32_t room_pts = (a_cap - n) / 2u;
|
||||
uint32_t col_left = (uint32_t)p_ctx->meta.pts_per_ch - (uint32_t)p_ctx->ch_done;
|
||||
uint32_t take;
|
||||
int32_t got;
|
||||
uint32_t i;
|
||||
|
||||
if (col_left == 0u) {
|
||||
p_ctx->ch++;
|
||||
p_ctx->ch_done = 0u;
|
||||
continue;
|
||||
}
|
||||
if (room_pts == 0u) {
|
||||
break;
|
||||
}
|
||||
take = (room_pts < col_left) ? room_pts : col_left;
|
||||
if (take > (uint32_t)WAVE_ST_BATCH) {
|
||||
take = (uint32_t)WAVE_ST_BATCH;
|
||||
}
|
||||
got = p_ctx->p_reader(p_ctx->p_user, p_ctx->ch, (uint32_t)p_ctx->ch_done,
|
||||
take, batch);
|
||||
if (got < 0) {
|
||||
return (int)got;
|
||||
}
|
||||
if ((uint32_t)got != take) {
|
||||
/* 数据源给不满就是数据源的问题(越界 / 未封存),不拿半批凑长度 */
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
uint32_t base_n = n;
|
||||
for (i = 0u; i < take; i++) {
|
||||
wave_st_put_code(&p_out[n], batch[i]);
|
||||
n += 2u;
|
||||
}
|
||||
p_ctx->crc_data = wave_store_crc_update(p_ctx->crc_data, &p_out[base_n], take * 2u);
|
||||
p_ctx->ch_done = (uint16_t)((uint32_t)p_ctx->ch_done + take);
|
||||
}
|
||||
if (p_ctx->ch >= p_ctx->meta.ch_count) {
|
||||
/* 最后一列的最后一点产出后,「列走完了」要到下一次进循环才发现,
|
||||
* 所以这一拍可能一个字节都没产。这时候不许当参数错报出去:整段就差
|
||||
* 最后两片 CRC,报 E_PARAM 会让落盘道把一次真故障判成落盘失败(白丢)。
|
||||
* 让它落到下面的尾段分支,在同一次调用里把尾产完。 */
|
||||
p_ctx->phase = (uint8_t)WAVE_PACK_TAIL;
|
||||
}
|
||||
p_ctx->sent += n;
|
||||
*p_len = n;
|
||||
if (n > 0u) {
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
if (p_ctx->phase != (uint8_t)WAVE_PACK_TAIL) {
|
||||
return WAVE_ST_E_PARAM; /* 容量连一个整样本都装不下 */
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 尾:数据区 CRC,低字节在前 --- */
|
||||
if (p_ctx->phase == (uint8_t)WAVE_PACK_TAIL) {
|
||||
if (p_ctx->tail_done == 0u) {
|
||||
p_out[n] = (uint8_t)(p_ctx->crc_data & 0xFFu);
|
||||
n++;
|
||||
p_ctx->tail_done = 1u;
|
||||
}
|
||||
if ((p_ctx->tail_done == 1u) && (n < a_cap)) {
|
||||
p_out[n] = (uint8_t)((p_ctx->crc_data >> 8) & 0xFFu);
|
||||
n++;
|
||||
p_ctx->tail_done = 2u;
|
||||
p_ctx->phase = (uint8_t)WAVE_PACK_DONE;
|
||||
}
|
||||
p_ctx->sent += n;
|
||||
*p_len = n;
|
||||
return (p_ctx->phase == (uint8_t)WAVE_PACK_DONE) ? WAVE_ST_DONE : WAVE_ST_OK;
|
||||
}
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:游标已产出字节数
|
||||
* 入口参数:p_ctx 游标
|
||||
* 返 回 值:字节数;空指针返回 0 uint32_t
|
||||
* 限定条件:任意上下文(只读)
|
||||
*/
|
||||
uint32_t wave_store_pack_offset(const wave_pack_ctx_t *p_ctx) {
|
||||
return (p_ctx == NULL) ? 0u : p_ctx->sent;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:游标对应整段的期望总长
|
||||
* 入口参数:p_ctx 游标
|
||||
* 返 回 值:字节数;空指针返回 0 uint32_t
|
||||
* 限定条件:任意上下文(只读)
|
||||
*/
|
||||
uint32_t wave_store_pack_total(const wave_pack_ctx_t *p_ctx) {
|
||||
return (p_ctx == NULL) ? 0u : p_ctx->total;
|
||||
}
|
||||
|
||||
/* ===== 段文件名轮转(只算名字,不碰文件系统) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:内部——把两位十进制写进缓冲
|
||||
* 入口参数:p 目标(至少 2 字节);a_idx 0..99
|
||||
* 返 回 值:无 void
|
||||
* 限定条件:a_idx 已由调用方限制在 0..99
|
||||
*/
|
||||
static void wave_st_put_idx2(char *p, uint8_t a_idx) {
|
||||
p[0] = (char)('0' + (a_idx / 10u));
|
||||
p[1] = (char)('0' + (a_idx % 10u));
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按段号生成段文件路径
|
||||
* 入口参数:p_out 缓冲;a_cap 容量;a_idx 段号 0..99
|
||||
* 返 回 值:>= 0 写入字符数(不含结束符);WAVE_ST_E_PARAM / WAVE_ST_E_RANGE int
|
||||
* 限定条件:任意上下文;纯字符拼装(不用格式化函数),板端与 PC 侧逐字节一致
|
||||
* 函数说明:形态 = 目录 / 前缀 + 两位十进制 + 扩展名,严格 8.3(§5.2 第三条)。
|
||||
*/
|
||||
int wave_store_seg_path(char *p_out, uint32_t a_cap, uint8_t a_idx) {
|
||||
static const char s_dir[] = WAVE_ST_DIR;
|
||||
static const char s_pfx[] = WAVE_ST_PFX;
|
||||
static const char s_ext[] = WAVE_ST_EXT;
|
||||
uint32_t pos = 0u;
|
||||
uint32_t i;
|
||||
char buf[WAVE_ST_PATH_MAX];
|
||||
|
||||
if (p_out == NULL) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
if (a_idx >= (uint8_t)WAVE_ST_SEG_MAX) {
|
||||
return WAVE_ST_E_RANGE;
|
||||
}
|
||||
for (i = 0u; i < ((uint32_t)sizeof(s_dir) - 1u); i++) {
|
||||
buf[pos++] = s_dir[i];
|
||||
}
|
||||
buf[pos++] = '/';
|
||||
for (i = 0u; i < ((uint32_t)sizeof(s_pfx) - 1u); i++) {
|
||||
buf[pos++] = s_pfx[i];
|
||||
}
|
||||
wave_st_put_idx2(&buf[pos], a_idx);
|
||||
pos += 2u;
|
||||
for (i = 0u; i < ((uint32_t)sizeof(s_ext) - 1u); i++) {
|
||||
buf[pos++] = s_ext[i];
|
||||
}
|
||||
if (pos >= (uint32_t)sizeof(buf)) {
|
||||
/* 宏与拼装长度漂移时的兜底:宁可给不出名字,也绝不把结束符写到别人缓冲外 */
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
buf[pos] = '\0';
|
||||
/* 容量必须容得下结束符:写成 a_cap < pos 会放过「刚好差一个字节」的调用方,
|
||||
* 于是把 p_out[pos] 写到别人缓冲外面(越界写在板子上是静默的) */
|
||||
if (a_cap <= pos) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
for (i = 0u; i <= pos; i++) {
|
||||
p_out[i] = buf[i];
|
||||
}
|
||||
return (int)pos;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:下一个轮转段号
|
||||
* 入口参数:a_idx 当前段号(越界按 0 处理)
|
||||
* 返 回 值:下一个段号 uint8_t
|
||||
* 限定条件:任意上下文
|
||||
*/
|
||||
uint8_t wave_store_next_idx(uint8_t a_idx) {
|
||||
if (a_idx >= (uint8_t)WAVE_ST_SEG_MAX) {
|
||||
return 0u;
|
||||
}
|
||||
if (a_idx >= ((uint8_t)WAVE_ST_SEG_MAX - 1u)) {
|
||||
return 0u;
|
||||
}
|
||||
return (uint8_t)(a_idx + 1u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:内部——大小写无关地比对一个字符
|
||||
* 入口参数:a 待测字符(视作 ASCII);b 期望字符(取自段名宏)
|
||||
* 返 回 值:1 相同;0 不同 uint8_t
|
||||
* 限定条件:两侧都是 ASCII;非字母字符原样比
|
||||
* 函数说明:**两侧都**归一化成小写再比。只归一化一侧是个真踩过一次的坑——
|
||||
* 段名宏 WAVE_ST_PFX / WAVE_ST_EXT 是大写形态(FatFs 8.3 短名就是大写),
|
||||
* 只把待测串转小写等于「小写 vs 大写」永不相等,反解会拒绝掉全部合法段名
|
||||
* (单测 用例:首轮 100% 拒绝就是这个)。盘上大小写与宏写法
|
||||
* 谁都不该去猜,故两侧各归一一次。
|
||||
*/
|
||||
static uint8_t wave_st_ci_eq(char a, char b) {
|
||||
char x = a;
|
||||
char y = b;
|
||||
|
||||
if ((x >= 'A') && (x <= 'Z')) {
|
||||
x = (char)(x + ('a' - 'A'));
|
||||
}
|
||||
if ((y >= 'A') && (y <= 'Z')) {
|
||||
y = (char)(y + ('a' - 'A'));
|
||||
}
|
||||
return (x == y) ? 1u : 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:从文件名或路径反解段号
|
||||
* 入口参数:p_path 路径或纯文件名;p_idx 输出
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_NAME 不是本层命名形态 int
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:只认 basename 上的「wave + 两位数字 + .bin」(大小写都收)。
|
||||
* 不匹配就返回错误,**绝不**把陌生名字当录波段——扫目录重建段账时误认一个
|
||||
* 别的文件,等于把别人的文件当波形解释(名字/参数类错误码严禁升级成卷操作,
|
||||
* 存储架构说明 §10.1 同一条教训)。
|
||||
*/
|
||||
int wave_store_idx_from_name(const char *p_path, uint8_t *p_idx) {
|
||||
static const char s_pfx[] = WAVE_ST_PFX;
|
||||
static const char s_ext[] = WAVE_ST_EXT;
|
||||
const char *base = p_path;
|
||||
const char *q = p_path;
|
||||
uint32_t i;
|
||||
uint32_t len = 0u;
|
||||
uint8_t hi;
|
||||
uint8_t lo;
|
||||
|
||||
if ((p_path == NULL) || (p_idx == NULL)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
/* 找 basename:支持反斜杠形态(PC 侧脚本拼出来的路径可能被原样传进来) */
|
||||
while (*q != '\0') {
|
||||
if ((*q == '/') || (*q == '\\')) {
|
||||
base = q + 1;
|
||||
}
|
||||
q++;
|
||||
}
|
||||
q = base;
|
||||
while ((*q != '\0') && (len < (uint32_t)WAVE_ST_PATH_MAX)) {
|
||||
q++;
|
||||
len++;
|
||||
}
|
||||
if ((*q != '\0') || (len != ((uint32_t)sizeof(s_pfx) - 1u) + 2u +
|
||||
((uint32_t)sizeof(s_ext) - 1u))) {
|
||||
return WAVE_ST_E_NAME;
|
||||
}
|
||||
for (i = 0u; i < ((uint32_t)sizeof(s_pfx) - 1u); i++) {
|
||||
if (wave_st_ci_eq(base[i], s_pfx[i]) == 0u) {
|
||||
return WAVE_ST_E_NAME;
|
||||
}
|
||||
}
|
||||
hi = (uint8_t)base[i];
|
||||
lo = (uint8_t)base[i + 1u];
|
||||
if ((hi < '0') || (hi > '9') || (lo < '0') || (lo > '9')) {
|
||||
return WAVE_ST_E_NAME;
|
||||
}
|
||||
for (i = (i + 2u); i < len; i++) {
|
||||
if (wave_st_ci_eq(base[i], s_ext[i - ((uint32_t)sizeof(s_pfx) - 1u) - 2u]) == 0u) {
|
||||
return WAVE_ST_E_NAME;
|
||||
}
|
||||
}
|
||||
*p_idx = (uint8_t)(((hi - '0') * 10u) + (lo - '0'));
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
* 模块名称:录波段格式层(故障录波 · 段文件字节布局编解码 + 打包游标 + 段名轮转)
|
||||
* 模块功能:把「一段录波」在内存字节缓冲与结构化元数据之间逐字节翻译,并给落盘道
|
||||
* 提供分次搬运的打包游标。口径唯一属主 = docs/子系统设计/故障录波设计说明.md §6.2
|
||||
* (字节布局)与 §6.3(九问裁定:板端编码器与 PC 解码器 test/host/wave_read.py
|
||||
* 两份独立实现的共同依据)。本文件是它的机器可执行形态,不在此重新定义任何口径;
|
||||
* 两处冲突以设计文档为准并回报 lead,不在代码里单方面「修正」。
|
||||
* 纯逻辑层纪律:不 include HAL / CMSIS / RTOS 头、不调 FatFs、不碰文件系统、
|
||||
* 不建任务、不打日志(文件 I/O 与任务钩子全在接线层 App/task/ 里做),
|
||||
* 因此可被主机侧单测 test/host/wave_store/ 原样纳入做逐字节断言。
|
||||
* 职责边界(为什么这样切):
|
||||
* 1) 固定头 + 通道表编解码、头 CRC 与数据区 CRC 自检 —— 格式知识只此一处;
|
||||
* 2) 打包游标(分次搬运):文件系统唯一写者只要「每次给我不超过 256 字节的下一片」,
|
||||
* 整段 5250 字节不驻留 RAM(设计说明 §7:搬运缓冲 256 B × 2,不做整段驻留);
|
||||
* 3) 段文件名轮转与反解:只算名字,不打开文件(FatFs FF_USE_LFN=0 只能严格
|
||||
* 8.3 短名,见 docs/架构设计/STM32F4-Base存储架构说明.md §10.1);
|
||||
* 4) 数据区偏移换算 + 小端解码:回放页只画一路(设计说明 §8.1),按通道分列
|
||||
* 让「只读那一路」成为一次连续读。
|
||||
* 不链 App/algo/adc_cal(零依赖 = 主机免桩可测):通道表里的 k_q16 / b_codes /
|
||||
* 可用性由调用方在封存瞬间取 int32 原值填进 meta 后交给本层编码。系数必须走
|
||||
* adc_cal_k_q16()(接线道补的 int32 直读入口),不得经 adc_cal_k_uv() 的 float
|
||||
* 通道回算 —— float 在 5.28e7 量级掉尾数(设计说明 §6.3「k / b 宽度与角色」行)。
|
||||
* CRC 一张表的原始说法不成立,lead 已在 §6.3 记录实测依据:全仓无可复用的
|
||||
* CRC-16 表(App/util/README.md 记 crc.c 与 crc.h 已于 2026-10-02 删除;屏板链路
|
||||
* Drivers/BSP/UI/ui_net_sink.c 的 remo_crc() 是单字节异或、不是 CRC-16)。
|
||||
* 故本层自带无表位运算 CRC-16/MODBUS,并用规范锚点自校验。
|
||||
* 适用平台:STM32F407ZGTx(Cortex-M4,小端);主机侧单测 test/host/wave_store/
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29(故障录波 task-1 · 数据面纯逻辑层)
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建:段头/通道表编解码、CRC-16/MODBUS 无表实现、
|
||||
* 打包游标、段名轮转与反解、数据区偏移换算与小端解码
|
||||
*
|
||||
* 二期不返工的结构约束(设计说明 §2.1,改本层前先读):
|
||||
* 1) 采样率与每周波点数是写进文件头的数据,不是编译期假设 —— 回放端与 PC 脚本一律
|
||||
* 从头里读,本层算长度也照 meta 里的值算,不在代码里写死 1000 / 20;
|
||||
* 2) 版本号并进魔数第三字节,换格式即变;本层解到不认识的版本一律判废;
|
||||
* 3) 「带 TEMP 就要连 flags_seq 与 clip_mask 一起升版本」同源(§6.2 那两行的括注),
|
||||
* 所以本层的通道数上限留到 9,但本波实际只用 8。
|
||||
*/
|
||||
|
||||
#ifndef WAVE_STORE_H
|
||||
#define WAVE_STORE_H
|
||||
|
||||
/* 同 App/algo/wave_ring.h:本头的 NULL 只出现在注释里(字段与入参的约束说明),
|
||||
* 代码只用 stdint 的类型,故不 include stddef.h;用 NULL 的 .c 自己带。 */
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ===== 一、段文件布局常量(逐字段照 docs/子系统设计/故障录波设计说明.md §6.2 与 §6.3) ===== */
|
||||
|
||||
/* 魔数前两字节固定 'W' 与 'R',第三字节 = 版本号(设计说明 §6.2 偏移 0 行) */
|
||||
#define WAVE_ST_MAGIC0 (0x57u) /* 'W' */
|
||||
#define WAVE_ST_MAGIC1 (0x52u) /* 'R' */
|
||||
/* 版本:本波恒 1。ver 字段必须同时等于魔数第三字节与本宏,否则整段判废(§6.2 末行) */
|
||||
#define WAVE_ST_VER (1u)
|
||||
|
||||
/* 固定头字段偏移(字节,小端) */
|
||||
#define WAVE_ST_OFF_MAGIC (0u) /* 3 字节:'W' 'R' ver */
|
||||
#define WAVE_ST_OFF_VER (3u) /* 1 字节 */
|
||||
#define WAVE_ST_OFF_FS_HZ (4u) /* 2 字节 本波 1000 */
|
||||
#define WAVE_ST_OFF_PTS_CYC (6u) /* 1 字节 本波 20 */
|
||||
#define WAVE_ST_OFF_CH_CNT (7u) /* 1 字节 本波 8 */
|
||||
#define WAVE_ST_OFF_PTS_CH (8u) /* 2 字节 本波 320 */
|
||||
#define WAVE_ST_OFF_PRE_PTS (10u) /* 2 字节 本波 160 */
|
||||
#define WAVE_ST_OFF_TRIG_SRC (12u) /* 1 字节 */
|
||||
#define WAVE_ST_OFF_TRIG_ELM (13u) /* 1 字节 */
|
||||
#define WAVE_ST_OFF_T0_MS (14u) /* 4 字节 u32 */
|
||||
#define WAVE_ST_OFF_WALL (18u) /* 4 字节 u32 */
|
||||
#define WAVE_ST_OFF_ENG (22u) /* 1 字节 */
|
||||
#define WAVE_ST_OFF_FLAGS (23u) /* 1 字节 */
|
||||
#define WAVE_ST_OFF_CLIP (24u) /* 2 字节 */
|
||||
#define WAVE_ST_OFF_SEQ (26u) /* 4 字节 u32 */
|
||||
#define WAVE_ST_OFF_TBL (30u) /* 通道表起点 */
|
||||
|
||||
/* 通道表一路 12 字节 = 语义号 1 + 来源转换器 1 + 位宽 1 + 可用性 1 + k_q16 4 + b_codes 4
|
||||
* 设计说明 §6.3「LSB 从哪来」:不设独立 LSB 字段,k_q16 就是 LSB(单位 µV/码 × 65536) */
|
||||
#define WAVE_ST_CH_OFF_SEM (0u) /* 1 字节 */
|
||||
#define WAVE_ST_CH_OFF_CONV (1u) /* 1 字节 */
|
||||
#define WAVE_ST_CH_OFF_BITS (2u) /* 1 字节 */
|
||||
#define WAVE_ST_CH_OFF_AVAIL (3u) /* 1 字节 = adc_cal_flags(sem) 低 8 位原值,不自造位义 */
|
||||
#define WAVE_ST_CH_OFF_KQ16 (4u) /* 4 字节 int32 */
|
||||
#define WAVE_ST_CH_OFF_BCODE (8u) /* 4 字节 int32 */
|
||||
#define WAVE_ST_CH_BYTES (12u)
|
||||
|
||||
/* 本波通道数固定 8(TEMP 不录:理由见 §6.3 末段与 docs/任务规划/数据可信.md §7) */
|
||||
#define WAVE_ST_CH_NUM (8u)
|
||||
/* 通道数上限:给二期「带 TEMP 且同时升版本」留位;真正的口径是 meta 里的 ch_count 字段 */
|
||||
#define WAVE_ST_CH_MAX (9u)
|
||||
/* 每通道点数默认(= 预触发 + 后触发 = 160 + 160;长度是文件里的数据,宏只作默认值) */
|
||||
#define WAVE_ST_PTS_DEF (320u)
|
||||
#define WAVE_ST_PRE_DEF (160u)
|
||||
#define WAVE_ST_FS_DEF (1000u)
|
||||
#define WAVE_ST_PPC_DEF (20u)
|
||||
|
||||
/* 尺寸一律由字段算出,不写死字面量(改通道数/点数时自动跟着走):
|
||||
* 头 CRC 覆盖区 = 30 + 12 × ch_count;头 CRC 自身紧随其后 2 字节;数据区自 32 + 12 × ch 起
|
||||
* ch_count = 8 时:覆盖区 126、整头 128、数据区 5120、整段 5250(§6.3「CRC 覆盖范围」行) */
|
||||
#define WAVE_ST_HEAD_CRC_POS(ch) ((uint32_t)(WAVE_ST_OFF_TBL) + \
|
||||
((uint32_t)(ch) * WAVE_ST_CH_BYTES))
|
||||
#define WAVE_ST_HEAD_BYTES(ch) ((uint32_t)(WAVE_ST_HEAD_CRC_POS(ch)) + 2u)
|
||||
#define WAVE_ST_DATA_BYTES(ch, pts) ((uint32_t)(pts) * (uint32_t)(ch) * 2u)
|
||||
#define WAVE_ST_SEG_TOTAL(ch, pts) (WAVE_ST_HEAD_BYTES(ch) + \
|
||||
WAVE_ST_DATA_BYTES(ch, pts) + 2u)
|
||||
/* 本波实际值(只给注释与单测引用,代码里一律用上面的参数化形态) */
|
||||
#define WAVE_ST_HEAD_SZ_CUR (128u)
|
||||
#define WAVE_ST_SEG_SZ_CUR (5250u)
|
||||
|
||||
/* 段目录与轮转(设计说明 §5.2 第三条:只能严格 8.3 短名;目录与 /log 平级、FTP 根下可见)
|
||||
* 名字形态 = 目录 + 前缀 + 两位十进制 + 扩展名,号 0..99 共 100 个轮转覆盖 */
|
||||
#define WAVE_ST_DIR "/wave"
|
||||
#define WAVE_ST_PFX "WAVE"
|
||||
#define WAVE_ST_EXT ".BIN"
|
||||
#define WAVE_ST_SEG_MAX (100u)
|
||||
#define WAVE_ST_PATH_MAX (17u) /* /wave/WAVE00.BIN = 16 字符 + 结束符 = 17 字节 */
|
||||
|
||||
/* CRC-16/MODBUS:多项式 0x8005 反射形态 = 0xA001、初值 0xFFFF、异或输出 0、低字节在前 */
|
||||
#define WAVE_ST_CRC_POLY (0xA001u)
|
||||
#define WAVE_ST_CRC_INIT (0xFFFFu)
|
||||
#define WAVE_ST_CRC_XOROUT (0x0000u)
|
||||
/* 自检锚点:串 123456789 的 CRC(§6.3 首行钉的常量) */
|
||||
#define WAVE_ST_CRC_CHECK (0x4B37u)
|
||||
|
||||
/* ===== 二、返回码(int 通道;调用方必须接住或显式 (void),不得裸丢) ===== */
|
||||
|
||||
#define WAVE_ST_OK (0) /* 成功 */
|
||||
#define WAVE_ST_DONE (1) /* 打包游标:整段已产完(不是错误) */
|
||||
#define WAVE_ST_E_PARAM (-1) /* 空指针 / 容量不足 / 字段非法 */
|
||||
#define WAVE_ST_E_CRC (-2) /* CRC 不符(头或数据区):整段判废 */
|
||||
#define WAVE_ST_E_FORMAT (-3) /* 魔数不符 / 版本不认识 / 表内语义号不等于下标 */
|
||||
#define WAVE_ST_E_SIZE (-4) /* 长度为 0 或长度与头部字段矛盾:整段判废 */
|
||||
#define WAVE_ST_E_RANGE (-5) /* 列号 / 点下标 / 段号越界 */
|
||||
#define WAVE_ST_E_STATE (-6) /* 状态错:未封存就读、未 start 就步进、缺数据源 */
|
||||
#define WAVE_ST_E_NAME (-7) /* 文件名不是本层命名形态(扫目录重建段账时用) */
|
||||
|
||||
/* ===== 三、类型 ===== */
|
||||
|
||||
/* 触发源(§6.2 偏移 12 的取值;0 表示「无触发」,只出现在段外的状态查询里) */
|
||||
typedef enum {
|
||||
WAVE_TRIG_NONE = 0, /* 无 */
|
||||
WAVE_TRIG_MANUAL = 1, /* 手动(UI「手动录波」确认,task-2 接线) */
|
||||
WAVE_TRIG_PROTECT = 2, /* 保护启动(App/algo/prot_elm.c 的 picked 上升沿) */
|
||||
WAVE_TRIG_BENCH = 3 /* bench 定周期自抓(只在 DTU_BENCH 构建里有这条来路) */
|
||||
} wave_trig_src_t;
|
||||
|
||||
/* 折算档位(§6.2 偏移 22;单位一律以头里的 eng 为准,PC 不许按当前固件口径猜) */
|
||||
typedef enum {
|
||||
WAVE_ENG_PIN = 0, /* 引脚域(未标定;V0.1 产品构建停在这档) */
|
||||
WAVE_ENG_SEC = 1, /* 二次值 */
|
||||
WAVE_ENG_PRI = 2 /* 一次值 */
|
||||
} wave_eng_t;
|
||||
|
||||
/* 通道表一路的内存形态(字段序 = §6.2 通道表字节序) */
|
||||
typedef struct {
|
||||
uint8_t sem; /* 语义号(adc_semantic_t 值;必须等于表内下标,§6.3) */
|
||||
uint8_t conv; /* 来源转换器(adc_source_t 值:0 = TPAFE,1 = STM32) */
|
||||
uint8_t bits; /* 转换器输出位宽(TPAFE 16 / STM32 12) */
|
||||
uint8_t avail; /* 可用性 = adc_cal_flags(sem) 低 8 位原值(不自造位义) */
|
||||
int32_t k_q16; /* 封存瞬间生效系数:µV/码 × 65536,就是 LSB */
|
||||
int32_t b_codes; /* 封存瞬间生效零点偏移:码值 */
|
||||
} wave_ch_desc_t;
|
||||
|
||||
/* 段头元数据 = 固定头 + 通道表的内存形态。
|
||||
* 这是「待编码的段头」,不是「已写盘的证据」:打包游标只读它、不改它。 */
|
||||
typedef struct {
|
||||
uint16_t fs_hz; /* 采样率 Hz(从文件读出来的数据,不是编译期常量) */
|
||||
uint8_t pts_per_cycle; /* 每周波点数 */
|
||||
uint8_t ch_count; /* 通道数(本波 8) */
|
||||
uint16_t pts_per_ch; /* 每通道点数(本波 320) */
|
||||
uint16_t pre_pts; /* 预触发点数(本波 160;回放画触发光标用) */
|
||||
uint8_t trig_src; /* 触发源(wave_trig_src_t) */
|
||||
uint8_t trig_elm; /* 触发元件序号(prot_elm 实例下标;手动与定时为 0) */
|
||||
uint32_t t0_ms; /* 段首相对上电毫秒 */
|
||||
uint32_t wall; /* 对时后的 epoch 秒(未对时 0) */
|
||||
uint8_t eng; /* 折算档位(wave_eng_t) */
|
||||
uint8_t flags_seq; /* bit s = 语义通道 s 的 ADC_CAL_F_VALID(§6.3) */
|
||||
uint16_t clip_mask; /* bit s = 语义通道 s 的 ADC_CAL_F_CLIPPED(V0.1 恒 0xE6) */
|
||||
uint32_t frame_seq; /* 段首帧序号(与串口周期行的 frames 对账) */
|
||||
wave_ch_desc_t ch[WAVE_ST_CH_MAX]; /* 通道表(前 ch_count 项有效) */
|
||||
} wave_seg_meta_t;
|
||||
|
||||
/* 打包游标阶段(对外只用于「已搬完」判定与观测日志;推进全在 wave_store_pack_next 内部) */
|
||||
typedef enum {
|
||||
WAVE_PACK_IDLE = 0, /* 未初始化 */
|
||||
WAVE_PACK_HEAD = 1, /* 正在产头(固定头 + 通道表 + 头 CRC) */
|
||||
WAVE_PACK_DATA = 2, /* 正在产数据区(按通道分列,一列一列产) */
|
||||
WAVE_PACK_TAIL = 3, /* 只剩数据区 CRC 两字节 */
|
||||
WAVE_PACK_DONE = 4 /* 整段已产完 */
|
||||
} wave_pack_phase_t;
|
||||
|
||||
/* 数据源回调:把「第 a_ch 列、段内第 a_from 点起的 a_cnt 个点」取进 p_dst。
|
||||
* 返 回 值:实际拷贝的点数(等于 a_cnt 表示全给到);负 = 错误码 int32_t
|
||||
* 只允许读已封存区:本层不提供、也不允许任何跨任务读活缓冲的路径
|
||||
* (设计说明 §5.2 第一条陷阱:App/algo/sample_ring.c 的 sample_ring_window() 无临界区,
|
||||
* 本波不复用它,跨任务只传「已封存」标志)。
|
||||
* 生产实现 = wave_ring_read_cb()(签名一致,可直接传给 wave_store_pack_start)。 */
|
||||
typedef int32_t (*wave_reader_fn)(void *p_user, uint8_t a_ch, uint32_t a_from,
|
||||
uint32_t a_cnt, int16_t *p_dst);
|
||||
|
||||
/* 打包游标:段文件字节流的「拉模式」状态机。
|
||||
* 为什么拉模式 + 小缓冲:defaultTask 是文件系统唯一写者,落盘窗口几十毫秒量级,
|
||||
* 一次心跳最多一段;整段驻留 RAM 既超预算也没必要(设计说明 §7)。
|
||||
* 游标不校验写回字节数 —— 那是调用方对着 f_write 做的事(§5.2 第二条陷阱:本移植
|
||||
* f_write 簇分配失败会返 FR_OK + 短写、没有错误码,必须靠比对写回字节数判失败)。 */
|
||||
typedef struct {
|
||||
uint8_t phase; /* wave_pack_phase_t */
|
||||
uint8_t ch; /* 数据区当前列 */
|
||||
uint16_t head_done; /* 头区已产出字节数(含头 CRC 两字节) */
|
||||
uint16_t ch_done; /* 当前列已产出点数 */
|
||||
uint8_t tail_done; /* 尾部数据 CRC 已产出字节数(0..2) */
|
||||
uint8_t rsv; /* 补齐用,保持结构体偶数字节 */
|
||||
uint16_t crc_data; /* 数据区运行 CRC(增量中间值,避免整段驻留) */
|
||||
uint32_t total; /* 整段期望字节数 */
|
||||
uint32_t sent; /* 已产出字节数(= 下一片的文件偏移) */
|
||||
wave_seg_meta_t meta; /* 段头快照(start 时拷入;搬运期间调用方改自己那份不影响) */
|
||||
wave_reader_fn p_reader; /* 数据源;NULL = 只产头 */
|
||||
void *p_user; /* 数据源上下文 */
|
||||
} wave_pack_ctx_t;
|
||||
|
||||
/* ===== 四、CRC-16/MODBUS(无表位运算) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:CRC-16/MODBUS 增量计算(输入输出均低位在前)
|
||||
* 入口参数:a_crc 上一次的中间值(首次传 WAVE_ST_CRC_INIT)
|
||||
* p_data 数据指针(NULL 且 a_len = 0 视为空操作,原样返回 a_crc)
|
||||
* a_len 字节数
|
||||
* 返 回 值:新的中间值(写文件时低字节在前,见 §6.3 首行) uint16_t
|
||||
* 限定条件:任意上下文(含 adcTask 之外的 defaultTask / uiTask);纯计算、无静态状态、
|
||||
* 不碰外设、不取临界区
|
||||
* 函数说明:为什么自带实现 —— 全仓没有可复用的 CRC-16 表(依据见本文件头注)。
|
||||
* 无表 = 零新增表 RAM 与表 Flash;数据区 5120 字节每字节 8 次移位,在 168 MHz
|
||||
* 上量级是几十微秒,且只在 defaultTask 的落盘链上出现,不在 1 kHz 采集链上。
|
||||
*/
|
||||
uint16_t wave_store_crc_update(uint16_t a_crc, const uint8_t *p_data, uint32_t a_len);
|
||||
|
||||
/*
|
||||
* 函数功能:CRC-16/MODBUS 一次算完(从初值起步)
|
||||
* 入口参数:p_data 数据指针
|
||||
* a_len 字节数
|
||||
* 返 回 值:CRC uint16_t
|
||||
* 限定条件:任意上下文。p_data 为 NULL 或 a_len = 0 时返回 WAVE_ST_CRC_INIT —— 这是
|
||||
* 空输入的定义值,不是「算好的段 CRC」:零长度一律判废,调用方必须先判长度
|
||||
* (设计说明 §6.2 末行:长度为 0 → 整段作废)
|
||||
*/
|
||||
uint16_t wave_store_crc(const uint8_t *p_data, uint32_t a_len);
|
||||
|
||||
/*
|
||||
* 函数功能:CRC 自检 —— 用规范锚点验算实现是否仍是 CRC-16/MODBUS
|
||||
* 入口参数:无
|
||||
* 返 回 值:WAVE_ST_OK 相符;WAVE_ST_E_FORMAT 不符(实现或编译配置被改坏) int
|
||||
* 限定条件:任意上下文;开机或首次落盘前调一次即可,不在每拍路径上
|
||||
* 函数说明:锚点 = 串 123456789 的 CRC = 0x4B37(§6.3)。这条是机器可执行的口径锚,
|
||||
* 防的是「自算自验永远相符」:只有对已知常数才咬得住(换机、换编译器、
|
||||
* 反射方向写反都在这里暴露)。落盘前失败就自弃本段并计入 err(§9 观测行)。
|
||||
*/
|
||||
int wave_store_crc_selftest(void);
|
||||
|
||||
/* ===== 五、段头编解码 ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:把段头元数据填成本波默认值(只填格式侧的静态事实)
|
||||
* 入口参数:p_meta 输出(调用方持有的结构;本层不返回静态地址)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||||
* 限定条件:任意上下文;不碰文件系统
|
||||
* 函数说明:填 fs_hz = 1000、pts_per_cycle = 20、ch_count = 8、pts_per_ch = 320、
|
||||
* pre_pts = 160,以及通道表的 sem = 下标 + conv / bits 按两片转换器
|
||||
* (UA / UN / IA 走 TPAFE 16 位;UB / UC / IB / IC / IN 走片内 ADC 12 位 ——
|
||||
* 通道归属口径属主是 docs/硬件平台/ADC通道映射说明.md,本函数只是它的抄本,
|
||||
* 单测里对 0xE6 的算术交叉验证会同时钉住这套顺序)。
|
||||
* 不填 avail / k_q16 / b_codes / trig_* / t0_ms / wall / eng / flags_seq /
|
||||
* clip_mask / frame_seq:那些是封存瞬间的事实,必须由调用方现取现填
|
||||
* (adc_cal_k_q16 / adc_cal_flags / sys_clock / wave_ring),本层不链它们。
|
||||
*/
|
||||
int wave_store_meta_init(wave_seg_meta_t *p_meta);
|
||||
|
||||
/*
|
||||
* 函数功能:把段头元数据编码成段文件头字节流(固定头 + 通道表 + 头 CRC,全小端)
|
||||
* 入口参数:p_meta 段头元数据(只读)
|
||||
* p_out 输出缓冲
|
||||
* a_cap 缓冲容量(须不小于按 ch_count 算出的整头字节数)
|
||||
* 返 回 值:不小于 0 = 实际写入字节数;负 = WAVE_ST_E_* int
|
||||
* 限定条件:任意上下文;无静态缓冲、可重入;不碰文件系统
|
||||
* 函数说明:编码前先自检字段一致性 —— ch_count 为 0 或越界、pts_per_ch = 0、
|
||||
* pre_pts 大于 pts_per_ch、表内语义号不等于下标(§6.3「通道表序与列序」那条
|
||||
* PC 侧判据在编码侧同样成立)一律拒绝:不把坏头写成看起来像样的文件。
|
||||
*/
|
||||
int wave_store_head_build(const wave_seg_meta_t *p_meta, uint8_t *p_out, uint32_t a_cap);
|
||||
|
||||
/*
|
||||
* 函数功能:把段文件头字节流解回元数据,并做全套判废检查(魔数 / 版本 / 长度 / 表序 / 头 CRC)
|
||||
* 入口参数:p_buf 头字节流
|
||||
* a_len 可读字节数
|
||||
* p_meta 输出元数据(判废时内容不保证有效,调用方不得消费)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针或缓冲不足;WAVE_ST_E_FORMAT 魔数 / 版本 /
|
||||
* 表序不符;WAVE_ST_E_SIZE 零长度或长度与字段矛盾;WAVE_ST_E_CRC 头 CRC 不符 int
|
||||
* 限定条件:任意上下文(uiTask 与 defaultTask 都可调)。UI 与 PC 侧必须用同一套判废语义:
|
||||
* 解不过就只能显示「该段不可读」,绝不允许退化画一条直线
|
||||
* (设计说明 §8.1 空态行:画直线等于伪造证据)
|
||||
* 函数说明:a_len 不足「按头里 ch_count 推出来的整头长度」时按缓冲不足返回,不猜没读到
|
||||
* 的字节;ver 不等于魔数第三字节、或不等于 WAVE_ST_VER 一律判废。
|
||||
*/
|
||||
int wave_store_head_parse(const uint8_t *p_buf, uint32_t a_len, wave_seg_meta_t *p_meta);
|
||||
|
||||
/* ===== 六、整段校验与长度 / 偏移换算(读侧) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:整段镜像自检 —— 头 + 数据区 + 数据 CRC 三道全过才算「这一段可读」
|
||||
* 入口参数:p_img 整段文件镜像(调用方已读进内存)
|
||||
* a_len 镜像长度
|
||||
* p_meta 输出段头(可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_SIZE 长度为 0 或与期望总长不符;
|
||||
* WAVE_ST_E_CRC 任一 CRC 不符;WAVE_ST_E_FORMAT 头本身判废 int
|
||||
* 限定条件:任意上下文;只在整段驻留(本波 5250 字节)时用。回放页不要走这条 ——
|
||||
* 用 wave_store_head_parse + wave_store_data_off + wave_store_crc_update 分段查,
|
||||
* 才符合 §7 的 256 字节读窗预算
|
||||
* 函数说明:这是「段文件 CRC 不符时脚本必须拒绝而不是画出东西」那条判据在板侧的同一份
|
||||
* 实现,也是本层与 PC 解码器逐字节同解的对照物(两套主机单测互验)。
|
||||
*/
|
||||
int wave_store_seg_check(const uint8_t *p_img, uint32_t a_len, wave_seg_meta_t *p_meta);
|
||||
|
||||
/*
|
||||
* 函数功能:按头部字段算整段文件应有的字节数(写侧记账与读侧长度判废共用)
|
||||
* 入口参数:p_meta 段头元数据
|
||||
* 返 回 值:期望字节数(8 路 320 点 = 5250);字段非法(含零长度)返回 0 uint32_t
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:返回 0 就是「这一段连长度都不成立」,据此判废。不要拿 0 去开缓冲,
|
||||
* 也不要拿 0 去比对 f_write 的写回字节数(那会把失败读成成功)。
|
||||
*/
|
||||
uint32_t wave_store_seg_size(const wave_seg_meta_t *p_meta);
|
||||
|
||||
/*
|
||||
* 函数功能:整头字节数(固定头 + 通道表 + 头 CRC 两字节)
|
||||
* 入口参数:a_ch_count 通道数
|
||||
* 返 回 值:字节数;通道数为 0 或越界返回 0 uint32_t
|
||||
* 限定条件:任意上下文
|
||||
*/
|
||||
uint32_t wave_store_head_bytes(uint8_t a_ch_count);
|
||||
|
||||
/*
|
||||
* 函数功能:数据区某一点在段文件里的绝对偏移(按通道分列 → 单通道是一整段连续区)
|
||||
* 入口参数:a_ch_count 通道数
|
||||
* a_pts_ch 每通道点数
|
||||
* a_ch 列号(= 通道表下标 = 语义号,0 到 a_ch_count-1)
|
||||
* a_pt 段内点下标(0 到 a_pts_ch-1)
|
||||
* 返 回 值:文件偏移;参数非法返回 0xFFFFFFFFu 哨兵(这个值不是任何合法偏移) uint32_t
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:回放页据此定位「只读那一路」,配合 wave_store_data_bytes_avail 一次连续读;
|
||||
* PC 侧同一算法,两份实现互验。
|
||||
*/
|
||||
uint32_t wave_store_data_off(uint8_t a_ch_count, uint16_t a_pts_ch,
|
||||
uint8_t a_ch, uint32_t a_pt);
|
||||
|
||||
/*
|
||||
* 函数功能:从某点起当前列还能连续读多少字节(不跨列、不越段尾)
|
||||
* 入口参数:a_pts_ch 每通道点数
|
||||
* a_pt 起始点下标
|
||||
* a_want 想读的点数
|
||||
* 返 回 值:可连续读字节数 = 2 × 实际可给点数;参数非法返回 0 uint32_t
|
||||
* 限定条件:任意上下文
|
||||
*/
|
||||
uint32_t wave_store_data_bytes_avail(uint16_t a_pts_ch, uint32_t a_pt, uint32_t a_want);
|
||||
|
||||
/*
|
||||
* 函数功能:把段文件读出的小端字节解码成 int16 点序列(按时间序,旧到新)
|
||||
* 入口参数:p_raw 原始字节(来自文件,2 × 点数)
|
||||
* a_len 字节数(须为偶数,且不大于 a_max_pts × 2)
|
||||
* p_out 输出 int16 数组
|
||||
* a_max_pts 输出容量(点数)
|
||||
* p_pts 输出实际点数(可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 / 奇数字节 / 容量不足 int
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:显式按小端逐字节拼装,不依赖宿主字节序,所以板端与 PC 侧同一判据。
|
||||
* 片内 ADC 那几路的值本就是 0..4095 的原样正数,不做中点偏置
|
||||
* (§6.3「样本编码」行:加偏置等于伪造双极性)。
|
||||
*/
|
||||
int wave_store_le_decode(const uint8_t *p_raw, uint32_t a_len,
|
||||
int16_t *p_out, uint32_t a_max_pts, uint32_t *p_pts);
|
||||
|
||||
/*
|
||||
* 函数功能:把一路的 int16 码值折成定点毫单位(回放页峰值标注用;纯整数,不用 float)
|
||||
* 入口参数:p_desc 该路通道表项(k_q16 / b_codes 来自段文件头,不来自当前固件)
|
||||
* a_code 原始码值
|
||||
* p_milli 输出四舍五入后的毫单位(k_q16 是 µV/码 → 输出单位 mV)(不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||||
* 限定条件:任意上下文(只在回放页与单测里用,不在 1 kHz 采集链上;内部用 int64 中间量,
|
||||
* 落在这条路径上时可接受)
|
||||
* 函数说明:算式 eng = (code + b) × k × 1e-6,与 App/algo/adc_cal.c 的 adc_cal_apply()
|
||||
* 同一模型(§6.3)。输出单位是 mV;单位含义由段头 eng 字段决定(eng = 0 是
|
||||
* 引脚域,别读成二次值)。必须用段文件里的 k / b,不许拿当前固件的系数解释
|
||||
* 老文件(§12 风险表「标定落地后老文件被解释错」)。
|
||||
*/
|
||||
int wave_store_code_to_milli(const wave_ch_desc_t *p_desc, int16_t a_code,
|
||||
int32_t *p_milli);
|
||||
|
||||
/* ===== 七、打包游标(分次搬运,给文件系统唯一写者) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:初始化打包游标(段头拷进游标,随后逐次拉整段字节流)
|
||||
* 入口参数:p_ctx 游标(调用方持有,栈上或静态都行;本层不记它的地址)
|
||||
* p_meta 段头元数据(内部拷贝;搬运期间调用方改自己那份不影响本段)
|
||||
* p_reader 数据源回调;NULL = 只产头(回放页只校验头 / 主机单测只测头)
|
||||
* p_user 数据源上下文(生产传 NULL,wave_ring 是单例不需要)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_FORMAT 段头字段非法;
|
||||
* WAVE_ST_E_SIZE 算不出合法长度 int
|
||||
* 限定条件:任意上下文(纯计算,无临界区);一段一个游标,搬完再 start 下一段
|
||||
* 函数说明:p_reader 为 NULL 而 ch_count 大于 0 时,游标仍能产头,但进数据区那一步
|
||||
* 返回 WAVE_ST_E_STATE —— 不静默跳过数据区,否则调用方会写出一个
|
||||
* 「长度看着对、内容是空的」段文件(那正是 §8.1 要防的伪造证据形态)。
|
||||
*/
|
||||
int wave_store_pack_start(wave_pack_ctx_t *p_ctx, const wave_seg_meta_t *p_meta,
|
||||
wave_reader_fn p_reader, void *p_user);
|
||||
|
||||
/*
|
||||
* 函数功能:取整段字节流的下一片(分次搬运的唯一推进口)
|
||||
* 入口参数:p_ctx 已 start 的游标
|
||||
* p_out 输出缓冲
|
||||
* a_cap 本片容量(建议 256 字节,见 §7;数据区不切半点,奇数容量会少产 1 字节)
|
||||
* p_len 输出本片实际字节数(不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK 产出本片且还有后续;WAVE_ST_DONE 整段已产完(*p_len 可为 0);
|
||||
* 负 = WAVE_ST_E_*(未 start / 容量为 0 / 数据源回调返错 / 游标状态错) int
|
||||
* 限定条件:任意上下文,但同一游标不得并发使用(文件系统唯一写者单线程推进)
|
||||
* 函数说明:头区字节流与 wave_store_head_build 完全一致(内部同一份写字段函数);
|
||||
* **头那一步要求 a_cap 不小于整头字节数**(本波 8 路 = 128),否则返回
|
||||
* WAVE_ST_E_PARAM 且不推进游标——不拆着头给,是为了让游标不必存 128 字节
|
||||
* 中间态;§7 的 256 字节搬运缓冲天然满足。
|
||||
* 数据区 CRC 走增量中间值,整段最后两片是数据 CRC 的低字节、高字节。
|
||||
* 调用方每写完一片都必须比对 f_write 写回字节数,短写按失败处理(§5.2)。
|
||||
*/
|
||||
int wave_store_pack_next(wave_pack_ctx_t *p_ctx, uint8_t *p_out,
|
||||
uint32_t a_cap, uint32_t *p_len);
|
||||
|
||||
/*
|
||||
* 函数功能:读游标已产出字节数(= 当前文件偏移;供记账、观测与「写没写全」判定)
|
||||
* 入口参数:p_ctx 游标
|
||||
* 返 回 值:已产出字节数;p_ctx 为 NULL 返回 0 uint32_t
|
||||
* 限定条件:任意上下文(只读)
|
||||
*/
|
||||
uint32_t wave_store_pack_offset(const wave_pack_ctx_t *p_ctx);
|
||||
|
||||
/*
|
||||
* 函数功能:读游标对应整段文件的期望总长(写完与 f_size / 累计写回字节数比对)
|
||||
* 入口参数:p_ctx 游标
|
||||
* 返 回 值:期望总字节数;p_ctx 为 NULL 返回 0 uint32_t
|
||||
* 限定条件:任意上下文(只读)
|
||||
*/
|
||||
uint32_t wave_store_pack_total(const wave_pack_ctx_t *p_ctx);
|
||||
|
||||
/* ===== 八、段文件名轮转(只算名字,不碰文件系统) ===== */
|
||||
|
||||
/*
|
||||
* 函数功能:按段号生成段文件路径(严格 8.3 短名:目录 + 前缀 + 两位十进制 + 扩展名)
|
||||
* 入口参数:p_out 输出缓冲(容量须不小于 WAVE_ST_PATH_MAX)
|
||||
* a_cap 容量
|
||||
* a_idx 段号 0 到 WAVE_ST_SEG_MAX-1
|
||||
* 返 回 值:不小于 0 = 写入的字符数(不含结束符;当前布局恒为 16,故缓冲至少要 17 字节);
|
||||
* WAVE_ST_E_PARAM 空指针或容量不足;
|
||||
* WAVE_ST_E_RANGE 段号越界 int
|
||||
* 限定条件:任意上下文;纯字符拼装(不用 sprintf,见 CODE-RULE-06 系),逐字节可预期
|
||||
* 函数说明:号 0..99 轮转覆盖,共 100 个名字,写满回绕盖最老的(§5.2 第三条)。
|
||||
* 本函数不检查文件是否存在、不建目录:那些都是接线道的职责。
|
||||
*/
|
||||
int wave_store_seg_path(char *p_out, uint32_t a_cap, uint8_t a_idx);
|
||||
|
||||
/*
|
||||
* 函数功能:下一个轮转段号(到 99 后回 0)
|
||||
* 入口参数:a_idx 当前段号(越界输入按 0 处理,不猜)
|
||||
* 返 回 值:下一个段号 uint8_t
|
||||
* 限定条件:任意上下文
|
||||
*/
|
||||
uint8_t wave_store_next_idx(uint8_t a_idx);
|
||||
|
||||
/*
|
||||
* 函数功能:从文件名或路径反解段号(掉电后靠扫目录重建段账用)
|
||||
* 入口参数:p_path 路径或纯文件名(只看最后一段,大小写都收)
|
||||
* p_idx 输出段号(不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_NAME 不是本层命名形态 int
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:只认「前缀 + 两位数字 + 扩展名」,其余(目录名、长名、以点开头)一律判不匹配
|
||||
* —— 扫目录重建账时不许把陌生名字当录波段;名字/参数类错误码严禁升级成卷操作
|
||||
* (docs/架构设计/STM32F4-Base存储架构说明.md §10.1 的同一条教训)。
|
||||
*/
|
||||
int wave_store_idx_from_name(const char *p_path, uint8_t *p_idx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WAVE_STORE_H */
|
||||
@@ -176,8 +176,8 @@ uint32_t iec104_pts_yc_ioa(uint8_t idx) {
|
||||
* 入口参数:q16 - 源侧品质(低字节与线上同值;高字节为 MEAS_QDS_CLIPPED 域内位)
|
||||
* 返 回 值:线上 QDS 字节 uint8_t
|
||||
* 函数说明:CLIPPED(硬件削顶,104 无对应位)映射为 IV——「削顶通道 104 恒
|
||||
* IV」口径(docs/任务规划/数据可信.md §8 风险表末行)改由快照品质
|
||||
* 驱动,不再靠点表绑死 -1;OV/NT/IV 透传,其余高位丢弃。
|
||||
* IV」口径(docs/任务规划/数据可信.md §8 风险表「削顶通道被当成可测」
|
||||
* 行)改由快照品质驱动,不再靠点表绑死 -1;OV/NT/IV 透传,其余高位丢弃。
|
||||
*/
|
||||
static uint8_t s_q_map(uint16_t q16) {
|
||||
uint8_t w = (uint8_t)(q16 & (uint16_t)(IEC104_QDS_OV | IEC104_QDS_NT
|
||||
|
||||
@@ -106,6 +106,15 @@
|
||||
* 不并进 cal: 行是因为 9 个码值会把该行推到 168 字符、
|
||||
* 撞 DBG_CON_LINE_MAX(192) 的静默截断边界(见该行注释)。
|
||||
* 无新增跨任务状态、无新增外设访问,栈 +36 B(局部码值数组)
|
||||
* v3.4 2026-10-29 DTU 项目组 故障录波数据面接线(task-2):新增 2c——每拍把 8 路
|
||||
* 语义原码压进 wave_ring 并 tick 推进;EVT_SEALED 那一拍现取
|
||||
* 折算/有效性/墙钟事实组 wave_seal_cal_t 并 latch_meta 恰好
|
||||
* 一次(握手次序照 App/algo/wave_ring.h 头注唯一合法次序);
|
||||
* 心跳末尾加 §9 唯一周期行 wave:(字段逐字,取数
|
||||
* wave_ring_get_stat);新增 2d——bench 定周期自抓,只在
|
||||
* DTU_BENCH 且 WAVE_BENCH_AUTOCAP 非 0 的双层编译门内存在,
|
||||
* 默认关闭 + 5 分钟时长上限 + 启动一次性告警(§10 红线);
|
||||
* adc_task_init 内接 wave_ring_init(init 禁日志约束不破)
|
||||
*/
|
||||
|
||||
#include "adc_task.h"
|
||||
@@ -123,6 +132,8 @@
|
||||
#include "console_log.h"
|
||||
#include "sys_log.h" /* SYSLOG_SELFTEST:采样故障留痕(仅故障才写) */
|
||||
#include "task_prof.h" /* 常开耗时探针:单拍耗时 */
|
||||
#include "wave_ring.h" /* 故障录波环(task-2 数据面接线):push/tick/latch_meta/get_stat */
|
||||
#include "sys_clock.h" /* 封存交棒的 wall 字段:sys_clock_get() epoch 秒(UINT32_MAX=未对时,写 0) */
|
||||
|
||||
#define DBG_TAG "[ADC_TASK]"
|
||||
#include "dbg_log.h"
|
||||
@@ -132,6 +143,17 @@
|
||||
* 消费节拍改由 TIM3 ISR 置位 ADC_WAKE_FLAG 提供,与生产同源,不再自定周期) */
|
||||
#define ADC_TASK_REPORT_MS 10000U
|
||||
|
||||
/* bench 定周期自抓(§10 红线「默认关闭 + 有时长上限 + 启动打一次告警」的落点):
|
||||
* WAVE_BENCH_AUTOCAP 默认 0——产品档连外层 DTU_BENCH 都没有,双层编译门保证
|
||||
* 产品镜像里 2d 段整段不存在;台架确实要定周期刷段时才本地改 1 重编译,
|
||||
* 用完立刻回 0(每 2 s 一段 = 每 5 分钟一轮 NAND 写,长挂白烧写次数,dhara
|
||||
* 坏块记账还是持久代价)。宏形态照 App/algo/adc_cal.c 的 adc_cal_prot_blocked():
|
||||
* 联合条件放 #if,未定义宏恒为假,不把 cppcheck 引到旁路上。
|
||||
* 周期/上限取自 §10 的算式:2 s/段、300 s 自动停。 */
|
||||
#define WAVE_BENCH_AUTOCAP 0
|
||||
#define WAVE_BENCH_PERIOD_MS 2000u
|
||||
#define WAVE_BENCH_MAX_MS 300000u
|
||||
|
||||
/* 统计:已消费帧数 / 无新帧次数(首拍必为无新帧,之后应稳定为 0 增量)。
|
||||
* 原始码值缓冲由 语义取数与有效性层 App/algo/adc_input.c 持有(消费侧按语义取数,不写死下标) */
|
||||
static uint32_t s_frames_consumed;
|
||||
@@ -157,6 +179,20 @@ static uint8_t s_rail_syslogged;
|
||||
* 200 ms(与 v2.6 修"重复采样污染 DFT 窗"同族误差,勿"优化"成拍计数) */
|
||||
static uint32_t s_meas_ms;
|
||||
|
||||
/* 故障录波环接线状态(task-2;状态机与计数全在 App/algo/wave_ring.c,这里只搬运):
|
||||
* s_wave_codes - 本拍 8 路语义原码(文件级 static,不上单拍的栈——本任务静态
|
||||
* 最坏链余量按 1024 B ISR 底线核)
|
||||
* s_wave_cal - EVT_SEALED 拍的交棒结构(同理不放局部:整块 ~160 B) */
|
||||
static int16_t s_wave_codes[WAVE_RING_CH];
|
||||
static wave_seal_cal_t s_wave_cal;
|
||||
#if defined(DTU_BENCH) && (WAVE_BENCH_AUTOCAP != 0)
|
||||
/* bench 自抓编排:on = 已过写者就绪门开始跑;off = 到时自动停(init 重置) */
|
||||
static uint8_t s_wave_bench_on;
|
||||
static uint8_t s_wave_bench_off;
|
||||
static uint32_t s_wave_bench_t0;
|
||||
static uint32_t s_wave_bench_next;
|
||||
#endif
|
||||
|
||||
|
||||
/* 消费者就绪门:由 adcTask 自己在进入等待前置 1;单写者 = 本任务,ISR 只读。
|
||||
* ⚠ 为什么必须有这道门:TIM3 在 app_main_init()(Src/main.c:140)里就
|
||||
@@ -288,6 +324,13 @@ int adc_task_init(void) {
|
||||
s_meas_ms = 0u;
|
||||
adc_health_init(); /* 内含 采样通道自检 贴轨观察器复位(rail_init) */
|
||||
(void)meas_init(); /* 遥测快照双区/序号复位(幂等;发布状态属本任务,init 无日志) */
|
||||
wave_ring_init(); /* 录波环纯清 RAM(无内核依赖;本函数在开闸前跑,init 禁日志不破) */
|
||||
#if defined(DTU_BENCH) && (WAVE_BENCH_AUTOCAP != 0)
|
||||
s_wave_bench_on = 0u;
|
||||
s_wave_bench_off = 0u;
|
||||
s_wave_bench_t0 = 0u;
|
||||
s_wave_bench_next = 0u;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -454,6 +497,103 @@ void adc_task_func(void *arg) {
|
||||
adc_health_report(&h_st);
|
||||
}
|
||||
|
||||
/* 2c) 故障录波环泵送(task-2 数据面接线;状态机与计数全在 App/algo/wave_ring.c,
|
||||
* 本段只做搬运)。每拍取 8 路语义**原码**压环 + tick 推进;触发那拍的样本
|
||||
* 落在段内点 WAVE_RING_PRE-1(点序 lead 钉死)。只存原码不存折算工程量——
|
||||
* §6.3 裁定:样本域 = 原始码,折算系数进段头,回放/PC 侧统一换算。
|
||||
* 某路取不到(开机头几拍)保持上值(与 adc: 心跳行同一兜底口径)。
|
||||
* tick 返 EVT_SEALED 的那拍必须**同拍** latch meta(头注唯一合法次序),
|
||||
* 漏了 = 2 s 后环自弃计 err,defaultTask 根本不会见到这一段。 */
|
||||
{
|
||||
uint8_t w;
|
||||
int32_t wev;
|
||||
|
||||
for (w = 0u; w < WAVE_RING_CH; w++) {
|
||||
int32_t wc = 0;
|
||||
|
||||
if (adc_input_raw((adc_semantic_t)w, &wc) == 0) {
|
||||
s_wave_codes[w] = (int16_t)wc;
|
||||
}
|
||||
}
|
||||
(void)wave_ring_push(s_wave_codes, WAVE_RING_CH);
|
||||
wev = wave_ring_tick(now, s_frames_consumed);
|
||||
|
||||
if (wev == WAVE_RING_EVT_SEALED) {
|
||||
uint8_t c;
|
||||
|
||||
/* 封存瞬间事实(交棒结构 s_wave_cal 为文件级 static,不上栈):
|
||||
* eng / VALID 位图 / CLIPPED 位图 / 墙钟 / 逐路 conv+bits+avail+k+b。
|
||||
* eng 口径 = adc_cal.h 注③ 的 8 路量纲标签(判据源
|
||||
* App/app/ui_data_feed.c feed_eng(),与 prot_blocked 的三路口径**不是**
|
||||
* 一件事):8 路电气量全 VALID 才是二次值;V0.1 无 CT 变比,一次值不可达。
|
||||
* wall 未对时写 0(sys_clock_get 失败 = UINT32_MAX,不许拿上电毫秒冒充)。
|
||||
* k 取 adc_cal_k_q16() int32 原值;b 的 float 回乘在本域无损
|
||||
* (|b| 限幅 8192 << 2^24,float32 精确表示整数;禁用浮点只针对 k)。 */
|
||||
s_wave_cal.flags_seq = 0u;
|
||||
s_wave_cal.clip_mask = 0u;
|
||||
for (c = 0u; c < WAVE_RING_CH; c++) {
|
||||
const uint32_t f = adc_cal_flags((adc_semantic_t)c);
|
||||
const adc_channel_desc_t *d = adc_input_desc((adc_semantic_t)c);
|
||||
|
||||
if ((f & ADC_CAL_F_VALID) != 0u) {
|
||||
s_wave_cal.flags_seq |= (uint8_t)(1u << c);
|
||||
}
|
||||
if ((f & ADC_CAL_F_CLIPPED) != 0u) {
|
||||
s_wave_cal.clip_mask |= (uint16_t)(1u << c);
|
||||
}
|
||||
s_wave_cal.ch[c].conv = (d != NULL) ? (uint8_t)d->source
|
||||
: (uint8_t)ADC_SRC_STM32;
|
||||
s_wave_cal.ch[c].bits = (s_wave_cal.ch[c].conv == (uint8_t)ADC_SRC_TPAFE)
|
||||
? 16u : 12u;
|
||||
s_wave_cal.ch[c].avail = (uint8_t)f;
|
||||
s_wave_cal.ch[c].k_q16 = adc_cal_k_q16((adc_semantic_t)c);
|
||||
s_wave_cal.ch[c].b_codes = (int32_t)adc_cal_b_codes((adc_semantic_t)c);
|
||||
}
|
||||
s_wave_cal.eng = (s_wave_cal.flags_seq == 0xFFu)
|
||||
? (uint8_t)WAVE_ENG_SEC : (uint8_t)WAVE_ENG_PIN;
|
||||
{
|
||||
uint32_t wall = sys_clock_get();
|
||||
|
||||
s_wave_cal.wall = (wall == UINT32_MAX) ? 0u : wall;
|
||||
}
|
||||
/* 只在 EVT_SEALED 拍调一次;返错不补救——环 2 s 超时自弃兜底,
|
||||
* 这里补第二行日志就违反 §9「不在已留痕处重复锚点」 */
|
||||
(void)wave_ring_latch_meta(&s_wave_cal);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2d) bench 定周期自抓(§10 红线落点:默认关闭 + 时长上限 + 启动一次性告警)。
|
||||
* 编译门 = DTU_BENCH 且 WAVE_BENCH_AUTOCAP 非 0,产品镜像无本段。
|
||||
* 节拍:每 WAVE_BENCH_PERIOD_MS 投一次 bench 请求(环侧冷却/去重自管,
|
||||
* 被丢的计入 ovf——这是 bench 的预期观测面,不是故障)。
|
||||
* 计时起点等写者就绪门(v2.1 理由:就绪前直发会与 defaultTask 存储
|
||||
* 日志字节级交错);到时自动停 + 一次性 STOP 行,之后不再投任何请求。
|
||||
* ⚠ 这两条 WARN/STOP 是一次性生命周期日志,不是周期行,§9 一条线约束不破 */
|
||||
#if defined(DTU_BENCH) && (WAVE_BENCH_AUTOCAP != 0)
|
||||
if (s_wave_bench_off == 0u) {
|
||||
if (s_wave_bench_on == 0u) {
|
||||
if (dbg_console_writer_ready() != 0u) {
|
||||
s_wave_bench_on = 1u;
|
||||
s_wave_bench_t0 = now;
|
||||
s_wave_bench_next = now + WAVE_BENCH_PERIOD_MS;
|
||||
/* [WARN] 进正文(本仓无 WARN 级;用 ERR 级会带 [ERR] 误触 @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));
|
||||
}
|
||||
} else if ((uint32_t)(now - s_wave_bench_t0) >= WAVE_BENCH_MAX_MS) {
|
||||
s_wave_bench_off = 1u;
|
||||
DBG_INFO("wave bench autocap STOP after %u s",
|
||||
(unsigned int)(WAVE_BENCH_MAX_MS / 1000u));
|
||||
} else if ((uint32_t)(now - s_wave_bench_next) >= WAVE_BENCH_PERIOD_MS) {
|
||||
s_wave_bench_next = now + WAVE_BENCH_PERIOD_MS;
|
||||
(void)wave_ring_request((uint8_t)WAVE_TRIG_BENCH, 0u);
|
||||
} else {
|
||||
/* 本拍未到周期点:无事可做(比较用无符号差值,回绕安全) */
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 3) 首帧码值上报:**写者就绪后**才落日志。写者未注册时 con_append 走
|
||||
* 无锁直发路径,与 defaultTask 的存储初始化日志会字节级交错
|
||||
* (2026-10-10 台架实测);此时挂起上报,就绪后补报最近一帧。
|
||||
@@ -610,6 +750,46 @@ void adc_task_func(void *arg) {
|
||||
(long)(ms.freq_hz * 100.0f),
|
||||
(unsigned)ms.freq_valid);
|
||||
}
|
||||
|
||||
/* 录波环周期行(docs/子系统设计/故障录波设计说明.md §9 逐字模板,
|
||||
* 字段名与顺序**不许动**——PC/台架脚本按行名 grep)。全链路只加这一条
|
||||
* 周期行(每条 DBG ≈90 B Flash + 216 B 栈,§9 明令不加第二条):
|
||||
* seg = 累计成功 ACK 的段数(wave_ring_stat.seg_total,不是轮转号)
|
||||
* armed= 1 表示此刻有段在飞(FREEZE/SEALED;IDLE/COOL 为 0——
|
||||
* COOL 只挡新触发不录新段,画进 armed 会让台架误读)
|
||||
* src = 触发源 M 手动 / P 保护 / T bench 定时,- = 本 boot 未触发过
|
||||
* err = 落盘失败 + SEALED 超时自弃(环内 err_cnt 收口,落盘道不数
|
||||
* 第二份账);ovf = ACK 前重复触发与冷却期丢弃数;
|
||||
* drop = 冻结窗口丢弃的录波点数(单位=点)
|
||||
* 判读:bench 自抓一轮后 seg_total 应增长;err 涨而 seg 不涨 =
|
||||
* 落盘道在丢段,查 defaultTask 侧 [WAVE] 失败行 */
|
||||
{
|
||||
wave_ring_stat_t wst;
|
||||
|
||||
(void)wave_ring_get_stat(&wst);
|
||||
{
|
||||
const char *sc;
|
||||
|
||||
if (wst.trig_src == (uint8_t)WAVE_TRIG_MANUAL) {
|
||||
sc = "M";
|
||||
} else if (wst.trig_src == (uint8_t)WAVE_TRIG_PROTECT) {
|
||||
sc = "P";
|
||||
} else if (wst.trig_src == (uint8_t)WAVE_TRIG_BENCH) {
|
||||
sc = "T";
|
||||
} else {
|
||||
sc = "-";
|
||||
}
|
||||
DBG_INFO("wave: seg=%lu armed=%u src=%s pts=%u err=%lu ovf=%lu drop=%lu",
|
||||
(unsigned long)wst.seg_total,
|
||||
(unsigned)((wst.state == (uint8_t)WAVE_RING_ST_FREEZE) ||
|
||||
(wst.state == (uint8_t)WAVE_RING_ST_SEALED)) ? 1u : 0u,
|
||||
sc,
|
||||
(unsigned int)WAVE_RING_PTS,
|
||||
(unsigned long)wst.err_cnt,
|
||||
(unsigned long)wst.ovf_cnt,
|
||||
(unsigned long)wst.drop_cnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
report_at = now + ADC_TASK_REPORT_MS;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,18 @@
|
||||
* v1.3 2026-10-25 DTU 项目组 写回缓存空闲 flush 接线:主循环 syslog 排空
|
||||
* 之后调 vol_idle_sync_poll()(卷空闲周期 CTRL_SYNC 兜底,
|
||||
* 模块 = App/fs/vol_idle_sync.[ch],道F 批 2)
|
||||
* v1.4 2026-10-29 DTU 项目组 故障录波落盘道(task-2):vol_idle_sync_poll 之后
|
||||
* 新增 wave_store_drain()——一次心跳整段搬完(lead 裁③:
|
||||
* 一次 try 锁内 pack_next 256 B 分片 + 完整 open...close),
|
||||
* 持锁耗时 >50 ms 补一条 WARN、峰值 ms 记本层静态;
|
||||
* 轮转号自持(启动扫 /wave 重建,成功 ACK 才推进,lead 裁(b));
|
||||
* 失败 = 先 unlink 半成品再 write_done(-1)(lead 裁①);
|
||||
* 卷未就绪 / 环未封存静默跳过(§5.1 所有权 + §9 一条线约束)
|
||||
* v1.5 2026-10-29 DTU 项目组 Lead 复核补第四道并发陷阱(本道与 adcTask 的抢占窗):
|
||||
* 搬段中途 adcTask 可能正好走到 SEALED 滞留自弃(2 s)甚至
|
||||
* 已封出下一段——CRC 是对"读到的字节"算的,半新半旧的段照样
|
||||
* 自洽,只能靠写完关档后复检环侧身份(握手量 + 段首 t0/帧序)
|
||||
* 判废:判废即 unlink、不 ACK、号不推进,失败按既有单条留痕
|
||||
*/
|
||||
|
||||
#include "default_task.h"
|
||||
@@ -53,6 +65,8 @@
|
||||
#include "task_prof.h" /* 各任务最坏耗时上报 */
|
||||
#include "vol_lock.h"
|
||||
#include "vol_idle_sync.h" /* 写回缓存空闲 flush 兜底(App/fs/vol_idle_sync.h) */
|
||||
#include "wave_ring.h" /* 录波环消费侧:sealed/sealed_meta/read_cb/write_done(task-2) */
|
||||
#include "wave_store.h" /* 段打包游标 + 段号轮转纯函数(落盘道本体,task-2) */
|
||||
#include "task_handles.h" /* 任务句柄与属性块的引用契约(定义处仍是 Src/freertos.c) */
|
||||
|
||||
/* Private defines -----------------------------------------------------------*/
|
||||
@@ -72,6 +86,14 @@
|
||||
* (自检已上报、卷保留、不重格式化),等下次上电重新 resume */
|
||||
#define STORAGE_BRINGUP_RETRIES 3u
|
||||
|
||||
/* 录波段落盘持锁耗时告警线(lead 裁③i):一次心跳整段 5250 B(约 21 次 256 B
|
||||
* f_write),设计 §4.2 预估落盘窗「几十 ms」;越过本线说明本段在占用 defaultTask
|
||||
* 喂狗链前的最慢一段,按事件补一条 WARN(不是周期行),并把历史峰值留在本层
|
||||
* 静态里供收工报告核喂狗余量(vol_lock.h 铁律「等锁即毁喂狗链」的前例在案) */
|
||||
#define WAVE_DRAIN_HELD_WARN_MS 50u
|
||||
/* 搬运缓冲(§7 的 256 B;wave_store_pack_next 头阶段要求 cap >= 128,lead 转告(d)) */
|
||||
#define WAVE_DRAIN_CHUNK_B 256u
|
||||
|
||||
/* Private typedef -------------------------------------------------------------*/
|
||||
typedef struct {
|
||||
const char *p_name;
|
||||
@@ -97,6 +119,7 @@ static void stack_probe_poll(void);
|
||||
#if !defined(TEST_SUITE_STORAGE)
|
||||
static void default_task_net_cfg_save_notify(void);
|
||||
static FRESULT storage_bring_up(FATFS *fs);
|
||||
static void wave_store_drain(void);
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -301,6 +324,10 @@ void default_task_func(void *argument) {
|
||||
* 一次 f_close 同型的同步段。放在 drain 之后:本拍若写过日志,close
|
||||
* 已同步过,这里随即 clean 短路——漏网脏页的驻留窗由周期封顶 */
|
||||
vol_idle_sync_poll();
|
||||
/* 录波段落盘(task-2):卷空闲 + 环已封存才搬,就绪门/try 锁全在函数内;
|
||||
* 一次心跳最多一段(lead 裁③)。放在 idle sync 之后:本拍若刚写过录波段,
|
||||
* 下一拍空闲 flush 会兜住残留脏页,语义与 syslog 排空同位 */
|
||||
wave_store_drain();
|
||||
#endif
|
||||
/* 栈水位观测:首次 5s 后报告,此后每 2s 采样、出现更深峰值才打印 */
|
||||
task_wd_seg(TASK_WD_SEG_STACK);
|
||||
@@ -565,6 +592,248 @@ static FRESULT storage_bring_up(FATFS *fs) {
|
||||
}
|
||||
#endif /* !TEST_SUITE_STORAGE */
|
||||
|
||||
/*
|
||||
* 函数功能:故障录波落盘道——把已封存的 wave_ring 段整段搬进 /wave(task-2)
|
||||
* 入口参数:无 void
|
||||
* 返回值:无 void
|
||||
* 限定条件:仅 defaultTask 主循环调用(vol_idle_sync_poll 之后);内部绝不阻塞——
|
||||
* try 锁 + 有界段长(5250 B / 256 B ≈ 21 次 f_write,设计 §4.2 预估几十 ms),
|
||||
* 绝不把喂狗点做成等待态(vol_lock.h 使用约定:defaultTask 一律 try)
|
||||
* 函数说明:所有权按 docs/子系统设计/故障录波设计说明.md §5.1——环的生产者是
|
||||
* adcTask,本道是唯一落盘消费者;跨任务握手只认 wave_ring_sealed()。
|
||||
* 本道承担五条口径:
|
||||
* ① 打开一律 FA_WRITE|FA_OPEN_APPEND:本移植 FA_OPEN_APPEND 不含 FA_WRITE
|
||||
* (docs/架构设计/STM32F4-Base存储架构说明.md §10.1 陷阱一),少写位
|
||||
* f_write 恒 FR_DENIED;
|
||||
* ② 每次 f_write 比对写回字节数:本移植簇分配失败返 FR_OK+短写无错误码
|
||||
* (同处陷阱二),短写一律判失败;
|
||||
* ③ 轮转号由本道自持(lead 转告(b)):ring.seg_idx 只是本 boot 本地计数,
|
||||
* 盘上权威下标 = 首次持锁扫 /wave 用 wave_store_idx_from_name() 重建
|
||||
* (取已存在最大号 +1,空目录 = 0,陌生名字不入账),写成功才
|
||||
* wave_store_next_idx() 推进;失败不推进、重试同号;
|
||||
* ④ 成败都必须 wave_ring_write_done() ACK——ACK 前环一直冻结;err 账
|
||||
* 由 ring 的 err_cnt 单点收口(§9),本道不数第二份(ACK 前环已
|
||||
* 超时自弃的那次例外:skip 掉 ACK,不去补第二笔账);
|
||||
* ⑤ 锁跨越一次完整 open...close(vol_lock.h 铁律),持锁耗时超
|
||||
* WAVE_DRAIN_HELD_WARN_MS 按事件补一条 WARN(§9 唯一周期行不在本道),
|
||||
* 历史峰值 s_wheld_ms_max 供核 defaultTask 喂狗余量(lead 裁③)。
|
||||
* 失败处置(lead 裁①):开过文件才 unlink 半成品(未开成的失败不动盘,
|
||||
* 绝不拿未初始化的路径误删旧段);unlink 失败照旧 ACK(-1)——最坏留
|
||||
* 一个坏段文件,PC 侧被 CRC 判废,不会画成证据。
|
||||
* 静默跳过(不刷屏):卷未就绪 / 环未封存 / try 锁没到(下轮再来,
|
||||
* 环侧 2 s 超时自弃是兜底账,本道不为此打日志)。
|
||||
* (上面五条口径就是本函数功能「把已封存段整段搬进 /wave」的逐条展开,
|
||||
* 细则出处 docs/子系统设计/故障录波设计说明.md §5.1 与 §10)
|
||||
*/
|
||||
static void wave_store_drain(void) {
|
||||
static FIL s_wfil; /* static:FF_FS_TINY=0 时 FIL ~528 B,
|
||||
* 绝不上栈(FS_VOL_PROBE 同一理由) */
|
||||
static uint8_t s_wbuf[WAVE_DRAIN_CHUNK_B];
|
||||
static wave_pack_ctx_t s_wpack;
|
||||
static wave_seg_meta_t s_wmeta;
|
||||
static char s_wpath[WAVE_ST_PATH_MAX];
|
||||
static DIR s_wdir;
|
||||
static FILINFO s_wfno;
|
||||
static uint8_t s_wnext_idx; /* 本道自持的下一个段号 0..99 */
|
||||
static uint8_t s_wscan_done; /* 1 = 本 boot 已扫过 /wave */
|
||||
static uint8_t s_wdir_ready; /* 1 = /wave 已建或已存在 */
|
||||
static uint8_t s_wcrc_state; /* 0 未验 1 过 2 败(败=永久自弃,不烧垃圾段) */
|
||||
static uint32_t s_wheld_ms_max; /* 持锁搬段历史峰值 ms(收工报告核余量用) */
|
||||
uint32_t t0_ms;
|
||||
uint32_t held_ms;
|
||||
uint32_t written = 0u;
|
||||
uint32_t len = 0u;
|
||||
uint32_t iter;
|
||||
uint8_t opened = 0u;
|
||||
uint8_t skip_ack = 0u; /* 1 = 环已自行了断本段,本道不 ACK 不补账 */
|
||||
wave_ring_stat_t wst2; /* 关档后复检环侧身份用(v1.5) */
|
||||
const char *why = NULL;
|
||||
FRESULT fr;
|
||||
UINT bw;
|
||||
int rc;
|
||||
|
||||
/* 就绪门(同 vol_idle_sync_poll 的四级门风格) */
|
||||
if (sys_evtHandle == NULL) {
|
||||
return;
|
||||
}
|
||||
if ((osEventFlagsGet(sys_evtHandle) & SYS_EVT_STORAGE_READY) == 0u) {
|
||||
return;
|
||||
}
|
||||
/* 封存门:只搬已封存段(唯一跨任务握手量,§5.1) */
|
||||
if (wave_ring_sealed() == 0u) {
|
||||
return;
|
||||
}
|
||||
/* 空闲门:try 锁;卷被 syslog/配置/FTP 占住 → 本轮放弃,不排队不打日志 */
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return;
|
||||
}
|
||||
t0_ms = (uint32_t)xTaskGetTickCount();
|
||||
|
||||
/* CRC 锚点自检每 boot 一次(落盘前失败 = 整族失败,永久自弃免得白烧写次数) */
|
||||
if (s_wcrc_state == 0u) {
|
||||
s_wcrc_state = (wave_store_crc_selftest() == WAVE_ST_OK) ? 1u : 2u;
|
||||
}
|
||||
if (s_wcrc_state == 2u) {
|
||||
why = "crc selftest FAIL";
|
||||
}
|
||||
if ((why == NULL) && (s_wdir_ready == 0u)) {
|
||||
fr = f_mkdir(WAVE_ST_DIR);
|
||||
if ((fr == FR_OK) || (fr == FR_EXIST)) {
|
||||
s_wdir_ready = 1u;
|
||||
} else {
|
||||
why = "mkdir /wave";
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (s_wscan_done == 0u)) {
|
||||
FRESULT rr;
|
||||
|
||||
fr = f_opendir(&s_wdir, WAVE_ST_DIR);
|
||||
if (fr == FR_OK) {
|
||||
uint8_t found = 0u;
|
||||
uint8_t mx = 0u;
|
||||
|
||||
for (;;) {
|
||||
uint8_t ix;
|
||||
|
||||
rr = f_readdir(&s_wdir, &s_wfno);
|
||||
if ((rr != FR_OK) || (s_wfno.fname[0] == 0)) {
|
||||
break;
|
||||
}
|
||||
if (wave_store_idx_from_name(s_wfno.fname, &ix) == WAVE_ST_OK) {
|
||||
if ((found == 0u) || (ix > mx)) {
|
||||
mx = ix;
|
||||
}
|
||||
found = 1u;
|
||||
}
|
||||
}
|
||||
(void)f_closedir(&s_wdir);
|
||||
if (rr == FR_OK) {
|
||||
/* max+1 即下一写号;回绕号复用由 PC 侧 t0/frame_seq 对账兜底,
|
||||
* 目录里没有的事实不去猜更多(陌生名字一律不入账) */
|
||||
s_wnext_idx = (found != 0u) ? wave_store_next_idx(mx) : 0u;
|
||||
s_wscan_done = 1u;
|
||||
} else {
|
||||
why = "readdir /wave";
|
||||
}
|
||||
} else {
|
||||
why = "opendir /wave";
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (s_wscan_done != 0u)) {
|
||||
if (wave_ring_sealed_meta(&s_wmeta) != WAVE_ST_OK) {
|
||||
/* sealed 检查与此读之间环 2 s 超时自弃(err 已由环计过):
|
||||
* 本道不 ACK、不补账、不打日志,放锁走人 */
|
||||
skip_ack = 1u;
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (skip_ack == 0u)) {
|
||||
/* lead 转告(e):E_FORMAT/E_SIZE = 坏段头,一律当落盘失败丢段计 err,
|
||||
* 不当参数错吞掉 */
|
||||
rc = wave_store_pack_start(&s_wpack, &s_wmeta, wave_ring_read_cb, NULL);
|
||||
if (rc != WAVE_ST_OK) {
|
||||
why = "pack_start";
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (skip_ack == 0u)) {
|
||||
if (wave_store_seg_path(s_wpath, (uint32_t)sizeof(s_wpath),
|
||||
s_wnext_idx) <= 0) {
|
||||
why = "seg_path";
|
||||
} else {
|
||||
fr = f_open(&s_wfil, s_wpath, FA_WRITE | FA_OPEN_APPEND);
|
||||
if (fr != FR_OK) {
|
||||
why = "f_open";
|
||||
} else {
|
||||
opened = 1u;
|
||||
}
|
||||
}
|
||||
}
|
||||
iter = 0u;
|
||||
while ((why == NULL) && (opened != 0u)) {
|
||||
rc = wave_store_pack_next(&s_wpack, s_wbuf,
|
||||
(uint32_t)sizeof(s_wbuf), &len);
|
||||
if ((rc != WAVE_ST_OK) && (rc != WAVE_ST_DONE)) {
|
||||
why = "pack_next";
|
||||
break;
|
||||
}
|
||||
if (len != 0u) {
|
||||
bw = 0u;
|
||||
fr = f_write(&s_wfil, s_wbuf, (UINT)len, &bw);
|
||||
if ((fr != FR_OK) || (bw != (UINT)len)) {
|
||||
why = "f_write short"; /* ②:FR_OK+短写 = 本移植独有失败形态 */
|
||||
break;
|
||||
}
|
||||
written += len;
|
||||
}
|
||||
if (rc == WAVE_ST_DONE) {
|
||||
break;
|
||||
}
|
||||
if (++iter > 32u) { /* 5250/256 = 21 片;游标卡死时的有界保险 */
|
||||
why = "pack stall";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opened != 0u) {
|
||||
fr = f_close(&s_wfil);
|
||||
if ((why == NULL) && (skip_ack == 0u) && (fr != FR_OK)) {
|
||||
why = "f_close";
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (skip_ack == 0u) && (opened != 0u)) {
|
||||
if (written != wave_store_pack_total(&s_wpack)) {
|
||||
why = "len mismatch"; /* 长度等式(§6.3):半截文件骗不过这条 */
|
||||
}
|
||||
}
|
||||
/* v1.5(lead 复核):adcTask 优先级高于本道,搬段中途它可能正好把滞留 2 s 的本段
|
||||
* 自弃(甚至已经封出下一段)。数据区的 CRC 是对"本道读到的字节"算的,半新半旧的
|
||||
* 段一样自洽 → 关档后必须复检:握手量没了、或段首身份不是本段,一律判废。
|
||||
* 判废走既有失败路:unlink 本道开过的文件 + write_done(-1)(此时多半回 E_STATE,
|
||||
* 环侧的 err 已由自弃计过,这里不重复补账)+ 段号不推进(同号重写覆盖)。 */
|
||||
if ((why == NULL) && (skip_ack == 0u)) {
|
||||
if ((wave_ring_sealed() == 0u) || (wave_ring_get_stat(&wst2) != WAVE_ST_OK)
|
||||
|| (wst2.last_t0_ms != s_wmeta.t0_ms) || (wst2.last_seq != s_wmeta.frame_seq)) {
|
||||
why = "ring abandoned";
|
||||
}
|
||||
}
|
||||
if (skip_ack == 0u) {
|
||||
/* 失败留痕边沿锁存(搬进本块,lead 裁方案 A):
|
||||
* ① 它是 static,初值只在编译期发生一次、不是每次进块重置;
|
||||
* ② 它是"上一拍是否处于失败态"的边沿锁存,所以必须跨调用存活;
|
||||
* ③ 单独放这里而不在函数入口那 12 连排台账里,因为 variableScope
|
||||
* 检查要求把局部量收进真正覆盖全部使用点的最小块(本仓刻意开了 style)。 */
|
||||
static uint8_t s_wfail_latch; /* 一个 episode 只留一条痕 */
|
||||
|
||||
if (why == NULL) {
|
||||
(void)wave_ring_write_done(0);
|
||||
s_wnext_idx = wave_store_next_idx(s_wnext_idx); /* 成功才推进(转告(b)) */
|
||||
s_wfail_latch = 0u;
|
||||
} else {
|
||||
if (opened != 0u) {
|
||||
(void)f_unlink(s_wpath); /* 只清本道开过的文件;unlink 失败照旧 ACK */
|
||||
}
|
||||
(void)wave_ring_write_done(-1);
|
||||
if (s_wfail_latch == 0u) {
|
||||
s_wfail_latch = 1u; /* 持续失败不刷屏;成功一次重新武装 */
|
||||
DBG_ERROR("[WAVE] seg drop: %s (idx=%u written=%lu)", why,
|
||||
(unsigned)s_wnext_idx, (unsigned long)written);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 持锁耗时台账(lead 裁③i/ii):超线按事件补一条 WARN(原先这里静默占锁,
|
||||
* 就是「等锁即毁喂狗链」前例的同族黑洞);峰值进静态供收工报告 */
|
||||
held_ms = (uint32_t)xTaskGetTickCount() - t0_ms;
|
||||
if (held_ms > s_wheld_ms_max) {
|
||||
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
|
||||
* 禁用标记):[WARN] 按 console_log.c 先例进正文,慢不是错(真失败另有 seg drop) */
|
||||
DBG_INFO("[WAVE][WARN] seg held %lu ms (idx=%u)",
|
||||
(unsigned long)held_ms, (unsigned)s_wnext_idx);
|
||||
}
|
||||
vol_lock_release(); /* 成败/跳过都放锁:锁泄漏比丢一段录波重一百倍 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:"保存网络配置"请求通知(注册给 net_cfg 的保存交接;
|
||||
* 2026-09-25 自 Src/freertos.c 迁出,逻辑零改动)
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
行为不变:判定式、调用位置(采样恢复分支内、巡检/压板
|
||||
之前)、边沿日志与 SOE 分型全部照旧;s_prev_cal_lock
|
||||
仍只作边沿日志用,**不是**判据缓存
|
||||
* v1.4 2026-10-29 DTU 项目组 故障录波触发接线(task-2,lead 裁②):
|
||||
* s_log_record_edge() 的 picked 上升沿分支补一次
|
||||
* wave_ring_request()——与 SYSLOG_START 同一事件点
|
||||
* (设计 §4.1「同源,避免两处各自判定」),判据本体
|
||||
* (prot_elm_run/判定逻辑)零改动;一拍只请求一次,
|
||||
* elm = 本拍最低位上升元件
|
||||
*/
|
||||
#include "protect_task.h"
|
||||
|
||||
@@ -62,6 +68,7 @@
|
||||
#include "prot_set.h"
|
||||
#include "soe_store.h" /* B9:动作/复归边沿 → SOE 投递(零 FS,队列异步) */
|
||||
#include "sys_log.h" /* task-10:事故 / 保护告警 / 保护启动 三类文本记录 */
|
||||
#include "wave_ring.h" /* task-2:picked 上升沿 → 录波请求(与 SYSLOG_START 同源同点) */
|
||||
#include "task_watchdog.h"
|
||||
#include "tpafe5160.h" /* tpafe5160_frame_seq():帧序号(BUSY EXTI 写) */
|
||||
|
||||
@@ -151,8 +158,24 @@ static void s_log_active_edge(uint32_t active) {
|
||||
static void s_log_record_edge(uint32_t active, uint32_t pick) {
|
||||
uint32_t p_chg = pick ^ s_prev_pick;
|
||||
uint32_t a_chg = active ^ s_prev_active;
|
||||
const uint32_t p_rise = p_chg & pick; /* picked 0->1 的元件位集合 */
|
||||
uint32_t i;
|
||||
|
||||
/* task-2 补(第 3 类边沿的用途):picked 上升沿同点请求录波。一拍只调一次——
|
||||
* 多元件同拍启动录同一段波就够(环本就不重入,后续请求由 ring 计 ovf,不当
|
||||
* 失败);元件号取最低位上升元件(位号最小 = 表中靠前,速断段优先于定时限段)。
|
||||
* 返回值刻意忽略:本函数在 protectTask 的最坏栈链上(B9 写进 DoD 的硬线),
|
||||
* 不为此加日志;err/ovf 两本账全在串口 wave: 周期行与环 stat 里
|
||||
* (docs/子系统设计/故障录波设计说明.md §9)。 */
|
||||
if (p_rise != 0u) {
|
||||
uint32_t e = 0u;
|
||||
|
||||
while (((p_rise >> e) & 1u) == 0u) {
|
||||
e++; /* p_rise != 0 → 必在 32 位内命中,无越界 */
|
||||
}
|
||||
(void)wave_ring_request((uint8_t)WAVE_TRIG_PROTECT, (uint8_t)e);
|
||||
}
|
||||
|
||||
for (i = 0u; (i < (uint32_t)PE_COUNT) && (((p_chg | a_chg) != 0u)); i++) {
|
||||
const uint32_t bit = 1u << i;
|
||||
|
||||
|
||||
@@ -60,6 +60,16 @@
|
||||
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
|
||||
#include "net_latency_test.h" /* M3 探针:ui_loop(生产循环实测周期) */
|
||||
#include "task_watchdog.h"
|
||||
/* 故障录波回放道(UI 道):录波注入表执行侧需要的四份头。
|
||||
* wave_ring.h 给触发投递(request 任意上下文可调、非阻塞);
|
||||
* wave_store.h 给段格式解析与定点 mV 换算(唯一口径,页面不复述算式);
|
||||
* vol_lock.h + ff.h:/wave 只读遍历按 soe_store.c 读者同款纪律——
|
||||
* try 锁非阻塞(卷忙本轮按「无段」处理,uiTask 绘制节拍绝不等待)、
|
||||
* 锁跨完整文件操作序列、小窗口分次读、整段 5250B 绝不驻留 RAM */
|
||||
#include "ff.h"
|
||||
#include "vol_lock.h"
|
||||
#include "wave_ring.h"
|
||||
#include "wave_store.h"
|
||||
#include "task_prof.h" /* P2 常开探针:循环周期 / 工作耗时 / 推流往返 */
|
||||
#include "sb_link.h" /* 屏板链路:帧 B 组帧 / 按键出队 / 统计与离线判定(批 2) */
|
||||
|
||||
@@ -359,6 +369,463 @@ static int ui_task_soe_get(uint32_t idx, ui_w_soe_rec_t *p_out) {
|
||||
|
||||
static const ui_soe_reader_t s_soe_reader = { ui_task_soe_count, ui_task_soe_get };
|
||||
|
||||
/* ═══════════════ 故障录波注入表执行侧(UI 道,2026-10-29)═══════════════
|
||||
* 纪律逐条对齐 soe_store.c 读者(vol_lock try / 锁跨完整文件序列 / 小窗口):
|
||||
* - 卷忙(vol_lock_wait(0) 返 0)一律按「本轮无段」处理——uiTask 是
|
||||
* LVGL + 推流节拍的所有者,等锁 = 停摆,宁可空一帧也不阻塞(lead 裁定);
|
||||
* - 段完整性判定只信 wave_store 自己的判据(尺寸等式 / magic+ver / 双 CRC /
|
||||
* sem==index),本胶水不读 avail/flags 做二次裁断(§6.3 与 2026-10-29
|
||||
* 「VALID+CLIPPED 非矛盾」裁定同源:可用性不是完整性);
|
||||
* - 整段 5250B 不驻留:256B 静态窗口分次读,头区一次、数据区流式过 CRC。
|
||||
* uiTask 阻塞上界(IWDG 800ms 窗、uiTask 在 required_mask 内;写法对齐上方
|
||||
* 维护通道那段上界,同文件先例):
|
||||
* - 第零项是「本块不等待」:vol_lock_wait(0) 拿不到锁即按「本轮无段」
|
||||
* 返回——不等锁 = 零阻塞,它本身就是上界的一项,不是只保心跳判读;
|
||||
* - 一次渲染实际发生的 I/O(进回放页首绘、或按键后重绘那帧)三件叠加:
|
||||
* ① 表重扫 = 1 次 f_opendir + 至多 WAVE_ST_SEG_MAX(100)次 f_readdir,
|
||||
* 收尾是 100 元插入排序(纯内存);
|
||||
* ② 段头装载 = 仅未缓存段才发生:1 次 f_open + 头区 1 次 128B 读 +
|
||||
* head_parse 内的头 CRC + 数据区 20 次 256B 流式读 + 尾 2B 一次
|
||||
* = 最坏 22 次小读;5120B 逐字节 CRC 与 sem 表校验是纯计算,
|
||||
* 168MHz 下微秒到毫秒量级;缓存命中零卷操作;
|
||||
* ③ 窗口装载 = 1 次 f_open + 1 次 f_lseek + 1 次 80B 读(40 点一窗),
|
||||
* 解码 40 点 + 逐点 code_to_milli 峰值扫描 40 次(int64 乘除,
|
||||
* 微秒级)——不是全段 8×320 扫描,回放页一屏只碰一个通道一窗;
|
||||
* - 纯内存不碰卷的路径(评审据此看最坏的落点):manual_req 只投一个
|
||||
* 请求字节;已缓存段的上下切道、左右切段的段号表环游;空态与坏态
|
||||
* 直接返回。所以最坏只落在「第一次进回放页 / 第一次切到未缓存段」
|
||||
* 这类一次性动作上,同段内连续按键不重复付 ②。
|
||||
* 没有实测数,上面是与落盘道同一把尺的**推算**:落盘道的告警线是
|
||||
* 50 ms / 21 次写,读路径无簇分配、不写 FAT 元数据,只会更便宜,故按
|
||||
* 同量级不高于它估——一次渲染最坏一窗 ≈ 段表 + 一段全验 + 一窗,仍远
|
||||
* 小于 800ms。实测归台架,仪器就用本文件已经在跑的 task_prof 单轮
|
||||
* 工作耗时探针,不必新加埋点。
|
||||
* 段序口径(V0.1 已知限制,终报点名):目录重建后按段号数值排序环游,
|
||||
* 盘上写满 100 段翻转后「数值序 = 时间序」不再严格成立——每屏顶行都带
|
||||
* 段号自证身份,查事故先看号再看序;翻转窗口排序升级留给台架结论。
|
||||
*/
|
||||
static uint8_t s_wave_idx[WAVE_ST_SEG_MAX]; /* 盘上现存段号升序表 */
|
||||
static uint8_t s_wave_cnt; /* 表内条数 */
|
||||
static uint8_t s_wave_scanned; /* 表是否有效(manual_req 后作废) */
|
||||
static uint8_t s_wave_cached_idx; /* 段头缓存属于哪一段 */
|
||||
static uint8_t s_wave_cache_ok; /* 该段已验完整(头+数据 CRC) */
|
||||
static wave_seg_meta_t s_wave_meta; /* 已验段的头(172B,唯一驻留) */
|
||||
static uint8_t s_wave_buf[256u]; /* 读窗:数据区流式 256B/次(lead 裁定
|
||||
* 2026-10-29 第 1 条·第一条:128B 抬 256B,
|
||||
* f_read 次数对半砍,RAM 只多 128B;
|
||||
* 不选 100 位「已验」位图——位图的作废纪律
|
||||
* 一旦漏做,「CRC 不符必须拒收」这条台架
|
||||
* 判据对旧段号会静默失效,多 128B 没这个面)*/
|
||||
|
||||
/*
|
||||
* 函数功能:重扫 /wave 目录,把现存段号收集成升序表
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 扫描完成(条数见 s_wave_cnt);-1 - 卷忙本轮跳过 int
|
||||
* 限定条件:uiTask;一次 f_opendir + 有界 f_readdir,全程锁内
|
||||
* 函数说明:目录不存在(从未录过波)与目录空都归「0 段」,不是错误;
|
||||
* 名字不合形态的文件(E_NAME)静默跳过——那是别的道写进来的
|
||||
* 东西,回放页不替它背书也不报警
|
||||
*/
|
||||
static int ui_task_wave_scan(void) {
|
||||
static DIR dir; /* static:同 FIL 理由,压栈深 */
|
||||
FILINFO finfo;
|
||||
uint8_t idx;
|
||||
uint8_t i;
|
||||
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return -1;
|
||||
}
|
||||
s_wave_cnt = 0u;
|
||||
if (f_opendir(&dir, WAVE_ST_DIR) == FR_OK) {
|
||||
for (;;) {
|
||||
if (f_readdir(&dir, &finfo) != FR_OK || finfo.fname[0] == '\0') {
|
||||
break;
|
||||
}
|
||||
if (finfo.fattrib & AM_DIR) {
|
||||
continue;
|
||||
}
|
||||
if (wave_store_idx_from_name(finfo.fname, &idx) == 0) {
|
||||
if (s_wave_cnt < WAVE_ST_SEG_MAX) {
|
||||
s_wave_idx[s_wave_cnt++] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
f_closedir(&dir);
|
||||
}
|
||||
vol_lock_release();
|
||||
/* 插入排序:条数上限 100,不引 qsort 的 stdlib 依赖 */
|
||||
for (i = 1u; i < s_wave_cnt; i++) {
|
||||
uint8_t j;
|
||||
uint8_t tmp;
|
||||
|
||||
tmp = s_wave_idx[i];
|
||||
j = i;
|
||||
while ((j > 0u) && (s_wave_idx[j - 1u] > tmp)) {
|
||||
s_wave_idx[j] = s_wave_idx[j - 1u];
|
||||
j--;
|
||||
}
|
||||
s_wave_idx[j] = tmp;
|
||||
}
|
||||
s_wave_scanned = 1u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:确保段表可用(必要时重扫)
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 表可用;-1 - 卷忙 int
|
||||
* 限定条件:uiTask 绘制/按键链
|
||||
*/
|
||||
static int ui_task_wave_table(void) {
|
||||
if (s_wave_scanned == 0u) {
|
||||
return ui_task_wave_scan();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:表内查段号下标
|
||||
* 入口参数:a_idx - 段号;a_pos - 出参下标
|
||||
* 返回值:1 - 命中;0 - 不在表内 uint8_t
|
||||
* 限定条件:表已可用(先过 ui_task_wave_table)
|
||||
*/
|
||||
static uint8_t ui_task_wave_find(uint8_t a_idx, uint8_t *a_pos) {
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0u; i < s_wave_cnt; i++) {
|
||||
if (s_wave_idx[i] == a_idx) {
|
||||
*a_pos = i;
|
||||
return 1u;
|
||||
}
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读段并做完整性判定(头 CRC 由 head_parse 判,数据 CRC 流式判),
|
||||
* 成功则把段头留在 s_wave_meta 供 seg_win 免重读
|
||||
* 入口参数:a_idx - 段号
|
||||
* 返回值:UI_WAVE_OK / UI_WAVE_NONE(卷忙/文件没了)/ UI_WAVE_BAD(判废) int
|
||||
* 限定条件:uiTask;锁跨 open→close 完整序列;头 128B 一次、数据区 256B
|
||||
* 分窗流式(共约 1 + 20 次 f_read),整段不驻留
|
||||
* 耗时账(lead 裁定 2026-10-29 第 1 条·第三条并存):未缓存段的完整性
|
||||
* 判定最坏约 21 次小读 + 5120B 逐位 CRC,全部在卷锁内、uiTask 上;
|
||||
* IWDG 800ms 窗内余量几何**实测毫秒归台架**(终报点名待测项)。
|
||||
* 缓存只留一段:左右切段 A→B→A 会把 B 的校验再来一遍——接受该
|
||||
* 成本的代价换「CRC 不符必须拒收」永远现算,不做跨键存的位图
|
||||
* 函数说明:缓存命中(同段且已验)直接返 OK——左右切段回到上一段、上下切
|
||||
* 通道都靠它把重复的 5250B 全扫压成一次;文件读中途消失(被别的
|
||||
* 写者轮转掉)按 NONE 处理,页面下轮自然重扫
|
||||
*/
|
||||
static int ui_task_wave_load_head(uint8_t a_idx) {
|
||||
static FIL f; /* static:FIL = 528B,理由同 soe_store_read */
|
||||
char path[WAVE_ST_PATH_MAX]; /* 容量 = WAVE_ST_PATH_MAX(17 字节口径,
|
||||
* 自己声明路径缓冲不得写死 16) */
|
||||
FRESULT fr;
|
||||
UINT br = 0u;
|
||||
int rc;
|
||||
|
||||
if ((s_wave_cache_ok != 0u) && (s_wave_cached_idx == a_idx)) {
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
if (wave_store_seg_path(path, (uint32_t)sizeof(path), a_idx) != 0) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return UI_WAVE_NONE; /* 卷忙 = 本轮按无段处理(不阻塞 uiTask) */
|
||||
}
|
||||
rc = UI_WAVE_NONE;
|
||||
if (f_open(&f, path, FA_READ) == FR_OK) {
|
||||
uint32_t size;
|
||||
uint32_t total;
|
||||
uint32_t n;
|
||||
|
||||
size = (uint32_t)f_size(&f);
|
||||
total = 0u;
|
||||
rc = UI_WAVE_BAD;
|
||||
/* 第一步:头区(首窗 128B = 当前形态整头);head_parse 自带判废 */
|
||||
n = (size < (uint32_t)WAVE_ST_HEAD_SZ_CUR) ? size
|
||||
: (uint32_t)WAVE_ST_HEAD_SZ_CUR;
|
||||
fr = f_read(&f, s_wave_buf, n, &br);
|
||||
if ((fr == FR_OK) && (br == n)) {
|
||||
if (wave_store_head_parse(s_wave_buf, n, &s_wave_meta) == 0) {
|
||||
total = wave_store_seg_size(&s_wave_meta);
|
||||
}
|
||||
}
|
||||
/* 第二步:尺寸等式(零长/长度与头部字段矛盾 = 整段判废,§6.3) */
|
||||
if ((total != 0u) && (size == total)) {
|
||||
uint32_t off;
|
||||
uint16_t crc;
|
||||
uint8_t tail[2];
|
||||
|
||||
/* 第三步:数据区流式 CRC(128B 窗;CRC 覆盖不含尾两字节自身) */
|
||||
crc = WAVE_ST_CRC_INIT;
|
||||
off = wave_store_head_bytes(s_wave_meta.ch_count);
|
||||
rc = UI_WAVE_BAD;
|
||||
while ((off + 2u) < size) {
|
||||
n = (size - 2u - off < (uint32_t)sizeof(s_wave_buf))
|
||||
? (size - 2u - off) : (uint32_t)sizeof(s_wave_buf);
|
||||
fr = f_read(&f, s_wave_buf, n, &br);
|
||||
if ((fr != FR_OK) || (br != n)) {
|
||||
rc = UI_WAVE_BAD;
|
||||
break;
|
||||
}
|
||||
crc = wave_store_crc_update(crc, s_wave_buf, n);
|
||||
off += n;
|
||||
}
|
||||
/* 第四步:读回存储值比对(低字节在前) */
|
||||
if ((rc == UI_WAVE_BAD) && (f_lseek(&f, size - 2u) == FR_OK) &&
|
||||
(f_read(&f, tail, 2u, &br) == FR_OK) && (br == 2u)) {
|
||||
if (crc == (uint16_t)(tail[0] | ((uint16_t)tail[1] << 8))) {
|
||||
rc = UI_WAVE_OK;
|
||||
}
|
||||
}
|
||||
} else if (total == 0u) {
|
||||
rc = UI_WAVE_BAD; /* seg_size 返 0 = 头字段自相矛盾,判废 */
|
||||
} else {
|
||||
rc = UI_WAVE_BAD; /* 长度与头部字段不符,判废 */
|
||||
}
|
||||
(void)f_close(&f);
|
||||
}
|
||||
vol_lock_release();
|
||||
if (rc == UI_WAVE_OK) {
|
||||
s_wave_cached_idx = a_idx;
|
||||
s_wave_cache_ok = 1u;
|
||||
} else {
|
||||
s_wave_cache_ok = 0u;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:录波触发页唯一写出口——投一次手动触发请求
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 已受理;非零 - 拒收(未 IDLE/冷却中/参数),页面显示 ERR int
|
||||
* 限定条件:uiTask(request 本身任意上下文可调,这里只是转发不等待)
|
||||
* 函数说明:受理 = ring 进冻结窗,成段最快也在约一秒后由默认任务落盘;
|
||||
* 顺手作废段表缓存,页面下次取数会看到新段
|
||||
*/
|
||||
static int ui_task_wave_manual_req(void) {
|
||||
int r = wave_ring_request((uint8_t)WAVE_TRIG_MANUAL, 0u);
|
||||
|
||||
if (r == 0) {
|
||||
s_wave_scanned = 0u;
|
||||
s_wave_cache_ok = 0u; /* lead 裁定 2026-10-29 第 3 条:段表与段头缓存
|
||||
* 一起作废——同名文件会被轮转重写,旧头字段
|
||||
* (pts/pre/k/b/逐路 avail)不作废就会顶着新段
|
||||
* 的号用旧尺子。单槽缓存本身 = 已知限制:
|
||||
* 只挡「本段验过不再重验」,多段来回切会重验 */
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取盘上「数值最大段号」(回放页开屏落点)
|
||||
* 入口参数:a_idx - 出参段号
|
||||
* 返回值:UI_WAVE_OK / NONE(无段或卷忙本轮)/ BAD 本件不产(表只含现存文件)
|
||||
* 函数说明:口径 = 数值最大,**不是时间最新**(lead 裁定 2026-10-29):
|
||||
* 回绕后数值序不等于时间序,页顶显示的段号即自证;真做时间序要
|
||||
* 逐段开文件读 frame_seq/t0,一百次开档不在回放页的账上,本波不做
|
||||
*/
|
||||
static int ui_task_wave_seg_first(uint8_t *a_idx) {
|
||||
if (ui_task_wave_table() != 0) {
|
||||
return UI_WAVE_NONE; /* 卷忙 = 本轮无段(同一条不阻塞纪律) */
|
||||
}
|
||||
if (s_wave_cnt == 0u) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
*a_idx = s_wave_idx[s_wave_cnt - 1u];
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:环游取邻段(a_dir:-1 更早 / +1 更新;段号数值序环回)
|
||||
* 入口参数:a_cur - 当前段号;a_out - 出参邻段段号
|
||||
* 返回值:UI_WAVE_OK / NONE(表不可用或已无邻段可去) int
|
||||
* 限定条件:uiTask;当前段已被轮转覆盖时先重扫一次再定位
|
||||
*/
|
||||
static int ui_task_wave_seg_around(uint8_t a_cur, int8_t a_dir, uint8_t *a_out) {
|
||||
uint8_t pos = 0u;
|
||||
|
||||
if (ui_task_wave_table() != 0) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
if (ui_task_wave_find(a_cur, &pos) == 0u) {
|
||||
if ((ui_task_wave_scan() != 0) || (s_wave_cnt == 0u) ||
|
||||
(ui_task_wave_find(a_cur, &pos) == 0u)) {
|
||||
/* 当前段没了(翻转覆盖):退回首段,画面由调用侧重载 */
|
||||
if (s_wave_cnt == 0u) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
*a_out = s_wave_idx[0u];
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
}
|
||||
if (s_wave_cnt == 1u) {
|
||||
*a_out = s_wave_idx[0u]; /* 单段:原地(用户看到的仍是这段) */
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
if (a_dir > 0) {
|
||||
*a_out = s_wave_idx[(uint8_t)((pos + 1u) % s_wave_cnt)];
|
||||
} else {
|
||||
*a_out = s_wave_idx[(uint8_t)((pos + s_wave_cnt - 1u) % s_wave_cnt)];
|
||||
}
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:切到段号表里的前一段(LR 左键的落点,数值升序表 → 「前」= 号更小)
|
||||
* 入口参数:a_cur 当前段号;a_out 输出切换后的段号(越界/空表不写)
|
||||
* 返 回 值:UI_WAVE_OK 已给出;UI_WAVE_NONE 盘上一段都没有 int
|
||||
* 限定条件:仅 uiTask 上下文(段号表是本任务私有缓存)
|
||||
* 函数说明:只是 ui_task_wave_seg_around 的方向包装,存在的理由是 ops 表要两个
|
||||
* 独立函数指针,不写第二份查找逻辑。
|
||||
*/
|
||||
static int ui_task_wave_seg_prev(uint8_t a_cur, uint8_t *a_out) {
|
||||
return ui_task_wave_seg_around(a_cur, -1, a_out);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:切到段号表里的后一段(LR 右键;数值升序表 → 「后」= 号更大)
|
||||
* 入口参数:a_cur 当前段号;a_out 输出切换后的段号(越界/空表不写)
|
||||
* 返 回 值:UI_WAVE_OK 已给出;UI_WAVE_NONE 盘上一段都没有 int
|
||||
* 限定条件:仅 uiTask 上下文(同上)
|
||||
* 函数说明:与 seg_prev 共用 ui_task_wave_seg_around,切段即触发一次懒加载;
|
||||
* 未缓存段的读盘 + 流式 CRC 也发生在这条调用链上(单轮上界见本
|
||||
* 文件「故障录波注入表执行侧」块首注释的 uiTask 阻塞上界段)。
|
||||
*/
|
||||
static int ui_task_wave_seg_next(uint8_t a_cur, uint8_t *a_out) {
|
||||
return ui_task_wave_seg_around(a_cur, 1, a_out);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读段头(含双 CRC 完整性判定——判定发生在 ui_task_wave_load_head)
|
||||
* 入口参数:a_idx - 段号;a_eng/a_trig/a_clip - 段头三字段原值
|
||||
* 返回值:UI_WAVE_OK / NONE / BAD int
|
||||
* 限定条件:uiTask;命中缓存零卷操作
|
||||
*/
|
||||
static int ui_task_wave_seg_head(uint8_t a_idx, uint8_t *a_eng,
|
||||
uint8_t *a_trig, uint8_t *a_clip) {
|
||||
int rc = ui_task_wave_load_head(a_idx);
|
||||
|
||||
if (rc != UI_WAVE_OK) {
|
||||
return rc;
|
||||
}
|
||||
*a_eng = s_wave_meta.eng;
|
||||
*a_trig = s_wave_meta.trig_src;
|
||||
*a_clip = (uint8_t)(s_wave_meta.clip_mask & 0xFFu);
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读固定显示窗(触发点前后各一周波)的码值与峰值
|
||||
* 入口参数:a_idx - 段号;a_ch - 通道号;a_codes - UI_WAVE_WIN_PTS 个码值
|
||||
* (已并入段头 b_codes);a_peak_milli - 窗内绝对值最大点的 mV
|
||||
* 返回值:UI_WAVE_OK / NONE(段头未验过/卷忙)/ BAD(数据区读不齐) int
|
||||
* 限定条件:uiTask;先段头后窗口(未过完整性判定的段不供数据,页面显示
|
||||
* 「该段不可读」而不是半截波形——判废优先于展示)
|
||||
* 函数说明:窗口起点用段头 pre_pts 现算(§8.2「数据不是编译常量」:
|
||||
* 起点 = pre_pts - 窗长一半,与光标居中是同一个 20 的一半,
|
||||
* 改窗宽只改 ui_data.h 的 UI_WAVE_WIN_PTS 一处);峰值的 mV
|
||||
* 换算逐点走 wave_store_code_to_milli —— 全仓唯一口径,
|
||||
* 这里不重抄公式(§6.3;PC 道同一条款)
|
||||
*/
|
||||
static int ui_task_wave_seg_win(uint8_t a_idx, uint8_t a_ch,
|
||||
int16_t *a_codes, int32_t *a_peak_milli) {
|
||||
static FIL f;
|
||||
char path[WAVE_ST_PATH_MAX];
|
||||
UINT br = 0u;
|
||||
uint32_t off;
|
||||
uint32_t want;
|
||||
uint32_t start;
|
||||
uint32_t got_pts = 0u;
|
||||
int16_t raw[UI_WAVE_WIN_PTS];
|
||||
int32_t milli;
|
||||
int rc;
|
||||
|
||||
if (ui_task_wave_load_head(a_idx) != UI_WAVE_OK) {
|
||||
return UI_WAVE_BAD; /* 段头坏了/读不到:窗口无从谈起 */
|
||||
}
|
||||
if (a_ch >= s_wave_meta.ch_count) {
|
||||
return UI_WAVE_BAD;
|
||||
}
|
||||
if ((s_wave_meta.pts_per_ch < UI_WAVE_WIN_PTS) ||
|
||||
(s_wave_meta.pre_pts < (UI_WAVE_WIN_PTS / 2u))) {
|
||||
return UI_WAVE_BAD;
|
||||
}
|
||||
start = (uint32_t)s_wave_meta.pre_pts - (UI_WAVE_WIN_PTS / 2u);
|
||||
off = wave_store_data_off(s_wave_meta.ch_count, s_wave_meta.pts_per_ch,
|
||||
a_ch, start);
|
||||
if (off == 0xFFFFFFFFu) {
|
||||
return UI_WAVE_BAD;
|
||||
}
|
||||
want = wave_store_data_bytes_avail(s_wave_meta.pts_per_ch, (uint16_t)start,
|
||||
UI_WAVE_WIN_PTS);
|
||||
if (want != UI_WAVE_WIN_PTS * 2u) {
|
||||
return UI_WAVE_BAD; /* 窗口贴不出完整 40 点:显式不可读 */
|
||||
}
|
||||
if (wave_store_seg_path(path, (uint32_t)sizeof(path), a_idx) != 0) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
rc = UI_WAVE_BAD;
|
||||
if (f_open(&f, path, FA_READ) == FR_OK) {
|
||||
if ((f_lseek(&f, off) == FR_OK) &&
|
||||
(f_read(&f, s_wave_buf, want, &br) == FR_OK) && (br == want)) {
|
||||
if ((wave_store_le_decode(s_wave_buf, want, raw, UI_WAVE_WIN_PTS,
|
||||
&got_pts) == 0) &&
|
||||
(got_pts == UI_WAVE_WIN_PTS)) {
|
||||
rc = UI_WAVE_OK;
|
||||
}
|
||||
}
|
||||
(void)f_close(&f);
|
||||
}
|
||||
vol_lock_release();
|
||||
if (rc == UI_WAVE_OK) {
|
||||
uint8_t i;
|
||||
int32_t peak = 0;
|
||||
|
||||
for (i = 0u; i < UI_WAVE_WIN_PTS; i++) {
|
||||
int32_t v;
|
||||
|
||||
v = (int32_t)raw[i] + s_wave_meta.ch[a_ch].b_codes;
|
||||
if (v > 32767L) {
|
||||
v = 32767L;
|
||||
} else if (v < (-32768L)) {
|
||||
v = -32768L;
|
||||
}
|
||||
a_codes[i] = (int16_t)v;
|
||||
/* 显示与度量同一把尺(「谁被显示谁被度量」,§6.3):
|
||||
* a_codes 画的是 raw+b,而 wave_store_code_to_milli 的公式是
|
||||
* (a_code + b_codes) × k_q16 / 65536000 取整(App/algo/
|
||||
* wave_store.c:567 实测口径)——传 raw 进去,b 已由唯一口径
|
||||
* 自己加过,PK 数与屏上波形天然同尺,这里不再加第二遍
|
||||
* (加两遍才是错);clamp 只护 int16 画布,b 的 V0.1 恒 0 */
|
||||
if (wave_store_code_to_milli(&s_wave_meta.ch[a_ch], raw[i],
|
||||
&milli) == 0) {
|
||||
if ((milli < 0 ? -milli : milli) >
|
||||
(peak < 0 ? -peak : peak)) {
|
||||
peak = milli;
|
||||
}
|
||||
}
|
||||
}
|
||||
*a_peak_milli = peak;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 录波通道函数表(六件齐全;ui_data_wave_bind 拒绝残表,缺件就是装配错) */
|
||||
static const ui_data_wave_ops_t s_wave_ops = {
|
||||
ui_task_wave_manual_req,
|
||||
ui_task_wave_seg_first,
|
||||
ui_task_wave_seg_prev,
|
||||
ui_task_wave_seg_next,
|
||||
ui_task_wave_seg_head,
|
||||
ui_task_wave_seg_win,
|
||||
};
|
||||
|
||||
/*
|
||||
* 函数功能:推流往返边界的心跳续命钩(F3,注册给 ui_net_sink)
|
||||
* 入口参数:无
|
||||
@@ -462,6 +929,12 @@ void ui_task_func(void *argument) {
|
||||
if (ui_data_maint_bind(&s_maint_ops) != 0) {
|
||||
DBG_ERROR("UI maint bind: FAIL");
|
||||
}
|
||||
/* 录波通道注入(故障录波道 UI 侧,2026-10-29):手动录波触发页与回放页
|
||||
* 的唯一数据入口,同样先于 ui_menu_init。未注入窗口里六件按「无录波段」
|
||||
* 空态渲染(不画线、不崩);残表 = 装配错,启动日志必须可见 */
|
||||
if (ui_data_wave_bind(&s_wave_ops) != 0) {
|
||||
DBG_ERROR("UI wave bind: FAIL");
|
||||
}
|
||||
/* 快照层复位:必须先于 ui_menu_init(首帧之前任何页面被打开都按"无数据"
|
||||
* 渲染 "--",不会读到上电残留)——UI_数据供给设计说明.md §5.3 */
|
||||
ui_data_init();
|
||||
|
||||
@@ -1087,3 +1087,87 @@ uint8_t ui_data_comm_pending(void) {
|
||||
}
|
||||
return s_comm->pending();
|
||||
}
|
||||
|
||||
/* ── 通道六:故障录波(wave)注入与包装 ─────────────────────────────────────
|
||||
* 包装层零判据:未注入统一按「无录波段」态返(页面显式空态文案,绝不
|
||||
* 画平线——设计 §8.1 空态行);六件的参数校验只到指针级,语义判定
|
||||
* (卷忙/段损坏)全在执行侧,与通道四/五同一条分层纪律。
|
||||
*/
|
||||
static const ui_data_wave_ops_t *s_wave = NULL;
|
||||
|
||||
/*
|
||||
* 函数功能:注入录波执行侧函数表(启动期一次)
|
||||
* 入口参数:ops - 六件齐;NULL = 撤销注入
|
||||
* 返回值:0 - 已注入/已撤销;-1 - 残表(存量不动,理由逐字同 comm bind) int
|
||||
* 限定条件:uiTask 启动期,先于 ui_menu_init()
|
||||
*/
|
||||
int ui_data_wave_bind(const ui_data_wave_ops_t *a_ops) {
|
||||
if (a_ops == NULL) {
|
||||
s_wave = NULL;
|
||||
return 0;
|
||||
}
|
||||
if ((a_ops->manual_req == NULL) || (a_ops->seg_first == NULL) ||
|
||||
(a_ops->seg_prev == NULL) || (a_ops->seg_next == NULL) ||
|
||||
(a_ops->seg_head == NULL) || (a_ops->seg_win == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
s_wave = a_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 函数功能:投手动录波请求(未注入 = 拒收,页面显示 ERR) bool */
|
||||
int ui_data_wave_manual_req(void) {
|
||||
if (s_wave == NULL) {
|
||||
return -1;
|
||||
}
|
||||
return s_wave->manual_req();
|
||||
}
|
||||
|
||||
/* 函数功能:录波通道三态查询的统一未注入兜底(返 NONE 显空态,不崩) int */
|
||||
static int wp_wave_none(void) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
|
||||
/* 函数功能:取数值最大段号(回绕后不保证时间最新——段号自证,见 .h 契约) int */
|
||||
int ui_data_wave_seg_first(uint8_t *a_idx) {
|
||||
if ((s_wave == NULL) || (a_idx == NULL)) {
|
||||
return wp_wave_none();
|
||||
}
|
||||
return s_wave->seg_first(a_idx);
|
||||
}
|
||||
|
||||
/* 函数功能:取更早一段段号 int */
|
||||
int ui_data_wave_seg_prev(uint8_t a_cur, uint8_t *a_out) {
|
||||
if ((s_wave == NULL) || (a_out == NULL)) {
|
||||
return wp_wave_none();
|
||||
}
|
||||
return s_wave->seg_prev(a_cur, a_out);
|
||||
}
|
||||
|
||||
/* 函数功能:取更新一段段号 int */
|
||||
int ui_data_wave_seg_next(uint8_t a_cur, uint8_t *a_out) {
|
||||
if ((s_wave == NULL) || (a_out == NULL)) {
|
||||
return wp_wave_none();
|
||||
}
|
||||
return s_wave->seg_next(a_cur, a_out);
|
||||
}
|
||||
|
||||
/* 函数功能:读段头(执行侧含 CRC 完整性判定) int */
|
||||
int ui_data_wave_seg_head(uint8_t a_idx, uint8_t *a_eng, uint8_t *a_trig,
|
||||
uint8_t *a_clip) {
|
||||
if ((s_wave == NULL) || (a_eng == NULL) || (a_trig == NULL) ||
|
||||
(a_clip == NULL)) {
|
||||
return wp_wave_none();
|
||||
}
|
||||
return s_wave->seg_head(a_idx, a_eng, a_trig, a_clip);
|
||||
}
|
||||
|
||||
/* 函数功能:读显示窗口码值与峰值 mV int */
|
||||
int ui_data_wave_seg_win(uint8_t a_idx, uint8_t a_ch, int16_t *a_codes,
|
||||
int32_t *a_peak_milli) {
|
||||
if ((s_wave == NULL) || (a_codes == NULL) || (a_peak_milli == NULL) ||
|
||||
(a_ch >= UI_WAVE_CH_CNT)) {
|
||||
return wp_wave_none();
|
||||
}
|
||||
return s_wave->seg_win(a_idx, a_ch, a_codes, a_peak_milli);
|
||||
}
|
||||
|
||||
@@ -924,6 +924,111 @@ int ui_data_comm_set(uint8_t a_idx, uint32_t a_val);
|
||||
*/
|
||||
uint8_t ui_data_comm_pending(void);
|
||||
|
||||
/* ── 通道六:故障录波(wave)────────────────────────────────────────────────
|
||||
* 页面零 App include、零存储触点:录波执行侧(触发投递 / 段索引 / 段头字段 /
|
||||
* 窗口码值与峰值 mV)全部经本注入表进来,真实现见 App/task/ui_task.c 的
|
||||
* s_wave_ops;PC 模拟器绑同一形状的假件(simulator/main.c)。取数口径的
|
||||
* 唯一真源是 docs/子系统设计/故障录波设计说明.md §6.3(定点 mV 换算走
|
||||
* wave_store_code_to_milli,eng 只做徽标、绝不进算式)。
|
||||
*/
|
||||
|
||||
/* 录波通道数(与段头 ch_count 恒等:电气八路,温度不入波) */
|
||||
#define UI_WAVE_CH_CNT 8u
|
||||
/* 回放显示窗口点数(两周波;口径推导与设计 §4.2 的触发点位置钉在一起,
|
||||
* 窗起点与虚线光标由执行侧与页面各按同一定义实现,页面侧写死 */
|
||||
#define UI_WAVE_WIN_PTS 40u
|
||||
|
||||
/* 通道查询返回态:段存在且可用 */
|
||||
#define UI_WAVE_OK 0
|
||||
/* 无段(目录扫空 / 卷忙跳过 / 执行侧未注入,页面显示「无录波段」) */
|
||||
#define UI_WAVE_NONE 1
|
||||
/* 有段但不可读(长度不符 / 版本不符 / 任一段 CRC 不符,页面显示
|
||||
* 「该段不可读」——两种态都必须显式说,画直线等于伪造证据(设计 §8.1) */
|
||||
#define UI_WAVE_BAD 2
|
||||
|
||||
/*
|
||||
* 录波执行侧注入表(六件齐全;ui_data_wave_bind 拒绝残表)
|
||||
* 返回值约定:除 manual_req 外,各件返回 UI_WAVE_OK / NONE / BAD 三态;
|
||||
* seg_win 的出参仅在返 UI_WAVE_OK 时可读(通道五同一条出参契约)。
|
||||
*/
|
||||
typedef struct {
|
||||
/* 投一次手动录波请求。返回值:0 - 已受理;非零 - 拒收(含未落段通道
|
||||
* 不可用),页面据此走确认链的 ERR 支 */
|
||||
int (*manual_req)(void);
|
||||
/* 取盘上数值最大的段号(回放页开屏落点;左右键从此起环游)。
|
||||
* 回绕后本序不保证时间最新,页顶段号即自证;时间序要靠段头
|
||||
* frame_seq/t0,代价是 100 次开文件,本波不做(lead 裁定 2026-10-29) */
|
||||
int (*seg_first)(uint8_t *a_idx);
|
||||
/* 取比 a_cur 更早一段的段号(到最早端后环回数值最大端) */
|
||||
int (*seg_prev)(uint8_t a_cur, uint8_t *a_out);
|
||||
/* 取比 a_cur 更新一段的段号(到最大端后环回数值最小端) */
|
||||
int (*seg_next)(uint8_t a_cur, uint8_t *a_out);
|
||||
/* 读段头并做完整性判定(头 CRC 与数据 CRC 都在执行侧验完)。
|
||||
* 出参:eng - 单位口径(UI_DATA_ENG_* 同值域);trig - 触发源
|
||||
* (段头 trig_src 原值:手动/保护/定时);clip - 形状受限通道位图
|
||||
* (段头 clip 字节,位号 = 通道号) */
|
||||
int (*seg_head)(uint8_t a_idx, uint8_t *a_eng, uint8_t *a_trig,
|
||||
uint8_t *a_clip);
|
||||
/* 读当前显示窗口的码值(已并入 b_codes 偏移)与窗口峰值。
|
||||
* 出参:a_codes - UI_WAVE_WIN_PTS 个码值;a_peak_milli - 窗口内
|
||||
* 绝对值最大的那一点折算的 mV(定点,执行侧经 code_to_milli 算好,
|
||||
* 页面不重复换算式——§6.3 的单一口径条款;b_codes 由 code_to_milli
|
||||
* 内部加,与 a_codes 里已并的偏移同一把尺:谁被显示谁被度量) */
|
||||
int (*seg_win)(uint8_t a_idx, uint8_t a_ch, int16_t *a_codes,
|
||||
int32_t *a_peak_milli);
|
||||
} ui_data_wave_ops_t;
|
||||
|
||||
/*
|
||||
* 函数功能:注入录波执行侧函数表(启动期一次;形状对齐 ui_data_comm_bind)
|
||||
* 入口参数:ops - 函数表(六件都不得为 NULL);NULL = 撤销注入(宿主测试用)
|
||||
* 返回值:0 - 已注入/已撤销;-1 - 残表(存量不动) int
|
||||
* 限定条件:uiTask 启动期、先于 ui_menu_init()
|
||||
*/
|
||||
int ui_data_wave_bind(const ui_data_wave_ops_t *a_ops);
|
||||
|
||||
/*
|
||||
* 函数功能:投一次手动录波请求(录波触发页唯一写出口)
|
||||
* 返回值:0 - 已受理;-1 - 未注入或拒收(未注入与拒收同码,同通道五) int
|
||||
*/
|
||||
int ui_data_wave_manual_req(void);
|
||||
|
||||
/*
|
||||
* 函数功能:取数值最大段号(排序口径见 ui_data.c 同名胶水注释:回绕后本序
|
||||
* 不保证时间最新,页顶段号即自证;真时间序要逐段读 frame_seq/t0,
|
||||
* 代价 100 次开文件,本波不做)
|
||||
* 入口参数:a_idx - 出参,段号(仅返 OK 时可读)
|
||||
* 返回值:UI_WAVE_OK / UI_WAVE_NONE / UI_WAVE_BAD(未注入按 NONE 渲染空态,不崩) int
|
||||
*/
|
||||
int ui_data_wave_seg_first(uint8_t *a_idx);
|
||||
|
||||
/*
|
||||
* 函数功能:取 a_cur 的更早一段段号(环形回绕)
|
||||
* 入口参数:a_cur - 当前段号;a_out - 出参(仅返 OK 时可读)
|
||||
* 返回值:UI_WAVE_OK / UI_WAVE_NONE / UI_WAVE_BAD int
|
||||
*/
|
||||
int ui_data_wave_seg_prev(uint8_t a_cur, uint8_t *a_out);
|
||||
|
||||
/*
|
||||
* 函数功能:取 a_cur 的更新一段段号(环形回绕,方向与 prev 相反)
|
||||
* 入口参数:a_cur - 当前段号;a_out - 出参(仅返 OK 时可读)
|
||||
* 返回值:UI_WAVE_OK / UI_WAVE_NONE / UI_WAVE_BAD int
|
||||
*/
|
||||
int ui_data_wave_seg_next(uint8_t a_cur, uint8_t *a_out);
|
||||
|
||||
/*
|
||||
* 函数功能:读段头(含执行侧 CRC 完整性判定)——出参仅返 OK 时可读
|
||||
* 返回值:UI_WAVE_OK / NONE / BAD(未注入按 NONE) int
|
||||
*/
|
||||
int ui_data_wave_seg_head(uint8_t a_idx, uint8_t *a_eng, uint8_t *a_trig,
|
||||
uint8_t *a_clip);
|
||||
|
||||
/*
|
||||
* 函数功能:读显示窗口码值与峰值 mV——出参仅返 OK 时可读
|
||||
* 返回值:UI_WAVE_OK / NONE / BAD(未注入按 NONE) int
|
||||
*/
|
||||
int ui_data_wave_seg_win(uint8_t a_idx, uint8_t a_ch, int16_t *a_codes,
|
||||
int32_t *a_peak_milli);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+258
-223
@@ -329,6 +329,10 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x2, 0x0, 0x40, 0x8, 0x1, 0x0, 0x20, 0x7f,
|
||||
0xf0,
|
||||
|
||||
/* U+4E0D "不" */
|
||||
0xff, 0xe0, 0x40, 0x8, 0x2, 0x0, 0xd0, 0x29,
|
||||
0x9, 0x16, 0x21, 0x4, 0x0, 0x80, 0x10, 0x0,
|
||||
|
||||
/* U+4E3B "主" */
|
||||
0x8, 0x0, 0x80, 0x0, 0x3f, 0xe0, 0x40, 0x8,
|
||||
0x1, 0x3, 0xfe, 0x4, 0x0, 0x80, 0x10, 0x7f,
|
||||
@@ -423,6 +427,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x5f, 0x4a, 0xa9, 0x55, 0x2a, 0x85, 0x70, 0x88,
|
||||
0x70,
|
||||
|
||||
/* U+524A "削" */
|
||||
0x10, 0x32, 0x45, 0x52, 0x88, 0x5f, 0xeb, 0x5,
|
||||
0x7f, 0xac, 0x15, 0xfe, 0xb0, 0x46, 0x8, 0xc3,
|
||||
0x30,
|
||||
|
||||
/* U+524D "前" */
|
||||
0x20, 0x82, 0x23, 0xff, 0x80, 0x7, 0xc4, 0x8a,
|
||||
0x9f, 0x52, 0x2a, 0x7d, 0x48, 0xa9, 0x11, 0x26,
|
||||
@@ -535,6 +544,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x51, 0xc, 0x3, 0x4, 0xf, 0xf8, 0x10, 0x7f,
|
||||
0xf0,
|
||||
|
||||
/* U+57DF "域" */
|
||||
0x40, 0xa8, 0x11, 0x3f, 0xa0, 0x4e, 0xe8, 0x95,
|
||||
0x52, 0xaa, 0x76, 0x40, 0xcc, 0x57, 0x35, 0x81,
|
||||
0x10,
|
||||
|
||||
/* U+590D "复" */
|
||||
0x3f, 0xe8, 0x2, 0xff, 0x10, 0x23, 0xfc, 0x40,
|
||||
0x8f, 0xf0, 0x80, 0x3f, 0xca, 0x10, 0x3c, 0x38,
|
||||
@@ -735,6 +749,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0xa4, 0x95, 0x94, 0xd1, 0x12, 0x50, 0x51, 0xc,
|
||||
0x10,
|
||||
|
||||
/* U+653E "放" */
|
||||
0x41, 0x4, 0x20, 0x4, 0x7c, 0xf4, 0x24, 0x8a,
|
||||
0x9e, 0x52, 0x4a, 0x49, 0x49, 0x11, 0x25, 0x4d,
|
||||
0x10,
|
||||
|
||||
/* U+6545 "故" */
|
||||
0x22, 0x4, 0x40, 0x88, 0x7d, 0xf2, 0x44, 0x44,
|
||||
0xbe, 0x94, 0x52, 0x8a, 0x91, 0x23, 0xea, 0x46,
|
||||
@@ -1134,6 +1153,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x53, 0xea, 0x45, 0x48, 0xad, 0xf5, 0x20, 0x80,
|
||||
0x60,
|
||||
|
||||
/* U+8BE5 "该" */
|
||||
0x42, 0x4, 0x20, 0x3f, 0x81, 0xe, 0x44, 0x5f,
|
||||
0x8, 0x49, 0x12, 0x2c, 0x86, 0x30, 0x89, 0x6,
|
||||
0x10,
|
||||
|
||||
/* U+8BEF "误" */
|
||||
0x8f, 0x89, 0x10, 0x22, 0x7, 0xcc, 0x0, 0xbf,
|
||||
0x90, 0x83, 0xff, 0x42, 0xc, 0xa1, 0x22, 0x8,
|
||||
@@ -1144,6 +1168,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0xd2, 0x12, 0x7e, 0x48, 0x49, 0xf9, 0xa1, 0x24,
|
||||
0x60,
|
||||
|
||||
/* U+8BFB "读" */
|
||||
0x82, 0x9, 0xf8, 0x8, 0xf, 0xfc, 0x2, 0x8a,
|
||||
0x14, 0xc2, 0x48, 0x5f, 0xec, 0x51, 0x11, 0xc,
|
||||
0x10,
|
||||
|
||||
/* U+8D1F "负" */
|
||||
0x20, 0x7, 0xe1, 0x4, 0x41, 0x7, 0xfc, 0x80,
|
||||
0x91, 0x12, 0x22, 0x44, 0x41, 0x60, 0xc3, 0x60,
|
||||
@@ -1462,218 +1491,224 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 511, .adv_w = 192, .box_w = 11, .box_h = 1, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 513, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 529, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 546, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 563, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 580, .adv_w = 192, .box_w = 11, .box_h = 9, .ofs_x = 0, .ofs_y = -1},
|
||||
{.bitmap_index = 593, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 610, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 627, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 644, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 661, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 678, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 695, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 712, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 729, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 746, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 763, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 780, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 797, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 814, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 831, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 848, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 865, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 882, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 899, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 916, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 930, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 946, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 962, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 546, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 562, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 579, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 596, .adv_w = 192, .box_w = 11, .box_h = 9, .ofs_x = 0, .ofs_y = -1},
|
||||
{.bitmap_index = 609, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 626, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 643, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 660, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 677, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 694, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 711, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 728, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 745, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 762, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 779, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 796, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 813, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 830, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 847, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 864, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 881, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 898, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 915, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 932, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 949, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 963, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 979, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 995, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1012, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1028, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1},
|
||||
{.bitmap_index = 1040, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1056, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1073, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1090, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1107, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1124, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1141, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1158, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 1172, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1189, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1206, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1223, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1240, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1257, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1274, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1291, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1308, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1028, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1045, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1061, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1},
|
||||
{.bitmap_index = 1073, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1089, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1106, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1123, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1140, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1157, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1174, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1191, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 1205, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1222, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1239, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1256, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1273, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1290, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1307, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1324, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1341, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1358, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1375, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1392, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1409, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 1423, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1440, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1457, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1474, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1491, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1508, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1525, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1358, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1374, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1391, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1408, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1425, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1442, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1459, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 1473, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1490, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1507, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1524, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1541, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1558, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1575, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 1589, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1606, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1623, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 1637, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1654, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1671, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1688, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1705, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1722, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1739, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1756, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1773, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1790, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1807, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1824, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1841, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1858, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1875, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1892, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1909, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1926, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1943, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1960, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1977, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1994, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2011, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2027, .adv_w = 192, .box_w = 9, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 2040, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2057, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2073, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2090, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2104, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2121, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2138, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2155, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2172, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2189, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2206, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2223, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2240, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2257, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2274, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2291, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2308, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2325, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2341, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2357, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2374, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2391, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2408, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2425, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2442, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2459, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2476, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2493, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2510, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2527, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2544, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2561, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2578, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2595, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2612, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2626, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2643, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2660, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2677, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 2692, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2709, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2726, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2743, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1575, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1591, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1608, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1625, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 1639, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1656, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1673, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 1687, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1704, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1721, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1738, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1755, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1772, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1789, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1806, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1823, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1840, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1857, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1874, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1891, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1908, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1925, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1942, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1959, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1976, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 1993, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2010, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2027, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2044, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2061, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2078, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2094, .adv_w = 192, .box_w = 9, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 2107, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2124, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2140, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2157, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2171, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2188, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2205, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2222, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2239, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2256, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2273, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2290, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2307, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2324, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2341, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2358, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2375, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2392, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2408, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2424, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2441, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2458, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2475, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2492, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2509, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2526, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2543, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2560, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2577, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2594, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2611, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2628, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2645, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2662, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2679, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2693, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2710, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2727, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2744, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 2759, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2776, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2792, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2809, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2776, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2793, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2810, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2826, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2843, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2860, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2877, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2894, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2911, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2928, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2945, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2962, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2979, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2996, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3013, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3030, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3047, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3064, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3081, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 3095, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3112, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3129, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3146, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3163, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3180, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3197, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3214, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3231, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3248, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3265, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3282, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3299, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3316, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3333, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3350, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3367, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3384, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3401, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3418, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3435, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3452, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3469, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3486, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3503, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3520, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3537, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3554, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3571, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3588, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3605, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3622, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3639, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2843, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2859, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2876, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2893, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2910, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2927, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2944, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2961, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2978, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 2995, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3012, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3029, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3046, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3063, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3080, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3097, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3114, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3131, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3148, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 3162, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3179, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3196, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3213, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3230, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3247, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3264, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3281, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3298, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3315, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3332, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3349, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3366, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3383, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3400, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3417, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3434, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3451, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3468, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3485, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3502, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3519, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3536, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3553, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3570, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3587, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3604, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3621, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3638, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3655, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3672, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3689, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3706, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3723, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3740, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3757, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3774, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3791, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 3806, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 3821, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 3836, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3853, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3870, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3887, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3904, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3921, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3740, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3756, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3773, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3790, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3807, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3824, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3841, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3858, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3875, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3892, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 3907, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 3922, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
|
||||
{.bitmap_index = 3937, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3954, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3971, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 3988, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4005, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4022, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4039, .adv_w = 192, .box_w = 2, .box_h = 4, .ofs_x = 2, .ofs_y = 0},
|
||||
{.bitmap_index = 4040, .adv_w = 192, .box_w = 2, .box_h = 6, .ofs_x = 2, .ofs_y = 0},
|
||||
{.bitmap_index = 4042, .adv_w = 165, .box_w = 8, .box_h = 10, .ofs_x = 1, .ofs_y = -1} /* U+25B6 */
|
||||
{.bitmap_index = 4022, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4038, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4055, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4072, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4089, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4106, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4123, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
|
||||
{.bitmap_index = 4140, .adv_w = 192, .box_w = 2, .box_h = 4, .ofs_x = 2, .ofs_y = 0},
|
||||
{.bitmap_index = 4141, .adv_w = 192, .box_w = 2, .box_h = 6, .ofs_x = 2, .ofs_y = 0},
|
||||
{.bitmap_index = 4143, .adv_w = 165, .box_w = 8, .box_h = 10, .ofs_x = 1, .ofs_y = -1} /* U+25B6 */
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@@ -1681,34 +1716,34 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_1[] = {
|
||||
0x0, 0x1, 0x2, 0x2ca0, 0x2ca9, 0x2caa, 0x2cdb, 0x2d2b,
|
||||
0x2d2c, 0x2d44, 0x2d96, 0x2ded, 0x2dee, 0x2dfc, 0x2e7d, 0x2e81,
|
||||
0x2edc, 0x3005, 0x300c, 0x3011, 0x3013, 0x3039, 0x309a, 0x30a6,
|
||||
0x30d6, 0x30ed, 0x3148, 0x31b6, 0x31da, 0x322b, 0x323f, 0x3262,
|
||||
0x326c, 0x3271, 0x3276, 0x3283, 0x328f, 0x3297, 0x32a8, 0x32cc,
|
||||
0x32cf, 0x32ea, 0x3308, 0x357e, 0x3580, 0x3594, 0x35d0, 0x35e0,
|
||||
0x362b, 0x37ad, 0x37b6, 0x37d1, 0x39f0, 0x39f7, 0x39f8, 0x3a3a,
|
||||
0x3a3e, 0x3a66, 0x3c92, 0x3cc7, 0x3cd8, 0x3d14, 0x3d2f, 0x3d46,
|
||||
0x3d96, 0x3da0, 0x3da2, 0x3daf, 0x3db5, 0x3dda, 0x3df2, 0x3df3,
|
||||
0x3df5, 0x3ea1, 0x3edb, 0x3f02, 0x3f0f, 0x40d7, 0x40eb, 0x4135,
|
||||
0x4136, 0x4144, 0x4145, 0x420e, 0x4229, 0x4245, 0x4247, 0x4249,
|
||||
0x436d, 0x43d6, 0x43e5, 0x43e8, 0x4410, 0x4414, 0x4480, 0x4485,
|
||||
0x4496, 0x44de, 0x45a0, 0x45a8, 0x45bf, 0x45ca, 0x45cc, 0x45da,
|
||||
0x461f, 0x4685, 0x46a7, 0x46c1, 0x46d7, 0x4760, 0x48c1, 0x49c0,
|
||||
0x49c1, 0x4a03, 0x4a1b, 0x4a55, 0x4ae2, 0x4b00, 0x4b82, 0x4be1,
|
||||
0x4beb, 0x4c28, 0x4ca5, 0x4cc9, 0x4f59, 0x50e7, 0x50e8, 0x5156,
|
||||
0x5177, 0x518c, 0x53c8, 0x53d5, 0x53ec, 0x5571, 0x5594, 0x55ab,
|
||||
0x5685, 0x56a1, 0x570c, 0x570e, 0x57da, 0x5872, 0x58ab, 0x5937,
|
||||
0x596b, 0x5979, 0x598f, 0x59cc, 0x5b1b, 0x5b9b, 0x5d5f, 0x5d68,
|
||||
0x5d7f, 0x5d8d, 0x5d94, 0x5df1, 0x5e0e, 0x5fba, 0x608a, 0x6122,
|
||||
0x614f, 0x61a3, 0x6217, 0x66ec, 0x6765, 0x6a06, 0x6a41, 0x6a44,
|
||||
0x6a4f, 0x6a50, 0x6a5e, 0x6a82, 0x6a8f, 0x6a97, 0x6bbf, 0x6bc5,
|
||||
0x6c2a, 0x6c93, 0x6e0f, 0x6e1d, 0x6e33, 0x6e67, 0x6e70, 0x6e71,
|
||||
0x6e74, 0x6ea0, 0x6ea1, 0x6eba, 0x6ef3, 0x6f05, 0x6f88, 0x6fed,
|
||||
0x7067, 0x706d, 0x706f, 0x733f, 0x7345, 0x73a1, 0x73b9, 0x741f,
|
||||
0x7488, 0x748d, 0x7494, 0x7498, 0x74d2, 0x7504, 0x753c, 0x7596,
|
||||
0x7602, 0x7715, 0x7731, 0x7828, 0x7836, 0x792c, 0x7d78, 0xddac,
|
||||
0xddba
|
||||
0x0, 0x1, 0x2, 0x2ca0, 0x2ca9, 0x2caa, 0x2cad, 0x2cdb,
|
||||
0x2d2b, 0x2d2c, 0x2d44, 0x2d96, 0x2ded, 0x2dee, 0x2dfc, 0x2e7d,
|
||||
0x2e81, 0x2edc, 0x3005, 0x300c, 0x3011, 0x3013, 0x3039, 0x309a,
|
||||
0x30a6, 0x30d6, 0x30ea, 0x30ed, 0x3148, 0x31b6, 0x31da, 0x322b,
|
||||
0x323f, 0x3262, 0x326c, 0x3271, 0x3276, 0x3283, 0x328f, 0x3297,
|
||||
0x32a8, 0x32cc, 0x32cf, 0x32ea, 0x3308, 0x357e, 0x3580, 0x3594,
|
||||
0x35d0, 0x35e0, 0x362b, 0x367f, 0x37ad, 0x37b6, 0x37d1, 0x39f0,
|
||||
0x39f7, 0x39f8, 0x3a3a, 0x3a3e, 0x3a66, 0x3c92, 0x3cc7, 0x3cd8,
|
||||
0x3d14, 0x3d2f, 0x3d46, 0x3d96, 0x3da0, 0x3da2, 0x3daf, 0x3db5,
|
||||
0x3dda, 0x3df2, 0x3df3, 0x3df5, 0x3ea1, 0x3edb, 0x3f02, 0x3f0f,
|
||||
0x40d7, 0x40eb, 0x4135, 0x4136, 0x4144, 0x4145, 0x420e, 0x4229,
|
||||
0x4245, 0x4247, 0x4249, 0x436d, 0x43d6, 0x43de, 0x43e5, 0x43e8,
|
||||
0x4410, 0x4414, 0x4480, 0x4485, 0x4496, 0x44de, 0x45a0, 0x45a8,
|
||||
0x45bf, 0x45ca, 0x45cc, 0x45da, 0x461f, 0x4685, 0x46a7, 0x46c1,
|
||||
0x46d7, 0x4760, 0x48c1, 0x49c0, 0x49c1, 0x4a03, 0x4a1b, 0x4a55,
|
||||
0x4ae2, 0x4b00, 0x4b82, 0x4be1, 0x4beb, 0x4c28, 0x4ca5, 0x4cc9,
|
||||
0x4f59, 0x50e7, 0x50e8, 0x5156, 0x5177, 0x518c, 0x53c8, 0x53d5,
|
||||
0x53ec, 0x5571, 0x5594, 0x55ab, 0x5685, 0x56a1, 0x570c, 0x570e,
|
||||
0x57da, 0x5872, 0x58ab, 0x5937, 0x596b, 0x5979, 0x598f, 0x59cc,
|
||||
0x5b1b, 0x5b9b, 0x5d5f, 0x5d68, 0x5d7f, 0x5d8d, 0x5d94, 0x5df1,
|
||||
0x5e0e, 0x5fba, 0x608a, 0x6122, 0x614f, 0x61a3, 0x6217, 0x66ec,
|
||||
0x6765, 0x6a06, 0x6a41, 0x6a44, 0x6a4f, 0x6a50, 0x6a5e, 0x6a82,
|
||||
0x6a85, 0x6a8f, 0x6a97, 0x6a9b, 0x6bbf, 0x6bc5, 0x6c2a, 0x6c93,
|
||||
0x6e0f, 0x6e1d, 0x6e33, 0x6e67, 0x6e70, 0x6e71, 0x6e74, 0x6ea0,
|
||||
0x6ea1, 0x6eba, 0x6ef3, 0x6f05, 0x6f88, 0x6fed, 0x7067, 0x706d,
|
||||
0x706f, 0x733f, 0x7345, 0x73a1, 0x73b9, 0x741f, 0x7488, 0x748d,
|
||||
0x7494, 0x7498, 0x74d2, 0x7504, 0x753c, 0x7596, 0x7602, 0x7715,
|
||||
0x7731, 0x7828, 0x7836, 0x792c, 0x7d78, 0xddac, 0xddba
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@@ -1719,12 +1754,12 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
|
||||
},
|
||||
{
|
||||
.range_start = 9654, .range_length = 1, .glyph_id_start = 313,
|
||||
.range_start = 9654, .range_length = 1, .glyph_id_start = 319,
|
||||
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
|
||||
},
|
||||
{
|
||||
.range_start = 8544, .range_length = 56763, .glyph_id_start = 96,
|
||||
.unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 217, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 223, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ const ui_menu_node_t g_menu_tab[UI_MENU_TAB_COUNT] = {
|
||||
{ 32, 0u, "自检记录" }, /* 39 二级菜单 */
|
||||
{ 32, 0u, "运行记录" }, /* 40 二级菜单 */
|
||||
{ 32, 0u, "运行报告" }, /* 41 二级菜单 */
|
||||
{ 32, 0u, "录波回放" }, /* 42 二级菜单 */
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -25,7 +25,7 @@ typedef struct {
|
||||
const char *name; /* 显示文本(字面量,存 Flash) */
|
||||
} ui_menu_node_t;
|
||||
|
||||
#define UI_MENU_TAB_COUNT 42
|
||||
#define UI_MENU_TAB_COUNT 43
|
||||
|
||||
/* 护栏:8 位索引上限 255 项(哨兵 0xFF 占用 255);超界则编译失败(C99 写法,兼容 ARMCC v5) */
|
||||
typedef char menu_tab_count_check[(UI_MENU_TAB_COUNT <= 255) ? 1 : -1];
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include "ui_page_view_plate.h"
|
||||
#include "ui_page_view_set.h"
|
||||
#include "ui_page_view_word.h"
|
||||
#include "ui_page_wave_manual.h"
|
||||
#include "ui_page_wave_play.h"
|
||||
#include "ui_page_yk.h"
|
||||
#include "ui_page_yx.h"
|
||||
|
||||
@@ -52,6 +54,7 @@ const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT] = {
|
||||
{ 19u, &ui_page_calib_desc }, /* node 19 遥测系数 */
|
||||
{ 24u, &ui_page_clock_desc }, /* node 24 时钟设置 */
|
||||
{ 25u, &ui_page_reset_desc }, /* node 25 强制复归 */
|
||||
{ 26u, &ui_page_wave_manual_desc }, /* node 26 手动录波 */
|
||||
{ 27u, &ui_page_clear_desc }, /* node 27 清除记录 */
|
||||
{ 29u, &ui_page_comm_desc }, /* node 29 通讯设置 */
|
||||
{ 30u, &ui_page_net_cfg_desc }, /* node 30 网口设置 */
|
||||
@@ -64,6 +67,7 @@ const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT] = {
|
||||
{ 39u, &ui_page_selftest_desc }, /* node 39 自检记录 */
|
||||
{ 40u, &ui_page_runlog_desc }, /* node 40 运行记录 */
|
||||
{ 41u, &ui_page_runreport_desc }, /* node 41 运行报告 */
|
||||
{ 42u, &ui_page_wave_play_desc }, /* node 42 录波回放 */
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -25,7 +25,7 @@ typedef struct {
|
||||
const content_page_def_t *desc; /* 页面描述符(Flash const) */
|
||||
} ui_page_entry_t;
|
||||
|
||||
#define UI_PAGE_MAP_COUNT 28u /* 绑定了内容页的叶子数 */
|
||||
#define UI_PAGE_MAP_COUNT 30u /* 绑定了内容页的叶子数 */
|
||||
|
||||
extern const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT];
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 模块名称:UI Page wave_manual (手动录波内容页)
|
||||
* 模块功能:装置维护 → 手动录波 叶子(node 26,#P 受密码保护)的内容页——
|
||||
* 复用 ui_page_action 共享确认链(一次确认 → 二次确认 → 执行回报):
|
||||
* 执行只做一件事——经录波注入表投一次「手动触发」请求,受理即回报
|
||||
* 「已请求录波」;采点、冻结、落盘全在 ring/store/落盘道,本页不
|
||||
* 等待也不查询结果(录波从触发到成段最快也要一秒后,页面上做同步
|
||||
* 等待等于把 uiTask 绘制定律「零 I/O」当场破掉);
|
||||
* 只读进入(查看密码)整页拒执行并显式 LOCK——与强制复归同一条
|
||||
* 「触发是操作不是查看」的权限判据(docs/任务规划/UI补齐.md §2
|
||||
* 维护组的既有口径,本页不自造第二种);
|
||||
* 本文件零 App include、零存储触点(分层方向由注入表守住)。
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建(故障录波道 UI 侧;触发入口形态 =
|
||||
* docs/子系统设计/故障录波设计说明.md §8.3;
|
||||
* 冷却/忽略的屏上反馈:拒收即 ERR,ring 侧
|
||||
* 「冷却中丢弃并计数」不在页面上细分原因——
|
||||
* 用户要的原因区分在回放页看段号)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include "ui_page_wave_manual.h"
|
||||
|
||||
#include "ui_content_page.h"
|
||||
#include "ui_data.h"
|
||||
#include "ui_page_action.h"
|
||||
|
||||
/* 私有宏定义区 */
|
||||
/* (无——文案词全部是 Flash 字面量,见下面静态配置) */
|
||||
|
||||
/* 私有函数声明区(静态初始化器要引用下面函数的地址,故先声明) */
|
||||
|
||||
static bool wv_ok(void);
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
/*
|
||||
* 本页操作页实例(配置全为 Flash 字面量与函数地址;运行态两字节由组件写)。
|
||||
* 字段顺序 = ui_page_action_t 声明序(ARMCC v5 = C99 要求指定顺序一致)。
|
||||
* 观测行留空(label 与 cb 成对给 NULL,组件允许):录波段数要扫卷才能知道,
|
||||
* 而组件 watch_cb 的契约是「绘制路径零 I/O 直调」——不为一个观测数破坏
|
||||
* 绘制定律,段数的事交给回放页(它本来就要读段)。
|
||||
*/
|
||||
static ui_page_action_t s_act = {
|
||||
"确认手动录波?", /* prompt:问句用字全在既有子集(确认/手动录波 均来自
|
||||
菜单与强制复归页现词,'?' 为 ASCII) */
|
||||
"已请求录波", /* done_text:回报「请求已受理」,不谎称「已存成段」——
|
||||
受理到落盘之间还隔着冻结窗与卷写,页面无权代言 */
|
||||
wv_ok, /* ok_cb */
|
||||
1u, /* need_modify:只读进入一律拒执行(触发是操作) */
|
||||
NULL, /* watch_label(观测行不给,理由见上方块注释) */
|
||||
NULL, /* watch_cb */
|
||||
0u, /* stage(运行态初值) */
|
||||
0u /* err (运行态初值) */
|
||||
};
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:执行投递——向录波环形缓冲投一次手动触发请求
|
||||
* 入口参数:无
|
||||
* 返回值:true = 已受理(组件转成功回报);false = 拒收(组件显示 ERR) bool
|
||||
* 限定条件:由 ui_page_action_key 在第二次 ENTER 时调用(uiTask 上下文)
|
||||
* 函数说明:ring 的 request 本身是「任意上下文可调的一次性请求」,非阻塞、
|
||||
* 不等待采点;注入未绑(装配缺件)同样返非零 → 屏上显 ERR,
|
||||
* 比「按了像成功其实什么都没发生」诚实(强制复归页同款纪律)。
|
||||
* 不补操作记录:录波不是五类审计记录之一(触发证据在段文件头里,
|
||||
* 落盘道已带 trig_src/t0/frame_seq),页面上再投一条 = 两处两个真源。
|
||||
*/
|
||||
static bool wv_ok(void) {
|
||||
return (ui_data_wave_manual_req() == 0) ? true : false;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:开页复位确认链(框架 init_cb)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:框架开页链内调用
|
||||
* 函数说明:复位由组件做(stage/err 是它的运行态),本页不重复清一遍
|
||||
*/
|
||||
static void pg_init(void) {
|
||||
ui_page_action_init(&s_act);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:自绘(框架 draw_cb;四行居中全由组件画)
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:框架绘制链末尾调用
|
||||
*/
|
||||
static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) {
|
||||
ui_page_action_draw(&s_act, a_layer, a_clip);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按键转发(框架 key_cb)
|
||||
* 入口参数:a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:true = 本页已消费;false = 交回框架 bool
|
||||
* 限定条件:框架按键链内
|
||||
* 函数说明:确认链的判定全在组件里(含只读拒执行),本页只如实透传返回值
|
||||
*/
|
||||
static bool pg_key(lv_key_t a_key) {
|
||||
return (ui_page_action_key(&s_act, a_key) != 0u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=wave_manual 绑定引用)
|
||||
* 操作页形态(强制复归页同款):无浅 V、无页脚、无行列表;
|
||||
* edit_capable=1 是「能不能改」的那半,另半(本次进入授予的权限)由
|
||||
* ui_content_page_is_editable() 给;key_intercept=0:LEFT 不拦截,关页 = 取消
|
||||
*/
|
||||
const content_page_def_t ui_page_wave_manual_desc = {
|
||||
.id = "wave_manual",
|
||||
.title = "手动录波",
|
||||
.hide_deco = 1u,
|
||||
.hide_footer = 1u,
|
||||
.selectable = 0u,
|
||||
.key_intercept = 0u,
|
||||
.edit_capable = 1u,
|
||||
.init_cb = pg_init,
|
||||
.draw_cb = pg_draw,
|
||||
.key_cb = pg_key,
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 模块名称:UI Page wave_manual (手动录波内容页)
|
||||
* 模块功能:装置维护 → 手动录波 叶子(node 26,#P 受密码保护)的内容页声明——
|
||||
* 向框架 ui_content_page 提供页面描述符;确认链全部走 ui_page_action
|
||||
* 共享实现,本模块零 App include、零存储触点(触发经录波注入表)
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建(故障录波道 UI 侧;形态与分工 =
|
||||
* docs/子系统设计/故障录波设计说明.md §8.3
|
||||
* 「手动录波操作页」一行)
|
||||
*/
|
||||
#ifndef UI_PAGE_WAVE_MANUAL_H
|
||||
#define UI_PAGE_WAVE_MANUAL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "ui_content_page.h"
|
||||
|
||||
/* 页面描述符(定义在 ui_page_wave_manual.c;生成物按菜单树 #page=wave_manual 引用本符号) */
|
||||
extern const content_page_def_t ui_page_wave_manual_desc;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* UI_PAGE_WAVE_MANUAL_H */
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* 模块名称:UI Page wave_play (录波回放内容页)
|
||||
* 模块功能:记录查询 → 录波回放 叶子的内容页——单通道阶梯波形回放(设计
|
||||
* docs/子系统设计/故障录波设计说明.md §8.1 元素表逐条落地):
|
||||
* 顶行 = 通道名 + 段号 + 触发源字母(M 手动 / P 保护 / T 定时);
|
||||
* 波形区 = 两周波 40 点、每点占 3 px 的**阶梯线**(1 bpp 下平滑
|
||||
* 折线的过零平缓段会被阈值吃掉,ui_deco.h 实测口径;阶梯线每段
|
||||
* 非水平即垂直,天然绕开,也更贴近「离散采样」的事实);
|
||||
* 参考线 = 0 线(虚线)+ 窗口峰值标注(定点 mV,ASCII);
|
||||
* 底行 = 单位徽标(只读段头 eng,eng 绝不参与算式——§6.3 钉死,
|
||||
* PC 道同一口径);形状受限通道右上打「削」徽标;
|
||||
* 空态显式文案「无录波段」/「该段不可读」——画成一条直线等于
|
||||
* 伪造证据(§8.1 空态行,此判据优先于「页面上总得画点东西」)。
|
||||
* 键位:上下切通道、左右切段(key_intercept=1 才能把 LEFT 从
|
||||
* 「关页」手里要来);ESC 关页。
|
||||
* 取数纪律:本文件零 App include——段号/段头/窗口码值/峰值 mV 全部经
|
||||
* ui_data.h 录波注入表进来(波形算式与定点换算在执行侧,页面
|
||||
* 连 b_codes 都不碰,收到的码值已并偏移);首帧绘制才懒取数
|
||||
* (开页链零 I/O,绘制定律不破)。
|
||||
* 显示窗口与光标(与落盘道/PC 道的同一裁定,写在这里免得两头漂):
|
||||
* 窗口 = 触发点前一周波 + 后一周波共 40 点,窗口起点 = 段内点号
|
||||
* 「预窗点数 减 二十」(即执行侧 seg_win 的固定窗);虚线竖光标
|
||||
* 落在窗内第 二十 与第 廿一 点之间(0 基第 19/20 点边界,即
|
||||
* 波形区正中 x)——依据 = 设计 §4.2 触发点位置 + App/algo/
|
||||
* wave_ring.h 的点序注释(触发 tick 采样 = 预窗最后一点),
|
||||
* 页面不复述任何换算式。
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建(故障录波道 UI 侧)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include "ui_page_wave_play.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "ui_content_page.h"
|
||||
#include "ui_data.h"
|
||||
#include "ui_tokens.h"
|
||||
|
||||
/* 私有宏定义区 */
|
||||
|
||||
/* 绘制区几何(屏宽 160:波形区左右各留 20 px 边;高 90 px) */
|
||||
#define WP_WX0 20 /* 波形区左边界 x */
|
||||
#define WP_PX_PER_PT 3 /* 每点占宽(§8.1 阶梯线口径) */
|
||||
#define WP_WTOP 34 /* 波形区顶 y(信息行下方) */
|
||||
#define WP_WBOT 124 /* 波形区底 y(0 线最多贴到这里) */
|
||||
#define WP_WSPANS 90 /* 纵向可用像素 = WP_WBOT - WP_WTOP */
|
||||
#define WP_CUR_X (WP_WX0 + (UI_WAVE_WIN_PTS / 2u) * WP_PX_PER_PT)
|
||||
/* 触发虚线光标 x(窗口正中,理由见文件头) */
|
||||
#define WP_INFO_Y 20 /* 顶行(通道/段号/触发 + 削徽标)条带 y */
|
||||
#define WP_HINT_Y 128 /* 倒数第二行(峰值/键位提示)条带 y */
|
||||
#define WP_ENG_Y 143 /* 底行(单位徽标)条带 y(页脚已隐藏) */
|
||||
#define WP_EMPTY_Y 60 /* 空态文案条带 y(近屏中部) */
|
||||
|
||||
/* 页面数据态:未取数前不画任何波形元素 */
|
||||
#define WP_ST_IDLE 0u /* 未取数/无段(空态文案) */
|
||||
#define WP_ST_OK 1u /* 段完整,可画波形 */
|
||||
#define WP_ST_BAD 2u /* 段不可读(显式文案,绝不画线) */
|
||||
|
||||
/* 私有函数声明区 */
|
||||
|
||||
static void wp_load_seg(void);
|
||||
static void wp_ensure(void);
|
||||
static void wp_draw_text(lv_layer_t *a_layer, const char *a_text,
|
||||
int32_t a_x, int32_t a_y, uint8_t a_align,
|
||||
const lv_area_t *a_clip);
|
||||
static int32_t wp_map_y(int32_t a_v, int32_t a_dmin, int32_t a_span);
|
||||
|
||||
/* 私有变量定义区 */
|
||||
|
||||
/*
|
||||
* 通道名(ASCII 段名 = 语义取数层的通道语义名,页面自带表避免为了
|
||||
* 六个字母再开一条注入边;与段头通道表的语义顺序一致:三相电压/零序/
|
||||
* 三相电流在前,形状受限路按段头 conv 位区分——名字本身两端一致)
|
||||
*/
|
||||
static const char *const s_ch_names[UI_WAVE_CH_CNT] = {
|
||||
"UA", "UB", "UC", "UN", "IA", "IB", "IC", "IN"
|
||||
};
|
||||
|
||||
/* 触发源字母(值域 = 段头 trig_src;未识别给 '?' 不给猜) */
|
||||
static const char s_trig_letters[] = { '?', 'M', 'P', 'T' };
|
||||
#define WP_TRIG_MAX ((uint8_t)(sizeof(s_trig_letters) - 1u))
|
||||
|
||||
static uint8_t s_state; /* WP_ST_* 三态 */
|
||||
static uint8_t s_idx; /* 当前段号(盘上编号,0..99) */
|
||||
static uint8_t s_ch; /* 当前通道(上下键切) */
|
||||
static uint8_t s_eng; /* 段头单位口径(只读徽标) */
|
||||
static uint8_t s_trig; /* 段头触发源 */
|
||||
static uint8_t s_clip; /* 段头形状受限位图(位号=通道) */
|
||||
static int16_t s_codes[UI_WAVE_WIN_PTS]; /* 当前窗码值(执行侧已并偏移) */
|
||||
static int32_t s_peak_milli; /* 窗口峰值 mV(执行侧算好) */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:取当前段的头与窗口(三态落到 s_state,坏态一律显式文案)
|
||||
* 入口参数:无(读 s_idx / s_ch)
|
||||
* 返回值:无
|
||||
* 限定条件:仅在 uiTask 绘制/按键链内调用(经注入表触到执行侧)
|
||||
* 函数说明:头都读不成时连顶行都不画——通道名以外的字段全来自段头,
|
||||
* 缺一半字段还硬凑顶行等于把「读不到」伪装成「读到了」
|
||||
*/
|
||||
static void wp_load_seg(void) {
|
||||
int rc = ui_data_wave_seg_head(s_idx, &s_eng, &s_trig, &s_clip);
|
||||
|
||||
if (rc != UI_WAVE_OK) {
|
||||
s_state = (rc == UI_WAVE_BAD) ? WP_ST_BAD : WP_ST_IDLE;
|
||||
return;
|
||||
}
|
||||
rc = ui_data_wave_seg_win(s_idx, s_ch, s_codes, &s_peak_milli);
|
||||
if (rc != UI_WAVE_OK) {
|
||||
s_state = (rc == UI_WAVE_BAD) ? WP_ST_BAD : WP_ST_IDLE;
|
||||
return;
|
||||
}
|
||||
s_state = WP_ST_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:首帧懒取数(开页链零 I/O 的落地方式:真正的卷读取推迟到
|
||||
* 第一次绘制——模拟器截图与真机重绘走同一条链,行为一致)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:draw_cb 开头调用
|
||||
* 函数说明:取不到段就停在空态;此后再按左右键仍会重试(手动录波从
|
||||
* 触发到成段有一秒级延迟,页开着等段出现是回放页的正常用法,
|
||||
* 不做「试过就没有了」的缓存)
|
||||
*/
|
||||
static void wp_ensure(void) {
|
||||
uint8_t idx = 0u;
|
||||
int rc;
|
||||
|
||||
if (s_state != WP_ST_IDLE) {
|
||||
return;
|
||||
}
|
||||
rc = ui_data_wave_seg_first(&idx);
|
||||
if (rc == UI_WAVE_OK) {
|
||||
s_idx = idx;
|
||||
wp_load_seg();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:单行文本绘制(对齐可选;ui_page_action 私有拷贝同法,此处
|
||||
* 多一个左/右对齐是因为顶行要把「削」徽标钉到右上角)
|
||||
* 入口参数:a_layer - 绘制层;a_text - Flash 字面量;a_x - 锚点 x(对齐
|
||||
* 含义:左=起点 中=屏宽轴 右=终点);a_y - 条带顶;
|
||||
* a_align - 0 左 / 1 中 / 2 右;a_clip - 重绘区
|
||||
* 返回值:无
|
||||
* 限定条件:a_text 非空;条带与 clip 不相交即返回(culling)
|
||||
* 函数说明:lv_text_get_width 必须传 strlen(传 0 不测量、传 -1 死循环,
|
||||
* 框架同款警示);本 LVGL 版条带矩形给 lv_draw_label 做底色无关
|
||||
* 文本定位用,字模颜色固定 UI_COL_FG
|
||||
*/
|
||||
static void wp_draw_text(lv_layer_t *a_layer, const char *a_text,
|
||||
int32_t a_x, int32_t a_y, uint8_t a_align,
|
||||
const lv_area_t *a_clip) {
|
||||
lv_area_t band;
|
||||
lv_draw_label_dsc_t ld;
|
||||
int32_t w = (int32_t)lv_text_get_width(a_text, (uint32_t)strlen(a_text),
|
||||
LV_FONT_DEFAULT, 0);
|
||||
|
||||
band.x1 = 0;
|
||||
band.x2 = UI_SCREEN_W - 1;
|
||||
band.y1 = a_y;
|
||||
band.y2 = a_y + UI_TEXT_BAND_H - 1;
|
||||
if ((band.x1 > a_clip->x2) || (band.x2 < a_clip->x1) ||
|
||||
(band.y1 > a_clip->y2) || (band.y2 < a_clip->y1)) {
|
||||
return;
|
||||
}
|
||||
lv_draw_label_dsc_init(&ld);
|
||||
ld.base.layer = a_layer;
|
||||
ld.text = a_text;
|
||||
ld.text_static = 1u;
|
||||
ld.color = UI_COL_FG;
|
||||
ld.ofs_x = (a_align == 1u) ? (a_x - (w / 2))
|
||||
: ((a_align == 2u) ? (a_x - w) : a_x);
|
||||
ld.ofs_y = 0;
|
||||
lv_draw_label(a_layer, &ld, &band);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:顶行触发字母(越界值给 '?' 不猜)
|
||||
* 入口参数:a_trig - 段头 trig_src 原值
|
||||
* 返回值:字母 ASCII char
|
||||
* 限定条件:仅绘制路径
|
||||
*/
|
||||
static char wp_trig_letter(uint8_t a_trig) {
|
||||
return (a_trig <= WP_TRIG_MAX) ? s_trig_letters[a_trig] : '?';
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:码值域到屏幕 y 的线性映射(0 基线必在域内:域取
|
||||
* 「min(0, 窗最小) .. max(0, 窗最大)」,削顶钉下轨的通道也能
|
||||
* 一眼看出贴的是 0 线还是别的什么)
|
||||
* 入口参数:a_v - 码值;a_dmin - 域底;a_span - 域高(域宽,恒 >0)
|
||||
* 返回值:波形区内 y 像素(WP_WBOT = 域底,WP_WTOP = 域顶) int32_t
|
||||
* 限定条件:绘制路径纯算术,零除由调用侧钳 span 保证
|
||||
* 函数说明:整数乘除即可——域高不超过 65534,先乘后除无溢出风险
|
||||
*/
|
||||
static int32_t wp_map_y(int32_t a_v, int32_t a_dmin, int32_t a_span) {
|
||||
return WP_WBOT - (((a_v - a_dmin) * WP_WSPANS) / a_span);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:波形区自绘——阶梯线 + 0 线(虚线)+ 触发点虚线竖光标
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:仅 s_state == WP_ST_OK 时被调用;s_codes 已备
|
||||
* 函数说明:阶梯 = 每点先画 3 px 水平段、再在点边界补一段垂直连线,
|
||||
* 全程只有水平与垂直两种线段(1 bpp 抗阈值裁定的直接落地);
|
||||
* 0 线与光标用 dash(ui_deco 先例只画过实线,dash 字段是 LVGL
|
||||
* 线段 dsc 自带的,单色层同样按位落墨);整区一次 cull,区内
|
||||
* 不再逐线判交——波形区就 120x90,逐线判交省不了多少,判错
|
||||
* 反而留下半截线
|
||||
*/
|
||||
static void wp_draw_wave(lv_layer_t *a_layer, const lv_area_t *a_clip) {
|
||||
lv_area_t band;
|
||||
lv_draw_line_dsc_t ld;
|
||||
int32_t vmin = s_codes[0];
|
||||
int32_t vmax = s_codes[0];
|
||||
int32_t dmin;
|
||||
int32_t dmax;
|
||||
int32_t span;
|
||||
int32_t i;
|
||||
int32_t y;
|
||||
int32_t y_prev = 0;
|
||||
|
||||
band.x1 = 0;
|
||||
band.x2 = UI_SCREEN_W - 1;
|
||||
band.y1 = WP_WTOP;
|
||||
band.y2 = WP_WBOT;
|
||||
if ((band.x1 > a_clip->x2) || (band.x2 < a_clip->x1) ||
|
||||
(band.y1 > a_clip->y2) || (band.y2 < a_clip->y1)) {
|
||||
return;
|
||||
}
|
||||
for (i = 1; i < (int32_t)UI_WAVE_WIN_PTS; i++) {
|
||||
int32_t v = s_codes[i];
|
||||
if (v < vmin) {
|
||||
vmin = v;
|
||||
}
|
||||
if (v > vmax) {
|
||||
vmax = v;
|
||||
}
|
||||
}
|
||||
dmin = (vmin < 0) ? vmin : 0;
|
||||
dmax = (vmax > 0) ? vmax : 0;
|
||||
span = dmax - dmin;
|
||||
if (span < 1) {
|
||||
span = 1; /* 全零窗:0 线与波形线重合贴在区底,诚实且无除零 */
|
||||
}
|
||||
|
||||
lv_draw_line_dsc_init(&ld);
|
||||
ld.base.layer = a_layer;
|
||||
ld.color = UI_COL_FG;
|
||||
ld.width = 1;
|
||||
|
||||
/* 0 线(虚线):恒在域内,永远画得出来 */
|
||||
ld.dash_width = 2;
|
||||
ld.dash_gap = 2;
|
||||
y = wp_map_y(0, dmin, span);
|
||||
ld.p1.x = WP_WX0 - 2;
|
||||
ld.p1.y = y;
|
||||
ld.p2.x = WP_WX0 + ((int32_t)UI_WAVE_WIN_PTS * WP_PX_PER_PT) + 1;
|
||||
ld.p2.y = y;
|
||||
lv_draw_line(a_layer, &ld);
|
||||
|
||||
/* 触发点竖光标(虚线):第 二十/廿一 点之间的点边界(见文件头裁定) */
|
||||
ld.p1.x = WP_CUR_X;
|
||||
ld.p1.y = WP_WTOP;
|
||||
ld.p2.x = WP_CUR_X;
|
||||
ld.p2.y = WP_WBOT;
|
||||
lv_draw_line(a_layer, &ld);
|
||||
|
||||
/* 阶梯线(实线) */
|
||||
ld.dash_width = 0;
|
||||
ld.dash_gap = 0;
|
||||
for (i = 0; i < (int32_t)UI_WAVE_WIN_PTS; i++) {
|
||||
int32_t x;
|
||||
|
||||
x = WP_WX0 + (i * WP_PX_PER_PT);
|
||||
y = wp_map_y((int32_t)s_codes[i], dmin, span);
|
||||
ld.p1.x = x;
|
||||
ld.p1.y = y;
|
||||
ld.p2.x = x + WP_PX_PER_PT - 1;
|
||||
ld.p2.y = y;
|
||||
lv_draw_line(a_layer, &ld);
|
||||
if ((i > 0) && (y != y_prev)) {
|
||||
ld.p1.x = x;
|
||||
ld.p1.y = y_prev;
|
||||
ld.p2.x = x;
|
||||
ld.p2.y = y;
|
||||
lv_draw_line(a_layer, &ld);
|
||||
}
|
||||
y_prev = y;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:单位徽标取词(只读 eng 选词,eng 不进任何算式——§6.3 口径;
|
||||
* 未识别值按最保守的「引脚域」渲染,不猜高口径)
|
||||
* 入口参数:a_eng - 段头 eng 原值
|
||||
* 返回值:徽标文案(Flash 字面量) const char *
|
||||
* 限定条件:仅绘制路径
|
||||
*/
|
||||
static const char *wp_eng_word(uint8_t a_eng) {
|
||||
if (a_eng == (uint8_t)UI_DATA_ENG_SEC) {
|
||||
return "二次值";
|
||||
}
|
||||
if (a_eng == (uint8_t)UI_DATA_ENG_PRI) {
|
||||
return "一次值";
|
||||
}
|
||||
return "引脚域";
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:开页复位(框架 init_cb)——通道回到 UA、数据回到未取态
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:框架开页链内;只清状态零 I/O(懒取数在首帧绘制)
|
||||
*/
|
||||
static void pg_init(void) {
|
||||
s_state = WP_ST_IDLE;
|
||||
s_ch = 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:自绘(框架 draw_cb)
|
||||
* 入口参数:a_layer / a_clip - 契约见 ui_content_page.h
|
||||
* 返回值:无
|
||||
* 限定条件:框架绘制链末尾调用
|
||||
*/
|
||||
static void pg_draw(lv_layer_t *a_layer, const lv_area_t *a_clip) {
|
||||
char buf[UI_BUF_STRLEN];
|
||||
|
||||
wp_ensure();
|
||||
if (s_state == WP_ST_BAD) {
|
||||
wp_draw_text(a_layer, "该段不可读", UI_SCREEN_W / 2, WP_EMPTY_Y, 1u,
|
||||
a_clip);
|
||||
return;
|
||||
}
|
||||
if (s_state != WP_ST_OK) {
|
||||
wp_draw_text(a_layer, "无录波段", UI_SCREEN_W / 2, WP_EMPTY_Y, 1u,
|
||||
a_clip);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 顶行:左 = 通道 + 段号 + 触发字母;右 = 形状受限「削」徽标 */
|
||||
(void)lv_snprintf(buf, sizeof(buf), "%s SEG%u %c", s_ch_names[s_ch],
|
||||
(unsigned int)s_idx, wp_trig_letter(s_trig));
|
||||
wp_draw_text(a_layer, buf, UI_LIST_LEFT_X, WP_INFO_Y, 0u, a_clip);
|
||||
if ((s_clip & (uint8_t)(1u << s_ch)) != 0u) {
|
||||
wp_draw_text(a_layer, "削", UI_SCREEN_W - 1, WP_INFO_Y, 2u, a_clip);
|
||||
}
|
||||
|
||||
wp_draw_wave(a_layer, a_clip);
|
||||
|
||||
/* 倒数第二行:左 = 键位提示(ASCII);右 = 窗口峰值 mV(定点,ASCII) */
|
||||
wp_draw_text(a_layer, "UD=CH LR=SEG", UI_LIST_LEFT_X, WP_HINT_Y, 0u,
|
||||
a_clip);
|
||||
(void)lv_snprintf(buf, sizeof(buf), "PK%dmV", (int)s_peak_milli);
|
||||
wp_draw_text(a_layer, buf, UI_SCREEN_W - 1, WP_HINT_Y, 2u, a_clip);
|
||||
|
||||
/* 底行:单位徽标(eng) */
|
||||
wp_draw_text(a_layer, wp_eng_word(s_eng), UI_SCREEN_W / 2, WP_ENG_Y, 1u,
|
||||
a_clip);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:按键处理(框架 key_cb)——上下切通道、左右切段
|
||||
* 入口参数:a_key - LVGL 键值 lv_key_t
|
||||
* 返回值:true = 本页已消费;false = 交回框架 bool
|
||||
* 限定条件:框架按键链内(ESC 不到这里;LEFT 因 key_intercept=1 先到这里)
|
||||
* 函数说明:环游语义 = 上下在八路间绕回、左右在现存段间绕回;切段失败
|
||||
* (卷忙/无邻段)保持当前画面不动——「按了没动」比「画面被
|
||||
* 忙态清成空」诚实,忙不忙在回放页本来就不是用户要的答案
|
||||
*/
|
||||
static bool pg_key(lv_key_t a_key) {
|
||||
uint8_t next;
|
||||
int rc;
|
||||
|
||||
switch (a_key) {
|
||||
case LV_KEY_UP:
|
||||
case LV_KEY_DOWN:
|
||||
next = (a_key == LV_KEY_UP)
|
||||
? (uint8_t)((s_ch + UI_WAVE_CH_CNT - 1u) % UI_WAVE_CH_CNT)
|
||||
: (uint8_t)((s_ch + 1u) % UI_WAVE_CH_CNT);
|
||||
s_ch = next;
|
||||
if (s_state == WP_ST_OK) {
|
||||
rc = ui_data_wave_seg_win(s_idx, s_ch, s_codes, &s_peak_milli);
|
||||
if (rc != UI_WAVE_OK) {
|
||||
s_state = (rc == UI_WAVE_BAD) ? WP_ST_BAD : WP_ST_IDLE;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case LV_KEY_LEFT:
|
||||
case LV_KEY_RIGHT:
|
||||
if (s_state == WP_ST_IDLE) {
|
||||
wp_ensure(); /* 空态下按左右 = 再探一次盘(新段可能已落) */
|
||||
return true;
|
||||
}
|
||||
rc = (a_key == LV_KEY_RIGHT) ? ui_data_wave_seg_next(s_idx, &next)
|
||||
: ui_data_wave_seg_prev(s_idx, &next);
|
||||
if (rc == UI_WAVE_OK) {
|
||||
s_idx = next;
|
||||
s_state = WP_ST_IDLE; /* 先回空态再载,坏段不会留着旧画面 */
|
||||
wp_load_seg();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=wave_play 绑定引用)
|
||||
* 查看页形态:无浅 V、无页脚(段号在顶行,页脚「共 N 页」与段号两套计数
|
||||
* 反而打架)、不可选(单元素自绘页)、可编辑位 0(本页零写动作);
|
||||
* key_intercept=1 是本页存在的理由级配置:LEFT 要拿来切上一段,不能让
|
||||
* 框架默认的「LEFT 关页」吃掉
|
||||
*/
|
||||
const content_page_def_t ui_page_wave_play_desc = {
|
||||
.id = "wave_play",
|
||||
.title = "录波回放",
|
||||
.hide_deco = 1u,
|
||||
.hide_footer = 1u,
|
||||
.selectable = 0u,
|
||||
.key_intercept = 1u,
|
||||
.edit_capable = 0u,
|
||||
.init_cb = pg_init,
|
||||
.draw_cb = pg_draw,
|
||||
.key_cb = pg_key,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 模块名称:UI Page wave_play (录波回放内容页)
|
||||
* 模块功能:记录查询 → 录波回放 叶子的内容页声明——向框架 ui_content_page
|
||||
* 提供页面描述符;单通道阶梯波形回放,数据全走录波注入表
|
||||
* (本模块零 App include、零存储触点、零波形算式)
|
||||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建(故障录波道 UI 侧;元素表 =
|
||||
* docs/子系统设计/故障录波设计说明.md §8.1)
|
||||
*/
|
||||
#ifndef UI_PAGE_WAVE_PLAY_H
|
||||
#define UI_PAGE_WAVE_PLAY_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "ui_content_page.h"
|
||||
|
||||
/* 页面描述符(定义在 ui_page_wave_play.c;生成物按菜单树 #page=wave_play 引用本符号) */
|
||||
extern const content_page_def_t ui_page_wave_play_desc;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* UI_PAGE_WAVE_PLAY_H */
|
||||
@@ -1057,6 +1057,16 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\algo\sb_led_map.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>wave_ring.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\algo\wave_ring.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>wave_store.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\algo\wave_store.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>ui_data_feed.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
@@ -3691,6 +3701,16 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\UI\ui_page_yk.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>ui_page_wave_manual.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\UI\ui_page_wave_manual.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>ui_page_wave_play.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\UI\ui_page_wave_play.c</FilePath>
|
||||
</File>
|
||||
</Files>
|
||||
</Group>
|
||||
<Group>
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
; *** 两条前提缺任何一条 = 静默数据损坏(2026-09-20 首轮 ***
|
||||
; *** 扩容实验实证)。CCM 存在性本机 ST-LINK 实测可读写; ***
|
||||
; *** 克隆片可能缺 SRAM3,换料必须重测! ***
|
||||
; *** 录波支路(2026-10-29 R6 台账同步):wave_ring.o 5,300B ***
|
||||
; *** (内含 int16×320×8 主环 5,120B)与落盘道那批 static 全 ***
|
||||
; *** 部按 .ANY 落 RW_IRAM1——环不是 DMA 可见对象,但按 R1 ***
|
||||
; *** CCM 只放 FreeRTOS 堆,且 CCM 只剩 7,904B(87.9%)本身 ***
|
||||
; *** 无位可给;实测三档 RW_CCM 恒 57,376B、RW_IRAM1 从 ***
|
||||
; *** 81,536B 升到 89,936B。凡「把大环挪进 CCM 腾 SRAM」的 ***
|
||||
; *** 念头,先过策略文档 §5 裁决与 §7 验证阶梯再动手。 ***
|
||||
; *************************************************************
|
||||
|
||||
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
|
||||
@@ -23,7 +30,7 @@ LR_IROM1 0x08000000 0x00100000 { ; load region size_region
|
||||
.ANY (+RW +ZI)
|
||||
}
|
||||
RW_CCM 0x10000000 0x0000FF00 { ; CCM 64KB 减 256B 尾: FreeRTOS 堆 only (no DMA!)
|
||||
heap_4.o (+RW +ZI) ; ucHeap 48KB: 任务栈/队列/定时器
|
||||
heap_4.o (+RW +ZI) ; ucHeap 56KB(2026-10-12 B5 由 48KB 扩容): 任务栈/队列/定时器
|
||||
}
|
||||
UNINIT_CCM 0x1000FF00 UNINIT 0x00000100 { ; round 8 取证: 复位不丢"进段打点"槽
|
||||
*(.noinit) ; IWDG/引脚复位不清 SRAM → 复位后仍可读
|
||||
|
||||
@@ -76,7 +76,3 @@
|
||||
- 本地等价脚本:[`../../scripts/ci_local.py`](../../scripts/ci_local.py)
|
||||
- runner 机器的工具链环境要求:[`../环境与部署/项目部署环境要求.md`](../环境与部署/项目部署环境要求.md)
|
||||
- `DTU_*` 工具路径变量机制:[`../环境与部署/本地工具路径环境变量方案.md`](../环境与部署/本地工具路径环境变量方案.md)
|
||||
|
||||
## 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -1350,9 +1350,3 @@ powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -DryRun
|
||||
|
||||
> 更早基于 Windows 服务的 `deploy_runner.ps1` / `install_service.ps1` 已删除
|
||||
> —— 那条路走不通(§3.0)。
|
||||
|
||||
---
|
||||
|
||||
## 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
+1
-7
@@ -91,7 +91,7 @@ docs/
|
||||
| [`运行与自检记录设计说明.md`](子系统设计/运行与自检记录设计说明.md) | `/log/run.log`·`selftest.log` 定长记录与待机显示 |
|
||||
| [`看门狗设计说明.md`](子系统设计/看门狗设计说明.md) | IWDG(CubeMX 配置)+ 任务心跳位图:选型与超时推导、掩码与埋点、取证机制、验证记录;已台架闭环(V2/V4/V5)。**§11 = 任务级饥饿的诊断方法与 `[PROF]` 实测基线**(诊断沉淀:判读口径、量化归因、三条脆弱点、明确不做的四条) |
|
||||
| [`ADC采样节拍设计说明.md`](子系统设计/ADC采样节拍设计说明.md) | **ADC 采集链路采样节拍(现状说明)**:TIM3 1 kHz 更新事件同时触发 TPAFE CONVST 与(经 TRGO)STM32 ADC1 + DMA 乒乓缓冲;节拍源为什么是 TIM3、DMA 硬约束、事件驱动消费与就绪门、实现落点、观测计数、性能特征与设计边界 |
|
||||
| [docs/子系统设计/故障录波设计说明.md](子系统设计/故障录波设计说明.md) | **故障录波设计与口径属主**:1 kHz 起步与 80 点口径的偏离登记、存原始码值的理由、四段所有权与三条并发陷阱、段文件字节布局、现场单通道回放显示契约、预算与 R6 前置 |
|
||||
| [docs/子系统设计/故障录波设计说明.md](子系统设计/故障录波设计说明.md) | **故障录波设计与口径属主**:1 kHz 起步与 80 点口径的偏离登记、存原始码值的理由、四段所有权与四条并发陷阱、段文件字节布局、现场单通道回放显示契约、预算与 R6 前置 |
|
||||
| [`IEC104上行设计说明.md`](子系统设计/IEC104上行设计说明.md) | **B4 上行规约**:IEC 60870-5-104 被控站(TCP 2404)范围/点表(V0.1 有效子集)/链路流程(k/w/t1~t3、总召、COV、遥控两段式、定值)/netTask 接入决策/预算与测试方案;协议核心纯逻辑 + 主机侧纯逻辑单测全栈单测 |
|
||||
| [`屏板链路设计说明.md`](子系统设计/屏板链路设计说明.md) | **屏板串口链路(`Drivers/BSP/ScreenLink/`)实现记录**:一句话架构与数据流、`sb_proto`/`sb_link` 全部对外接口、八条关键实现决定(不留影子缓冲/busy 冻结/中断起发等)、资源占用实测(`s_frame` 落 RW_IRAM1 非 CCM)、单测与台架/真屏板冒烟证据链、接线教训(排针丝印视角陷阱)、坑账本、**尚未实测项清单**;任务分解与协议事实源仍在 `docs/任务规划/屏板链路.md` |
|
||||
| [`网络/BSD_Socket_API_使用指南.md`](网络/BSD_Socket_API_使用指南.md) | **上层视图**:BSD Socket API 公开语义、示例、常见问题 |
|
||||
@@ -279,9 +279,3 @@ docs/
|
||||
| ~~`docs/文档规范.md` 未创建~~ | — | ✅ 已创建(2026-09-25):[`文档规范.md`](文档规范.md) |
|
||||
|
||||
> 未闭环的文档类问题**只剩仓库膨胀**(登记簿条目「仓库体积膨胀观察」,已决策暂不处理)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -186,7 +186,7 @@ MCU 端 `Drivers/BSP/dbg_log.h` 会引用 `sys_clock.h`(RTC 时间戳),PC
|
||||
- `LV_USE_THEME_MONO` 是否 = 1 且在 `sim_port_init` 中调了 `lv_theme_mono_init`(1bpp 无主题 = 黑底黑字)
|
||||
- 主循环是否调用 `lv_refr_now(NULL)` 强制首帧输出(PARTIAL 模式下保证)
|
||||
- 是否重复设置 tick:SDL 驱动内部 `lv_tick_set_cb(SDL_GetTicks)`,**不要**再 `SDL_AddTimer` + `lv_tick_inc`
|
||||
2. **中文字体缺字**:`ui_font_cn_12` 是子集字体(当前 218 汉字/符号 + ASCII 0x20~0x7E,共 313 字形;2026-10-28 实测)。**缺字才会显示为空白**——新增汉字必须重生成字体(见下文 6.3)。早期只有 83 字、当时菜单需要 186 字,那是 2026-09-03 的历史快照,不是现状。
|
||||
2. **中文字体缺字**:`ui_font_cn_12` 是子集字体(当前 224 汉字/符号 + ASCII 0x20~0x7E,共 319 字形;2026-10-29 实测)。**缺字才会显示为空白**——新增汉字必须重生成字体(见下文 6.3)。早期只有 83 字、当时菜单需要 186 字,那是 2026-09-03 的历史快照,不是现状。
|
||||
3. **`LV_SDL_RENDER_MODE` 必须为 `PARTIAL`**:1bpp + SDL 若非 PARTIAL,`lv_sdl_window.c` 会 `#error` 编译失败。
|
||||
|
||||
### 6.3 字体生成(如需补全中文字符)
|
||||
@@ -234,7 +234,7 @@ A: `LV_CONF_DIR` 变量未在 `add_subdirectory(lvgl)` 前定义,检查 `simul
|
||||
A: 按 6.2 黑屏排查顺序检查:mono 主题 → `lv_refr_now` → tick 不重复。
|
||||
|
||||
### Q4: 菜单某些中文显示空白?
|
||||
A: 子集字体未含该字(当前字符集 218 汉字/符号 + ASCII 0x20~0x7E,共 313 字形;2026-10-28 实测),见 6.3 重新生成字体。
|
||||
A: 子集字体未含该字(当前字符集 224 汉字/符号 + ASCII 0x20~0x7E,共 319 字形;2026-10-29 实测),见 6.3 重新生成字体。
|
||||
|
||||
### Q5: 修改了 UI 代码但模拟器没变化?
|
||||
A: 确认修改的是 `Drivers/BSP/UI/ui_menu.c` 而非 `simulator/` 内的副本(本工程无副本);然后重新 `cmake --build`。
|
||||
|
||||
@@ -55,7 +55,7 @@ scanned 13 files; unique non-ASCII chars: 78
|
||||
-> D:\Code\DTU Code\STM32F4-Base\ui_cn_chars.txt
|
||||
```
|
||||
|
||||
产物 `ui_cn_chars.txt` 为本工程实际显示的汉字清单(当前 218 字,含 ▶/,/: 等符号;2026-10-28 UI 补齐批「记录 5 页 + 运行报告」+33 字,185→218——本行旧数 183 是未回填的历史值,改前实测 185;其中 60 字为 2026-10-25 按页面扩充规划显示串预置,同日道E 落 SOE 记录显示名映射时新用 采/样/恢/效 4 字;清单里源码暂未在用的字由 font-charset 门禁记 WARN 放行),可留作字符集记录。
|
||||
产物 `ui_cn_chars.txt` 为本工程实际显示的汉字清单(当前 224 字,含 ▶/,/: 等符号;2026-10-29 录波 UI 道 +6 字(218→224);2026-10-28 UI 补齐批「记录 5 页 + 运行报告」+33 字,185→218——本行旧数 183 是未回填的历史值,改前实测 185;其中 60 字为 2026-10-25 按页面扩充规划显示串预置,同日道E 落 SOE 记录显示名映射时新用 采/样/恢/效 4 字;清单里源码暂未在用的字由 font-charset 门禁记 WARN 放行),可留作字符集记录。
|
||||
|
||||
---
|
||||
|
||||
@@ -152,8 +152,8 @@ OK 67325 bytes -> ...\Drivers\BSP\UI\ui_font_cn_12.c
|
||||
1. **1bpp 细笔画发虚**:宋体在 1bpp 下可能丢像素。若上板/客户端中文偏淡,把第 4 节 `--bpp` 改为 `2` 重生成即可(子集小,体积可接受)。
|
||||
2. **字符集变更**:以后新增中文菜单项,须重跑第 3、4 节(重新提取 `ui_cn_chars.txt` 并生成),否则新字显示为方框。
|
||||
3. **编码**:UI 源文件须为 **UTF-8(带 BOM)**,LVGL 按 UTF-8 解码中文串;字体 `.c` 为纯 ASCII,无编码风险。当前 Keil 已加 Misc Controls 抑制项,无需额外 `--locale=english`。
|
||||
4. **字体体积**:12px + 1bpp + 218 个汉字/符号(**不含 ASCII**——ASCII 0x20~0x7E 由 make_font 的 -r 参数一并打进子集,
|
||||
故字形数 = 218 + 95 = **313**,`glyph_dsc` 数组实含 notdef 共 314 条;2026-10-28 实测)源文件 ≈ 65.7KB(2026-10-28 实测 67,325 B;改前 185 字/280 字形/58.2KB,旧文本 183 字/278 字形系未回填);ROM 增量以 `.map` 为准(2026-10-25 扩 60 字实测 `RO-data` +1,528 B、RAM 0;同日 SOE 显示名再用 4 字实测 `RO-data` +108 B、RAM 0——1bpp 12px 一个字约 27 B,含字形描述符);改 bpp 2 约翻倍,仍在 F407 Flash 余量内。
|
||||
4. **字体体积**:12px + 1bpp + 224 个汉字/符号(**不含 ASCII**——ASCII 0x20~0x7E 由 make_font 的 -r 参数一并打进子集,
|
||||
故字形数 = 224 + 95 = **319**,`glyph_dsc` 数组实含 notdef 共 320 条;2026-10-29 录波 UI 道实测)源文件 ≈ 67.1KB(2026-10-29 实测 68,729 B;改前 185 字/280 字形/58.2KB,旧文本 183 字/278 字形系未回填);ROM 增量以 `.map` 为准(2026-10-25 扩 60 字实测 `RO-data` +1,528 B、RAM 0;同日 SOE 显示名再用 4 字实测 `RO-data` +108 B、RAM 0——1bpp 12px 一个字约 27 B,含字形描述符);改 bpp 2 约翻倍,仍在 F407 Flash 余量内。
|
||||
5. **换字体**:黑体用 `simhei.ttf`(单文件免抽取);其余字体同理替换 `--font` 路径后重生成。
|
||||
|
||||
---
|
||||
|
||||
@@ -694,7 +694,7 @@ typedef struct {
|
||||
| 4 | 未标定时页面明确标识(不把码值当伏特) | 目检 + 文本断言(`eng=0` 时带标记) |
|
||||
| 5 | 首帧前显示 `--` 而非 `0.000` | 模拟器:不 publish 直接开页,断言文本 |
|
||||
| 6 | 定值整定全链闭环 | 就地改 → 保存 → 重启 → 值保持 + 校验和自检通过 + 主站 C_RD 一致。**半收口(2026-10-26)**:模拟器侧「改 → 保存 → OK → 回首屏新值已在帧里」已由像素 + 文本断言覆盖(黄金用例 `sparam_save` / `sparam_landed`,越界侧 `sparam_oob`);**重启保持与主站 C_RD 一致只能上板**——模拟器没有 NAND,`apply()` 到 `prot_set_set_sealed()` 就是终点,这条差距已登记在 `docs/测试说明/模拟器差距清单.md`;另注意 40 项里有 13 项 `comm_addr=0`(纯本地项),主站侧根本读不到,不能拿 C_RD 当它们的判据 |
|
||||
| 7 | 像素回归稳定 | `ui-golden` 连续两次运行零差异。**已验(2026-10-26,当时下限为 27 页)**:27 页连跑两次逐字节一致(含整定 9 态)——**集合现已扩至 60 张**,双次一致需按同口径重跑回填;轮询定时器不影响——无头模式不调 `lv_timer_handler()`(§9.2 注 6) |
|
||||
| 7 | 像素回归稳定 | `ui-golden` 连续两次运行零差异。**已验(2026-10-26,当时下限为 27 页)**:27 页连跑两次逐字节一致(含整定 9 态)——**集合现已扩至 66 张**,**双次一致已验(2026-10-29:同一批 66 张连跑三次逐字节一致,池水位 5,448 B;菜单 8 状态连跑三次亦一致,5,368 B)**;轮询定时器不影响——无头模式不调 `lv_timer_handler()`(§9.2 注 6) |
|
||||
| 9 | 「写不进去」与「改了不动作」两条事实互不冒充 | 反向文本断言(`check_content_pages.py` 的 `ABSENT`:查看页与只读态不得出现「未标定」徽标与「保存」行)+ 台架:未制造该态——要它必须改坏 /prot_set.bin(破坏产品配置存储,用户裁定 2026-10-26 不做),故 LOCK 态目前只有代码级证据与三重反证(ENTER 被正常消费、遥信 YX31=OFF、开机 CORRUPT=0),**没有像素证据** |
|
||||
| 8 | 主机单测覆盖数据模块 | `test/host/ui_data` 全绿并挂 pre-commit 钩子 |
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ flowchart TD
|
||||
|---|---|---|---|
|
||||
| C1 | 屏幕 | 160×160,1bpp 单色(`LV_COLOR_FORMAT_I1`),无物理屏(远端客户端显示) | `ui_port.h` |
|
||||
| C2 | 字体 | **唯一**:`ui_font_cn_12`(宋体 12px 1bpp)。汉字 12px/字,ASCII 6px/字,行高 13px。需要其他字号 = 大工程(新字体 + 双端 `lv_conf.h`),默认禁止 | `ui_font_cn_12.c` |
|
||||
| C3 | 字符集 | 子集字体(当前 218 汉字/符号 + ASCII 0x20-0x7E,共 313 字形);**缺字显示空白**;新字必须重生成字体 | `tools/font_gen/` |
|
||||
| C3 | 字符集 | 子集字体(当前 224 汉字/符号 + ASCII 0x20-0x7E,共 319 字形);**缺字显示空白**;新字必须重生成字体 | `tools/font_gen/` |
|
||||
| C4 | LVGL 内存池 | 16KB(`LV_MEM_SIZE`,2026-10-15 由 40KB 收缩;实测峰值 6,568 B = 池的 40%);任一时刻只放"当前导航路径 + 当前内容页" | `Lib/lv_conf.h` |
|
||||
| C5 | 文本 | 一律静态字符串(`lv_label_set_text_static`),禁止动态字符串/strdup | 架构文档 |
|
||||
| C6 | 编码 | `Drivers/BSP/UI/*`、`ui_task.c`、`Lib/lv_conf.h` 为 **UTF-8 带 BOM**;改动只用 Edit 工具,**禁止** PowerShell/cmd 整文件重写(U+FFFD 不可逆) | AGENTS.md 铁律 |
|
||||
|
||||
@@ -26,7 +26,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
|
||||
| 步 | 脚本/命令 | 门禁内容 | 预期输出 |
|
||||
|---|---|---|---|
|
||||
| 1 | `simulator/tests/check_menu_rows.py` | 菜单 8 状态:选中行白带 / 非选行黑底白字 / 框体 1px 边框 / 往返像素一致(MENU-STATE-03==MENU-STATE-01、MENU-STATE-06==MENU-STATE-01、MENU-STATE-07==MENU-STATE-02)+ MENU-STATE-00 池基线 | `PASS: 8 状态全部断言通过(8 图,池基线 5368 B)` |
|
||||
| 2 | `simulator/tests/check_content_pages.py` | ① 文本级断言(字形模板匹配,正向 425 条 + 反向 36 条,独立于 `ref/`)→ ② 60 张截图与 `ref/` 黄金参考**逐字节**比对 + 开页-关页往返池水位 | `PASS: 60 内容页 + 往返池水位全部通过`(文本断言先于像素比对,失败即中止) |
|
||||
| 2 | `simulator/tests/check_content_pages.py` | ① 文本级断言(字形模板匹配,正向 444 条 + 反向 45 条,独立于 `ref/`)→ ② 66 张截图与 `ref/` 黄金参考**逐字节**比对 + 开页-关页往返池水位 | `PASS: 66 内容页 + 往返池水位全部通过`(文本断言先于像素比对,失败即中止) |
|
||||
| 3 | `keil-build` skill(`keil_build.py`) | MCU 端编译(UI 源码双端共用,改一端必须过另一端) | 退出码 0 = BUILD_OK |
|
||||
| 4 | `scripts/check_coding_standard.py` | 代码规范 V1.0 机械检查 | PASS |
|
||||
|
||||
@@ -52,7 +52,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
|
||||
| `POOL_AFTER_ROUNDTRIP` | check_content_pages.py | **5448 B** | 开页-关页往返后水位 = 基线 + 一次性残留 80 B |
|
||||
|
||||
> 水位历史:4784 →(2026-09-14 待机模块常驻 +656)5440 / 5520 →(2026-10-11 裁剪 LVGL 控件类 −72)5368 / 5448。
|
||||
> 后一次漂移已核实为**有意且更优**:像素逐字节未变(**裁剪当时黄金参考只有 11 页**,在裁剪**前**生成、裁剪后全部一致;该集合现已扩至 60 张),
|
||||
> 后一次漂移已核实为**有意且更优**:像素逐字节未变(**裁剪当时黄金参考只有 11 页**,在裁剪**前**生成、裁剪后全部一致;该集合现已扩至 66 张),
|
||||
> 且一次性残留仍是 80 B(5448 − 5368)→ 泄漏特征未变,只是 `lv_theme_apply` 不再为已不存在的控件类登记样式条目。
|
||||
|
||||
**漂移处理**:菜单/页面结构或样式变更导致门禁失败时,先确认新值合理(残留分解见
|
||||
@@ -61,7 +61,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
|
||||
|
||||
## 四、黄金参考(入库)与目检 PNG(本地产物)
|
||||
|
||||
`simulator/tests/ref/*.bin`(像素黄金参考,60 张 × 3200 B)**随仓库入库**;派生的 `.png`
|
||||
`simulator/tests/ref/*.bin`(像素黄金参考,66 张 × 3200 B)**随仓库入库**;派生的 `.png`
|
||||
目检图仍是本地产物(`.gitignore`)。入库的理由:比对是**逐字节**的,基线不入库时任何新克隆
|
||||
或新工作树跑 `ui-golden` 都必然 FAIL(缺基准),这条门禁就只在"有老副本的机器"上真实存在。
|
||||
可复现性实测(2026-10-22):两份各自独立构建的 `lvgl_sim`(不同构建目录、相隔 11 天的代码)
|
||||
@@ -148,7 +148,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
|
||||
### 7.3 用法与门禁档位
|
||||
|
||||
```powershell
|
||||
# 认屏快检:60 张黄金参考(+ 若有台架留档帧),秒级,不需要构建产物
|
||||
# 认屏快检:66 张黄金参考(+ 若有台架留档帧),秒级,不需要构建产物
|
||||
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --selftest-quick
|
||||
# 完整自检:再加全绑定页闭环导航(需模拟器可执行文件,约 30 s)
|
||||
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --selftest
|
||||
@@ -200,8 +200,3 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
|
||||
(两套走法迟早分叉,而分叉的那一边往往是真机)。导航器**默认只过查看密码**:判据需要写
|
||||
路径权限时必须显式传 `modify=True`,**不因「能进去」就自动升格成可写**——只读判据偷偷拿到
|
||||
修改权限,等于把判据的边界让给了实现。
|
||||
|
||||
---
|
||||
## 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -38,7 +38,7 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓
|
||||
| LVGL 内存池 | 16 KB(`LV_MEM_SIZE`,2026-10-15 由 40 KB 收缩;实测峰值 6,568 B = 池的 40%) | `Lib/lv_conf.h` |
|
||||
| 刷新周期 | 10 ms(`ui_port_step(10)` + `osDelay(10)`) | `ui_task.c` |
|
||||
| 推流端口 | 7003(TCP,与 DTU-RemoteLCD 客户端对齐) | `ui_net_sink.h` |
|
||||
| 默认字体 | `ui_font_cn_12`(12px 1bpp,218 汉字/符号 + ASCII 0x20–0x7E,共 313 字形) | `Lib/lv_conf.h` |
|
||||
| 默认字体 | `ui_font_cn_12`(12px 1bpp,224 汉字/符号 + ASCII 0x20–0x7E,共 319 字形) | `Lib/lv_conf.h` |
|
||||
| uiTask 栈 | 16384 words(BelowNormal) | `Src/freertos.c` |
|
||||
|
||||
### 1.4 硬约束(改 UI 必须遵守)
|
||||
@@ -164,7 +164,7 @@ SDL 键盘 → LVGL SDL 驱动内部 5ms 定时器(sdl_event_handler)
|
||||
|
||||
### 4.2 数据:父子索引表
|
||||
|
||||
- `g_menu_tab[]`:42 节点,每项 = 名称 + parent 下标(`MENU_ROOT`=0xFF 为根);树序即表序
|
||||
- `g_menu_tab[]`:43 节点,每项 = 名称 + parent 下标(`MENU_ROOT`=0xFF 为根);树序即表序
|
||||
- "页" = 对表的一次过滤:节点 N 的页条目 = 全部 `parent == N` 的行(按表序)
|
||||
- 菜单 vs 叶子由"是否有子项"自动推导:`has_children(N)` 非空 → 菜单行(带 ▶),为空 → 叶子(无箭头,ENTER 进空白终点框)
|
||||
|
||||
@@ -236,7 +236,7 @@ ENTER/→ 进子框:
|
||||
|
||||
### 5.3 框按需建/销
|
||||
|
||||
- LVGL 池仅 16KB(`LV_MEM_SIZE`,2026-10-15 由 40KB 收缩),全量预建 42 个节点的所有页面必然爆内存
|
||||
- LVGL 池仅 16KB(`LV_MEM_SIZE`,2026-10-15 由 40KB 收缩),全量预建 43 个节点的所有页面必然爆内存
|
||||
- 只建"当前路径"上的框:行自绘无对象,池内存 = 屏上框体(实测根框 used=5,368 B、最深四级级联 used=6,040 B、网口设置保存态峰值 used=6,568 B = 池的 40%)
|
||||
- 返回即 `lv_obj_delete` 释放,内存随时回落到根框水位
|
||||
|
||||
@@ -288,7 +288,3 @@ ENTER/→ 进子框:
|
||||
- **残留暴露**(刻意不归本票):单个往返自身仍无上界(osMessageQueuePut(osWaitForever)
|
||||
+ xTaskNotifyWait(portMAX_DELAY)),netTask 若被单消息拖 >800ms,UI 位连击照成立——
|
||||
那是「判据抓对了人」,处置在 net 侧,不在心跳侧。
|
||||
|
||||
## 六、变更记录(简表)
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
说明:选中行(第 1 行)无 ▶ 箭头;其余行 ▶ 紧跟在 4 字文本后(非右对齐)。行内文字左对齐、左留白 8px。
|
||||
|
||||
## 三、字符集核对(对照当时(2026-09-03)的 ui_font_cn_12:78 汉字 + ASCII 0x20~0x7E;本 mockup 的核对结论只对该时点成立,现值 = 218 汉字/符号 + ASCII,共 313 字形,见 docs/UI设计/UI_中文字体生成与配置.md §7)
|
||||
## 三、字符集核对(对照当时(2026-09-03)的 ui_font_cn_12:78 汉字 + ASCII 0x20~0x7E;本 mockup 的核对结论只对该时点成立,现值 = 224 汉字/符号 + ASCII,共 319 字形,见 docs/UI设计/UI_中文字体生成与配置.md §7)
|
||||
|
||||
- **菜单 7 项**共 22 个唯一汉字:命中 21(装 置 信 息 实 时 数 据 参 定 值 遥 设 维 护 通 讯 记 录 查 询),**新增 1(三)**。
|
||||
- **状态行**"当前模式 : 无模式":**新增 5(当 前 模 式 无)** + **全角冒号 :(U+FF1A,新增)**;空格为 ASCII(已在字库)。
|
||||
|
||||
+1
-1
@@ -453,7 +453,7 @@ extern const content_page_def_t ui_page_device_info_desc; /* 描述符(Flash
|
||||
## 八、验证与回归
|
||||
|
||||
- **门禁**(`ui-verify` skill / `UI_自动化测试流程.md`):
|
||||
0) 模拟器构建 0 警告 → 1) 菜单 8 状态像素回归 + 池基线 → 2) 内容页文本断言 + 60 张黄金参考逐字节比对
|
||||
0) 模拟器构建 0 警告 → 1) 菜单 8 状态像素回归 + 池基线 → 2) 内容页文本断言 + 66 张黄金参考逐字节比对
|
||||
(网口设置 4 态 + 密码页 4 态等)+ 开页-关页往返水位 → 3) Keil 构建 0 错 0 警 → 4) 代码规范检查
|
||||
- **门禁常量**(脚本头部):`MENU_POOL_BASELINE = 5368 B`、`POOL_AFTER_ROUNDTRIP = 5448 B`
|
||||
- **失败处置**:像素不一致 = 代码 bug(修代码,**禁止**刷参考掩盖);**有意**改视觉 →
|
||||
|
||||
+18
-15
@@ -1,6 +1,6 @@
|
||||
# 菜单页面设计
|
||||
|
||||
> **用途**:定义 DTU 主菜单的**内容(42 节点层级树)、视觉规范、三层代码架构**(数据层 / 显示层 / 逻辑层),以及叶子节点进入后的**内容页模块**(§九)。
|
||||
> **用途**:定义 DTU 主菜单的**内容(43 节点层级树)、视觉规范、三层代码架构**(数据层 / 显示层 / 逻辑层),以及叶子节点进入后的**内容页模块**(§九)。
|
||||
> **范围**:菜单层级(节点/父子/层级/进出导航)+ 三个已实现的内容页(装置信息/交流量/直流量,§九);其余叶子的内容页后续按同模式扩展。
|
||||
> **来源**:用户提供的主菜单截图 7 屏(根页 + 实时数据/参数定值/三遥设置/装置维护/通讯参数/记录查询各一级子页 + 参数定值·整定二级)。
|
||||
> **导航模型**:树形**逐级进入**——`ENTER`/`→` 进入子页,`ESC`/`←` 返回上一级;最多 **4 级(主界面 → 三级菜单)**。
|
||||
@@ -37,7 +37,7 @@
|
||||
flowchart TD
|
||||
K(["按键(远端键 / 模拟器键)"]) --> L["逻辑层 ui_menu.c<br/>唯一 17 B 导航状态(path/选择/深度/滚动)<br/>键语义:移动 / 进入 / 退出"]
|
||||
L -- "单向驱动: frame_build / frame_destroy / frame_refresh / focus" --> V["显示层 ui_menu_view.c/h<br/>持全部 LVGL 对象,纯渲染,零导航状态"]
|
||||
L -- "只读查询: children / valid" --> T["数据层 ui_menu_tab.c/h(生成物,只读)<br/>g_menu_tab[42] 父子索引表 + 4 个查询 API"]
|
||||
L -- "只读查询: children / valid" --> T["数据层 ui_menu_tab.c/h(生成物,只读)<br/>g_menu_tab[43] 父子索引表 + 4 个查询 API"]
|
||||
V -- "只读查询: children / has_children / name" --> T
|
||||
```
|
||||
|
||||
@@ -127,7 +127,7 @@ sequenceDiagram
|
||||
|
||||
---
|
||||
|
||||
## 二、菜单内容:42 节点层级树
|
||||
## 二、菜单内容:43 节点层级树
|
||||
|
||||
### 2.1 层级总览
|
||||
|
||||
@@ -193,10 +193,11 @@ sequenceDiagram
|
||||
├─ 遥控记录 [叶子]
|
||||
├─ 自检记录 [叶子]
|
||||
├─ 运行记录 [叶子]
|
||||
└─ 运行报告 [叶子]
|
||||
├─ 运行报告 [叶子]
|
||||
└─ 录波回放 [叶子]
|
||||
```
|
||||
|
||||
**统计**:菜单节点 **9** 个(含根),叶子节点 **33** 个,共 **42** 个节点;最大深度 **4 级**。
|
||||
**统计**:菜单节点 **9** 个(含根),叶子节点 **34** 个,共 **43** 个节点;最大深度 **4 级**。
|
||||
|
||||
### 2.3 节点定义表(逻辑层级定义)
|
||||
|
||||
@@ -236,7 +237,7 @@ sequenceDiagram
|
||||
| 29 | 通讯设置 | 二级菜单 | 叶子 | 28 | 0 |
|
||||
| 30 | 网口设置 | 二级菜单 | 叶子 | 28 | 0 |
|
||||
| 31 | SNTP设置 | 二级菜单 | 叶子 | 28 | 0 |
|
||||
| 32 | 记录查询 | 一级菜单 | 菜单 | 0 | 9 |
|
||||
| 32 | 记录查询 | 一级菜单 | 菜单 | 0 | 10 |
|
||||
| 33 | SOE记录 | 二级菜单 | 叶子 | 32 | 0 |
|
||||
| 34 | 事故记录 | 二级菜单 | 叶子 | 32 | 0 |
|
||||
| 35 | 操作记录 | 二级菜单 | 叶子 | 32 | 0 |
|
||||
@@ -246,12 +247,13 @@ sequenceDiagram
|
||||
| 39 | 自检记录 | 二级菜单 | 叶子 | 32 | 0 |
|
||||
| 40 | 运行记录 | 二级菜单 | 叶子 | 32 | 0 |
|
||||
| 41 | 运行报告 | 二级菜单 | 叶子 | 32 | 0 |
|
||||
| 42 | 录波回放 | 二级菜单 | 叶子 | 32 | 0 |
|
||||
|
||||
---
|
||||
|
||||
## 三、数据层:ui_menu_tab(生成物,只读)
|
||||
|
||||
> **一句话职责**:提供"有什么条目"——一张扁平父子索引表(42 节点,`const` 存 Flash、只读)+ 4 个查询 API,供逻辑层 / 显示层共用。
|
||||
> **一句话职责**:提供"有什么条目"——一张扁平父子索引表(43 节点,`const` 存 Flash、只读)+ 4 个查询 API,供逻辑层 / 显示层共用。
|
||||
> **绝不做**:持有任何状态、依赖 LVGL、被手改(改菜单走 §3.6 生成器)。
|
||||
|
||||
### 3.1 选型:扁平"父子索引表"(邻接表 / 关系表模型)
|
||||
@@ -288,7 +290,7 @@ typedef struct {
|
||||
|
||||
> 实现落在生成器产物 `Drivers/BSP/UI/ui_menu_tab.h`(见 §3.6);字段/语义与此一致。
|
||||
|
||||
### 3.3 完整 42 节点表(按 §2.3 节点定义表落地)
|
||||
### 3.3 完整 43 节点表(按 §2.3 节点定义表落地)
|
||||
|
||||
> 产物 `ui_menu_tab.c`(§3.6 生成):`const ui_menu_node_t g_menu_tab[UI_MENU_TAB_COUNT]`,内容与下表一致。
|
||||
|
||||
@@ -337,10 +339,11 @@ const ui_menu_node_t g_menu_tab[UI_MENU_TAB_COUNT] = {
|
||||
{ 32, "自检记录" }, /* 39 二级菜单叶子 */
|
||||
{ 32, "运行记录" }, /* 40 二级菜单叶子 */
|
||||
{ 32, "运行报告" }, /* 41 二级菜单叶子 */
|
||||
{ 32, "录波回放" }, /* 42 二级菜单叶子 */
|
||||
};
|
||||
```
|
||||
|
||||
> `UI_MENU_TAB_COUNT`(=42)与 `MENU_ROOT` 在 `ui_menu_tab.h` 定义。
|
||||
> `UI_MENU_TAB_COUNT`(=43)与 `MENU_ROOT` 在 `ui_menu_tab.h` 定义。
|
||||
|
||||
### 3.4 查询 API(逻辑层 / 显示层共用)
|
||||
|
||||
@@ -392,14 +395,14 @@ for (uint8_t i = 0; i < cnt; i++) {
|
||||
| `ui_menu_tree.txt` | **人工编辑源**(缩进即层级);改菜单改这里 |
|
||||
| `tools/menu_gen/gen_menu.py` | 生成器(同 `make_font.py` 模式):解析缩进→推导 parent→产出 C 表;含根唯一/缩进对齐/层级上限自检 |
|
||||
| `Drivers/BSP/UI/ui_menu_tab.h` | 产物:`ui_menu_node_t` 定义 + `MENU_ROOT` + `UI_MENU_TAB_COUNT` + `extern g_menu_tab[]` |
|
||||
| `Drivers/BSP/UI/ui_menu_tab.c` | 产物:`g_menu_tab[UI_MENU_TAB_COUNT]` 完整 42 节点表 |
|
||||
| `Drivers/BSP/UI/ui_menu_tab.c` | 产物:`g_menu_tab[UI_MENU_TAB_COUNT]` 完整 43 节点表 |
|
||||
|
||||
重新生成:
|
||||
```
|
||||
python tools/menu_gen/gen_menu.py
|
||||
```
|
||||
|
||||
> 产物 `ui_menu_tab.c/.h` 含 42 节点(9 菜单 + 33 叶子,最深 三级菜单);`ui_menu.c` 不持有本地表,导航/显示一律走 `ui_menu_tab_*` 查询 API(§3.4)。
|
||||
> 产物 `ui_menu_tab.c/.h` 含 43 节点(9 菜单 + 34 叶子,最深 三级菜单;2026-10-29 录波道加两叶后由 42 升到 43);`ui_menu.c` 不持有本地表,导航/显示一律走 `ui_menu_tab_*` 查询 API(§3.4)。
|
||||
|
||||
> **编码陷阱(已踩)**:产物含中文**字符串字面量**,必须 **UTF-8 带 BOM**(同 `ui_menu.c`)。
|
||||
> 无 BOM 时 cl.exe(代码页 936/GBK)会按 GBK 误读 UTF-8 字节 → 字符串解析错乱(C2001/C4819)。`gen_menu.py` 已用 `utf-8-sig` 输出。
|
||||
@@ -421,9 +424,9 @@ python tools/menu_gen/gen_menu.py
|
||||
| 符号 | 定义处 | 内容 | 占用 |
|
||||
|---|---|---|---|
|
||||
| `ui_menu_node_t` | `ui_menu_tab.h` | `{ uint8_t parent; uint8_t pwd; const char *name; }`(32 位 8 B/项) | 类型定义 |
|
||||
| `g_menu_tab[UI_MENU_TAB_COUNT]` | `ui_menu_tab.c`(生成) | 42 节点扁平父子表 | ≈ 42×8 B ≈ 336 B |
|
||||
| 名字字面量 | 同上 | 42 个 `const char[]` | ≈ 400 B |
|
||||
| `UI_MENU_TAB_COUNT` / `MENU_ROOT` | `ui_menu_tab.h` | 节点数 42 / 根哨兵 0xFF(8 位,上限 255 项) | 宏 |
|
||||
| `g_menu_tab[UI_MENU_TAB_COUNT]` | `ui_menu_tab.c`(生成) | 43 节点扁平父子表 | ≈ 43×8 B ≈ 344 B |
|
||||
| 名字字面量 | 同上 | 43 个 `const char[]` | ≈ 400 B |
|
||||
| `UI_MENU_TAB_COUNT` / `MENU_ROOT` | `ui_menu_tab.h` | 节点数 43 / 根哨兵 0xFF(8 位,上限 255 项) | 宏 |
|
||||
|
||||
> 逻辑层/显示层**只读**它(§3.4 的 API 都是对它的过滤/查询),从不修改。合计 ≈ **0.7 KB Flash**。
|
||||
|
||||
@@ -792,7 +795,7 @@ ESC/LEFT : d>0 → menu_back() (根框 d=0 不响
|
||||
1. 本 LVGL v9.3 的 `lv_text_get_width(txt, length, font, letter_space)` **length=0 时返回 0**(不是"整串");必须传 `strlen`——否则框宽计算全 0(根框退化 8 px)。
|
||||
2. 无头模式**不得**调 `lv_timer_handler()` / `lv_refr_now()`:I1 + `LV_DISPLAY_RENDER_MODE_FULL` 全屏刷新会进渲染死循环(截图因此走 `lv_snapshot`);`--keys` 注键是同步处理,无需驱动帧。
|
||||
3. 选中行**保留 ▶**(随反色变黑):参考图 02/04 原图即"选中行箭头仍在、仅随反色变黑",勿实现成"选中隐箭头"(§4.4)。
|
||||
4. 字体缺字形 = 方框:菜单树新增/改名的汉字必须同步加入字符集 `ui_cn_chars.txt`(当前 218 唯一字,2026-10-28 实测)并重生成字体(重跑 `extract_cn.py` + `make_font.py`),否则新字显示为方框(流程见 `UI_中文字体生成与配置.md`)。
|
||||
4. 字体缺字形 = 方框:菜单树新增/改名的汉字必须同步加入字符集 `ui_cn_chars.txt`(当前 224 唯一字,2026-10-29 实测)并重生成字体(重跑 `extract_cn.py` + `make_font.py`),否则新字显示为方框(流程见 `UI_中文字体生成与配置.md`)。
|
||||
5. **参考图保真度**:参考图为手工 mockup,4 个样本(子框顶位置)不满足单一公式;实现采用自洽规则(顶对齐/底对齐上移),实测与参考图偏差:顶位置 2/4/6/1 px(三遥/装置维护/通讯参数/记录查询),框宽 2/8/0/2 px(4字+箭头/4字/3字/2字+箭头)。两端极例(三遥、记录查询)精确吻合;统一规则优于逐屏特调,偏差范围内不影响识读与操作。
|
||||
6. 装饰"浅 V"两腿线宽必须 3(水平线 2):1bpp 下细斜线边缘被阈值吃掉,腿不粗于水平线即消失;且依赖 `LV_DRAW_SW_COMPLEX=1`(见 `UI_设计架构.md` §1.4 约束 7)。
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
已绑定 6 页:`装置信息`(node 1)、`交流量`(3)、`直流量`(4)、`网口设置`(30)、`自检记录`(39)、`运行记录`(40)。
|
||||
|
||||
> ⚠ **本表 = 2026-10-25 立项时快照,按历史冻结不回改。** 2026-10-28 复扫现值:**已绑 28 叶 / 未绑 5 叶**(判据两处互证:`ui_menu_tree.txt` 剔掉文件头注释里的示例行后 `#page=` 实数 = 28,与生成物 `ui_page_registry.h` 的 `UI_PAGE_MAP_COUNT` 一致);未绑 5 叶 = 遥信类型 / 遥信防抖 / 双点遥信 / 手动录波 / SNTP 设置。现状口径见 `docs/任务规划/UI补齐.md` §1 与 `docs/产品需求/产品需求设计文档.md` §8.2 / §8.3。
|
||||
> ⚠ **本表 = 2026-10-25 立项时快照,按历史冻结不回改。** 2026-10-28 复扫现值:**已绑 28 叶 / 未绑 5 叶**;**2026-10-29 再复扫:已绑 30 叶 / 未绑 4 叶**(手动录波与录波回放两叶已绑)(判据两处互证:`ui_menu_tree.txt` 剔掉文件头注释里的示例行后 `#page=` 实数 = 30,与生成物 `ui_page_registry.h` 的 `UI_PAGE_MAP_COUNT` 一致);未绑 5 叶 = 遥信类型 / 遥信防抖 / 双点遥信 / 手动录波 / SNTP 设置。现状口径见 `docs/任务规划/UI补齐.md` §1 与 `docs/产品需求/产品需求设计文档.md` §8.2 / §8.3。
|
||||
|
||||
### 1.2 27 个空叶子清单(按菜单顺序)
|
||||
|
||||
@@ -542,7 +542,7 @@ void ui_w_soe_refresh(ui_w_soe_t *inst, uint16_t row, char *name, char *valu
|
||||
|
||||
## 5. 菜单树改动
|
||||
|
||||
**本批不改菜单树**——27 个空叶子全在现有 42 节点里,`#page=` 注记逐个补上即可(改 `ui_menu_tree.txt` 后重跑 `python tools/menu_gen/gen_menu.py`)。
|
||||
**本批不改菜单树**——当时 27 个空叶子全在现有 42 节点里(节点数现值 43,2026-10-29 录波道加「录波回放」叶),`#page=` 注记逐个补上即可(改 `ui_menu_tree.txt` 后重跑 `python tools/menu_gen/gen_menu.py`)。
|
||||
|
||||
> 【2026-10-26 已改一笔】整定 4 叶(参数 / 定值 / 控制字 / 软压板)已补 `#page=`,
|
||||
> 绑的是与查看列**同一批模块**(一个模块被两个叶子绑定是生成器允许的,`validate_pages()` 按
|
||||
@@ -626,7 +626,7 @@ SNTP 客户端 ──────→ 批4 SNTP设置
|
||||
| 4 | 无源点显示 `--` | 遥信量无效点、直流量直流通道 |
|
||||
| 5 | 定值整定闭环 | 改 → 保存 → 重启 → 值保持 + 校验和自检通过 + 主站 C_RD 一致。**2026-10-26 半收口**:模拟器侧「改 → 保存 → `OK` → 回首屏新值已在帧里」与「越界被拒、值不落盘」已由像素 + 文本断言覆盖(`sparam_save` / `sparam_landed` / `sparam_oob`);**重启保持与 C_RD 一致只能上板**(模拟器无 NAND)。另注意 40 项里 **13 项 `comm_addr = 0`**(投退、返回系数、过频/低频段、全局软压板)主站不可寻址,不能拿 C_RD 当它们的判据 |
|
||||
| 6 | 分层门禁无新增 | `check_layer_deps.py` 退出码 0,基线 6 → 3。**现状:3**(记录 reader 已削 3 条;写路径走注入式 ops 函数表,未新增 include,见 `docs/UI设计/UI_数据供给设计说明.md` §6.2) |
|
||||
| 7 | 像素回归稳定 | `ui-golden` 连续两次零差异。**已验(2026-10-26)**:27 页连跑两次逐字节一致——**该下限此后已扩至 60 张**(新增 clock / reset / clear 系列 13 张,其后 47→53→60),60 张上的双次逐字节一致需按同口径重跑一次再回填本行 |
|
||||
| 7 | 像素回归稳定 | `ui-golden` 连续两次零差异。**已验(2026-10-26)**:27 页连跑两次逐字节一致——**该下限此后已扩至 66 张**(新增 clock / reset / clear 系列 13 张,其后 47→53→60→66,2026-10-29 录波 UI 道 +6 张),66 张上的双次逐字节一致**已验(2026-10-29:任务树重建模拟器后同一批 66 张连跑三次逐字节一致,页面池水位 5,448 B;菜单侧 `check_menu_rows.py` 8 状态连跑三次亦逐字节一致,5,368 B;三次之间未用更新模式写过任何参考图,故这是同口径的传递性证据)** |
|
||||
| 8 | 每个新页有 golden 参考 | `simulator/tests/ref/` 新增对应 `.bin`。**整定 4 页已有 9 张**(含保存/越界/落定/回绕各态);参考图入库,改它 = 改判据,须逐页过目 |
|
||||
|
||||
---
|
||||
|
||||
+9
-7
@@ -1,6 +1,7 @@
|
||||
# UI 页面补齐规划(2026-10-23)
|
||||
|
||||
> **定位**:盘清 42 节点菜单里**还没有内容页的 27 个叶子**,按"数据是否就绪 + 页面形态"归并成可施工的包,给出分批顺序与每页规格。
|
||||
> **定位**:盘清当时 42 节点菜单里**还没有内容页的 27 个叶子**(节点数现值 43 = 9 菜单 + 34 叶,
|
||||
> 2026-10-29 录波道加「录波回放」叶后升级;本文其余数字是 2026-10-23 立项快照,按 §1 的「完成即冻结」不回改),按"数据是否就绪 + 页面形态"归并成可施工的包,给出分批顺序与每页规格。
|
||||
> **前置阅读**:`docs/UI设计/内容页架构.md`(框架机制与新增页面六步)、`docs/UI设计/菜单页面设计.md`(菜单树与几何)、`docs/UI设计/UI_自动化测试流程.md`(像素回归门禁)。
|
||||
> **不重复登记**:页面级待办落地时按语义标识进 `docs/任务规划/登记簿.md`;本文只出规划与规格。
|
||||
> **逐页细化**:每页显示哪些行、数据从哪来、要改哪些代码,见 `docs/UI设计/页面扩充详细方案.md`(本文的落地细化版)。
|
||||
@@ -27,7 +28,7 @@
|
||||
### 2.1 规模
|
||||
|
||||
> ⚠ **本表是 2026-10-23 立项当天快照**(节点 42 / 有内容页叶子 6 / 空叶子 27 / 黄金参考 11 份均为当日实数,按"完成即冻结"不回改)。
|
||||
> 页面扩充落地后现值已变——黄金参考现为 60 份(`simulator/tests/ref/` 入库的 `shot_*.bin` 实数,2026-10-28 记录 5 页与运行报告页落地 +7 张),现状口径见
|
||||
> 页面扩充落地后现值已变——黄金参考现为 66 份(`simulator/tests/ref/` 入库的 `shot_*.bin` 实数,2026-10-28 记录 5 页与运行报告页落地 +7 张;2026-10-29 录波 UI 道 +6 张),现状口径见
|
||||
> `docs/UI设计/页面扩充详细方案.md` 与 `docs/UI设计/UI_数据供给设计说明.md`。
|
||||
|
||||
| 项 | 数 |
|
||||
@@ -77,7 +78,7 @@
|
||||
| 22 | 双点遥信 | 可编辑列表 | 开入量 | ❌ 同上 |
|
||||
| 24 | 时钟设置 | 表单(6 字段) | `sys_clock_get_time/set` | ✅ |
|
||||
| 25 | 强制复归 | 操作页(确认) | `prot_elm_request_reset()` | ✅ |
|
||||
| 26 | 手动录波 | 操作页(确认) | 录波模块(docs/任务规划/故障录波.md 已立项) | 🔶 录波立项未落地 |
|
||||
| 26 | 手动录波 | 操作页(确认) | `wave_ring_request` 手动触发经 `ui_data_wave_ops_t` 注入 | ✅ 已落地(`ui_page_wave_manual.c`,2026-10-29 录波 UI 道;配套新增「录波回放」叶 node 43 = `ui_page_wave_play.c`,不在本表 27 叶之列) |
|
||||
| 27 | 清除记录 | 操作页(选类型 + 确认) | `sys_log`(**缺删除 API**) | 🔶 需补 API |
|
||||
| 29 | 通讯设置 | 可编辑列表(4~6 项) | 104 公共地址/端口/主站(当前固定) | 🔶 需 `comm_cfg` |
|
||||
| 31 | SNTP设置 | 可编辑列表(3 项) | SNTP 客户端 | ❌ 未实现(产品口径亦未定) |
|
||||
@@ -91,7 +92,8 @@
|
||||
|
||||
**统计(2026-10-25 复核)**:本表 27 叶中 **6 叶已落地**(遥信量 / 查看 4 页 / SOE),剩 21 叶 = 数据现成 **8** 项(整定 4 + 时钟 + 复归 + 死区 + 系数)· 需新数据层 **2** 项(清除记录 / 通讯设置)· 数据源不存在 **11** 项(遥信 3 + 手动录波 + SNTP + 记录 5 + 运行报告)。**分批与 DoD 见 `docs/任务规划/UI补齐.md`**——本表只讲逐页形态与数据源,不重复排期。
|
||||
|
||||
**2026-10-28 复核**:本表 27 叶中 **22 叶已落地**(本批 +6:记录 5 页与运行报告,见 docs/任务规划/UI补齐.md §2 与 docs/任务规划/登记簿.md §7);未落地的 5 叶 = 遥信类型 / 遥信防抖 / 双点遥信(无开入硬件)+ 手动录波(无录波模块)+ SNTP设置(产品口径未定),取舍与重开条件见 docs/任务规划/UI补齐.md §5。口径闭合:本表 22 叶 + 立项前就已绑定、不在本表内的 6 叶(装置信息 / 交流量 / 直流量 / 网口设置 / 自检记录 / 运行记录)= 现值已绑 28 叶(= UI_PAGE_MAP_COUNT);本表未落地 5 叶 = 菜单树未绑 5 叶(33 − 28)。复核方法:按缩进判叶子(有更深缩进后继者即非叶子),再按行内 #page= 数已绑。**2026-10-29 补充**:「手动录波」已随故障录波独立立项(docs/任务规划/故障录波.md 与 docs/子系统设计/故障录波设计说明.md),上面那行数字仍是 2026-10-28 当日复核值,未随立项改动
|
||||
**2026-10-28 复核**:本表 27 叶中 **22 叶已落地**(本批 +6:记录 5 页与运行报告,见 docs/任务规划/UI补齐.md §2 与 docs/任务规划/登记簿.md §7);未落地的 5 叶 = 遥信类型 / 遥信防抖 / 双点遥信(无开入硬件)+ 手动录波(无录波模块)+ SNTP设置(产品口径未定),取舍与重开条件见 docs/任务规划/UI补齐.md §5。口径闭合:本表 22 叶 + 立项前就已绑定、不在本表内的 6 叶(装置信息 / 交流量 / 直流量 / 网口设置 / 自检记录 / 运行记录)= 现值已绑 28 叶(= UI_PAGE_MAP_COUNT);本表未落地 5 叶 = 菜单树未绑 5 叶(33 − 28)。复核方法:按缩进判叶子(有更深缩进后继者即非叶子),再按行内 #page= 数已绑。**2026-10-29 补充**:「手动录波」已随故障录波独立立项(docs/任务规划/故障录波.md 与 docs/子系统设计/故障录波设计说明.md),上面那行数字仍是 2026-10-28 当日复核值,未随立项改动。
|
||||
**2026-10-29 二补(录波 UI 道落地)**:本表「手动录波」转已落地(23 叶);菜单树另新增「录波回放」叶(node 43,不在本表立项快照内)——现值 43 节点 / 34 叶 / 已绑 30 / 未绑 4(= 遥信 3 + SNTP 1,UI_PAGE_MAP_COUNT = 30),口径复核方法与上一段相同
|
||||
|
||||
---
|
||||
|
||||
@@ -211,7 +213,7 @@ ui_page_ac / ui_page_yx / ui_page_setlist ...
|
||||
|
||||
12. **三遥设置 5 页**(18~22)——等运行参数段与开入量。
|
||||
13. **事故/操作/告警/启动/遥控 5 页**(34~38)——等记录写入方(每类记录 = 一个写入点 + 一个 `syslog_id_t`)。
|
||||
14. **手动录波**(26)——等录波模块。
|
||||
14. **手动录波**(26):操作页(`ui_page_action` 确认链,受理后回「已请求录波」)——已落地(2026-10-29);同批新增「录波回放」单页(上下键切通道、左右键切段、阶梯线 3px/点、0 线虚线、峰值 mV 经 `wave_store_code_to_milli` 唯一口径、削徽标、空态/坏段显式文案,规格见 docs/子系统设计/故障录波设计说明.md §8.1)。
|
||||
15. **运行报告**(41)——等计数器(上电次数/运行时长/复位原因/104 统计)。
|
||||
|
||||
### 批 4(可选):厂家设置
|
||||
@@ -253,7 +255,7 @@ ui_page_ac / ui_page_yx / ui_page_setlist ...
|
||||
|
||||
> **虚拟注入的价值**:当前送检等标准源(B10),而虚拟注入能让"改一个假值 → 看 UI 是否变 → 看 104 是否上送 → 看 SOE 是否记录"这条链在没有硬件激励的情况下闭环。它是**测试基础设施**,不是产品功能,建议放在厂家菜单并加明显标识(标题后缀"调试")。
|
||||
>
|
||||
> **代价**:菜单树从 42 节点扩到约 48~50 节点,`ui_menu_tree.txt` 改动会触发菜单 8 状态像素回归与 `task-table` 式索引文档同步。**这是需要拍板的项**——见 §8。
|
||||
> **代价**:菜单树从当时 42 节点扩到约 48~50 节点(节点数现值 43,2026-10-29 录波道加「录波回放」叶),`ui_menu_tree.txt` 改动会触发菜单 8 状态像素回归与 `task-table` 式索引文档同步。**这是需要拍板的项**——见 §8。
|
||||
|
||||
---
|
||||
|
||||
@@ -261,7 +263,7 @@ ui_page_ac / ui_page_yx / ui_page_setlist ...
|
||||
|
||||
| # | 问题 | 影响 |
|
||||
|---|---|---|
|
||||
| 1 | 菜单是否扩到 48~50 节点(加厂家设置支)? | 42 是产品需求里的硬数字;扩了要改 `产品需求设计文档.md` §8.2 与菜单像素回归 |
|
||||
| 1 | 菜单是否扩到 48~50 节点(加厂家设置支)? | 节点数以 `产品需求设计文档.md` §8.2 为准(2026-10-29 现值 43,此前 42);扩了要改该节与菜单像素回归 |
|
||||
| 2 | **直流量页怎么办?**(V0.1 没有直流采样通道)三选一:改成"温度 + 备用"页 / 从菜单删掉 / 保留空页显示 `--` | 决定行表与菜单节点数 |
|
||||
| 3 | 定值名用全称还是缩写?(字体子集大小 vs 可读性) | 40 条名 × 平均字数,直接影响字库与 `font-charset` |
|
||||
| 4 | 事故/操作/告警/启动/遥控 5 类记录,是否本轮就要? | 每类 = 一个写入方 + 一个 `syslog_id_t` + 一个页面,是本规划里最大的一块后端工作 |
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,235 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="990" height="784" viewBox="0 0 990 784" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect width="990" height="784" fill="#ffffff"/>
|
||||
<text x="28.0" y="40.0" font-size="17" fill="#0f172a" font-weight="bold" text-anchor="start">菜单树总览(层级 · 绑页 · 密码门)</text>
|
||||
<text x="28.0" y="61.0" font-size="11" fill="#475569" font-weight="normal" text-anchor="start">方框 = 菜单(可展开);细线 = 叶(对应一个页面模块,或一处空缺)</text>
|
||||
<rect x="138.0" y="87.0" width="486.0" height="18.0" rx="6" fill="#f8fafc" stroke="none" stroke-width="0"/>
|
||||
<rect x="138.0" y="105.0" width="486.0" height="54.0" rx="6" fill="#ffffff" stroke="none" stroke-width="0"/>
|
||||
<rect x="138.0" y="159.0" width="486.0" height="144.0" rx="6" fill="#f8fafc" stroke="none" stroke-width="0"/>
|
||||
<rect x="138.0" y="303.0" width="486.0" height="90.0" rx="6" fill="#ffffff" stroke="none" stroke-width="0"/>
|
||||
<rect x="138.0" y="393.0" width="486.0" height="72.0" rx="6" fill="#f8fafc" stroke="none" stroke-width="0"/>
|
||||
<rect x="138.0" y="465.0" width="486.0" height="54.0" rx="6" fill="#ffffff" stroke="none" stroke-width="0"/>
|
||||
<rect x="138.0" y="519.0" width="486.0" height="180.0" rx="6" fill="#f8fafc" stroke="none" stroke-width="0"/>
|
||||
<rect x="296.0" y="159.0" width="328.0" height="72.0" rx="5" fill="#f5f3ff" stroke="none" stroke-width="0"/>
|
||||
<rect x="296.0" y="231.0" width="328.0" height="72.0" rx="5" fill="#f5f3ff" stroke="none" stroke-width="0"/>
|
||||
<line x1="128.0" y1="96.0" x2="128.0" y2="609.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="85.0" y1="352.5" x2="128.0" y2="352.5" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="128.0" y1="96.0" x2="153.0" y2="96.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="128.0" y1="132.0" x2="150.0" y2="132.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="128.0" y1="231.0" x2="150.0" y2="231.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="128.0" y1="348.0" x2="150.0" y2="348.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="128.0" y1="429.0" x2="150.0" y2="429.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="128.0" y1="492.0" x2="150.0" y2="492.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="128.0" y1="609.0" x2="150.0" y2="609.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="114.0" x2="284.0" y2="150.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="216.0" y1="132.0" x2="284.0" y2="132.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="114.0" x2="309.0" y2="114.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="132.0" x2="309.0" y2="132.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="150.0" x2="309.0" y2="150.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="195.0" x2="284.0" y2="267.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="216.0" y1="231.0" x2="284.0" y2="231.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="195.0" x2="306.0" y2="195.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="267.0" x2="306.0" y2="267.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="168.0" x2="452.0" y2="222.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="348.0" y1="195.0" x2="452.0" y2="195.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="168.0" x2="477.0" y2="168.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="186.0" x2="477.0" y2="186.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="204.0" x2="477.0" y2="204.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="222.0" x2="477.0" y2="222.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="240.0" x2="452.0" y2="294.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="348.0" y1="267.0" x2="452.0" y2="267.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="240.0" x2="477.0" y2="240.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="258.0" x2="477.0" y2="258.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="276.0" x2="477.0" y2="276.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="452.0" y1="294.0" x2="477.0" y2="294.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="312.0" x2="284.0" y2="384.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="216.0" y1="348.0" x2="284.0" y2="348.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="312.0" x2="309.0" y2="312.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="330.0" x2="309.0" y2="330.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="348.0" x2="309.0" y2="348.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="366.0" x2="309.0" y2="366.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="384.0" x2="309.0" y2="384.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="402.0" x2="284.0" y2="456.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="216.0" y1="429.0" x2="284.0" y2="429.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="402.0" x2="309.0" y2="402.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="420.0" x2="309.0" y2="420.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="438.0" x2="309.0" y2="438.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="456.0" x2="309.0" y2="456.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="474.0" x2="284.0" y2="510.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="216.0" y1="492.0" x2="284.0" y2="492.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="474.0" x2="309.0" y2="474.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="492.0" x2="309.0" y2="492.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="510.0" x2="309.0" y2="510.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="528.0" x2="284.0" y2="690.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="216.0" y1="609.0" x2="284.0" y2="609.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="528.0" x2="309.0" y2="528.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="546.0" x2="309.0" y2="546.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="564.0" x2="309.0" y2="564.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="582.0" x2="309.0" y2="582.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="600.0" x2="309.0" y2="600.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="618.0" x2="309.0" y2="618.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="636.0" x2="309.0" y2="636.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="654.0" x2="309.0" y2="654.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="672.0" x2="309.0" y2="672.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<line x1="284.0" y1="690.0" x2="309.0" y2="690.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<rect x="28.0" y="339.5" width="57.0" height="26.0" rx="5" fill="#eff6ff" stroke="#1d4ed8" stroke-width="1.2"/>
|
||||
<text x="56.5" y="357.2" font-size="13" fill="#1d4ed8" font-weight="bold" text-anchor="middle">主界面</text>
|
||||
<rect x="150.0" y="121.0" width="66.0" height="22.0" rx="5" fill="#ffffff" stroke="#334155" stroke-width="1.2"/>
|
||||
<text x="183.0" y="136.3" font-size="12" fill="#0f172a" font-weight="bold" text-anchor="middle">实时数据</text>
|
||||
<rect x="150.0" y="220.0" width="66.0" height="22.0" rx="5" fill="#ffffff" stroke="#334155" stroke-width="1.2"/>
|
||||
<text x="183.0" y="235.3" font-size="12" fill="#0f172a" font-weight="bold" text-anchor="middle">参数定值</text>
|
||||
<rect x="306.0" y="185.0" width="42.0" height="20.0" rx="5" fill="#ffffff" stroke="#6d28d9" stroke-width="1.2"/>
|
||||
<text x="327.0" y="199.3" font-size="12" fill="#6d28d9" font-weight="bold" text-anchor="middle">整定</text>
|
||||
<rect x="306.0" y="257.0" width="42.0" height="20.0" rx="5" fill="#ffffff" stroke="#6d28d9" stroke-width="1.2"/>
|
||||
<text x="327.0" y="271.3" font-size="12" fill="#6d28d9" font-weight="bold" text-anchor="middle">查看</text>
|
||||
<rect x="150.0" y="337.0" width="66.0" height="22.0" rx="5" fill="#ffffff" stroke="#334155" stroke-width="1.2"/>
|
||||
<text x="183.0" y="352.3" font-size="12" fill="#0f172a" font-weight="bold" text-anchor="middle">三遥设置</text>
|
||||
<rect x="150.0" y="418.0" width="66.0" height="22.0" rx="5" fill="#ffffff" stroke="#334155" stroke-width="1.2"/>
|
||||
<text x="183.0" y="433.3" font-size="12" fill="#0f172a" font-weight="bold" text-anchor="middle">装置维护</text>
|
||||
<rect x="150.0" y="481.0" width="66.0" height="22.0" rx="5" fill="#ffffff" stroke="#334155" stroke-width="1.2"/>
|
||||
<text x="183.0" y="496.3" font-size="12" fill="#0f172a" font-weight="bold" text-anchor="middle">通讯参数</text>
|
||||
<rect x="150.0" y="598.0" width="66.0" height="22.0" rx="5" fill="#ffffff" stroke="#334155" stroke-width="1.2"/>
|
||||
<text x="183.0" y="613.3" font-size="12" fill="#0f172a" font-weight="bold" text-anchor="middle">记录查询</text>
|
||||
<line x1="152.0" y1="96.0" x2="159.0" y2="96.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="164.0" y="100.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">装置信息</text>
|
||||
<text x="222.0" y="99.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">device_info</text>
|
||||
<line x1="308.0" y1="114.0" x2="315.0" y2="114.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="118.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">交流量</text>
|
||||
<text x="366.0" y="117.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">ac</text>
|
||||
<line x1="308.0" y1="132.0" x2="315.0" y2="132.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="136.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">直流量</text>
|
||||
<text x="366.0" y="135.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">dc</text>
|
||||
<line x1="308.0" y1="150.0" x2="315.0" y2="150.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="154.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">遥信量</text>
|
||||
<text x="366.0" y="153.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">yx</text>
|
||||
<line x1="476.0" y1="168.0" x2="483.0" y2="168.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="488.0" y="172.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">参数</text>
|
||||
<text x="522.0" y="171.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">view_param</text>
|
||||
<text x="583.2" y="171.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="476.0" y1="186.0" x2="483.0" y2="186.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="488.0" y="190.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">定值</text>
|
||||
<text x="522.0" y="189.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">view_set</text>
|
||||
<text x="572.8" y="189.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="476.0" y1="204.0" x2="483.0" y2="204.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="488.0" y="208.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">控制字</text>
|
||||
<text x="534.0" y="207.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">view_word</text>
|
||||
<text x="590.0" y="207.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="476.0" y1="222.0" x2="483.0" y2="222.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="488.0" y="226.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">软压板</text>
|
||||
<text x="534.0" y="225.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">view_plate</text>
|
||||
<text x="595.2" y="225.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="476.0" y1="240.0" x2="483.0" y2="240.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="488.0" y="244.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">参数</text>
|
||||
<text x="522.0" y="243.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">view_param</text>
|
||||
<line x1="476.0" y1="258.0" x2="483.0" y2="258.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="488.0" y="262.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">定值</text>
|
||||
<text x="522.0" y="261.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">view_set</text>
|
||||
<line x1="476.0" y1="276.0" x2="483.0" y2="276.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="488.0" y="280.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">控制字</text>
|
||||
<text x="534.0" y="279.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">view_word</text>
|
||||
<line x1="476.0" y1="294.0" x2="483.0" y2="294.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="488.0" y="298.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">软压板</text>
|
||||
<text x="534.0" y="297.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">view_plate</text>
|
||||
<line x1="308.0" y1="312.0" x2="315.0" y2="312.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="316.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">遥测死区</text>
|
||||
<text x="378.0" y="315.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">deadband</text>
|
||||
<text x="428.8" y="315.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="330.0" x2="315.0" y2="330.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="334.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">遥测系数</text>
|
||||
<text x="378.0" y="333.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">calib</text>
|
||||
<text x="413.1" y="333.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="348.0" x2="315.0" y2="348.0" stroke="#be123c" stroke-width="1.2" stroke-dasharray="3,2"/>
|
||||
<text x="320.0" y="352.0" font-size="12" fill="#be123c" font-weight="normal" text-anchor="start">遥信类型</text>
|
||||
<text x="377.0" y="351.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="366.0" x2="315.0" y2="366.0" stroke="#be123c" stroke-width="1.2" stroke-dasharray="3,2"/>
|
||||
<text x="320.0" y="370.0" font-size="12" fill="#be123c" font-weight="normal" text-anchor="start">遥信防抖</text>
|
||||
<text x="377.0" y="369.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="384.0" x2="315.0" y2="384.0" stroke="#be123c" stroke-width="1.2" stroke-dasharray="3,2"/>
|
||||
<text x="320.0" y="388.0" font-size="12" fill="#be123c" font-weight="normal" text-anchor="start">双点遥信</text>
|
||||
<text x="377.0" y="387.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="402.0" x2="315.0" y2="402.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="406.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">时钟设置</text>
|
||||
<text x="378.0" y="405.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">clock</text>
|
||||
<text x="413.1" y="405.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="420.0" x2="315.0" y2="420.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="424.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">强制复归</text>
|
||||
<text x="378.0" y="423.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">reset</text>
|
||||
<text x="413.1" y="423.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<rect x="307.0" y="429.5" width="71.0" height="17.0" rx="4" fill="#f0fdf4" stroke="#15803d" stroke-width="0.9"/>
|
||||
<line x1="308.0" y1="438.0" x2="315.0" y2="438.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="442.0" font-size="12" fill="#15803d" font-weight="normal" text-anchor="start">手动录波</text>
|
||||
<text x="378.0" y="441.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">wave_manual</text>
|
||||
<text x="444.4" y="441.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="456.0" x2="315.0" y2="456.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="460.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">清除记录</text>
|
||||
<text x="378.0" y="459.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">clear</text>
|
||||
<text x="413.1" y="459.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="474.0" x2="315.0" y2="474.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="478.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">通讯设置</text>
|
||||
<text x="378.0" y="477.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">comm</text>
|
||||
<text x="407.9" y="477.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="492.0" x2="315.0" y2="492.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="496.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">网口设置</text>
|
||||
<text x="378.0" y="495.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">net_cfg</text>
|
||||
<text x="423.5" y="495.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="510.0" x2="315.0" y2="510.0" stroke="#be123c" stroke-width="1.2" stroke-dasharray="3,2"/>
|
||||
<text x="320.0" y="514.0" font-size="12" fill="#be123c" font-weight="normal" text-anchor="start">SNTP设置</text>
|
||||
<text x="380.8" y="513.5" font-size="9" fill="#b45309" font-weight="bold" text-anchor="start">P</text>
|
||||
<line x1="308.0" y1="528.0" x2="315.0" y2="528.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="532.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">SOE记录</text>
|
||||
<text x="374.9" y="531.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">soe</text>
|
||||
<line x1="308.0" y1="546.0" x2="315.0" y2="546.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="550.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">事故记录</text>
|
||||
<text x="378.0" y="549.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">accident</text>
|
||||
<line x1="308.0" y1="564.0" x2="315.0" y2="564.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="568.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">操作记录</text>
|
||||
<text x="378.0" y="567.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">oper</text>
|
||||
<line x1="308.0" y1="582.0" x2="315.0" y2="582.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="586.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">保护告警</text>
|
||||
<text x="378.0" y="585.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">alarm</text>
|
||||
<line x1="308.0" y1="600.0" x2="315.0" y2="600.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="604.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">保护启动</text>
|
||||
<text x="378.0" y="603.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">start</text>
|
||||
<line x1="308.0" y1="618.0" x2="315.0" y2="618.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="622.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">遥控记录</text>
|
||||
<text x="378.0" y="621.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">yk</text>
|
||||
<line x1="308.0" y1="636.0" x2="315.0" y2="636.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="640.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">自检记录</text>
|
||||
<text x="378.0" y="639.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">selftest</text>
|
||||
<line x1="308.0" y1="654.0" x2="315.0" y2="654.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="658.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">运行记录</text>
|
||||
<text x="378.0" y="657.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">runlog</text>
|
||||
<line x1="308.0" y1="672.0" x2="315.0" y2="672.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="676.0" font-size="12" fill="#0f172a" font-weight="normal" text-anchor="start">运行报告</text>
|
||||
<text x="378.0" y="675.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">runreport</text>
|
||||
<rect x="307.0" y="681.5" width="71.0" height="17.0" rx="4" fill="#f0fdf4" stroke="#15803d" stroke-width="0.9"/>
|
||||
<line x1="308.0" y1="690.0" x2="315.0" y2="690.0" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="320.0" y="694.0" font-size="12" fill="#15803d" font-weight="normal" text-anchor="start">录波回放</text>
|
||||
<text x="378.0" y="693.5" font-size="9" fill="#94a3b8" font-weight="normal" text-anchor="start">wave_play</text>
|
||||
<rect x="624.0" y="72.0" width="350.0" height="413.0" rx="8" fill="#ffffff" stroke="#cbd5e1" stroke-width="1.2"/>
|
||||
<text x="640.0" y="102.0" font-size="12.5" fill="#0f172a" font-weight="bold" text-anchor="start">本图读数</text>
|
||||
<text x="640.0" y="125.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">节点 43 = 菜单 9 + 叶 34</text>
|
||||
<text x="640.0" y="143.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">已绑页 30 · 未绑页 4 · 密码门 16 叶</text>
|
||||
<text x="640.0" y="161.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">最深 4 级(主界面 → 叶)</text>
|
||||
<text x="640.0" y="193.0" font-size="12.5" fill="#0f172a" font-weight="bold" text-anchor="start">怎么看</text>
|
||||
<rect x="640.0" y="206.0" width="15.0" height="12.0" rx="3" fill="#eff6ff" stroke="#1d4ed8" stroke-width="1.0"/>
|
||||
<text x="662.0" y="216.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">根:主界面本身,不是一个页面</text>
|
||||
<rect x="640.0" y="224.0" width="15.0" height="12.0" rx="3" fill="#ffffff" stroke="#334155" stroke-width="1.0"/>
|
||||
<text x="662.0" y="234.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">白框:一级菜单,可展开、不绑页</text>
|
||||
<rect x="640.0" y="242.0" width="15.0" height="12.0" rx="3" fill="#f5f3ff" stroke="#6d28d9" stroke-width="1.0"/>
|
||||
<text x="662.0" y="252.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">紫底:二级菜单与它那一支的行带</text>
|
||||
<rect x="640.0" y="260.0" width="15.0" height="12.0" rx="3" fill="#f0fdf4" stroke="#15803d" stroke-width="1.0"/>
|
||||
<text x="662.0" y="270.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">绿底:录波两叶,已绑页面模块</text>
|
||||
<rect x="640.0" y="278.0" width="15.0" height="12.0" rx="3" fill="#ffffff" stroke="#be123c" stroke-width="1.0"/>
|
||||
<line x1="640.0" y1="284.0" x2="655.0" y2="284.0" stroke="#be123c" stroke-width="1.0" stroke-dasharray="3,2"/>
|
||||
<text x="662.0" y="288.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">红字 + 虚线:未绑页的叶,进去只有菜单</text>
|
||||
<text x="640.0" y="306.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">灰色小字 = 页面模块名(页面注册表的键)</text>
|
||||
<text x="640.0" y="324.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">行末的 P = 进这一叶要先过密码</text>
|
||||
<text x="640.0" y="342.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">整定 与 查看 复用同一批页面模块</text>
|
||||
<text x="640.0" y="374.0" font-size="12.5" fill="#0f172a" font-weight="bold" text-anchor="start">未绑页的 4 叶</text>
|
||||
<text x="640.0" y="397.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">· 遥信类型(三遥设置 下)</text>
|
||||
<text x="640.0" y="415.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">· 遥信防抖(三遥设置 下)</text>
|
||||
<text x="640.0" y="433.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">· 双点遥信(三遥设置 下)</text>
|
||||
<text x="640.0" y="451.0" font-size="10.5" fill="#475569" font-weight="normal" text-anchor="start">· SNTP设置(通讯参数 下)</text>
|
||||
<text x="28.0" y="750.0" font-size="9.5" fill="#94a3b8" font-weight="normal" text-anchor="start">真值 = ui_menu_tree.txt(tools/menu_gen/gen_menu.py 的输入);本图由 tools/arch_diagram/gen_menu_tree_svg.py 生成 —— 改菜单树要同步脚本内的 NODE_SRC 重跑,勿手改 SVG</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
+28
-24
@@ -201,7 +201,7 @@ flowchart LR
|
||||
| 7 | 开入 KI | `APP/EquipCfg/Const_IO.c` `g_tInputModelTab` | **138 点** | 0(无硬件,X3) | ⬜ |
|
||||
| 8 | 开出 KO | 同上 `g_tOutputModelTab` | **31 点** | 0(无硬件,X3) | ⬜ |
|
||||
| 9 | 动作事件 | `APP/EquipCfg/Const_Rec.c` `g_tActRecTab` | **33 条**(421~453) | 7 类记录(`App/log/sys_log.h`) | 🔶 |
|
||||
| 10 | 录波测量通道 | 同上 `g_tMeasureModelTab` | **65 通道** | 0 | ⬜ |
|
||||
| 10 | 录波测量通道 | 同上 `g_tMeasureModelTab` | **65 通道** | 8(本波口径,2026-10-29 代码面已交付;65 通道仍为 0,参考机口径未改)| 🔶 |
|
||||
| 11 | 软压板 | `APP/EquipCfg/Const_Soft.c` `g_tSoftTab` | **9 面**(表容量 32) | 1 面(`PS_GLOBAL_EN`) | 🔶 |
|
||||
| 12 | 保护元件 | `RelayUnit/*.c` | **45 个模块** | 9 个实例 | 🔶 |
|
||||
| 13 | 记录队列 | `RecUnit/RecFile.c` + `GlobalDef/Const_Sys.h` | **13 类**(128/256 条) | 8 通道(7 文本 + SOE) | 🔶 |
|
||||
@@ -329,7 +329,7 @@ tTimer_NoUI 无压无流判据(CN_
|
||||
|---|---|---|
|
||||
| **外部资源**(标准源、端子变比) | 定值标定、保护判据解卡 | 两点标定(§9.2 第 4 步) |
|
||||
| **硬件缺失**(X3 / X4) | 开入 138、开出 31、电度 8、同期、三相通道 | V0.2 硬件 |
|
||||
| **立项未排** | 多规约、SNTP、定值铺开 | 按 §9.2 顺序;**录波已立项**(2026-10-29):卡片 docs/任务规划/故障录波.md、设计 docs/子系统设计/故障录波设计说明.md,1 kHz 起步与本文 80 点/周波口径的偏离在该设计文档 §2 显式登记 |
|
||||
| **立项未排** | 多规约、SNTP、定值铺开 | 按 §9.2 顺序。**录波最小闭环已交付代码面**(2026-10-29,卡片 docs/任务规划/故障录波.md、设计 docs/子系统设计/故障录波设计说明.md;1 kHz 起步与本文 80 点/周波口径的偏离在该设计文档 §2 显式登记);**4 kHz 全速、多段、COMTRADE、104 文件传输仍未排**,属二期范围 |
|
||||
| **算力 / 形态**(X1 / X2) | 双核分工、VxWorks | 不解除。**屏板链路已从范围外项移入范围内待做**(规格 §5.16,执行规划 docs/任务规划/屏板链路.md):主机侧链路与参考 1:1 同构,不属"替代方案" |
|
||||
|
||||
> **结论**:本产品与参考产品的差距**全部是"进度"而非"范围"**——没有一项是"参考有而我们不做"。
|
||||
@@ -660,7 +660,7 @@ sequenceDiagram
|
||||
|
||||
**FR-UI-2 菜单与内容页**
|
||||
- 需求:42 节点层级菜单(最大 4 级)+ 内容页(装置信息/交流量/直流量等)。
|
||||
- 实现:三层拆分(数据层 `ui_menu_tab` 生成物 / 逻辑层 `ui_menu` 17 B 导航状态 / 显示层 `ui_menu_view` 级联框);内容页框架 + 页面插件描述符。
|
||||
- 实现:三层拆分(数据层 `ui_menu_tab` 生成物 / 逻辑层 `ui_menu` 17 B 导航状态 / 显示层 `ui_menu_view` 级联框);内容页框架 + 页面插件描述符。2026-10-29 实到 43 节点(新增「录波回放」叶,需求原文的 42 是 V0.1 立项数)。
|
||||
- 判据:`UI menu init: OK`;菜单像素回归全 PASS。
|
||||
|
||||
**FR-UI-3 待机屏**
|
||||
@@ -890,7 +890,7 @@ sequenceDiagram
|
||||
上电次数 + 本次复位原因快照)与 iec104_serv 的四个只读观测计数,经 info 帧下发
|
||||
(口径 docs/UI设计/UI_数据供给设计说明.md §4.4)。
|
||||
- 判据:五类记录各有写入点、可翻最近 20 条、空记录明确显示"无记录"、共用 reader——**软件面已满足**
|
||||
(空态与四个范围标签由 60 张像素黄金钉住;模拟器侧这些文件没有写入方,只拍到空态)。
|
||||
(空态与四个范围标签由 66 张像素黄金钉住;模拟器侧这些文件没有写入方,只拍到空态)。
|
||||
真记录翻页、掉电后保持、清除记录后归零、boot_count 自增与四态判定**待台架复核**
|
||||
(docs/任务规划/登记簿.md §7)。
|
||||
|
||||
@@ -942,7 +942,7 @@ sequenceDiagram
|
||||
| 遥控选择 / 撤销 / 执行 | ≤5 s | 同轮应答(ms 级) |
|
||||
| 读定值 / 改定值预置 / 执行 | ≤10 s / ≤5 s | 同轮应答(ms 级) |
|
||||
| 读文件目录 / 文件 | ≤30 s | ⬜ 未实现(文件传输未立项) |
|
||||
| 录波文件存储 | ≤15 s | ⬜ 未实现(**已立项**,见 docs/任务规划/故障录波.md:本波按 1 kHz / 8 路 / 单段做,≤15 s 与 80 点口径属二期) |
|
||||
| 录波文件存储 | ≤15 s | ⬜ 未验收(代码面 2026-10-29 已交付:/wave 段文件 + FTP 取回;本波按 1 kHz / 8 路 / 单段做;≤15 s 时限未测,80 点/周波属二期;卡片见 docs/任务规划/故障录波.md) |
|
||||
|
||||
**FR-IEC104-8 单连接与自愈**
|
||||
- 需求:单连接语义(CH395F 单 socket);断链自动重听;网络重建(改 IP)后服务自愈;协议故障主动拆链。
|
||||
@@ -1807,14 +1807,17 @@ pre-commit run --all-files
|
||||
| 刷新 | `uiTask` 10 ms 循环 |
|
||||
| 绘制缓冲 | I1 draw buffer 需 +8 B palette 头 |
|
||||
|
||||
### 8.2 菜单树(42 节点)
|
||||
### 8.2 菜单树(43 节点)
|
||||
|
||||

|
||||

|
||||
|
||||
**规模**:菜单节点 **9** 个(含根)+ 叶子节点 **33** 个 = **42** 节点;最大深度 **4 级(主界面 / 一级 / 二级 / 三级菜单)**。
|
||||
**绑定现状**(2026-10-28 实测):**已绑内容页 28 叶 / 未绑 5 叶**(页面注册表 `UI_PAGE_MAP_COUNT` = 28,
|
||||
与已绑叶数一致);未绑 5 叶 = 无开入硬件 3 + 手动录波 1 + SNTP 1(取舍与重开条件见
|
||||
docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5 页 + 运行报告)已于 2026-10-28 落地绑页。
|
||||
> 图里的节点数、绑页与密码门读数由 `tools/arch_diagram/gen_menu_tree_svg.py` 现算自真值 `ui_menu_tree.txt`(改菜单树 → 同步脚本内 `NODE_SRC` 重跑);级联框在屏上怎么摆(几何)→ `docs/UI设计/菜单页面设计.md` §4.1。
|
||||
|
||||
**规模**:菜单节点 **9** 个(含根)+ 叶子节点 **34** 个 = **43** 节点;最大深度 **4 级(主界面 / 一级 / 二级 / 三级菜单)**。
|
||||
**绑定现状**(2026-10-29 复扫):**已绑内容页 30 叶 / 未绑 4 叶**(页面注册表 `UI_PAGE_MAP_COUNT` = 30,
|
||||
与已绑叶数一致);未绑 4 叶 = 无开入硬件 3 + SNTP 1(取舍与重开条件见
|
||||
docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5 页 + 运行报告)已于 2026-10-28 落地绑页,
|
||||
「手动录波」与「录波回放」两叶于 2026-10-29 随录波道绑页(判据见 docs/子系统设计/故障录波设计说明.md §4.1 与 §8.1)。
|
||||
|
||||
**根页(主界面)7 个一级条目**:装置信息 / 实时数据 / 参数定值 / 三遥设置 / 装置维护 / 通讯参数 / 记录查询。
|
||||
|
||||
@@ -1832,10 +1835,10 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
├─ 装置维护 ▶(时钟设置 / 强制复归 / 手动录波 / 清除记录)
|
||||
├─ 通讯参数 ▶(通讯设置 / 网口设置 / SNTP设置)
|
||||
└─ 记录查询 ▶(SOE记录 / 事故记录 / 操作记录 / 保护告警 / 保护启动 /
|
||||
遥控记录 / 自检记录 / 运行记录 / 运行报告)
|
||||
遥控记录 / 自检记录 / 运行记录 / 运行报告 / 录波回放)
|
||||
```
|
||||
|
||||
**三层代码架构**:数据层 `ui_menu_tab`(生成物,`g_menu_tab[42]` 扁平父子索引表,≈0.7 KB Flash)
|
||||
**三层代码架构**:数据层 `ui_menu_tab`(生成物,`g_menu_tab[43]` 扁平父子索引表,≈0.7 KB Flash)
|
||||
+ 逻辑层 `ui_menu`(唯一 **17 B** 导航状态)+ 显示层 `ui_menu_view`(持全部 LVGL 对象,零导航状态)。
|
||||
|
||||
**导航模型**:树形逐级进入——`ENTER`/`→` 进入子页,`ESC`/`←` 返回上一级;页面 = 对表的一次
|
||||
@@ -1848,7 +1851,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
|
||||
### 8.3 内容页
|
||||
|
||||
**已绑 28 页**(按分组;逐页形态见 [`docs/UI设计/页面补齐规划.md`](../UI设计/页面补齐规划.md)):
|
||||
**已绑 30 页**(按分组;逐页形态见 [`docs/UI设计/页面补齐规划.md`](../UI设计/页面补齐规划.md)):
|
||||
|
||||
| 分组 | 页面 | 形态 |
|
||||
|---|---|---|
|
||||
@@ -1857,21 +1860,22 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
| 参数定值 | 查看 4 页(参数 / 定值 / 控制字 / 软压板) | 只读列表 |
|
||||
| 参数定值 | 整定 4 页(参数 / 定值 / 控制字 / 软压板) | 可编辑(写值 + 置脏 + 保存反馈) |
|
||||
| 三遥设置 | 遥测死区 / 遥测系数 | 可编辑(运行参数段,`0x8030~0x804F`) |
|
||||
| 装置维护 | 时钟设置 / 强制复归 / 清除记录 | 操作页(执行类必须出真反馈,不得"点了没效果") |
|
||||
| 装置维护 | 时钟设置 / 强制复归 / 清除记录 / 手动录波 | 操作页(执行类必须出真反馈,不得"点了没效果";手动录波触发后回读同一份计数,判据见 docs/子系统设计/故障录波设计说明.md §8.2) |
|
||||
| 通讯参数 | 通讯设置 / 网口设置 | 可编辑(`[comm]` / `[network]` 段,保存后按各自生效时机) |
|
||||
| 记录查询 | SOE 记录 / 自检记录 / 运行记录 | 记录列表(共用 reader) |
|
||||
| 记录查询 | 事故记录 / 操作记录 / 保护告警 / 保护启动 / 遥控记录 | 记录列表(每记录 2 行、空态显「无记录」;写入点与判据见 App/log/README.md §五类记录) |
|
||||
| 记录查询 | 运行报告 | 只读列表 13 行分 2 页;四个数逐个挂范围标签(自电池周期 / 本次开机),跨开机累计按裁定不做 |
|
||||
| 记录查询 | 录波回放 | 单通道波形页(阶梯线自绘、8 路切换、单位徽标、削顶标记、空态与不可读态显式文案);显示契约见 docs/子系统设计/故障录波设计说明.md §8.1 |
|
||||
|
||||
**未绑 5 叶**(2026-10-28:记录 5 页与运行报告页已落地绑页,原「当前执行 6 项」清零):
|
||||
遥信类型 / 遥信防抖 / 双点遥信 = 等开入硬件(§9.1.1「出口与开入」);手动录波 = 等录波子系统
|
||||
(§9.1.2「录波子系统」);SNTP 设置 = 等 SNTP 对时(§9.1.2「SNTP 对时」)。
|
||||
**未绑 4 叶**(2026-10-28:记录 5 页与运行报告页已落地绑页,原「当前执行 6 项」清零;2026-10-29:
|
||||
录波道落地,「手动录波」叶子随之一并绑页):
|
||||
遥信类型 / 遥信防抖 / 双点遥信 = 等开入硬件(§9.1.1「出口与开入」);SNTP 设置 = 等 SNTP 对时(§9.1.2「SNTP 对时」)。
|
||||
|
||||
- 内容页为**独立全屏屏**,由框架一次性自绘(**零 LVGL 子对象**),`ESC` 返回菜单屏。
|
||||
- 菜单导航状态**不压层**,返回即原样还原;同一时刻至多一个内容页打开。
|
||||
- 节点→页映射在逻辑层 `menu_content_page_for(node)`;未绑叶子进入空白终点框(不伪造数据)。
|
||||
- 数据供给走**注入通道**(rt / par / info / 遥测死区与系数第四表),页面不直接触算法层;
|
||||
像素黄金用例当前 **60 张**(菜单 8 状态另计;2026-10-28 记录 5 页与运行报告 2 页落地,+7 张)。
|
||||
像素黄金用例当前 **66 张**(菜单 8 状态另计;2026-10-28 记录 5 页与运行报告 2 页落地 +7 张;2026-10-29 录波 UI 道 6 张:手动录波 3 态 + 录波回放 3 态)。
|
||||
|
||||
### 8.4 密码与权限
|
||||
|
||||
@@ -1884,7 +1888,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
|
||||
### 8.5 中文字体
|
||||
|
||||
- 字体 `ui_font_cn_12.c`:宋体 12px 1bpp **子集**(当前 **218 唯一字 / 313 字形**,2026-10-28 实测;
|
||||
- 字体 `ui_font_cn_12.c`:宋体 12px 1bpp **子集**(当前 **224 唯一字 / 319 字形**,2026-10-29 实测;
|
||||
122/217、179/274、185/280 均为历史快照。字形数 = 汉字/符号数 + ASCII 0x20~0x7E 共 95 个)。
|
||||
- 生成流程:`tools/font_gen/extract_cn.py`(扫 UI 源码 → `ui_cn_chars.txt`)→ `tools/font_gen/make_font.py`。
|
||||
- **约束**:任何 UI 文案新增汉字后必须走此流程,否则显示为空白方块。
|
||||
@@ -1927,7 +1931,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
| **映射铺开** | 3 | 107 项 `0x8820~0x8891` | 40 项局部 | +67 项 |
|
||||
| **元件铺开** | 12 | 45 个模块 | 8 个模块 / 9 个实例 | +37 个模块(过流类 6 / 电压类 5 / 重合闸 5 / 方向功率 4 / 涌流故障 5 / 自动控制 5 / 记录 5 / 录波采样 2) |
|
||||
| **遥测铺开** | 4、5 | 31 点 + 模型 35 行 | 9 点 | +22 点 + 模型表 |
|
||||
| **录波子系统** | 10、15 | 3 类型 / 6 段 / 65 通道 / RAM 16 个 / COMTRADE 64·64·16 | 0 | 整子系统 |
|
||||
| **录波子系统** | 10、15 | 3 类型 / 6 段 / 65 通道 / RAM 16 个 / COMTRADE 64·64·16 | **最小闭环代码面已交付**(2026-10-29):1 kHz / 8 路原始码值 / 单段 320 点 / 轮转段文件 / 手动与保护启动 / 单通道回放页;**板级判据一轮未跑** | 仍差 4 kHz 全速与 80 点/周波、多段与 65 通道、COMTRADE、104 文件传输;口径偏离登记见 docs/子系统设计/故障录波设计说明.md §2 |
|
||||
| **记录体系** | 9、13 | 13 类队列(128/256 条) | 8 类 | +5 类(越限 / 闭锁 / COS / 顺控 / 顺控执行) |
|
||||
| **UI 记录页** | 9、13 | 参考有完整记录页与运行报告 | 六页已绑并纳入像素黄金回归(2026-10-28) | 只剩真机判据(docs/任务规划/登记簿.md §7) |
|
||||
| **多规约栈** | 17 | 12 种 | 1 种(104) | +11 种(101 / 102 / 103 / CDT / ModBus / Whm_GB / 61850 / CAN + 子站侧 3 类) |
|
||||
@@ -1951,14 +1955,14 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
|
||||
| 顺序 | 工作 | 类别 | 当前状态 | 开始条件 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | UI 补齐(记录 5 页 → 运行报告) | 规模型 | **清单已清空(2026-10-28)** | 六页已落地;余 5 叶属不做项,重开条件见 docs/任务规划/UI补齐.md §5 |
|
||||
| 1 | UI 补齐(记录 5 页 → 运行报告) | 规模型 | **清单已清空(2026-10-28)** | 六页已落地;余 4 叶属不做项(手动录波与录波回放两叶已随录波道绑页),重开条件见 docs/任务规划/UI补齐.md §5 |
|
||||
| 2 | net 层跨上下文收口 | 平台 | **下一顺位(未开工)** | UI 清单已清空,切换条件已满足;**开工待用户点头** |
|
||||
| 3 | 其余结构与可靠性评估 | 平台 | 排队 | net 收口后 |
|
||||
| 4 | 端子/子板变比确认 → 两点标定 | 阻塞型 | 等外部资源 | 子板实物/原理图、标准源到位可插入 |
|
||||
| 5 | 标准源送检与第三方 104 主站验收 | 阻塞型 | 等标定 | 保护 VALID 置位;送检窗口前 2 日冻结特性 |
|
||||
| 6 | 定值铺开 / 映射铺开 / 遥测铺开 / 软压板铺开 | 规模型 | 等排期 | 标定后(工程量才有意义) |
|
||||
| 7 | 元件铺开与遥测增强 | 规模型 | 等标准源/硬件 | 先解除未标定闭锁;三相类等 V0.2 |
|
||||
| 8 | 录波子系统 → 记录体系 → 多规约栈 | 规模型 | 不排 | 各自重启条件 |
|
||||
| 8 | 录波子系统 → 记录体系 → 多规约栈 | 规模型 | **录波最小闭环已提前插入并交付代码面(用户裁定 2026-10-29 录波先行,见 docs/任务规划/故障录波.md);本行剩下的参考机完整形态(多段 / 65 通道 / COMTRADE / 104 文件传输)仍不排** | 各自重启条件 |
|
||||
| 9 | 验证窗口事项(CI 远端、PC peer、负向注入) | 平台 | 逢条件顺带 | 条件到位即执行 |
|
||||
| 10 | 出口与开入 / 三相与同期 / 电度计量 / 对下采集 | 阻塞型 | 不排 | V0.2 硬件 / 用户决策 |
|
||||
|
||||
@@ -2503,7 +2507,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|
||||
| 元件铺开 | 45 个模块对应 45 个实例;每个实例有:定值项齐全、启动/动作/返回三态可观测、事件码正确、单测覆盖判据边界 |
|
||||
| 遥测铺开 | 31 点全部非 IV(除物理不可达点明确标 IV);线电压与相量满足 `Uab = Ua − Ub` 误差 ≤ 0.5 % |
|
||||
| 开入开出 | 27 路硬件开入可读、31 路开出可驱动;开入防抖可整定;双点遥信四态正确(00/01/10/11) |
|
||||
| 录波 | 4000 Hz 采样;三类录波各生成一份 COMTRADE 且被第三方工具打开;配额 64/64/16 循环覆盖正确 |
|
||||
| 录波 | **(二期口径;本波按 1 kHz / 8 路 / 单段 / 无 COMTRADE,对照表见 §9.1 第 10 行)**4000 Hz 采样;三类录波各生成一份 COMTRADE 且被第三方工具打开;配额 64/64/16 循环覆盖正确 |
|
||||
| 记录体系 | 13 类队列各有落点;每类容量达标;断电不丢已确认记录 |
|
||||
| 多规约 | 每种规约与对应主站/子站工具完成一次完整交互(连接 / 总召 / 变位 / 遥控 / 断链重连) |
|
||||
| 国密 | SM2 签名验签、SM3 摘要与标准测试向量一致 |
|
||||
|
||||
@@ -32,7 +32,7 @@ git commit
|
||||
└─ manual(pre-commit --hook-stage manual;不随 git commit 跑)
|
||||
├─ cppcheck 全量 C 静态分析(自有代码)
|
||||
├─ test-cppcheck test/ 侧静态分析
|
||||
└─ test-coverage 主机单测覆盖率总闸(41 套 + 回退台账)
|
||||
└─ test-coverage 主机单测覆盖率总闸(44 套 + 回退台账)
|
||||
```
|
||||
|
||||
**阶段**:`pre-commit` 阶段拦截代码问题、`commit-msg` 阶段拦截格式问题,任一失败 → commit 中止;另有 `manual` 阶段(`stages: [manual]` 的慢钩子)**不随 `git commit` 跑**,由远端 CI 与 `scripts/ci_local.py` 的 manual 段承担(见下)。一次 `git commit` 实跑 **48** 根 = `pre-commit` 阶段 47 根 + `gitlint` 1 根。
|
||||
@@ -332,7 +332,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
|
||||
|
||||
| 文件 | 位置 | 说明 |
|
||||
|---|---|---|
|
||||
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**52 个钩子**声明数,一次 `git commit` 实跑 49 根;口径 = YAML 解析后的 hook 条目数,自查命令 → `docs/门禁说明/README.md` §2。声明数与一次提交的实跑数分列,勿混写;计数改动的完整台账只记在 AGENTS.md 提交门禁节与本目录 README §2,本行不重述)。逐项详解在 `docs/门禁说明/`(索引 `docs/门禁说明/README.md`;主机侧单测 **22 根**合一篇,含各套件来历与「19 套无专属钩子」台账 `docs/门禁说明/host-unit-tests.md` §6;UI 侧另有 `font-charset` 与 `ui-golden` 两篇) |
|
||||
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**52 个钩子**声明数,一次 `git commit` 实跑 49 根;口径 = YAML 解析后的 hook 条目数,自查命令 → `docs/门禁说明/README.md` §2。声明数与一次提交的实跑数分列,勿混写;计数改动的完整台账只记在 AGENTS.md 提交门禁节与本目录 README §2,本行不重述)。逐项详解在 `docs/门禁说明/`(索引 `docs/门禁说明/README.md`;主机侧单测 **22 根**合一篇,含各套件来历与「22 套无专属钩子」台账(现值 2026-10-29) `docs/门禁说明/host-unit-tests.md` §6;UI 侧另有 `font-charset` 与 `ui-golden` 两篇) |
|
||||
| `.gitlint` | 仓库根 | gitlint 规则(宽松,兼容中文) |
|
||||
| `.cubemx_survival_baseline.json` | 仓库根 | CubeMX 易失定制基线(哈希;`--write` 重建) |
|
||||
| `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 |
|
||||
|
||||
+8
-7
@@ -32,14 +32,15 @@
|
||||
|
||||
## 1. UI 补齐(执行清单已于 2026-10-28 清空)
|
||||
|
||||
菜单树共 33 叶(节点行合计 42):按行内 #page= 实测**已绑 28 叶、未绑 5 叶**(2026-10-28 复核,
|
||||
判定规则与重跑命令见 docs/任务规划/UI补齐.md §1「叶数判定与复核」)。未绑的 5 叶因缺少开入硬件、
|
||||
录波模块或 SNTP 产品定义而不做(原因与重开条件见 docs/任务规划/UI补齐.md §5)。
|
||||
**口径闭合**:未绑 5 = 待做 0(§1 清单已清空)+ 不做 5(遥信 3 / 手动录波 1 / SNTP 1);**42 是节点行数、不是叶数**(= 9 个菜单节点 + 33 叶)。
|
||||
已绑叶数以生成物 UI_PAGE_MAP_COUNT 为准:不剔 ui_menu_tree.txt 文件头的注释示例行时,按 #page= 文本计数会得 29(= 28 + 1 行示例)。
|
||||
菜单树共 34 叶(节点行合计 43):按行内 #page= 实测**已绑 30 叶、未绑 4 叶**(2026-10-29 复扫,
|
||||
判定规则与重跑命令见 docs/任务规划/UI补齐.md §1「叶数判定与复核」)。未绑的 4 叶因缺少开入硬件
|
||||
或 SNTP 产品定义而不做(原因与重开条件见 docs/任务规划/UI补齐.md §5)——**手动录波与录波回放两叶
|
||||
已随录波道绑页**,「不做」清单里的录波那一项已销。
|
||||
**口径闭合**:未绑 4 = 待做 0(§1 清单已清空)+ 不做 4(遥信 3 / SNTP 1);**43 是节点行数、不是叶数**(= 9 个菜单节点 + 34 叶)。
|
||||
已绑叶数以生成物 UI_PAGE_MAP_COUNT 为准:不剔 ui_menu_tree.txt 文件头的注释示例行时,按 #page= 文本计数会得 31(= 30 + 1 行示例)。
|
||||
|
||||
**UI 补齐执行清单已于 2026-10-28 清空**:记录 5 页(事故 / 操作 / 保护告警 / 保护启动 / 遥控)与
|
||||
运行报告页同日落地并绑页,内容页黄金参考 53 → 60 张(runreport 分 2 页,第 2 页单独拍一张;判据 = simulator/tests/ref/ 入库 shot_*.bin 实数,张数 = 页面个数 + 分页多拍的那一张)。
|
||||
运行报告页同日落地并绑页,内容页黄金参考 53 → 60 张(runreport 分 2 页,第 2 页单独拍一张;判据 = simulator/tests/ref/ 入库 shot_*.bin 实数,张数 = 页面个数 + 分页多拍的那一张;**该集合 2026-10-29 已扩至 66 张**,多出的是录波回放页 6 态)。
|
||||
上板判据(真记录翻页、掉电保持、清除后归零、boot_count 自增、RESTARTED、rst_cause=IWDG、conn_up +1)
|
||||
已登记登记簿 §7「UI 六页真机判据」,六页文件头各留「台架待补」段;模拟器差距见
|
||||
docs/测试说明/模拟器差距清单.md §2.3「开机账本 boot_stats」一行。
|
||||
@@ -66,7 +67,7 @@ docs/测试说明/模拟器差距清单.md §2.3「开机账本 boot_stats」一
|
||||
| **7** | 验证窗口事项 | 逢条件顺带 | CI runner、PC peer、CH340、负向故障注入条件到位时执行 |
|
||||
| **8** | 独立模块与暂缓项 | 当前不排 | 开入开出、SNTP、104 文件传输单独立项;RS-485 与内存优化按各自重启条件 |
|
||||
| **独立** | **屏板链路(对上屏板)** | **规划已出(未开工)** | 独立立项,插在任一顺位之间都能做,不占主序列;**端口已裁定(2026-10-28):屏板占 USART1,日志迁 RS-232 通道**,所以原来的 250 kbps 收发器阻塞已解除;剩下两条前置——**台架串口线挪到 CN1、日志口迁移**(见 docs/任务规划/屏板链路.md §0 与 §3 批 0);**软件可先用 PC + USB 转 TTL 台架验证,不必等屏板实物** |
|
||||
| **独立** | **故障录波(1 kHz · 8 路原始码值 · 单通道回放)** | **设计定稿(代码一行未动)** | 用户裁定 2026-10-29 **录波先行**:数据面最小闭环 → 手动与保护启动触发 → 回放页,卡片判据见 docs/任务规划/故障录波.md,口径与设计见 docs/子系统设计/故障录波设计说明.md;**与 UI 道互斥**(菜单树与中文字库是整文件生成物,同刻只允许一条道写) |
|
||||
| **独立** | **故障录波(1 kHz · 8 路原始码值 · 单通道回放)** | **代码面已交付(板级判据一轮未跑)** | 用户裁定 2026-10-29 **录波先行**:数据面最小闭环 → 手动与保护启动触发 → 回放页。三张卡的**代码侧判据已收口**(主机单测 100 / 170 / 110 全 rc=0、双端编译 0 错误 0 警告、板端编码器与 PC 侧解码器字节级互验、手动录波与录波回放两叶已绑页、内容页黄金参考扩至 66 张);**没跑的是板级那一轮**(注 1 V / 50 Hz、FTP 拉回、与 `meas:` 行和 104 浮点遥测对表、段文件 CRC 不符必须拒收、回放页看真段)。卡片判据见 docs/任务规划/故障录波.md,口径与设计见 docs/子系统设计/故障录波设计说明.md §10;**与 UI 道互斥**(菜单树与中文字库是整文件生成物,同刻只允许一条道写)——本波按此串行执行:UI 道在数据面与接线冻结之后才动生成物 |
|
||||
|
||||
### 2.1 关键依赖
|
||||
|
||||
|
||||
+19
-16
@@ -1,38 +1,41 @@
|
||||
# 当前任务:UI 补齐
|
||||
|
||||
> **执行清单已清空(2026-10-28)**:记录 5 页与运行报告页已落地、绑页并纳入像素黄金回归。
|
||||
> **2026-10-29 录波 UI 道**:「手动录波」由不做转落地(绑操作页),新增「录波回放」叶(节点 43);
|
||||
> 叶数口径同步为 43/34/30/4(下方逐项)。
|
||||
> 本文件保留为 UI 补齐的**范围与判据口径**(§1 叶数、§4 每页统一动作、§5 不做的取舍);
|
||||
> 下一顺位见 docs/任务规划/README.md §2.0(换主线待用户点头),上板待复核项见 docs/任务规划/登记簿.md §7。
|
||||
|
||||
## 1. 目标与范围
|
||||
|
||||
菜单树共 **33 叶**(节点行合计 42,缩进即层级):按行内 `#page=` 实测**已绑 28 叶、未绑 5 叶**,
|
||||
页面映射表 `UI_PAGE_MAP_COUNT` = 28(与已绑叶数一致)。未绑 5 叶 = §5 明确不做的 **5 叶**
|
||||
(遥信 3 / 手动录波 1 / SNTP 1),与下面分类表逐项对得上;2026-10-28 记录 5 页与运行报告页
|
||||
落地后,原「需补配置或写入方」的 6 叶缺口清零并按销项删行(该行内容见本节末与 §3)。
|
||||
菜单树共 **34 叶**(节点行合计 43,缩进即层级):按行内 `#page=` 实测**已绑 30 叶、未绑 4 叶**,
|
||||
页面映射表 `UI_PAGE_MAP_COUNT` = 30(与已绑叶数一致)。未绑 4 叶 = §5 明确不做的 **4 叶**
|
||||
(遥信 3 / SNTP 1),与下面分类表逐项对得上;2026-10-29 录波 UI 道把「手动录波」从不做转为
|
||||
落地并新增「录波回放」叶,绑 +2(手动转绑 + 新叶)/ 叶 +1;2026-10-28 记录 5 页与运行报告页落地后,
|
||||
原「需补配置或写入方」的 6 叶缺口清零并按销项删行(该行内容见本节末与 §3)。
|
||||
|
||||
| 分类 | 数量 | 当前怎么处理 |
|
||||
|---|---:|---|
|
||||
| 无开入硬件 | 3 | 不做:遥信类型、遥信防抖、双点遥信 |
|
||||
| 缺独立模块或产品定义 | 2 | 不做:手动录波(2026-10-29 已随故障录波立项,落地前仍按未绑计)、SNTP 设置 |
|
||||
| 缺独立模块或产品定义 | 1 | 不做:SNTP 设置(手动录波已于 2026-10-29 随故障录波 UI 道落地转绑定,不再列不做) |
|
||||
|
||||
现有基础:`ui_w_editor` / `ui_w_setlist` / `ui_page_action` 已就绪;`ui_data` 已扩到**四通道**
|
||||
(rt / par / info + 遥测死区与系数用的第四张注入表);像素黄金用例与字符集门禁已存在,内容页黄金
|
||||
现有 **60 张**(菜单 8 状态另计)。**装置维护 3 页、遥测死区/系数 2 页、通讯设置页、记录 5 页与
|
||||
现有 **66 张**(菜单 8 状态另计;2026-10-29 录波 UI 道 +6 张:手动录波 3 态 + 录波回放 3 态)。**装置维护 3 页、遥测死区/系数 2 页、通讯设置页、记录 5 页与
|
||||
运行报告页都是把这套基础接上真实消费者做出来的,不另发明一套机制。**
|
||||
逐页形态见 `docs/UI设计/页面补齐规划.md`;数据契约见
|
||||
`docs/UI设计/UI_数据供给设计说明.md`。本文件保留范围、完成判据与不做的取舍口径(执行清单已于 2026-10-28 清空)。
|
||||
|
||||
### 叶数判定与复核(2026-10-28 复核)
|
||||
|
||||
1. **节点行** = `ui_menu_tree.txt` 里非空、非 `#` 开头的行(当前 **42** 行,含根「主界面」)。
|
||||
1. **节点行** = `ui_menu_tree.txt` 里非空、非 `#` 开头的行(当前 **43** 行,含根「主界面」)。
|
||||
2. **缩进即层级**(每级 2 空格;Tab 会被 `gen_menu.py` 直接拒绝):某节点行之后再没有更深缩进的行 = **叶**
|
||||
(当前 **33** 叶);有更深缩进后继者 = 菜单节点(当前 **9** 个)。等价于生成物口径 `ui_menu_tab_has_children(node) == false`。
|
||||
3. **绑定**:叶行行尾带 ` #page=` = 已绑(当前 **28** 叶);不带的 = 未绑(当前 **5** 叶)。
|
||||
4. **闭合**:`未绑 = 待做 + 不做` —— 当前 待做 **0**(§2 清单已清空)+ 不做 **5**(遥信 3 / 手动录波 1 / SNTP 1)= **5**,
|
||||
(当前 **34** 叶);有更深缩进后继者 = 菜单节点(当前 **9** 个)。等价于生成物口径 `ui_menu_tab_has_children(node) == false`。
|
||||
3. **绑定**:叶行行尾带 ` #page=` = 已绑(当前 **30** 叶);不带的 = 未绑(当前 **4** 叶)。
|
||||
4. **闭合**:`未绑 = 待做 + 不做` —— 当前待做 **0**(§2 清单已清空)+ 不做 **4**(遥信 3 / SNTP 1)= **4**,
|
||||
与上面的分类表、§5「当前不做」表逐项对得上。
|
||||
5. **别拿 `grep -c '#page='` 当已绑叶数**:2026-10-28 实测它得 **29**,比真值多 1 —— 文件头第 5 行是规则说明里的
|
||||
` #page=<模块名>` 示例行(不是节点行),必须先剔注释/示例行;剔完的 28 才等于生成物 `UI_PAGE_MAP_COUNT`。
|
||||
` #page=<模块名>` 示例行(不是节点行),必须先剔注释/示例行;剔完的 30 才等于生成物 `UI_PAGE_MAP_COUNT`。
|
||||
|
||||
复核命令(复用生成器自己的解析器 `tools/menu_gen/gen_menu.py` 的 `parse_tree`,不另写一套计数逻辑,免得与生成物脱钩):
|
||||
|
||||
@@ -40,16 +43,16 @@
|
||||
& '.venv\Scripts\python.exe' -c "import io,sys; sys.path.insert(0,'tools/menu_gen'); import gen_menu as g; n,_=g.parse_tree(io.open('ui_menu_tree.txt',encoding='utf-8').read()); leaf=[i for i,x in enumerate(n) if all(y[0]!=i for y in n)]; b=sum(1 for i in leaf if n[i][4]); print('nodes=%d leaves=%d bound=%d unbound=%d' % (len(n),len(leaf),b,len(leaf)-b))"
|
||||
```
|
||||
|
||||
输出 `nodes=42 leaves=33 bound=28 unbound=5`;交叉核对生成物:`ui_menu_tab.h` 的 `UI_MENU_TAB_COUNT` = 42(节点行数)、
|
||||
`ui_page_registry.h` 的 `UI_PAGE_MAP_COUNT` = 28(已绑叶数)——一对 42、一对 28,与上面逐项对得上。
|
||||
输出 `nodes=43 leaves=34 bound=30 unbound=4`(2026-10-29 录波 UI 道落地后实测);交叉核对生成物:`ui_menu_tab.h` 的 `UI_MENU_TAB_COUNT` = 43(节点行数)、
|
||||
`ui_page_registry.h` 的 `UI_PAGE_MAP_COUNT` = 30(已绑叶数)——一对 43、一对 30,与上面逐项对得上。
|
||||
|
||||
三个已踩过的错算法(每种都同时给**当时**与**2026-10-28 重跑**的产物——错法不会自己失效,只是错数跟着已绑叶数一起漂):
|
||||
**把 42 个节点行当叶子**(多算 9 个菜单节点:当时已绑 21 叶 → 得「未绑 21」,2026-10-28 同一错法得「未绑 14」= 42 − 28);
|
||||
**`grep -c '#page='` 当时得 23**(当时已绑 22 叶,多的那 1 正是文件头注释第 5 行的 `#page=<模块名>` 示例字样,必须先剔注释行;
|
||||
2026-10-28 同一命令得 29 = 已绑 28 + 该示例行 1);**按叶子名去重得 29**(`参数`/`定值`/`控制字`/`软压板` 在「整定」「查看」下各一份,
|
||||
2026-10-28 同一命令得 29 = 当时已绑 28 + 该示例行 1;2026-10-29 现值 31 = 已绑 30 + 示例行 1);**按叶子名去重得 29**(`参数`/`定值`/`控制字`/`软压板` 在「整定」「查看」下各一份,
|
||||
4 对同名不能合并;2026-10-28 重跑仍是 29 = 33 叶 − 4 对同名)。
|
||||
(21 / 23 两个错数分属 2026-10-27「已绑 21 叶」与「已绑 22 叶」两个时点的实测,只用来说明错法,不代表现值;
|
||||
去重 29 与上面 grep 现值 29 **只是撞号**:一个是「合并同名叶」、一个是「不剔注释行」,都与真值 28 无关。)
|
||||
去重 29 与上面 grep 现值 29 **只是撞号**:一个是「合并同名叶」、一个是「不剔注释行」,都与真值 30(2026-10-29 现值)无关。)
|
||||
|
||||
---
|
||||
|
||||
@@ -76,6 +79,7 @@
|
||||
|---|---|---|
|
||||
| 通讯设置 | 页面与六态黄金已随 `728d6067` 入库;`[comm]` 段与端口真源在 `App/protocol/iec104/iec104_comm.{c,h}` | 「改后重启即生效」真机判据、配置段数与键数上限核对(台架窗口统一补做并留证) |
|
||||
| 记录 5 页 | 五类记录 ID / 文件 / 写入点已齐(docs/任务规划/登记簿.md §7 与 App/log/README.md §五类记录);reader 注入两端已映射 7 个记录源 | 页面已交付;只剩真机判据(真记录翻页、掉电保持、清除后归零),见 docs/任务规划/登记簿.md §7 |
|
||||
| 录波两页 | 手动录波操作页 + 录波回放页已落地绑页(2026-10-29 录波 UI 道);数据源 = `App/algo/wave_ring`/`wave_store` 真实现经 `ui_data_wave_ops_t` 注入,回放读盘走 `vol_lock` try 锁小窗口 | 页面已交付;真机判据归台架(真实触发落盘的段号连续性、削徽标真值、切段切道实测毫秒与卷锁忙「本轮无段」行为,见 `docs/子系统设计/故障录波设计说明.md` §10 台架判据) |
|
||||
| 运行报告 | boot_stats(SD2506 用户 SRAM 上电次数 + 本次复位原因快照,道G task-12)、iec104_serv 四个只读观测计数、现成 uptime_s | 页面已交付;**跨开机累计不做**(用户裁定只报本次开机)。真机待复核 SRAM 自增与四态判定,见 docs/测试说明/模拟器差距清单.md §2.3 |
|
||||
|
||||
---
|
||||
@@ -108,7 +112,6 @@ UTC 写入,后写者赢 ⇒ 装置墙钟与主机本地时刻恒差 8 小时
|
||||
| 项目 | 不做原因 | 重开条件 |
|
||||
|---|---|---|
|
||||
| 遥信类型 / 防抖 / 双点 3 页 | V0.1 无开入硬件,配置没有消费者 | 开入固件立项后,与开入开出一起做 |
|
||||
| 手动录波 | 录波模块尚未落地;旧口径还挂着「工程量口径未闭环」这道前置,**已被「录波存原始码值」解掉**(docs/子系统设计/故障录波设计说明.md §3.2) | **重开条件已满足**(2026-10-29 立项):随 docs/任务规划/故障录波.md 的「手动触发与保护启动接线」那张卡一起做 |
|
||||
| SNTP 设置 | 当前使用 8888 私有对时协议,SNTP 链路与产品口径均未建立 | SNTP 客户端立项 |
|
||||
| net 层与其他结构债 | 当前只做 UI,避免两条主线互相拖慢 | UI 当前清单完成后,按 README §2 切换 |
|
||||
| RTC 时基统一 | 两套写入口径并存(面板与 8888 写本地钟面、104 写 UTC),恒差 8 小时;统一任一侧都要动 104 上行语义或 UI 判据 | 用户裁定时基方向后,作为跨模块变更单独立项 |
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# 分工说明:RS-485 解锁与门禁口径对账(2026-10-28)
|
||||
|
||||
**目标**:**A 线**补齐 `Drivers/BSP/RS485/` 两条未修契约缺陷,为 `rs485_task` 解除 **RS-485 接线门禁** 的技术前提;**B 线**把 `AGENTS.md` 与 `docs/门禁说明/README.md` 的 **钩子口径** 对账到实跑。
|
||||
**口径**:本文件只分工、**不改行为**;两条线 **不交叉编辑** 对方文件,跨线接口 **先在本文件登记** 再动手。
|
||||
|
||||
## A 线 · RS-485 驱动契约(负责人 A)
|
||||
|
||||
- **涉及文件**:`Drivers/BSP/RS485/rs485.c`、`Drivers/BSP/RS485/rs485.h`、`App/task/rs485_task.c`(**新建**)、`test/host/rs485/`(**新建**)
|
||||
- **交付物**:
|
||||
1. 修 **单缓冲包撕裂**:RX 改 **双缓冲 + 帧序号**,消费者一次取整帧,对齐 `tpafe5160_snapshot()` 的 **临界区原子快照** 语义(旧的两步取数在两步之间被抢占会混帧);
|
||||
2. 修 **TX 超时拉 DE**:超时路径 **先等 TC 再拉 DE**,避免尾字节被截;错误码走 `Drivers/BSP/bsp_err.h` 通道,返回值 **不得裸丢弃**(`bsp-err` 的 consume-result);
|
||||
3. `[计划] test/host/rs485/run_unit_test.py` 覆盖 **撕裂**、**TX 超时**、**DE 时序** 三例;新增 `.c` 须 **双端注册**(`MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt`,见 `new-source-file` skill);
|
||||
4. 结论回填 `[计划] docs/问题记录/RS485_Trap_Records.md`,并在 `docs/任务规划/暂缓-RS485.md` 写清 **解锁条件是否满足**(接线节奏仍受门禁约束,**不由本线决定**)。
|
||||
- **禁止**:`malloc/calloc/realloc/strdup/free`(`coding-standard` CODE-RULE-20);`osKernelStart` 之前调任何创建类 API;注释里写 **星号形态的路径**(`nested-comment` 9-D)。
|
||||
- **预算**:建任务 **前后各跑一次** `scripts/stack_report.py` 与 `scripts/ram_report.py`;**建任务/抬栈属 Ask first**。
|
||||
|
||||
## B 线 · 门禁与文档口径(负责人 B)
|
||||
|
||||
- **涉及文件**:`AGENTS.md`、`docs/门禁说明/README.md`、`docs/代码规范/pre-commit使用说明.md` §2;`.pre-commit-config.yaml` **只读核对、不改**。
|
||||
- **交付物**:
|
||||
1. **口径 = YAML 解析后的 hook 条目数**:用 `& ".venv\Scripts\python.exe"` 解析 `.pre-commit-config.yaml` 取实数,与 `AGENTS.md` 的 **51** 做 **声明 / 文档 / 实跑** 三数同源核对(**注释掉的候选钩子不计**,直接 grep 声明行会多算 1);
|
||||
2. `docs/门禁说明/README.md` 的 **篇数 ↔ 钩子数** 差集清单,核对 **27 篇 + 1 指针** 是否仍成立;
|
||||
3. 有差异 **只改文档、不加钩子**(Lead 裁定:`test/host/` 41 套里只有 22 根专属钩子,**没有专属钩子 ≠ 没被跑**,余量由 `test-coverage` 总闸兜);新条目标识用 **语义短语**,**不再发字母+数字编号** → `docs/编号规范.md` §1。
|
||||
- **禁止**:手工全局替换改名/挪目录(走 `scripts/rename_ref.py`,先 dry-run);章节引用写成非紧邻形态(`doc-links` 带 `--sections-strict` 拦提交);在配置注释里复述那个 grep 字面量(文本计数再虚高 1)。
|
||||
|
||||
## 共用约束(A/B 都要守)
|
||||
|
||||
- 改代码/改文档 **只用 Edit 精确替换**;**绝不**用 `Get-Content`/`Set-Content`/`cmd` 重写含中文的文件(`U+FFFD` 不可逆);带 BOM 的 `Drivers/BSP/UI/`、`App/cfg/cfg_file.c`、`Lib/lv_conf.h` 改完核对 `EF BB BF`(`bom-guard`)。
|
||||
- **字符串字面量纯 ASCII**,中文只进注释;行尾 **LF**(`.gitattributes` 已写死 `* text=auto eol=lf`);怀疑乱码先数 `EF BF BD` 字节,**不要 `git checkout`**。
|
||||
- 门禁 **只在提交时** 统一跑:`@build` + `& ".venv\Scripts\python.exe" scripts/ci_local.py` + `scripts/check_doc_links.py --all`,**不要中途反复跑**。
|
||||
- `git commit` / `git push` / 烧录 / CubeMX 重生成 / 登记簿删行销项 / 开 `ENABLE_DESTRUCTIVE_TESTS` —— **Ask first**。
|
||||
|
||||
## 验收(合并前)
|
||||
|
||||
| 线 | 判据 | 证据 |
|
||||
|---|---|---|
|
||||
| A | RS485 两条 ❌ 清零,`rs485_task` 的技术门禁解除(**是否接线仍由用户裁定**) | `test/host/rs485` 全绿 + `MDK-ARM/build_log.txt` **0 错误 0 警告** |
|
||||
| B | 声明 / 文档 / 实跑 **三数一致** | 解析计数输出 + `docs/门禁说明/README.md` 差集为空 |
|
||||
| A+B | `registration-check`、`bsp-err`、`nested-comment`、`id-check` 基线 **只减不增** | `scripts/ci_local.py` 输出 + `scripts/archive/id_gate/id_known_backlog.json` 台账 |
|
||||
|
||||
## 交接
|
||||
|
||||
- 两条线各自 **只回报**:**改了哪些文件、跑了哪些自检、剩余风险**;跨线接口(`rs485_frame_take()` 签名)变更 **先在本文件登记**。
|
||||
- `docs/任务规划/README.md` 的 **完成即销项** 由协调者在用户确认后统一做,**A/B 都不得自行删行**。
|
||||
@@ -1,44 +0,0 @@
|
||||
# 分工说明:采集链与上送链台架联调(2026-10-28)
|
||||
|
||||
**目标**:把 **语义取数与有效性→全周傅氏相量→测频与遥测快照** 取数链与 **测频与遥测快照→104/UI** 上送链在同一次台架烧录里对齐,提交门禁 **0 错误 0 警告**。
|
||||
**口径**:本文件只分工,不改行为;任何一条线单独验收通过后合并,**不交叉编辑**对方文件。
|
||||
|
||||
## A 线 · 采集链(负责人 A)
|
||||
|
||||
- **涉及文件**:`App/algo/adc_input.c`、`App/algo/adc_health.c`、`App/algo/sample_ring.c`、`App/algo/phasor.c`、`Src/freertos.c`(**只写 `USER CODE` 区**)
|
||||
- **交付物**:
|
||||
1. `App/algo/README.md` 补 **「快照时序」** 一节:`tpafe5160_snapshot()` 临界区原子性、`adc_tick_irq_notify()` 的「消费者就绪」门(TIM3 比调度器早 ≈100 ms);
|
||||
2. `test/host/adc_input/run_unit_test.py` 增加 **帧序号回绕** 与 **双缓冲半帧** 两个用例;
|
||||
3. `docs/子系统设计/ADC采样节拍设计说明.md` §5 表格与代码实际节拍(**TIM3 PSC 83 / ARR 999 → 1 kHz + TRGO_UPDATE**)核对后回填差异。
|
||||
- **禁止**:`malloc/calloc/realloc/strdup/free`(门禁 CODE-RULE-20);跨转换器做 **相位/功率/功率因数** 运算(V0.1 硬件缺陷);`Drivers/BSP/` 里 include `App/` 的头(`layer-deps`)。
|
||||
- **取数只用** `adc_input_poll()` / `adc_input_raw()` **按语义**取值,**不写死下标**(`buf[2]=IA`、`buf[5]=UN`、`buf[6]=UA` 见 `docs/硬件平台/ADC通道映射说明.md`)。
|
||||
|
||||
## B 线 · 上送链(负责人 B)
|
||||
|
||||
- **涉及文件**:`App/algo/meas.c`、`App/task/iec104_task.c`、`Drivers/BSP/UI/ui_data.c`(**带 UTF-8 BOM:改完核对 `EF BB BF`**)、`test/host/meas/run_unit_test.py`
|
||||
- **交付物**:
|
||||
1. `App/algo/meas.c` 的 **200 ms 双区快照** 写一个 **单写者/多读者** 一致性说明,落到 `docs/架构设计/项目架构总览.md` 的跨层依赖表;
|
||||
2. `Drivers/BSP/UI/ui_data.c` 与 104 上送 **共用同一份上游**:补 `test/host/ui_data/run_unit_test.py` 的 **陈旧代次(generation)** 用例;
|
||||
3. 若新增 `.c`:`MDK-ARM/STM32F4-Base.uvprojx` + `simulator/CMakeLists.txt` **双端注册**(`new-source-file`)。
|
||||
- **禁止**:直接编辑 `Drivers/BSP/UI/` 之外的 `Src/`、`Inc/` 标记区外内容;把 `*.uvprojx` 的 UV4 抖动改动入库;改 `Lib/lv_conf.h` 不同步 `simulator/` 那份(`lv-conf-sync`)。
|
||||
- **UI 侧铁律**:字符串字面量 **纯 ASCII**,中文只进注释;注释里写路径 **不要出现星号形态**(`nested-comment` 9-D),写目录或用尖括号。
|
||||
|
||||
## 共用约束(A/B 都要守)
|
||||
|
||||
- 改代码/改文档 **只用 Edit 精确替换**;**绝不**用 `Get-Content`/`Set-Content`/`cmd` 重写含中文的文件(`U+FFFD` 不可逆)。
|
||||
- 行尾 **LF**(`.gitattributes` 已写死 `* text=auto eol=lf`);怀疑乱码先数 `EF BF BD` 字节,**不要 `git checkout`**。
|
||||
- 门禁只在 **提交时** 统一跑:`@build` + `& '.venv\Scripts\python.exe' scripts/ci_local.py` + `check_doc_links.py --all`;**不要中途反复跑**。
|
||||
- 文档引用章节 **紧邻写法**:`docs/算法/采集与算法层设计说明.md` §10.1;改名/挪目录走 `scripts/rename_ref.py`(先 dry-run)。
|
||||
- `git commit` / `git push` / 烧录 / `CubeMX` 重生成 / 抬栈 —— **须用户明确指示**(抬栈前后各跑 `scripts/stack_report.py`)。
|
||||
|
||||
## 验收(合并前)
|
||||
|
||||
| 线 | 判据 | 证据 |
|
||||
|---|---|---|
|
||||
| A | 语义取数与有效性→全周傅氏相量与 1 kHz 节拍一致,无半帧 | `test/host/adc_input` 全绿 + `docs/子系统设计/ADC采样节拍设计说明.md` |
|
||||
| B | `meas` 双区 **104 与 UI 同源**,无撕裂 | `test/host/meas`、`test/host/ui_data` 全绿 + `docs/编号规范.md` 无新撞号 |
|
||||
| A+B | 台架一次烧录 **`-Rst -Run` + 回读校验**(`No difference found`),串口日志无 `[ERR]` | `@test` 报告 + `MDK-ARM/build_log.txt` |
|
||||
|
||||
## 交接
|
||||
|
||||
- 两条线各自 **只回报:改了哪些文件、跑了哪些自检、剩余风险**;跨线接口(`meas` 快照字段)变更 **先在本文件登记**再动手。
|
||||
+6
-4
@@ -7,7 +7,7 @@
|
||||
|
||||
## 1. 现状一句话
|
||||
|
||||
全仓**没有任何录波代码**:`App/` 下 grep 无实现,`Drivers/BSP/UI/ui_menu_tab.c:41` 那条「手动录波」叶子(菜单第 26 叶)**至今未绑内容页**;产品需求把「录波文件存储」「文件传输(录波)」两行标为未实现/未开始;旧口径把录波挂在「工程量口径实测闭环之后」,本波用**原始码值**解掉这个依赖(`docs/子系统设计/故障录波设计说明.md` §3.2)。
|
||||
**数据面与接线已在仓内,板级判据一轮未跑**(诚实口径,2026-10-29 更新):录波环与段格式编解码在 `App/algo/wave_ring.c` 与 `App/algo/wave_store.c`,压环与落盘与保护启动接线、以及两条 UI 页的代码侧判据已过(三套主机单测 100 / 170 / 110 全 rc=0、双端编译 0 错误 0 警告、板端编码器与 PC 解码器两份独立实现字节级互验通过;「手动录波」第 26 叶与「录波回放」两叶均已绑页,`UI_PAGE_MAP_COUNT` = 30,内容页黄金参考 60 → 66 张含录波 6 态)。**没做完的是板级那一轮**:注 1 V / 50 Hz、FTP 拉回、与 `meas:` 行和 104 浮点 YC 对表、CRC 不符必须拒收的实测、真机上看回放页放一段真波;产品需求把「录波文件存储」「文件传输(录波)」两行的更新排在台架轮次之后。旧口径把录波挂在「工程量口径实测闭环之后」,本波用**原始码值**解掉了这个依赖(`docs/子系统设计/故障录波设计说明.md` §3.2)。
|
||||
|
||||
三条已裁定口径(用户裁定 2026-10-29,理由见`docs/子系统设计/故障录波设计说明.md` §1.2 与 §2):
|
||||
|
||||
@@ -23,11 +23,13 @@
|
||||
|
||||
| 卡 | 内容 | 退出判据 | 资源 | 依赖 |
|
||||
|---|---|---|---|---|
|
||||
| **录波数据面最小闭环** | 新增专用录波环与段格式编解码(`docs/子系统设计/故障录波设计说明.md` §5 到 §7);bench 构建定周期自抓;FTP 拉回;仓内新增一个纯标准库 PC 脚本读段、画图、重算全周傅氏 | 注入 1 V / 50 Hz 正弦 → 拉回段文件 → PC 脚本量出周波数、幅值、极性与注入一致;削顶路数与头里 `clip_mask` 一致;**PC 重算的基波有效值与板上 `meas:` 行、104 浮点 YC 逐个对表通过**;段文件 CRC 不符时脚本必须拒绝而不是画出东西 | 🟡+🟢(需信号发生器与板,不需标准源) | 无(可与固件其他道并行;独占构建锁) |
|
||||
| **手动触发与保护启动接线** | 触发状态机接两路真实来路:UI「手动录波」操作页(第 26 叶绑页,形态沿用已规划好的操作页确认模式)+ `App/algo/prot_elm.c` 的 `picked` 上升沿 | 面板按一次出一段;造一次过流,段号与该时刻 `/log/start.log` 事件能对齐;冷却期内二次触发只加 `ovf` 计数、不重录;冻结窗口内 104 上送与保护判据不受影响(**这条是"丢录波样本不丢采集"的验收**) | 🟡+🟢+UI 道(菜单树与字库是整文件生成物,须单写者) | 上一张卡 |
|
||||
| **现场单通道回放页** | 新增「录波回放」叶子与内容页:单通道 2 周波 40 点、阶梯线、0 线与峰值标注、单位徽标、削顶标记、空态文案(`docs/子系统设计/故障录波设计说明.md` §8) | `ui-verify` 五步全过;回放页入 `ui-golden` 黄金参考;远端看图能拍到此页(只作留证,不当波形通路);**空态与 CRC 失败态绝不允许画成一条直线**;菜单叶数与字库生成物同步重跑且 `generated-fresh` 一致 | 🟢+UI 道(模拟器与黄金参考) | 上一张卡 |
|
||||
| **录波数据面最小闭环**〔代码侧完成 / 板级待台架〕 | 新增专用录波环与段格式编解码(`docs/子系统设计/故障录波设计说明.md` §5 到 §7);bench 构建定周期自抓;FTP 拉回;仓内新增一个纯标准库 PC 脚本读段、画图、重算全周傅氏 | **代码侧判据已完成**:三套主机单测 100 / 170 / 110 全 rc=0(环 100、段格式 170、触发状态机 110)、双端编译 0 错误 0 警告、板端编码器与 PC 侧解码器两份独立实现**字节级互验通过**(同一份字节两边同解)。**下列判据要板子与台架,一轮未跑**:注入 1 V / 50 Hz 正弦 → 拉回段文件 → PC 脚本量出周波数、幅值、极性与注入一致(**绝对相位不可比**,段内相位以点 0 为参考、触发瞬间在段内点 159,与发生器无相位锁定关系,可核对的是相对相位与极性——见 docs/子系统设计/故障录波设计说明.md §4.2 与 §10);钉轨路数与头里 `clip_mask` 的合格判据是「头位图 ⊇ 实测钉轨路数,且差异必须能由注入条件解释」(`clip_mask` 按来源派生、非本段实测,V0.2 后该位恒 0、这条退化为只查实测);**强观察项**:V0.1 下这五路做交流注入时反而没钉轨 → 先查接线或该路未驱动,属告警面不属失败面;**PC 重算的基波有效值与板上 `meas:` 行、104 浮点 YC 逐个对表通过**;段文件 CRC 不符时脚本必须拒绝而不是画出东西 | 🟡+🟢(需信号发生器与板,不需标准源) | 无(可与固件其他道并行;独占构建锁) |
|
||||
| **手动触发与保护启动接线**〔代码侧完成 / 板级待台架〕 | 触发状态机接两路真实来路:UI「手动录波」操作页(第 26 叶绑页,形态沿用已规划好的操作页确认模式)+ 保护启动的元件 picked 上升沿(落点 `App/task/protect_task.c` 的 `s_log_record_edge()`,不是判据核本身) | **代码侧已完成**:保护启动来路与并发语义(同拍多请求只录一段、冷却期内二次触发只加 `ovf`、冻结窗口只丢录波样本不丢采集)已在主机单测里钉住,双端编译 0 错误 0 警告。**下列属板级未完成**(第 26 叶已于 2026-10-29 绑页,模拟器侧手动触发路径已有黄金用例;剩下的必须在真机面板上按):面板按一次出一段;造一次过流,段号与该时刻 `/log/start.log` 事件能对齐;冷却期内二次触发只加 `ovf` 计数、不重录(**同时看 `armed` 与那一刻 `/log/start.log` 的时刻,不要只盯 `ovf` 数字**:这两个计数是跨上下文自增、并发拒绝时可能少计一次,容差见 docs/子系统设计/故障录波设计说明.md §9);冻结窗口内 104 上送与保护判据不受影响(**这条是"丢录波样本不丢采集"的验收**) | 🟡+🟢+UI 道(菜单树与字库是整文件生成物,须单写者) | 上一张卡 |
|
||||
| **现场单通道回放页**〔代码侧完成 / 板级待台架〕 | 新增「录波回放」叶子与内容页:单通道 2 周波 40 点、阶梯线、0 线与峰值标注、单位徽标、削顶标记、空态文案(`docs/子系统设计/故障录波设计说明.md` §8) | **代码侧判据已完成**:两叶绑页(`UI_PAGE_MAP_COUNT` = 30)、`ui-golden` 内容页黄金参考 60 → 66 张(录波 6 态:手动录波 3 态 + 录波回放 3 态),任务树重建模拟器后同一批 66 张连跑三次逐字节一致(页面池水位 5,448 B)、菜单侧 8 状态连跑三次一致(5,368 B),`generated-fresh` 退出码 0(菜单树与中文字库生成物与源一致),双端编译 0 错误 0 警告。**下列判据要板子与台架,一轮未跑**:真机上放一段真波(不是模拟器合成节拍)、远端看图能拍到此页(只作留证,不当波形通路)、**空态与 CRC 失败态绝不允许画成一条直线**要在真机上用坏段复现一次 | 🟢+🟡+UI 道(模拟器与黄金参考) | 上一张卡 |
|
||||
| 二期(不在本波) | 4 kHz 全速与 80 点/周波、突变量启动、COMTRADE、104 文件传输、分段与参考机形态(录波 65 通道、多类型多段) | 不在本波验收面内;重开条件见 §4 | 🔴/独立立项 | — |
|
||||
|
||||
**落盘道的失败留痕名(单板上一律一行 `[WAVE] seg drop: <原因> (idx=… written=…)`,统一打印点 `App/task/default_task.c:816`)**:`crc selftest FAIL` / `mkdir /wave` / `opendir /wave` / `readdir /wave` / `seg_path` / `pack_start` / `f_open` / `pack_next` / `f_write short` / `pack stall` / `f_close` / `len mismatch` / **`ring abandoned`**。最后一条是 docs/子系统设计/故障录波设计说明.md §5.2 第四条陷阱的处置留痕(写完关档后复检环侧身份不过 → 判废本段、`err` 加一、段号不推进),**代码与主机单测面已有、板级未测**;另有一条非失败的慢段留痕 `[WAVE][WARN] seg held <毫秒> ms (idx=…)`(`App/task/default_task.c:831`)——搬得慢不是错,真失败才走 `seg drop`。
|
||||
|
||||
**为什么录波排在信号发生器动态验证之前**(用户裁定):动态验证真正缺的不是信号源,是**样本出口**——录波段文件一旦有了,动态验证就从「比对几个派生标量」升级成「PC 独立重算 vs 板上读数」的对表,同一套台架接线两次都用得上,不必再造一条串口通路。`docs/子系统设计/故障录波设计说明.md` §11 记了因此而变的三处口径。
|
||||
|
||||
---
|
||||
|
||||
+16
-9
@@ -307,16 +307,20 @@ typedef struct {
|
||||
|
||||
#### 4.6.2 判据 D1~D6
|
||||
|
||||
> **分工**:本节定"验什么、过判据是多少";**期望值的算术、接法细节与执行步骤**在
|
||||
> `docs/测试说明/ADC前端注入验证流程.md` §8(同一轮次的手册,两边于 2026-10-29 已对齐)。
|
||||
|
||||
| # | 验什么 | 接法 | 读哪 | 过判据 |
|
||||
|---|---|---|---|---|
|
||||
| **D1** | 基波幅值计量 | UA(R31 / `AIN1`)注 1.000 Vrms、50.000 Hz 正弦 | `meas:` 的 `ua_mv`;或 104 `0x4011` | 读数 = 万用表 AC 档读数,偏差 ≤2 %(与静态同档)。⚠ 别用 `phasor(hb)` 行比幅值——那是 ×0.1 定点,注 1 V 只剩 10 格(§4.6.3) |
|
||||
| **D2** | 读数是否只跟基波走 | 同幅值改方波、三角 | 同上 | 期望值 = **基波分量有效值**(方波基波 = 4/π × 峰值/√2 ≈ 0.90×峰值;三角 = 峰值/√6),**不是**真有效值;偏差 ≤2 %。顺带把"这台仪表读的是基波"这条口径钉进文档——用户拿真有效值表去对会以为差了 10 % |
|
||||
| **D3** | 相位 | UA 与 UN 两路**同片**(都走 TPAFE)注同幅、同相 | `phasor(hb)` 各行 `Re=` / `Im=` | 两路 `atan2(fIm, fRe)` 之差 ≤2°;改变发生器相位 90°,差值跟着走。**只许比同片内**——跨转换器无同刻性(§9),STM32 那五路还有 1 拍乒乓滞后 |
|
||||
| **D2** | 读数是否只跟基波走 | 同幅值改方波、三角 | 同上 | 期望值 = **基波分量有效值**(同峰值下的基波 / 峰值比:方波 ≈ **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 的 D2 卡。顺带把"这台仪表读的是基波"这条口径钉进文档——用户拿真有效值表去对会以为差了 10 % |
|
||||
| **D3** | 相位 | 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 拍乒乓滞后 |
|
||||
| **D4** | 测频 | 同 D1 的 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 风险行 |
|
||||
| **D5** | 削顶与品质路由(**负向用例**) | 给 UB(R52 / `AIN2`)注同幅正弦 | `cal:` 行的 `rail=` / `edge=`;`rail: edge=…` 的 `[ERR]` 行;104 `0x4012` | 负半周钉下轨 → **连续 20 拍(正好一个 50 Hz 周波)后该路贴轨位锁存**、`edge` 起沿、104 上该路 IV 且值清零。⚠ 这条会**故意造出 `[ERR]`**,而 `@test` 的 forbidden 标记含 `[ERR]` → 本轮要么人工判读、要么把这五路排在最后单独一轮(§8 第一行) |
|
||||
| **D6** | 谐波轨 | 基波 + 20 % 二次 + 20 % 五次(IA 上注) | `phasor(hb) C04-IA-F2` / `C05-IA-F5` 两行(`log_phasors()` 逐实例打印,实例表加行即自动覆盖) | 谐波轨读数 = 注入分量(±2 %)。⚠ 这两根轨**目前没有任何下游消费者**(`prot_elm.c` 无引用、`meas` 只算 `harm=1`),所以 D6 只证"算得对",**不证"有人用"**——出口归 §9 的谐波轨立项 |
|
||||
| **D5** | 削顶与品质路由(**负向用例**) | 给 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 的 D5 卡 | 负半周钉下轨 → **连续 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 第一行) |
|
||||
| **D6** | 谐波轨 | 基波 + 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`),所以 D6 只证"算得对",**不证"有人用"**——出口归 §9 的谐波轨立项 |
|
||||
|
||||
**先做 D1 / D2 / D5**(纯读数、无跨通道约定),D3 / D6 次之,D4 视 §7 #8 的裁定。
|
||||
**先做 D1 / D2 / D5**(纯读数、无跨通道约定),D3 / D6 次之,D4 视 §7 #8 的裁定。每条的两档跑法(周期行标量档 /
|
||||
录波段文件定量档)、触发动作、耗时排布与失败分诊在 `docs/测试说明/ADC前端注入验证流程.md` §8.6。
|
||||
|
||||
#### 4.6.3 读数出口(零固件改动)
|
||||
|
||||
@@ -325,7 +329,8 @@ typedef struct {
|
||||
| 串口每 10 s 心跳(`App/task/adc_task.c` 心跳块) | `adc:`(9 路码值)、`cal:`(三路 mV + `vl`/`cl`/`rail`/`edge` 位图)、`phasor(hb)`(逐实例 `Re` / `Im` / `rms`)、`freq(hb)`、`meas:`(快照轨) | MicroLIB 无 `%f`,全是定点:`phasor` ×0.1(**1 V 注入只剩 10 格**)、`freq` ×0.01 Hz、`cal:` / `meas:` ×1000(1 mV)。10 s 一行的单点瞬时值对 50 Hz 等价于随机相位抽样 |
|
||||
| 104 YC(现成 PC 主站 `test/host/iec104_master.py`) | `0x4011`~`0x4018` 八个电气量 + `0x401D` 频率,**浮点无量化**,还带品质 | UB/UC/IB/IC/IN 恒 IV、值清零 → 那五路只能回读串口码值域 |
|
||||
| UI 交流量页 | `Ua` / `U0` / `Ia` / `Ia2` / `Ia5` / `Hz` / 标定徽标 共 7 行 | 160×160 单色;`--` **同时**表示"没数"和"有数但不可信" → 不能当判据,只能当旁证 |
|
||||
| **板上没有的(本波现状)** | 把窗里 20 / 40 个点导出来的任何出口:无命令控制台、无 dump、当前还不落文件 | 所以现在只能靠**派生标量**证;要看形状就拿示波器直接看注入点(只对照发生器,别拿它当判据)。**这条限制是临时的**:故障录波已立项,段文件就是现成的样本出口,届时判据升级为「PC 独立重算 vs 板上读数」对表(见 `docs/子系统设计/故障录波设计说明.md` §11) |
|
||||
| **段文件样本出口(代码侧已落地,2026-10-29)** | 每次触发一段:`/wave/WAVE00.BIN` 起共 100 个号轮转(FTP 根下可见),8 路 × 320 点原始码值加封存瞬间的标定与品质快照;PC 侧 `test/host/wave_read.py` 提供 dump / fft / shape / plot 四个子命令(bench 工具,纯标准库,自带 `--selftest`) | 「比对派生标量」已可升级成「PC 独立重算 vs 板上读数」对表(判据与执行步骤见 `docs/测试说明/ADC前端注入验证流程.md` §4,格式属主见 `docs/子系统设计/故障录波设计说明.md` §6.2 与 §6.3)。**但板级那一轮还没跑**(注 1 V / 50 Hz、与 `meas:` 与 104 YC 对表、CRC 不符必须拒收的实测,见 `docs/任务规划/故障录波.md` 卡片判据);示波器仍旧只作旁证、不当判据。段内点序:触发瞬间落在段内点 159(预触发 160 点的最后一点),段的绝对相位与发生器无锁定关系,可核对的是相对相位与极性 |
|
||||
| **仍然没有的(2026-10-29 更新)** | 没有命令控制台、也没有「把窗里 20 / 40 个点导出来」的定点 dump 子命令——**但样本出口本身已成立**(上一行的段文件:一次 320 点 / 8 路,比原来规划的 20 / 40 点窗更宽) | 现场取回一律走现成 FTP(21 端口),形状就地看回放页单通道、或在 PC 侧用同一脚本出图;示波器只对照发生器、不当判据。**判据已升级为「PC 独立重算 vs 板上读数」对表**,对表的执行轮次待台架(见 `docs/子系统设计/故障录波设计说明.md` §11) |
|
||||
|
||||
#### 4.6.4 接法与红线(照抄既有红线,不新发明)
|
||||
|
||||
@@ -342,7 +347,7 @@ typedef struct {
|
||||
|---|---|---|
|
||||
| 端子二次值 | 注入点在 ④ 之前,算出来还是引脚域 | §5「端子/子板变比确认」 |
|
||||
| 精度等级(0.5 级 / 1 级) | 发生器自己的幅值误差本身就是要被校的量,不能冒充标准源 | §5「两点标定与精度验收」+ §7 #1 |
|
||||
| 跨转换器相位 / 功率 / 对称分量 | 两片转换器无共同采样触发(V0.1 硬件缺陷)+ STM32 侧 1 拍乒乓滞后。量纲链路口径见 `docs/硬件平台/ADC通道映射说明.md` §7;处置见 §9 |
|
||||
| 跨转换器相位 / 功率 / 对称分量 | 两片转换器无共同采样触发(V0.1 硬件缺陷)+ STM32 侧 1 拍乒乓滞后;量纲链路口径见通道映射说明的链路分段一节 | §9(本波不做矢量类运算) |
|
||||
| THD / 谐波方和根 | 固件里只有两根谐波轨,没有合成出口 | §9(挂谐波轨立项) |
|
||||
| 温升、EMC、长时间漂移 | 单轮台架不覆盖 | 送检与型式试验 |
|
||||
|
||||
@@ -420,6 +425,8 @@ flowchart TD
|
||||
|
||||
## 8. 风险
|
||||
|
||||
> 引用本表某一行时**写行名**(例:「削顶通道被当成可测」行),别写「末行 / 第 N 行」——本表会插行。
|
||||
|
||||
| 风险 | 触发条件 | 缓解 |
|
||||
|---|---|---|
|
||||
| **削顶负向用例被当成固件 bug** | 做 D5 时 STM32 那五路必然钉下轨 → `rail: edge=…` 是 `[ERR]` 行,而 `@test` 的 forbidden 标记含 `[ERR]` → 整轮被判 FAIL | 判读规则写进流程文档:"做 D5 的这一轮 `[ERR] rail:` 属预期,只人工判读、不计入 `@test` 结论",或把这五路排在最后单独一轮并事先声明。同时别忘 `cl=0xE6` 是**来源派生**(§2.6),不是本轮新削出来的 |
|
||||
@@ -443,7 +450,7 @@ flowchart TD
|
||||
| THD / 谐波方和根真有效值、快照 4 点平均 | 挂 `docs/算法/继电保护功能复现规划.md` 的「遥测谐波轨(可选)」一起做;快照保持"基波有效值 + 直取相量"口径(§4.5) |
|
||||
| **谐波轨的消费出口**(C04 / C05 目前无人消费) | D6 只证"算得对";接线到判据或上送属谐波轨立项范围,本波不动 |
|
||||
| 端子级额定电压、绝缘耐压、温升 | 要标准源 / 型式试验条件;本波所有注入都在 `U5` 排针侧 ±2 V 以内 |
|
||||
| 录波(原始码值段文件) | **已独立立项**(2026-10-29):`docs/任务规划/故障录波.md`;本卡「动态波形验证」的样本出口将改由录波段文件承担,排序也随之调整(`docs/子系统设计/故障录波设计说明.md` §11) |
|
||||
| 录波(原始码值段文件) | **已交付代码面**(2026-10-29 立项并当日落地):`docs/任务规划/故障录波.md`;本卡「动态波形验证」的样本出口**已**由录波段文件承担(§4.6.3 出口表已改),排序也随之调整(`docs/子系统设计/故障录波设计说明.md` §11);**没跑的是板级那一轮**(§10) |
|
||||
| 104 文件传输(规约侧读文件) | 仍独立待排;现场与台架取回一律走现成 FTP(21 端口),规约侧文件服务等送检或第三方交接需求再议 |
|
||||
| 等周波调频 | 与标定正交、无标准源时判据不可达,见 `docs/算法/继电保护功能复现规划.md` §3.1 |
|
||||
| 功率 / 相位跨相比较 / 功率因数 / 对称分量 | **V0.1 硬件禁止**:跨转换器无同一时刻性,`docs/硬件平台/ADC通道映射说明.md` §7 通道归属行 |
|
||||
@@ -461,7 +468,7 @@ flowchart TD
|
||||
|---|---|
|
||||
| **`docs/测试说明/ADC前端注入验证流程.md`** | **执行步骤的属主**。本文只写"证到哪、判据是什么、为什么这么排";怎么接表笔、读哪一行、填哪张表一律在那篇里。动态验证的**步骤**同样落那篇(新开"交流"节),本文 §4.6 只留判据 |
|
||||
| **通道映射说明** | `docs/硬件平台/ADC通道映射说明.md` §7.1(①②③④ 链路分段)与 §8.1(静态六项实测)是**量纲与实测记录的属主**;本文 §2.1 / §2.6 是它的**消费者**,不另立口径,动态实测结论也回写该文件 |
|
||||
| **`docs/子系统设计/故障录波设计说明.md`** | 录波段文件是本卡「动态波形验证」的**样本出口**:录波落地后判据从「比对派生标量」升级为「PC 独立重算 vs 板上读数」对表,动态验证的排序也因此让位给录波数据面(用户裁定 2026-10-29,理由与三处口径变更见该文 §11) |
|
||||
| **`docs/子系统设计/故障录波设计说明.md`** | 录波段文件是本卡「动态波形验证」的**样本出口**,数据面代码侧已落地(2026-10-29:环与段格式在 `App/algo/wave_ring.c` / `App/algo/wave_store.c`,PC 侧 `test/host/wave_read.py`);判据从「比对派生标量」升级为「PC 独立重算 vs 板上读数」对表的**执行轮次待台架**,动态验证的排序已因此让位给录波数据面(用户裁定 2026-10-29,理由与三处口径变更见 docs/子系统设计/故障录波设计说明.md §11) |
|
||||
| **`docs/算法/采集与算法层设计说明.md`** | 算法与判据正文(全周傅氏、测频、保护元件);本文只规定"这些算法的信度到什么程度" |
|
||||
| **`docs/子系统设计/IEC104上行设计说明.md`** | 动态验证的**读数出口**(`0x4011`~`0x4018` + `0x401D`);送检验收索引在 `docs/任务规划/登记簿.md` §7 |
|
||||
| **送检验收** | 标准源送检依赖本文「两点标定与精度验收」;顺序不可倒(先变比 → 再标定 → 才解卡 → 才送检,§6) |
|
||||
|
||||
@@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 268,220 B (261.93 KB) | 1,048,576 B (1024 KB) | 25.6% |
|
||||
| RW_IRAM1 | 85,904 B (83.89 KB) | 131,072 B (128 KB) | 65.5% |
|
||||
| ER_IROM1 | 275,976 B (269.51 KB) | 1,048,576 B (1024 KB) | 26.3% |
|
||||
| RW_IRAM1 | 94,312 B (92.10 KB) | 131,072 B (128 KB) | 72.0% |
|
||||
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
|
||||
| **Flash (ROM 合计)** | **270,152 B (263.8 KB)** | 1,048,576 B (1024 KB) | **25.8%** |
|
||||
| **Flash (ROM 合计)** | **278,012 B (271.5 KB)** | 1,048,576 B (1024 KB) | **26.5%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
+14
-9
@@ -15,19 +15,19 @@
|
||||
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|
||||
|---|---|---|---|---|
|
||||
| ER_IROM1 (Flash) | 268,220 B | 1,048,576 B | 25.6% | 参考 |
|
||||
| ER_IROM1 (Flash) | 275,976 B | 1,048,576 B | 26.3% | 参考 |
|
||||
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
|
||||
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
|
||||
| RW_IRAM1 | 85,904 B | 131,072 B | 65.5% | PASS |
|
||||
| RW_IRAM1 | 94,312 B | 131,072 B | 72.0% | PASS |
|
||||
|
||||
| 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 |
|
||||
|---|---|---|---|
|
||||
| 网络 | 13,702 B | 12 KB | ⚠ 超 1.4 KB |
|
||||
| 存储 | 7,072 B | 12 KB | OK |
|
||||
| UI | 34,657 B | 16 KB | ⚠ 超 17.8 KB |
|
||||
| UI | 36,429 B | 16 KB | ⚠ 超 19.6 KB |
|
||||
| RTOS 堆 | 57,376 B | 56 KB | 堆=配额 |
|
||||
| 应用 | 15,552 B | 8 KB | ⚠ 超 7.2 KB |
|
||||
| 其它 | 14,917 B | — | 参考 |
|
||||
| 应用 | 26,333 B | 8 KB | ⚠ 超 17.7 KB |
|
||||
| 其它 | 10,768 B | — | 参考 |
|
||||
<!-- MEM_BUDGET:END -->
|
||||
|
||||
- **门禁**:pre-commit `mem-budget` 钩子(`scripts/ram_baseline.py --gate`)——
|
||||
@@ -51,8 +51,8 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名
|
||||
| 存储 | `gd5f*` `nand_*` `ff*` `diskio*` `vol_lock*` + dhara 裸名(`map/journal/nand/fctl/era.o`) | `cfg_*`(算应用) |
|
||||
| UI | `lv_*` `ui_*`(含 `ui_net_sink`、`ui_task`) | — |
|
||||
| RTOS 堆 | `heap_4.o`(CCM)——配额 = `configTOTAL_HEAP_SIZE` 本身 | 内核对象(`tasks/queue/timers/...` 归"其它") |
|
||||
| 应用 | `App/` 其余:采集 `adc_*`、保护 `prot_*`、相量 `phasor/sample_ring`、配置 `cfg_*`、日志 `sys_/dbg_/console_log`、对时 `time_sync*`、`sys_clock`、`task_watchdog`、`sha256`、`tpafe*`、`sd2506`、`default_task` 等 | — |
|
||||
| 其它 | 内核对象、HAL、CMSIS、启动文件、C 库、链接器填充 | — |
|
||||
| 应用 | `App/` 其余——**前缀清单以 `scripts/ram_baseline.py` 的 `subsystem_of()` 为准**:`adc_*` `tpafe*` `sd2506` `prot_*` `protect_task` `phasor` `sample_ring` `wave_*` `meas` `freq` `run_par` `iec104*` `soe_*` `cfg_*` `sys_*` `dbg_*` `console_log` `time_sync*` `sys_clock` `task_watchdog` `sha256` `default_task` `test_task` `task_prof` `boot_stats` `vol_idle_sync` | `App/task/ui_task*`(首匹配算 UI) |
|
||||
| 其它 | 内核对象、HAL、CMSIS、启动文件、C 库、链接器填充——**不含 `App/` 任何对象**:漏登记会被 `--gate` 的兜底 WARN 逐条点名(2026-10-29 加) | — |
|
||||
|
||||
**现状判读(2026-10-12 建档)**:
|
||||
|
||||
@@ -62,9 +62,12 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名
|
||||
**这条 ⚠ 会长期挂着**,判读时按已知挂账处理,别每轮当新问题追;重开条件 → `RAM使用分析与优化方案.md` §4。
|
||||
- **RTOS 堆 = 配额本身**:56 KB 即 `configTOTAL_HEAP_SIZE`(2026-10-12 B5 由 48 KB 扩容,见 §6;
|
||||
`heap_4.o` 另含 32 B 元数据,不计超);**改堆 = 改配额**,必须先过本文件 §6 登记。
|
||||
- **应用 ⚠ 小幅超**:8 KB 为报告规划值;2026-10-10 栈整改把 3 处 `FIL` 从栈搬进 BSS
|
||||
- **应用 ⚠ 超配额**(现值见 §1 受管区):8 KB 为报告规划值;2026-10-10 栈整改把 3 处 `FIL` 从栈搬进 BSS
|
||||
(静态 +4,128 B,见 `docs/内存管理/RAM使用分析与优化方案.md` §4"2026-10-10 复测")——
|
||||
属已评审的"以空间换栈安全",差额在此如实体现。
|
||||
属已评审的"以空间换栈安全";2026-10-29 故障录波落盘道又把 `default_task.o` 推高 1,204 B。
|
||||
**同日修正归集口径**:`subsystem_of()` 此前没有 `wave_` `iec104*` `soe_` `meas` `freq` `run_par`
|
||||
等前缀,12 个 `App/` 对象 9,445 B 被静默算进「其它」(当时「应用」行只有 16,888 B,看着离配额不远);
|
||||
补齐后本行才真含录波环 `wave_ring.o`(约 5.3 KB)。per-file 全貌 → `docs/内存管理/内存内容分配策略.md` §6。
|
||||
|
||||
## 3. CCM 分配台账(谁能用 CCM)
|
||||
|
||||
@@ -96,3 +99,5 @@ UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名
|
||||
| 2026-10-12 | 建档:三件套落地(本台账 + `scripts/ram_baseline.py` + `scripts/ram_baseline.tsv` + pre-commit `mem-budget` 钩子) | 登记簿 N-27 前半 |
|
||||
| 2026-10-12 | **RTOS 堆配额 48 KB → 56 KB**(`QUOTAS` 同步) | B5 protectTask 立项:iec104Task(2.5KB)+protectTask(2KB)+TCB 入堆后 48KB 池 min_ever 预估跌破 3,072 B 告警线 → `.ioc` 语义取数与有效性参数 `configTOTAL_HEAP_SIZE` 49152→57344(CCM 水位 75.3%→87.9%,仍 <92% 门禁;CCM 空闲降至 ≈8 KB)。连带影响见 §3 迁 CCM 后半链注记 |
|
||||
| 2026-10-12 | **任务栈 +1536 B(堆内用量,配额 56 KB 不变)**:defaultTask 1024→1280 words(+1024 B)、timeSyncTask 512→640 words(+512 B);产品任务栈合计 37,632→39,168 B | 登记簿 N-33 栈收口:B7 定值持久化把 defaultTask 静态链推到 3016 B(余 1080 < 30% 线)、timeSyncTask 链 1264 B(余 784 < ISR 底线 1024);B6 台架背书 min_ever=10000 → 抬后实测 `heap free=16400 / min_ever=8464`(与预算 −1536 完全吻合,>3072 告警线);`stack_report` 9 任务 0 FAIL + `@test` PASS,见 `RAM使用分析与优化方案.md` §1.4 |
|
||||
| 2026-10-29 | **故障录波数据面入册**:新增对象 `wave_ring.o` 5,300 B(ZI 5,256 + RW 44,落 SRAM1、CCM 增量 0);`default_task.o` 静态 2,736 → 3,940 B(+1,204 B,落盘道 `FIL` 528 + 分片缓冲 256 + 段头副本 136 + 段路径 17 + 打包上下文与 `DIR`/`FILINFO`);`RW_IRAM1` 81,536 → 89,936 B(62.2 % → 68.6 %,仍远低于 92 % 门禁);`RW_CCM` 三档恒 57,376 B | 决定人 lead。实测数字出自 lead 的权威构建(本工作树 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`,0 错误 0 警告,`scripts/ram_baseline.py --update` 同批入 tsv);预估约 5.9 KB vs 实测数据面 +6,760 B 的差额构成与三档对照见 `docs/内存管理/内存内容分配策略.md` §6;**未抬栈**(`scripts/stack_report.py` 九任务全 PASS),判据出处 `docs/子系统设计/故障录波设计说明.md` §7 |
|
||||
| 2026-10-29 | **归集口径修正(非配额改动)**:`subsystem_of()` 补 `wave_` `iec104*` `soe_*` `meas` `freq` `run_par` `protect_task` `task_prof` `boot_stats` `vol_idle_sync` 等前缀——12 个 `App/` 对象 9,445 B 此前静默落「其它」;「应用」16,888 → 26,333 B、「其它」15,849 → 6,404 B,区域水位与 92% 红线均未变。同脚本加兜底:`App/**/*.c` 的对象落进「其它」时 `--gate` 逐条 WARN(不拦) | 配额表看不见录波/SOE 支路;口径依据本文件 §2「应用 = `App/` 其余」 |
|
||||
|
||||
+21
-3
@@ -110,17 +110,35 @@ CCM 64KB 只能容纳 {FreeRTOS 堆 48KB} 与 {LVGL 集合 ~52.5KB} 之一(两
|
||||
触发条件:测试构建 SRAM 余量不足,**且**已耗尽 `RAM使用分析与优化方案.md` 的帧副本去重(−6.4~9.6KB)与 LVGL 池缩减等低代价手段。
|
||||
流程:改 scatter → 走 §7 完整验证阶梯 → 同步更新本文档、`AGENTS.md`、`.sct` 头注释。在此之前,方案 A 是唯一有效布局。
|
||||
|
||||
## 6. 当前容量台账(2026-10-12,产品构建;2026-09-21 最坏构建=存储套件的历史基线见 `docs/内存管理/RAM预算台账.md` 趋势)
|
||||
## 6. 当前容量台账(2026-10-29,产品构建 task/wave-record 分支终值;2026-09-21 最坏构建=存储套件的历史基线见 `docs/内存管理/RAM预算台账.md` 趋势)
|
||||
|
||||
| 区域 | 总量 | 已用 | 空闲 | 驻留内容 |
|
||||
|---|---|---|---|---|
|
||||
| RW_IRAM1(SRAM) | 131,072B (128KB) | 95,816B (~93.6KB, 73.1%) | 35,256B (~34.4KB) | 全部静态(除堆),含 LVGL 集合 53,805B |
|
||||
| RW_CCM | 65,536B (64KB) | 57,376B (56KB, 87.9%) | 8,160B (~8KB) | **仅** `heap_4.o`(ucHeap 56KB,2026-10-12 B5 扩容) |
|
||||
| RW_IRAM1(SRAM) | 131,072B (128KB) | 89,936B (87.83KB, 68.6%) | 41,136B (~40.2KB) | 全部静态(除堆),含录波环 `wave_ring.o` 5,300B 与 LVGL 池 16,384B(`lv_mem_core_builtin.o` 实测) |
|
||||
| RW_CCM | 65,280B(64KB 减 256B `UNINIT_CCM` 尾,与 `.sct` 一致) | 57,376B (56.03KB, 87.9%) | 7,904B | **仅** `heap_4.o`(ucHeap 56KB,2026-10-12 B5 扩容);256B 尾区现用 48B |
|
||||
|
||||
数字身份:`RW_IRAM1` / `RW_CCM` / `ER_IROM1` 三行取自 lead 2026-10-29 权威构建(本工作树 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`,0 错误 0 警告,`scripts/ram_baseline.py --update` 同批入 tsv)。受管快照区在 `docs/内存管理/RAM使用分析与优化方案.md`,子系统配额表在 `docs/内存管理/RAM预算台账.md`,两处由脚本渲染、本卡不手改。
|
||||
|
||||
LVGL 集合明细(均在 SRAM):LVGL 池 16,384B(`lv_mem_core_builtin.o`,`LV_MEM_ADR=0` 内部静态)+ `g_ui_draw_buf` 3,232B(3200+32 保护带)+ `g_ui_fbuf` 3,200B + `g_tx` 3,209B + `g_lcdmem_data` 3,204B。
|
||||
|
||||
产品构建比套件构建少 testTask/测试缓冲(约 40KB 级),SRAM 余量更大。数字以 map 实测为准,布局变更后必须重测并更新本表。
|
||||
|
||||
录波支路静态(2026-10-29 入册,R6 要求;**全部落 RW_IRAM1、无 section 属性,CCM 增量 0**):
|
||||
|
||||
| 归属对象 | 增量 | 构成 |
|
||||
|---|---|---|
|
||||
| `wave_ring.o`(`App/algo/wave_ring.c`) | 5,300 B(ZI 5,256 + RW 44) | 录波环 int16×320 点×8 路 = 5,120 B 主体 + 已封存段头副本 136 B + 状态/索引/观测计数;map 比代码侧手算多 2 B,是链接器对齐 |
|
||||
| `default_task.o`(落盘道,函数内 `static`) | +1,204 B(2,736 → 3,940,同 tsv 的 RW+ZI 口径) | `FIL` 528 B(`FF_FS_TINY=0`,刻意不上栈)+ 分片搬运缓冲 256 B + 段头副本 136 B + 段路径 17 B + 打包上下文/`DIR`/`FILINFO`/状态字节约 267 B(对象级增量,未逐项拆分实测) |
|
||||
| `adc_task.o` 及其余数据面 | 约 256 B | 8 路语义原码暂存与封存瞬间交棒结构(`App/task/adc_task.c`);与上两行相加 = 数据面实测 +6,760 B |
|
||||
| UI 道:`ui_page_wave_play.o` 92 B、`ui_page_wave_manual.o` 28 B、`ui_task.o`、`ui_data.o` | 区级合计 +1,640 B | 两个新页面模块按 map 计 120 B;`ui_task.o`/`ui_data.o` 的增量拆分需要本波之前的 per-file 快照,本卡没有 → **只记区级增量,不猜分项** |
|
||||
|
||||
- **三档对照(SRAM1)**:81,536 B / 62.2 %(`scripts/ram_baseline.tsv` 2026-10-28 行)→ 88,296 B(录波数据面终值)→ **89,936 B / 68.6 %**(含 UI 道终值),累计 **+8,400 B**;`RW_CCM` 三档恒 57,376 B —— 设计文档 `docs/子系统设计/故障录波设计说明.md` §7 的「不进 CCM」落位达成,也没有挤动 CCM。
|
||||
- **预估 vs 实测都要留**:设计文档 §7 原估静态增量约 5.9 KB(环 5,120 + 索引状态 32 + 双缓冲 512 + 读窗 256),实测数据面 +6,760 B。差额构成:`FIL` 528 B、`DIR`/`FILINFO`、段头副本 136 B、打包上下文(内含一份 136 B 段头)原估未记账;反向一项——落盘实际是**单缓冲 256 B**,不是预估的 512 B 双缓冲。只写实测会让下一次估算照着 6.7 KB 走、看不到原估漏了哪几项。
|
||||
- **Flash**:`ER_IROM1` 264,580 → 270,248(数据面 +5,668 B)→ 272,300(UI 道 +2,052 B)→ 272,360(落盘判废复检 +60 B)→ **272,336 B**(cppcheck 返工:把变量声明搬进最小作用域,纯搬声明 −24 B),累计 **+7,756 B**、占 26.0 %;`Total ROM` 274,308 B(26.2 %)、`Code` 243,552 B。RAM 三项(RW 1,972 / ZI 145,388 / RW_IRAM1 89,936 B / 68.6 %)与 CCM 57,376 B / 87.9 % 均未变。
|
||||
- **栈:9 任务全 PASS、未抬栈**(`scripts/stack_report.py` 静态最坏链)——adcTask 1,424 B(余 1,136 B / 44 %)、defaultTask 3,040 B 不变、protectTask 880 B、uiTask 1,336 B(余 15,048 B / 91 %)。R6 三处同步里 `AGENTS.md` 栈表这一处的结论是**核对过、数值未变**。
|
||||
- **为什么比上一版低**:本表 2026-10-12 版记 95,816 B / 73.1 %,高于今天的 89,936 B。原因是此后 LVGL 内部池由 40 KB 缩到 16 KB(`docs/内存管理/RAM预算台账.md` §3「缩池 ✅ 2026-10-15」行;`scripts/ram_baseline.tsv` 2026-10-12 与 2026-10-24 两行的 `top_rwzi` 列可见 `lv_mem_core_builtin.o` 40960 → 16384)——**不是本波回收了什么**。
|
||||
|
||||
- **子系统归集的坑(本卡实测 → 2026-10-29 已修)**:`scripts/ram_baseline.py` 的 `subsystem_of()` 按对象名前缀首匹配,当时表里没有 `wave_` 前缀 → 录波环 `wave_ring.o` 5,300 B 落进 **「其它」(当时 15,853 B,参考值、无配额)**,配额 8 KB 的「应用」行看不见它;落盘道 `default_task.o`(+1,204 B)与 `adc_task.o` 才计入「应用」(当时现值 16,888 B,本波前该行已在配额外,成因见 `docs/内存管理/RAM预算台账.md` §2 的应用条目)。**同日后续**:补齐 12 个 `App/` 对象(9,445 B)的前缀,并加兜底——`App/**/*.c` 编出的对象只要落进「其它」,`python scripts/ram_baseline.py --gate` 就逐条 WARN 点名(软提醒不拦)。结论仍然有效:**子系统配额行不是录波预算的判据**,要按本节 per-file 台账读;差别只在「应用」行现在真含录波支路,两个口径不再互相看不见。
|
||||
## 7. 布局变更验证阶梯(强制执行)
|
||||
|
||||
任何 scatter / CCM / 堆大小变更后,依次通过以下全部关卡方可认定生效:
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
折算层 2026-10-25 落地;二次值等两点标定置 VALID)。槽位值/品质整体透传,本层
|
||||
**不再自行判新旧**(快照 `seq` 仅供 COV 去重)。
|
||||
- 品质映射:快照 `MEAS_QDS_*` 低字节与线上 QDS 同值直传;**CLIPPED → IV**(「削顶通道
|
||||
恒 IV」口径不变、改由品质驱动,docs/任务规划/数据可信.md §8 风险表末行的单测要求);
|
||||
恒 IV」口径不变、改由品质驱动,docs/任务规划/数据可信.md §8 风险表「削顶通道被当成可测」行的单测要求);
|
||||
快照从未发布(开机 <200 ms)→ IV|NT;判定顺序 IV 优先于 NT。
|
||||
- 死区(104 改读快照):从「越死区即报」改为**连续 2 个新测量点越限才确认**——板端测量点 =
|
||||
快照换帧(100 ms 轮询 vs 200 ms 快照按 `seq` 去重,防同一帧把单点噪声重复计满 2 点;
|
||||
|
||||
@@ -481,9 +481,3 @@ D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏
|
||||
| `tx_timeout` 读口接线(STALL 证据行加字段) | §13-O2 | 涉 `App/util/task_watchdog.c` 与被解析证据行的台架判据,非本卡写域;维持 O2“后续项” |
|
||||
| 未注册期裸发互斥 / `printf` 遗留开销 | §13-O1/O3 | 现状 = 纪律已落地,开机日志密度上升再议 |
|
||||
| 日志静默观察项 | 登记簿曾登记(已销项) | 见 A.3:V5 复跑双零后销项(归 lead) |
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
+67
-18
@@ -1,8 +1,8 @@
|
||||
# 故障录波设计说明(1 kHz · 8 路原始码值 · 现场单通道回放)
|
||||
|
||||
> **模块名称**:故障录波——采样源沿用 TIM3 1 kHz 节拍,专用录波环(拟新增 `App/algo/wave_ring.c`)、触发、落盘(defaultTask 是文件系统唯一写者)、取回(FTP 21 端口)、现场回放(UI 单通道页)。
|
||||
> **模块名称**:故障录波——采样源沿用 TIM3 1 kHz 节拍,专用录波环(`App/algo/wave_ring.c`,已落地)、触发、落盘(defaultTask 是文件系统唯一写者)、取回(FTP 21 端口)、现场回放(UI 单通道页)。
|
||||
> **文档定位**:本波的**设计属主**——口径、通道集、数据形态、触发模型、文件格式、预算与所有权边界、显示契约、二期不返工的结构约束在此定稿。**排期与卡片判据在 `docs/任务规划/故障录波.md`**,本文不写排期。
|
||||
> **状态**:P0 设计定稿(2026-10-29),**代码一行未动**。三条已裁定口径见 §1.2。
|
||||
> **状态**:设计已定稿,**数据面 + 触发与落盘接线 + UI 两个页面(手动录波、录波回放)的代码面已交付(2026-10-29)**——主机单测 113 / 170 / 110 全 rc=0、双端编译 0 错误 0 警告、板端编码器与 PC 侧解码器两份独立实现字节级互验通过。**没做完的只有板级判据那一轮**(注量对表、FTP 拉回、与 `meas:` 和 104 浮点 YC 对表、CRC 不符必须拒收的实测、回放页放一段真波、两个待台架核数值),逐条列在 docs/任务规划/故障录波.md 各卡的板级段。三条已裁定口径见 §1.2。
|
||||
> **相关文档**:
|
||||
> `docs/子系统设计/ADC采样节拍设计说明.md`(节拍与时序账的属主;本文 §3 的数据源就是它)
|
||||
> `docs/硬件平台/ADC通道映射说明.md`(网名 `AINn` 不等于板级 `ADCn`,也不等于转换器通道号;链路分段与削顶归属)
|
||||
@@ -106,16 +106,19 @@
|
||||
|
||||
| 触发 | 来源 | 本波 |
|
||||
|---|---|---|
|
||||
| **手动** | UI「手动录波」操作页(该叶子早已在菜单里:`ui_menu_tree.txt` 装置维护组,现未绑页) | 做 |
|
||||
| **保护启动** | `App/algo/prot_elm.c` 的 `picked` 上升沿(元件启动),与 `/log/start.log` 同源,避免两处各自判定 | 做(复用事件点,不新增判据) |
|
||||
| **手动** | UI「手动录波」操作页(叶子在菜单装置维护组,页面模块 `Drivers/BSP/UI/ui_page_wave_manual.c`,2026-10-29 已绑页;确认键在 `App/task/ui_task.c`:582 行调 `wave_ring_request()`,走手动来路、元件号填 0) | 做(已落地) |
|
||||
| **保护启动** | 落点在 `App/task/protect_task.c` 的 `s_log_record_edge()`(函数体自 158 行起,picked 上升沿分支 170 到 177 行,实际调用在 176 行:`wave_ring_request(WAVE_TRIG_PROTECT, 最低位上升元件)`),与 SYSLOG_START 同一条边沿、同一事件点,避免两处各自判定。**刻意不落在 `App/algo/prot_elm.c`**:判据核被三套主机单测源码级纳入编译,往它加录波依赖会一次全破——这条就是「判据核零改动」的前提。多元件同拍启动只录一段(环不重入,后续请求由环计 ovf,不算失败) | 做(复用事件点,不新增判据) |
|
||||
| 定周期自抓 | `DTU_BENCH` 构建下每 N 秒强制一段,供信号发生器对表 | 做,**只在 bench 构建**,产品构建无此路径 |
|
||||
| **突变量启动** | 需要增量化 `Δi` 类算法与定值 | 不做,归二期(`docs/算法/继电保护功能复现规划.md` 的「故障启动(波形启动)」行) |
|
||||
|
||||
### 4.2 段长与冻结语义(RAM 就靠这一条省下来)
|
||||
|
||||
- 一段 = **预触发 8 周波 + 后触发 8 周波 = 320 点/路**(1 kHz 下 320 ms);
|
||||
- **段内点序**(两份实现共同遵守,PC 与回放页都按这条):触发那一拍的样本落在**段内点 159**(预触发 160 点的最后一点),后触发第一拍是**点 160**,两半不重叠,故障时刻就是这条分界。因此段内点 0 相对工频周期边界有任意偏移(触发瞬间在周期内任意落点),**段字节里没有绝对时间参考**——能核对的是相对相位与极性,绝对相位只能以段内点 0 为参考报出(§10 板级判据据此不比绝对相位);
|
||||
- **触发即冻结环**:停止推进录波写指针,defaultTask 直接从已冻结的环落盘,落完解冻;
|
||||
- **冻结期间丢的是录波样本,不是采集**——`sample_ring`、相量、保护判据照常跑(这就是 §2.1 第 3 条要求解耦的原因),冻结不拖慢保护动作,也不让 104 断数;
|
||||
- **解冻的唯一入口是落盘方回执**(`wave_ring_write_done(result)`,只在 defaultTask 上下文调):写成功才推进轮转段号,写失败只计 err 且**下一段重写同一个号**(不留半截文件);冷却期从回执之后才开始计时,回执前新触发一律只加 ovf。「落完解冻」这条原本按时间窗口的假设,实现里改由回执保证,冷却数值只是二次抑制。
|
||||
- 兜底(绝不允许录波把环焊死):封存态滞留超过 `WAVE_SEAL_TIMEOUT_MS`(当前 2000 拍 = 2 s,**待台架核**)就自弃本段、计 err 并解冻——卡在一次落盘上,代价是下一次真故障无波可录。
|
||||
- 代价:一次触发的落盘窗口(约几十 ms)内再来触发只记第一次(冷却期),**不做多段排队**——那是二期「6 段 / RAM 16 个」的形态。
|
||||
|
||||
```mermaid
|
||||
@@ -150,13 +153,14 @@ flowchart TD
|
||||
| **uiTask**(回放页) | 只读窗口取段数据 → 自绘(与 `App/log/soe_store.c` 读侧同构) | 不写文件 |
|
||||
| **netTask / lftpd** | 把 /wave 暴露给 FTP 取回 | 不在 netTask 里读录波环 |
|
||||
|
||||
### 5.2 三条已知陷阱,本波必须绕开
|
||||
### 5.2 四条已知陷阱,本波必须绕开
|
||||
|
||||
| 陷阱 | 事实出处 | 本波处置 | |
|
||||
| ------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------- |
|
||||
| **取窗竞态** | `App/algo/sample_ring.c` 的 `sample_ring_window()` 没有临界区(读 `s_head` 后逐点拷),现在只有 adcTask 同任务调用才安全 | 录波**不复用它**:adcTask 在自己的节拍里取「本拍帧」压专用环;跨任务只传递「已封存」标志,杜绝跨任务读活缓冲 | |
|
||||
| **FatFs 追加写** | 该移植 `FA_OPEN_APPEND` 不含 `FA_WRITE`,且 `f_write` 簇分配失败时返回 `FR_OK` 加短写、无错误码(`docs/架构设计/STM32F4-Base存储架构说明.md` §10.1) | 打开一律 `FA_WRITE | FA_OPEN_APPEND`;**判成功必须比对写回字节数**,短写按失败处理(丢段 + 日志),绝不当成功记账 |
|
||||
| **只能 8.3 短名** | 出处同一张存储架构说明的短名限制:`FF_USE_LFN=0`,不得以点开头、不得长名 | 文件名 = WAVE 加两位十进制再加 .BIN,共 100 个名字轮转覆盖;目录 /wave(与 /log 平级,FTP 根下可见) | |
|
||||
| 陷阱 | 事实出处 | 本波处置 |
|
||||
|---|---|---|
|
||||
| **取窗竞态** | `App/algo/sample_ring.c` 的 `sample_ring_window()` 没有临界区(读 `s_head` 后逐点拷),现在只有 adcTask 同任务调用才安全 | 录波**不复用它**:adcTask 在自己的节拍里取「本拍帧」压专用环;跨任务只传递「已封存」标志,杜绝跨任务读活缓冲 |
|
||||
| **FatFs 追加写** | 该移植 `FA_OPEN_APPEND` 不含 `FA_WRITE`,且 `f_write` 簇分配失败时返回 `FR_OK` 加短写、无错误码(`docs/架构设计/STM32F4-Base存储架构说明.md` §10.1) | 打开一律 `FA_WRITE\|FA_OPEN_APPEND`;**判成功必须比对写回字节数**,短写按失败处理(丢段 + 日志),绝不当成功记账 |
|
||||
| **只能 8.3 短名** | 出处同一张存储架构说明的短名限制:`FF_USE_LFN=0`,不得以点开头、不得长名 | 文件名 = WAVE 加两位十进制再加 .BIN,共 100 个名字轮转覆盖;目录 /wave(与 /log 平级,FTP 根下可见) |
|
||||
| **落盘途中环被解冻、甚至已换段**(实现期复核新增第四条,2026-10-29) | adcTask 优先级高于 defaultTask:搬段窗口里 adcTask 可能正好走到 SEALED 滞留 2 s 自弃,甚至已经封出下一段。**数据区 CRC 是对「本道实际读到的字节」算的**,半新半旧的段照样自洽——长度等式照过、两个 CRC 照对,文件骗得过任何读者,也骗过我们自己 | 写完关档后**复检环侧身份**:`wave_ring_sealed()` 仍为 1,且 `wave_ring_get_stat()` 的 `last_t0_ms` 与 `last_seq` 跟本道开始搬时那份段头一致;不符即走既有失败路——unlink 本道开过的文件 + `write_done(-1)`(此时多半回 `E_STATE`,环侧 `err` 已由自弃计过,不重复补账)+ 段号不推进(下一段同号重写覆盖)+ 单条 `[ERR] … seg drop: ring abandoned` 留痕。落点 `App/task/default_task.c`:786 到 796 行。**当前只有代码与单测面,板级未测**;可观察项见 §10 板级行 |
|
||||
|
||||

|
||||
|
||||
@@ -193,16 +197,44 @@ flowchart TD
|
||||
| 14 | t0_ms | 4 | 段首相对上电时刻;配合墙钟还原绝对时刻 |
|
||||
| 18 | wall | 4 | 对时后的 epoch 秒(未对时为 0) |
|
||||
| 22 | eng | 1 | 封存瞬间的折算档位:0 引脚域、1 二次值、2 一次值 |
|
||||
| 23 | flags_seq | 1 | 有效性位图(引 `adc_cal_flags()` 口径) |
|
||||
| 24 | clip_mask | 2 | 削顶归属(V0.1 恒 0xE6,见 `docs/任务规划/数据可信.md` §2.7) |
|
||||
| 23 | flags_seq | 1 | 每路 VALID 压成的位图:bit s = 语义通道 s 的 `ADC_CAL_F_VALID`(电气 8 路恰好占满 1 字节;TEMP 不参与标定,若要带 TEMP 连本行与下一行一起升版本) |
|
||||
| 24 | clip_mask | 2 | 削顶归属位图:bit s = 语义通道 s,与 `App/algo/meas.h` 同名参数同口径(封存瞬间 `ADC_CAL_F_CLIPPED` 快照,V0.1 恒 0xE6 = UB/UC/IB/IC/IN,见 `docs/任务规划/数据可信.md` §2.7)。⚠ **这个位图的本体是「链路可信性标记(按来源派生)」,不是「本段实测饱和标记」**:置位条件一次样本都不看(`App/algo/adc_cal.c` 291 到 296 行,该路 ROM 系数属片内 ADC 侧即置位);「这一段有没有真钉轨」走另一条链——`App/algo/adc_health.c` 的 `adc_health_rail_watch()`(280 行起)与 `adc_health_rail_sticky()`,门限按满量程百分比由 `adc_cal_rail_ths()` 给、须连续 `ADC_RAIL_N_RUN` 拍。V0.2(8 路全接 TPAFE)后本位自动恒 0,届时「与 clip_mask 对表」这条判据退化为只查钉轨实测(§10) |
|
||||
| 26 | frame_seq | 4 | 段首帧序号,可与串口周期行的 frames 对账 |
|
||||
| 30 | 通道表 | 每路 12 | 语义号 1 + 来源转换器 1 + 位宽 1 + 有效位 1 + LSB(µV) 4 + k 2(定点)+ b 2(定点) |
|
||||
| — | CRC16(头) | 2 | 与屏板链路同一张 CRC-16/MODBUS 表,不新造 |
|
||||
| 30 | 通道表 | 每路 12 | 语义号 1 + 来源转换器 1 + 位宽 1 + 可用性 1(= `adc_cal_flags(sem)` 低 8 位原值,不自造位义)+ `k_q16` 4(int32,µV/码 × 65536)+ `b_codes` 4(int32,码值)。**没有独立 LSB 字段**,LSB 就是 `k_q16`,见 §6.3 |
|
||||
| — | CRC16(头) | 2 | CRC-16/MODBUS,**低字节在前**(与全文件小端统一);覆盖 `[0, 30+12×ch_count)`,本字段自身紧随其后 |
|
||||
| — | 数据区 | pts_per_ch × ch_count × 2 | **按通道分列**:一路连续 320 点,单通道回放只读那一路 |
|
||||
| — | CRC16(数据区) | 2 | 长度为 0 或 CRC 不符 → 整段作废,页面必须显示「该段不可读」 |
|
||||
| — | CRC16(数据区) | 2 | 同上字节序;覆盖整个数据区、自身在文件尾(8 路 320 点整段 5250 B);长度为 0、任一 CRC 不符、或 `ver` 与魔数第三字节不等 → 整段作废,页面必须显示「该段不可读」 |
|
||||
|
||||
**段名轮转的呈现语义**(回放页与 PC 侧取「哪一段最新」都按这条):段表按**数值升序**呈现,开屏落点取**数值最大的段号**。回绕(99 之后回到 00)之后「数值最大」不再等于「时间最新」——要真时间序必须逐段读段头 `frame_seq` / `t0_ms`,代价是 100 次开文件,**本波不做**。所以页顶把段号本身显示出来(§8.1 顶部行),让读者自己看见这是第几号,不去伪造一个「最新」的假象。
|
||||
|
||||
### 6.3 字段口径裁定(两份独立实现的共同依据)
|
||||
|
||||
本波段格式有**两份独立实现**:板端编码器 `App/algo/wave_store.c` 与 PC 解码器 `test/host/wave_read.py`,两份必须逐字节同解,所以动工前把 §6.2 的含糊处一次钉死(PC 道九问的逐条答复):
|
||||
|
||||
| 问题 | 裁定 |
|
||||
|---|---|
|
||||
| CRC 字节序 | **低字节在前**,与全文件小端一致。原稿「与屏板链路同一张 CRC-16/MODBUS 表」不成立:全仓**没有**可复用的 CRC-16 表——`App/util/README.md` 记 `crc.c/h` 已于 2026-10-02 删除,屏板链路 `Drivers/BSP/UI/ui_net_sink.c` 的 `remo_crc()` 是单字节异或、不是 CRC-16。本波在 `wave_store.c` 内自带**无表位运算** CRC-16/MODBUS(0x8005 反射、初值 0xFFFF、异或输出 0),单测锚点:串 123456789 的 CRC = 0x4B37 |
|
||||
| CRC 覆盖范围 | 头 CRC 覆盖 `[0, 30+12×ch_count)`、自身在 `30+12×ch_count`;数据区自 `32+12×ch_count` 起;数据 CRC 覆盖整个数据区、自身在文件尾。8 路 320 点整段 5250 B |
|
||||
| LSB 从哪来 | **不设独立 LSB 字段**:通道表的 `k_q16` 就是 LSB(µV/码 × 65536),与 `App/algo/adc_cal.h` 的 `ADC_CAL_K_Q16_TPAFE 10000000` / `ADC_CAL_K_Q16_STM32 52800000` 同口径,Q16 下无舍入(选型依据 `docs/任务规划/数据可信.md` §4.1)。PC 侧 `µV = k_q16 / 65536`,不许再乘一遍 |
|
||||
| k / b 宽度与角色 | `k_q16` int32、`b_codes` int32 = 封存瞬间该路生效的两个标定量。`adc_cal_apply()` 的算式是 `eng = (code + b) × k × 1e-6`,文件里存的正是等号右边两个数加原始 code,PC 照此重算。**必须取 int32 原值**,不得经 `adc_cal_k_uv()` 的 float 通道回算(float 在 5.28e7 量级掉尾数)→ 接线道在 `App/algo/adc_cal.[ch]` 补一个 int32 直读入口 `adc_cal_k_q16()`(与 `adc_cal_k_uv()` 孪生);eng=0 时两者是 ROM 默认值,PC 折出来的单位是**引脚域 µV** 而非二次值——单位一律以头里的 `eng` 为准,PC 不许按当前固件口径猜 |
|
||||
| `flags_seq` 位义 | bit s = 语义通道 s 的 `ADC_CAL_F_VALID`;本波 `ch_count` 固定 8 |
|
||||
| 可用性字段真值表 | 两行,实现期按 `App/algo/adc_cal.c` 280 到 298 行核出(VALID 需文件位图与 k 合法带**同时**成立;CLIPPED 纯由来源派生——该路 ROM 系数属片内 ADC 侧即置位,与样本内容无关):① **未标定**(产品构建,两点标定未做)逐路 availability = UA / UN / IA 取 `0x00`,UB / UC / IB / IC / IN 取 `0x02`,`flags_seq` = `0x00`,`clip_mask` = 0xE6;② **已标定**(电气 8 路系数全部写入且落合法带)逐路 = `0x01`(TPAFE 三路)/ `0x03`(片内 ADC 五路,VALID 与 CLIPPED 同时为真),`flags_seq` = `0xFF`,`clip_mask` 仍是 0xE6。**VALID 与 CLIPPED 是两件独立事实**:写侧如实透传 `adc_cal_flags()` 逐路原值(`App/task/adc_task.c` 548 行),读侧不得据任何品质组合判废,PC 与回放页只给软告警(见下面「判废面与软告警面」一行) |
|
||||
| eng 派生式 | `eng` 不是独立事实,是逐路有效位图的派生量:**电气 8 路全 VALID → 二次值(1),缺任意一路 → 引脚域(0)**。真机两处必须同口径,行号一并登记:判据本体 `App/app/ui_data_feed.c` 226 到 235 行的 `feed_eng()`(屏显侧),段头字段填写 `App/task/adc_task.c` 532 到 553 行(其中 552 到 553 行是段头侧的同一派生)。两处分布在**不同任务里现算**,理论上会撞上配置重载的毫秒级沉降窗而不一致——这是允许态,见下一行 |
|
||||
| 判废面与软告警面 | **完整性判废面自始至终只有这些**:精确尺寸等式、`magic` 与 `ver`(含 `ver` 须等于魔数第三字节)、头 CRC 与数据 CRC、表序 `sem` 等于下标、`ch_count` 上限、`pts_per_cycle` / `pts_per_ch` / `pre_pts` 下限、长度为零、文件短读。**品质组合与单位标签永远不进这道门**。本轮新增的只是**软告警面**:`avail` 同时带 VALID 与 CLIPPED 时 PC 在 NOTE 列打「同时为真,两点标定完成后属正常,非故障」;`eng` 与 `flags_seq` 违反上一条派生式时打 WARN。两者都**不改退出码、不影响出图、不拒渲染**。理由:把「单位标签不一致」当数据损坏,等于让一次改标定文件的沉降窗销毁故障证据 |
|
||||
| 通道表序与列序 | 第 i 个通道表项 ↔ 数据区第 i 列,按语义号**升序**:UA=0 UB=1 UC=2 UN=3 IA=4 IB=5 IC=6 IN=7(`App/algo/adc_input.h` 的 `adc_semantic_t`)。PC 必须校验表内语义号等于下标,不等即整段作废。交叉验证:`clip_mask` 恒 0xE6 = 2+4+32+64+128,恰好是 UB/UC/IB/IC/IN 五个语义号位,这份算术就是这个顺序的证据 |
|
||||
| 样本编码 | int16 小端;TPAFE 侧 16 位补码双极性;STM32 侧 0..4095 原样存正数,**不加中点偏置**——V0.1 单极性无偏置是硬件现状,加偏置等于伪造双极性(`docs/任务规划/数据可信.md` §2.7) |
|
||||
| 三个时间字段 | `t0_ms`(上电毫秒)、`wall`(epoch 秒,未对时 0)、`frame_seq`(段首帧序号)均 u32 无符号小端 |
|
||||
|
||||
**本波 TEMP 不录**(`ch_count` = 8):温度非线性未折算,进录波只多占一路字节、不增加可解释信息(`docs/任务规划/数据可信.md` §7 的「温度不参与线性标定」已定案)。
|
||||
|
||||
**为什么按通道分列而不是按帧交错**:回放页只画一路 → 只需连续读那一路;PC 重算全周傅氏也是逐路独立。交错布局两种用法都要跨步长取数,白付代价。
|
||||
|
||||
**实现期纠正记录(三条,只作追溯,不构成规格)**
|
||||
|
||||
1. `flags_seq` 的 V0.1 真值一度被写成 `0x27`:那是把「按来源派生的 CLIPPED」当成了 VALID。按 `App/algo/adc_cal.c` 280 到 298 行,未标定态就是 `0x00`(见上面真值表第一行)。凡引用这个数字的地方一律以真值表为准。
|
||||
2. 一版互验夹具写成 `eng` = 0 而 `flags_seq` = `0xFF`——违反上面的 eng 派生式,是一串**真机写不出来**的字节。已改为按派生式自洽,并在 `test/host/wave_store` 那套单测里加了夹具自洽断言防复发。教训一句话:**互验夹具的资格来自「真机能写出这串字节」**,一份不可能状态的字节只能验证两个读者对幻觉的一致反应,正好掩盖真分歧。
|
||||
3. 顺带钉一条因果,免得后文写反:PC 侧早期一次 `shape` 子命令在 `--strict` 下 rc=1,与品质位**无关**,那是「实测触轨路数 vs 段头 `clip_mask`」对表不符(合成夹具的波形本来就没真触轨)。那条对表是 `docs/任务规划/数据可信.md` §4.6 与本文 §10 板级判据的本职,不是解码器的判废扩展。
|
||||
|
||||
---
|
||||
|
||||
## 7. 预算(先算后写,R6 是硬前置)
|
||||
@@ -216,12 +248,24 @@ flowchart TD
|
||||
| **合计静态增量** | — | **约 5.9 KB** |
|
||||
|
||||
- **大于 4 KB ⇒ 触发 R6**:`docs/内存管理/内存内容分配策略.md` 的 R6 要求「任何新增静态缓冲 >4 KB 必须先核 §6 台账余量,改后同步 §6 数字、.sct 头注释与 AGENTS 栈表」;加大缓冲本身属 **Ask-first**;
|
||||
- **落位 = SRAM1,不进 CCM**:CCM 受管区已 87.9 %、只剩 7,904 B,SRAM1 为 62.2 %(数字出自 `docs/内存管理/RAM预算台账.md` §1 受管区表);录波环不是 DMA 可见对象,没有「CCM 不可达」的约束,但 CCM 也**没有余量**给它;
|
||||
- **落位 = SRAM1,不进 CCM**:CCM 受管区已 87.9 %、只剩 7,904 B,本波开工时 SRAM1 为 62.2 %(数字出自 `docs/内存管理/RAM预算台账.md` §1 受管区表;录波落地后的终值见本节末「实测回填」);录波环不是 DMA 可见对象,没有「CCM 不可达」的约束,但 CCM 也**没有余量**给它;
|
||||
- **UI 子系统配额早已超**(同一张表的子系统行)→ 回放页的描述符与名表仍要在台账记账,别拿「反正已超」当免检;
|
||||
- 前后各跑一次 `scripts/ram_report.py` 与 `scripts/stack_report.py`(defaultTask 多了落盘链、uiTask 多了读窗与自绘);`mem-budget` 钩子是总量红线,`ram_baseline.py --update` 回填趋势;
|
||||
- **Flash**:段格式编解码 + 状态机 + 回放页 + 一条日志行,粗估 3 到 5 KB(Flash 基线在 25 % 量级,不是瓶颈);
|
||||
- **NAND**:一段约 5.2 KB 数据加头。轮转 100 段约 0.5 MB,对 256 MB 单分区是零头——**瓶颈不是容量,是写次数**(§10 红线)。
|
||||
|
||||
**实测回填(2026-10-29 权威构建,0 错误 0 警告;逐对象台账见 `docs/内存管理/内存内容分配策略.md` §6)**
|
||||
|
||||
| 口径 | 上面的预估 | 实测 |
|
||||
|---|---|---|
|
||||
| SRAM1 静态增量 | 约 5.9 KB | 数据面 **+6,760 B**(81,536 → 88,296);UI 道另 +1,640 B;累计 **+8,400 B** → 终值 **89,936 B / 68.6 %** |
|
||||
| CCM | 0(不得进 CCM) | **0**:`RW_CCM` 三档恒 57,376 B / 87.9 %,既没进 CCM 也没挤动它 |
|
||||
| Flash | 3 到 5 KB | `ER_IROM1` 264,580 → 270,248(数据面 +5,668)→ 272,300(UI 道 +2,052)→ 272,360(落盘判废复检 +60)→ **272,336** B(cppcheck 返工纯搬声明,−24),累计 **+7,756 B**、占 26.0 %;`Total ROM` 274,308 B、`Code` 243,552 B |
|
||||
| 任务栈 | 未估 | 九任务全 PASS、**未抬栈**:adcTask 静态最坏链 1,424 B(余 1,136 B / 44 %)、defaultTask 3,040 B(余 2,080 B / 40 %)、uiTask 1,336 B(余 15,048 B / 91 %) |
|
||||
|
||||
- **差额构成(比预估多 860 B,可对账)**:原估漏记 `FIL` 528 B(`FF_FS_TINY=0`,落盘道刻意不上栈)、`DIR`/`FILINFO`、已封存段头副本 136 B、打包上下文(内部又含一份 136 B 段头);反向少记一项——落盘实际是**单缓冲 256 B**,不是预估的 512 B 双缓冲。**预估与实测都留在本节**:只写实测,下一次估算就看不见原估漏在哪。
|
||||
- **回放页读窗不在数据面那 6,760 B 内**:它计入 UI 道的 +1,640 B;两张表不能相加当总量,逐对象拆分见 `docs/内存管理/内存内容分配策略.md` §6。
|
||||
|
||||
## 8. 现场回放显示契约
|
||||
|
||||
### 8.1 一屏画什么
|
||||
@@ -264,8 +308,9 @@ flowchart TD
|
||||
wave: seg=NN armed=1 src=P pts=320 err=N ovf=N drop=N
|
||||
|
||||
- `err` 是落盘失败或 CRC 自弃次数(**字节数不符就算失败**,见 §5.2);`ovf` 是冷却期丢弃的触发数;`drop` 是冻结窗口丢掉的录波点数;
|
||||
- **实际落地形态(逐字)**:打印点在 `App/task/adc_task.c`:782 行,格式串 `"wave: seg=%lu armed=%u src=%s pts=%u err=%lu ovf=%lu drop=%lu"`——**字段名与顺序就是上面的模板**(台架与 PC 脚本按行名 grep,动它就断脚本);`src` 取 M(手动)/ P(保护)/ T(定时自抓)/ `-`(本次上电还没触发过);`armed` 只在 FREEZE 与 SEALED 为 1(COOL 期只挡新触发不录新段,画进 armed 会被台架误读);`seg` 是累计**成功回执**的段数、不是轮转号;`err` = 落盘失败 + SEALED 滞留超时自弃(含 §5.2 第四条的 ring abandoned 判废),由环内计数收口,落盘道不数第二份账。`err` 与 `ovf` 的**已知容差**(lead 裁定 2026-10-29,不修只入档):这两个计数是跨上下文的读-改-自增(`ovf` 由任意上下文的拒绝路径加、`err` 由 adcTask 的滞留自弃与 defaultTask 的落盘失败分别加),本层双端编译、不许引 FreeRTOS / CMSIS 的临界区内在函数,所以并发拒绝或并发失败时**理论上可能少计一次**。定性为**观测计数的已知容差**:只用于趋势与判读,**不作为任何判据的唯一依据**——要坐实「冷却期内确实来了第二次触发」,同时看 `armed` 与那一刻 `/log/start.log` 的时刻。契约原文见 `App/algo/wave_ring.h` 文件头「观测计数的已知容差」段;
|
||||
- 只在原先静默的黑洞处补锚点,别重复已有步骤——每条 DBG 日志约 90 B Flash 加 216 B 栈(`AGENTS.md` 的日志与工具链节);
|
||||
- 该行落地时同步进 `docs/测试说明/ADC前端注入验证流程.md` 的读数出口表。
|
||||
- 该行已同步进 `docs/测试说明/ADC前端注入验证流程.md` 的读数出口表(2026-10-29;同一张表还登记了段文件出口与 PC 侧读段工具)。
|
||||
|
||||
---
|
||||
|
||||
@@ -274,10 +319,13 @@ flowchart TD
|
||||
| 层 | 内容 |
|
||||
|---|---|
|
||||
| 主机侧纯逻辑单测(新增,进 `test/host/`) | ① 环:预触发与后触发点数、冻结与解冻、冷却丢弃计数;② 段格式编解码往返,含 CRC 不符、零长度、字节数不符三条失败路径;③ 触发状态机三条来路(手动 / 保护 / bench)。**环与格式必须是纯逻辑**(不碰寄存器、不碰 RTOS)才能这样测——这是 `App/algo/` 既有分层纪律 |
|
||||
| 板级 | 信号发生器注 1 V / 50 Hz → 手动一段 → FTP 拉回 → PC 脚本量出周波数、幅值、极性、削顶路数与 `clip_mask` 一致;再把 PC 重算的基波有效值与板上 `meas:` 与 104 浮点 YC 对表 |
|
||||
| PC 侧工具的定性 | `test/host/wave_read.py` 是 **bench 工具**(`test/host/` 下的 loose .py,先例 `test/host/ftp_capture.py`),**不是**主机单测套件 → 不建 `run_unit_test.py`、不加 pre-commit 专属钩子(专属钩子默认不补,按 Lead 裁定 2026-10-27);它自带 `--selftest` 就是它的自带回归,跑法与四个子命令见 `docs/测试说明/ADC前端注入验证流程.md` §4 |
|
||||
| 跨工具链分叉(实证,写进来是为了下一个模块别重踩) | 两份录波 .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` 两处显式列表) |
|
||||
| 台账 | R6 三处同步 + `scripts/ram_baseline.py --update` + `mem-numbers` 与 `mem-budget` 钩子 |
|
||||
| 板级(读路径耗时,2026-10-29 补) | 进回放页后**连续切段 + 连续切道**,读 `task_prof` 里 uiTask 的单轮工作耗时峰值与 800 ms 窗的余量;**不要为此新加埋点**(现成读数够用)。判读:峰值贴着窗走就是读路径该再加节流(先想缓存与分次,别去动心跳窗) |
|
||||
| 图与生成物 | 本设计文档的配图生成器受 `generated-fresh` 逐字节幂等约束;脚本与产物同一笔提交 |
|
||||
|
||||
**bench 定时自抓的红线**:定周期自抓在 1 kHz / 8 路下每 2 s 一段,每 5 分钟就是一轮 NAND 写。**长时间挂着它白烧写次数**,而 dhara 的坏块记账是持久代价、普通恢复清不掉(`AGENTS.md` 测试构建节,`docs/测试说明/存储维护与容量恢复.md`)→ 定时模式必须**默认关闭 + 有时长上限 + 启动时打一次告警**,用完立刻回产品构建。
|
||||
@@ -303,6 +351,7 @@ flowchart TD
|
||||
| 削顶五路被读成「系统有零序 / 有谐波」 | 现场误判 | 头里 `clip_mask` + 页面「削」徽标 + 空态不许画直线(§8.1) |
|
||||
| 有人把 U 与 I 两段叠图比相位 | 得出**没有物理意义**的相位差 | 回放不做多路叠加;头里写明来源转换器;与 `docs/任务规划/数据可信.md` §9 的 V0.1 禁止行同源 |
|
||||
| 落盘把 defaultTask 拖住 | 日志与配置引擎变慢(defaultTask 是两者唯一写者) | 分次写(256 B 双缓冲)、一次心跳最多一段、`f_write` 前后有耗时观测;不做整段驻留再写 |
|
||||
| **回放读路径把 uiTask 拖住**(2026-10-29 lead 评审补,此前风险表只有写侧没有读侧) | 一次渲染的实码 I/O(`App/task/ui_task.c` 376 到 398 行):段表刷新 1 次 `f_opendir` + 至多 100 次 `f_readdir`;未缓存段的段头装载 = 1 次 `f_open` + 1 次 128 B 头读 + **20 次 256 B 数据区读并边读边算数据 CRC** + 尾 2 B 一次 = **最坏 22 次小读**;已验过则只剩窗装载 = 1 次 `f_open` + 1 次 `f_lseek` + 1 次 80 B 窗读 + 40 点解码与峰值扫描(**一屏只碰一路通道一个窗,不是 8 路 320 点**)。uiTask 在 IWDG `required_mask` 内、窗 800 ms(口径同 `docs/子系统设计/看门狗设计说明.md` §11.3 的解耦心跳)→ **现象先是 LVGL 停摆与远程推流缺席,不是复位**(心跳与画屏解耦) | 卷锁 try 非阻塞(拿不到就显示「存储忙」,不等)+ 256 B 分次读 + 已验段结果缓存(同段同道不重复验)+ 只在首次进页 / 切段 / 切道时读。**单轮耗时上界未实测**:不新加埋点,用现成的 `task_prof` 读数(板级动作见 §10) |
|
||||
| 触发风暴 + 轮转覆盖 | 现场唯一一段有效波形被后到的抖动冲掉 | 冷却期 + `ovf` 计数;「最新段号」进快照;分段优先级属二期 |
|
||||
| 定时自抓忘关 | 白烧 NAND 写次数(持久代价) | 默认关 + 上限 + 告警(§10 红线) |
|
||||
| 标定落地后老文件被解释错 | 同一个系数字段值变了含义漂移 | 头里存**封存瞬间**的 `eng` 与系数快照;PC 脚本按头解释,绝不按当前固件口径解释 |
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
- **菜单树已含目标叶子**:`ui_menu_tree.txt` "记录查询"下已有 **自检记录 = node 39、运行记录 = node 40**(当前为空白终点框,无内容页映射);无需改树。
|
||||
- **密码页** `ui_page_password.c`:3 位数字框 + ↑↓←→/ENTER;输错仅清空重输(R1 缺口)。
|
||||
- **按键流**:物理/远端键 → LVGL keypad → 默认输入组 → 焦点对象事件回调(菜单容器 `menu_key_cb` / 内容页容器 `cp_key_cb`)——空闲检测可在此统一挂钩。
|
||||
- **字体**:子集位图字体 `ui_font_cn_12`,字符集 `ui_cn_chars.txt`(当前 218 汉字/符号 + ASCII 0x20~0x7E,共 313 字形;2026-10-28 实测。122/217 是 2026-09-14 的历史快照,见 §6.1)。
|
||||
- **字体**:子集位图字体 `ui_font_cn_12`,字符集 `ui_cn_chars.txt`(当前 224 汉字/符号 + ASCII 0x20~0x7E,共 319 字形;2026-10-29 实测。122/217 是 2026-09-14 的历史快照,见 §6.1)。
|
||||
|
||||
### 2.2 存储与时间
|
||||
|
||||
|
||||
+5
-8
@@ -264,7 +264,7 @@
|
||||
|
||||
**章节引用(D 类)另有的跳过**:昵称式引用(「内存方案 §2.1」)、非紧邻写法、变更记录小节内的历史指称、外部手册编号(RM0090 等)、目标文档本身不用数字编号标题。
|
||||
|
||||
> 「变更记录小节内的历史指称」这条跳过规则为何还留着:2026-10-28 起新文档不再设该节(见 §5.1),它现在只为**保留变更记录的存量 5 篇**与 `scripts/archive/` 下的归档文档服务——历史行里的编号是「当时那个编号」,既不回改也不校验。
|
||||
> 「变更记录小节内的历史指称」这条跳过规则为何还留着:2026-10-28 起新文档不再设该节(见 §5.1),它现在只为**保留变更记录的 3 篇真台账**与 `scripts/archive/` 下的归档文档服务——历史行里的编号是「当时那个编号」,既不回改也不校验。
|
||||
|
||||
> ⚠️ 状态词按**就近**(±40 字符)判定,不是整行——否则文件名本身含「不存在」「删除」等字样时会误豁免同行的其它真实引用(2026-09-25 实测修正)。
|
||||
|
||||
@@ -318,7 +318,10 @@
|
||||
| **一级标题** | 文档首行为 `# 标题`,标题即主题,不写"文档""说明"等冗余后缀 |
|
||||
| **开篇引言** | 可选 `>` 引用块,写明**适用对象 / 阅读前提 / 相关文档** |
|
||||
| **章节编号** | 多章节文档用 `## 1.` `### 1.1` 编号,便于交叉引用(`§3.2`) |
|
||||
| **变更记录** | **2026-10-28 裁定:新文档不再设「变更记录」节**——文档只写现状、改了什么是正文说了算,改动过程查 `git log`。**例外**(整表保留、被别处当台账或唯一证据出处引用的 5 篇):`docs/子系统设计/看门狗设计说明.md`、`docs/子系统设计/配置文件设计说明.md`、`docs/内存管理/RAM预算台账.md`、`docs/UI设计/内容页架构.md`、`docs/环境与部署/本地工具路径环境变量方案.md` |
|
||||
| **变更记录** | **一律不设该节**(用户裁定 2026-10-28 起新文档、2026-10-29 起老文档也清完;标题变体 `修订记录` / 带编号的 `## 6. 变更记录` / `## 九、变更记录` 同规则)——文档只写现状、改了什么是正文说了算,改动过程查 `git log`。**例外只有 5 篇**(整表保留:表里是真台账、且被别处当唯一证据出处引用):`docs/子系统设计/看门狗设计说明.md`、`docs/子系统设计/配置文件设计说明.md`、`docs/内存管理/RAM预算台账.md`、`docs/UI设计/内容页架构.md`、`docs/环境与部署/本地工具路径环境变量方案.md`;`scripts/archive/` 下的归档文档整片都是历史,不受本条约束 |
|
||||
|
||||
> 数"还剩几篇留着这节"的命令(**别只 grep 裸标题**——带编号的 `## 6. 变更记录`、中文序号的 `## 九、变更记录`、`## 修订记录` 三种写法都要算):
|
||||
> `grep -rn "^## " docs scripts` 再看命中的标题;2026-10-29 实测 **5 篇**真台账(上表那五篇)+ `scripts/archive/` 归档一篇。
|
||||
|
||||
### 5.2 新增文档检查清单
|
||||
|
||||
@@ -373,9 +376,3 @@
|
||||
| 提交 diff 出现整文件行尾变化 | 脚本改写时强制了 LF/CRLF | 改写须按原换行符回写(见 §2) |
|
||||
| 图片不显示 | 文档迁入子目录后未改 `../images/` | 补 `../`(见 §3.2) |
|
||||
| 引用"计划中"文件被拦 | 没加 `[计划] ` 前缀 | 加前缀 |
|
||||
|
||||
---
|
||||
|
||||
## 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -54,6 +54,22 @@
|
||||
| `gen_data_trust_ladder_svg.py` | `docs/images/数据可信台阶.svg` | `STEPS` 五级台阶(状态 / 问题 / 依据)+ `BAND_LABEL_1` / `GATE_LABEL` / `FOOTNOTE`(台阶状态变了才重画) |
|
||||
| `gen_data_trust_dc_ac_svg.py` | `docs/images/ADC直流标定与交流验证.svg` | `BOXES_A` / `BOXES_B` 两条泳道 + `CHIPS` 六件事 + `NOTES` 红线与"证不到"清单 |
|
||||
| `gen_wave_record_flow_svg.py` | `docs/images/故障录波数据流.svg` | `CHAIN` 主链四段 + `EDGE` 跨边界标签 + `TRAPS` 三条陷阱 + `FORBID` 四条红线(所有权、段形态或红线变了才重画) |
|
||||
| `gen_menu_tree_svg.py` | `docs/images/菜单树总览.svg` | **内嵌 `NODE_SRC`**(= `ui_menu_tree.txt` 的 43 行节点)+ 版面常量;仓库里跑会与真值逐行互验,不一致 exit 1(临时目录里读不到真值 → 用内嵌表,输出逐字节一致) |
|
||||
|
||||
### 2.1 钩子扫不到的图:refgraph 产物与历史手画稿
|
||||
|
||||
`generated-fresh` 的 `docs-svg` 条目只把 `tools/arch_diagram/gen_*.py` 拷进临时目录重跑逐字节比对。
|
||||
上表没列到的 `docs/images/*.svg` 就是下面这几张,**它们全都不在这条钩子的扫描面里**——改了不会有任何
|
||||
人报警,这就是「42 个节点的旧图挂在 43 个节点的标题下」能一路活到今天的原因。
|
||||
|
||||
| 图 | 谁画的 | 用在哪 | 维护口径 |
|
||||
|---|---|---|---|
|
||||
| `docs/images/引用关系图.svg`、`docs/images/引用关系图_模块.svg` | `tools/refgraph/gen_refgraph.py`(graphviz,不在 `gen_*.py` 命名面里) | `docs/架构设计/引用关系图说明.md` 的产品 | 改了代码引用就跑 `tools\refgraph\gen_refgraph.bat` 重生成,**svg 与改动同一笔提交**,别只更新 `.dot`;口径 → `docs/架构设计/引用关系图说明.md` |
|
||||
| `docs/images/级联框总览.svg` | 2026-10-26 手画 | `docs/UI设计/菜单页面设计.md` §4.1(级联框显示几何:根框 + 整定子框) | 只作**显示几何示意**,不承载菜单树事实;树的结构 / 层级 / 绑页 → `docs/images/菜单树总览.svg`(有生成器) |
|
||||
| `docs/images/级联框几何计算.svg` | 同上 | `docs/UI设计/菜单页面设计.md` §5.3(逐框坐标推导) | 同上;坐标公式变了要重算重画,别在 SVG 上描字 |
|
||||
|
||||
新增精确布局图一律走 `tools/arch_diagram/gen_*.py`(`docs-svg` 条目自动发现,**不必登记基线**);上面这几张
|
||||
是这套纪律的**例外面**,再多一张先说清为什么不能用生成器。
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-1
@@ -256,7 +256,7 @@ flowchart TB
|
||||
- 内容页为**独立全屏屏**,由框架一次性自绘(**零 LVGL 子对象**),ESC 返回菜单屏。
|
||||
- 菜单受保护叶子(`ui_menu_tree.txt` 行尾 ` #P`):先弹密码页,授权后 `menu_password_ok` **先关页再续进入动作**。
|
||||
- 待机屏:空闲超时(默认 60s)自动切入,仅确认键回菜单;数据源同实时量页。
|
||||
- 字体 `ui_font_cn_12.c` 由 `tools/font_gen/` 生成(宋体 12px 1bpp 子集,**218 字/313 字形**,2026-10-28 实测;字形数 = 字符数 + ASCII 95 个)。
|
||||
- 字体 `ui_font_cn_12.c` 由 `tools/font_gen/` 生成(宋体 12px 1bpp 子集,**224 字/319 字形**,2026-10-29 实测;字形数 = 字符数 + ASCII 95 个)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
+390
-7
@@ -3,13 +3,17 @@
|
||||
> **这篇是什么**:已知电压注入验证的**执行侧**——判据与实测记录在 `docs/硬件平台/ADC通道映射说明.md` §8.1,
|
||||
> 这边写怎么接表笔、读哪一行日志、填哪张表、结果往哪回写。
|
||||
>
|
||||
> **本文只验直流**:零点 / 线性 / 通道映射 / 极性——它证的是"一条水平线的高度"。
|
||||
> **交流(正弦)动态验证不用等标准源**:一台信号发生器接在本文 §3 的同一批注入点上就能跑,
|
||||
> 判据与接法见 `docs/任务规划/数据可信.md` §4.6;等标准源的只是**精度等级**那一步
|
||||
> (`docs/任务规划/数据可信.md` §5 的「两点标定与精度验收」)。V0.1 有五路单极性削顶
|
||||
> (`UB/UC/IB/IC/IN`),它们在交流轮次里只能作"预期削顶"的负向用例。
|
||||
> **本文分两个轮次**:§1~§7 = **直流轮次**(零点 / 线性 / 通道映射 / 极性,2026-10-27 六项 PASS,
|
||||
> 证的是"一条水平线的高度";**不需要**示波器、信号源、标准源,**只需要**一块万用表 + 一个电位器 + 串口日志)。
|
||||
> §8 = **交流(正弦)动态验证轮次**(D1~D6,证的是"给定电压算得对不对、品质报得实不实、节拍稳不稳")——
|
||||
> 一台信号发生器接在本文 §3 的同一批注入点上就能跑,**同样不用等标准源**;
|
||||
> 等标准源的只是**精度等级**那一步(`docs/任务规划/数据可信.md` §5 的「两点标定与精度验收」)。
|
||||
> V0.1 有五路单极性削顶(`UB/UC/IB/IC/IN`),它们在交流轮次里只能作"预期削顶"的负向用例。
|
||||
>
|
||||
> **不需要**示波器、信号源、标准源。**只需要**一块万用表 + 一个电位器 + 串口日志。
|
||||
> **判据的属主是 `docs/任务规划/数据可信.md` §4.6.2**(D1~D6 验什么、过判据是多少),本文 §8 只写**怎么跑**。
|
||||
> 两边于 2026-10-29 对齐;若将来再分叉,分两类裁定:**「验什么、算不算过」从
|
||||
> `docs/任务规划/数据可信.md` §4.6.2**,**「期望值怎么算、怎么接线怎么跑」从本文 §8**。
|
||||
> 这一跳为什么不用等标准源、又证不到什么,见其 §4.6.1 与 §4.6.5,本文不重抄。
|
||||
|
||||
---
|
||||
|
||||
@@ -67,7 +71,7 @@
|
||||
|
||||
## 4. 读数出口(固件只有下面这些周期行,没有命令控制台)
|
||||
|
||||
> **样本出口将变**:故障录波已立项(2026-10-29,docs/任务规划/故障录波.md)。落地后段文件会直接给出原始码值样本,动态验证的判据随之从「比对下表这些派生标量」升级为「PC 独立重算 vs 板上读数」对表,口径变更见 docs/子系统设计/故障录波设计说明.md §11。下表仍是**当前**唯一可读的东西。
|
||||
> **样本出口已落地(代码侧,2026-10-29)**:故障录波的数据面已进仓(立项与卡片见 docs/任务规划/故障录波.md,格式与判据属主见 docs/子系统设计/故障录波设计说明.md §6.2 与 §6.3、口径变更见该文 §11)——每次触发一段,段文件直接给出 8 路 × 320 点原始码值,动态验证的判据因此**可以**从「比对下表这些派生标量」升级为「PC 独立重算 vs 板上读数」对表。下表仍是固件唯一的**周期行**出口(段文件不是周期行,只在触发时产生),两种出口对表用。
|
||||
|
||||
| 日志行 | 频率 | 内容 | 用途 |
|
||||
| ----------------------------------------------------------- | ------ | ------------------------ | ------------------- |
|
||||
@@ -77,6 +81,8 @@
|
||||
| `frames=.. idle=.. adc1_err=.. tgap=..` | 每 10 s | 采集健康计数 | 步骤 4 的节奏判据 |
|
||||
| `phasor(hb) C0x-…: Re=.. Im=.. rms=..`(逐实例)、`freq(hb): f=.. valid=..` | 每 10 s | 基波相量分量与测频读数(×0.1 / ×0.01 定点) | **交流轮次的主读数**(直流轮次只作旁证) |
|
||||
| `meas: seq=.. v=.. ua_mv=.. un_mv=.. ia_mv=.. f=.. fv=..` | 每 10 s | 200 ms 遥测快照轨(基波有效值 mV) | 交流轮次的幅值比对(1 mV 分辨率,比 phasor 行细) |
|
||||
| **段文件** `/wave/WAVE00.BIN` 起共 100 个号轮转(FTP 根下可见,与 /log 平级) | 每次触发一段(手动 / 保护启动 / bench 定时) | 8 路 × 320 点原始码值 + 封存瞬间的标定与品质快照(布局见 docs/子系统设计/故障录波设计说明.md §6.2) | **样本出口**:交流轮次的主对表源——预触发 160 点 + 后触发 160 点,触发瞬间落在段内点 159 |
|
||||
| PC 侧脚本 `test/host/wave_read.py`(不是固件日志,bench 工具,纯标准库) | 手动跑;`--selftest` 是它的自带回归 | 四个子命令:dump(逐字段与双 CRC)/ fft(全周傅氏重算基波有效值与相位)/ shape(削顶与形态判定,可 `--strict`)/ plot(出 SVG 或 PNG,PNG 用 `-o` 落文件) | 把段文件还原成周波数、幅值、相位、削顶判定与图 = 「PC 独立重算」这条判据的执行体;CRC 不符或零长度它必须**拒绝**而不是画出东西 |
|
||||
|
||||
> ⚠ 直流轮次用不到后两行,但**它们一直都在**。交流轮次(判据见 `docs/任务规划/数据可信.md` §4.6)
|
||||
> 必须用 `meas:` 或 104 YC 比幅值,别用 `phasor(hb)` 行——那行是 ×0.1 定点,注 1 V 只剩 10 格。
|
||||
@@ -273,3 +279,380 @@ TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性,
|
||||
与"加压试保护"是两件事,后者要等端子/子板变比确认(`docs/硬件平台/ADC通道映射说明.md` §7.1 链路分段表 ④ 行)之后单独排。
|
||||
4. **引脚侧注入必须外串 10 k**,且先接线后上电、先断电后拆线。
|
||||
5. 注入源与板子**共地**;电位器一端接 3.3 V、一端接 AGND、中间抽头串 10 k 到注入点。
|
||||
|
||||
---
|
||||
|
||||
## 8. 交流(正弦)动态验证轮次(D1~D6)
|
||||
|
||||
> **判据的属主是 `docs/任务规划/数据可信.md` §4.6.2**(D1~D6 验什么、过判据是多少),本节只写**怎么跑**:
|
||||
> 接哪个点、发生器怎么设、读哪一行的哪个字段、期望值怎么算、结果往哪回写。两边于 2026-10-29 对齐;将来
|
||||
> 若再分叉,按两类裁定——**「验什么、算不算过」从 `docs/任务规划/数据可信.md` §4.6.2,「期望值怎么算、
|
||||
> 怎么接线怎么跑」从本节**。为什么这一跳不用等标准源、以及它证不到什么,看其 §4.6.1 与 §4.6.5,本节不重抄。
|
||||
>
|
||||
> **引用写法**:本节凡引用判据文档的小节,一律写成「文件名 + 小节号」的紧邻形式;只写 §8.x 的是本文自己的节。
|
||||
|
||||
### 8.1 动手前先把出口分辨率算清楚(决定今天能判到哪一档)
|
||||
|
||||
这一轮和直流轮次**不一样:样本出口已经有了**。2026-10-29 故障录波的数据面并入主干,一次触发就是一段
|
||||
`/wave/WAVE<NN>.BIN`(`NN` = 00~99 轮转号;8 路 × 320 点原始码值 + 封存瞬间的标定与品质快照),PC 侧 `test/host/wave_read.py` 把它还原成
|
||||
周波数、幅值、**相位**、谐波与削顶判定。所以本轮每条用例都有两个档:**周期行标量档**(只读串口 / 104 / UI,
|
||||
快,不用 FTP)与**段文件对表档**(多一次触发 + 一次取段 + 跑脚本,判得到位)。各出口的实际分辨率如下——
|
||||
这张表决定每条用例走哪个档:
|
||||
|
||||
| 出口 | 打印处 | 分辨率 | 能判 | 判不动 |
|
||||
|---|---|---|---|---|
|
||||
| `meas: seq=.. v=.. ua_mv=.. un_mv=.. ia_mv=.. f=.. fv=..` | `App/task/adc_task.c:605` | **1 mV**(整数毫伏,只发 UA/UN/IA) | D1、D2 的幅值 ≤2 % | 相位、谐波、其余五路 |
|
||||
| 104 YC `0x4011`~`0x4018` + `0x401D`(短浮点 + 品质) | `App/protocol/iec104/iec104_pts.c:70` | 3 位有效数字 ≈ 0.01 V | 单个静态工作点的幅值交叉读数、品质 IV | **小步进**(每点 0.5 V / 0.005 A 变化死区)、相位、谐波 |
|
||||
| `phasor(hb) C0x-…-F1: Re=.. Im=.. rms=.. (x0.1)` | `App/task/adc_task.c:209` | **0.1 V,且向下截断** | 有数没数、量级、象限 | ⚠ **D3 的 ≤2° 与 D6 的 ±2 %**:注 1.000 Vrms 时 `rms` 只有 10 格,20 % 的谐波只有 2 格 |
|
||||
| `freq(hb): f=.. valid=.. (x0.01)` | `App/task/adc_task.c:224` | 0.01 Hz | D4 的读数与新鲜度 | — |
|
||||
| UI 交流量页 `Ua/U0/Ia/Ia2/Ia5`(3 位小数)、`Hz`(2 位) | `Drivers/BSP/UI/ui_page_ac.c:47` | **1 mV**(人工目检 + 拍照留证) | D6 谐波幅值、D1 交叉旁证、D4 的 `Hz` 显 `--` | 相位(页面上没有角度) |
|
||||
| 录波段文件 `/wave/WAVE<NN>.BIN`(`NN` = 00~99,100 个号轮转;FTP 根下,与 `/log` 平级)+ `test/host/wave_read.py`(`dump` / `fft` / `shape` / `plot`,纯标准库) | **已落地**(2026-10-29 并入 `v2-on-ui`;格式属主 `docs/子系统设计/故障录波设计说明.md` §6.2 与 §6.3) | 一次触发 8 路 × 320 点原始码值;`fft` 给基波 RMS、相位、2 次与 5 次谐波 RMS(mV) | **D3 的 ≤2° 与 D6 的 ±2 % 现在判得动**:PC 独立重算 vs 板上读数对表 | 段与发生器**无绝对相位锁定**(触发点在段内点 159)→ 只能比同段内相对相位;320 ms 快照不等于长时间稳定性 |
|
||||
|
||||
**所以每条用例按两个档摆**(出口从"缺样本"变成"有段文件"的经过与口径变更,见 `docs/任务规划/故障录波.md` §2 末
|
||||
与 `docs/子系统设计/故障录波设计说明.md` §11):
|
||||
|
||||
| 档 | 条目 | 说明 |
|
||||
|---|---|---|
|
||||
| **周期行就判得动** | D1、D2、D4、D5(D5 的接法要按 §8.5 改,原因写在那张卡里) | 判据在 `meas:` / 104 / `freq(hb)` / `rail:` 上分辨率都够,不取段也能收工 |
|
||||
| **段文件档才判到位** | D3、D6 | 段文件已落地 → 浮点重算,≤2° 与 ±2 % 都是**定量**判据;周期行退为旁证,只在没取到段时走定性档。触发动作与命令写在 §8.5 的 D3 / D6 卡 |
|
||||
| **要改代码的退路** | D3 抬倍数、D4 B 档 | 段文件落地后 D3 这条基本不必走;D4 B 档(放宽 `FREQ_AMP_MIN`)仍是唯一路子。两者都要重编 + 烧录 + 按 §8.9 还原,**动手前先问用户** |
|
||||
|
||||
### 8.2 设备清单与开机前置门
|
||||
|
||||
| 项 | 要求 | 为什么 / 怎么核 |
|
||||
|---|---|---|
|
||||
| 信号发生器 | 正弦 / 方波 / 三角,**并有 DC 档**;频率可锁 50.000 Hz、也能设到 2 Hz;幅度 0~3 Vpp | **只要求"波形干净、幅度稳、频率锁得住"**,它的刻度不当基准(`docs/任务规划/数据可信.md` §7 #9)。DC 档是给 D5 改法 A 用的 |
|
||||
| ⚠ 发生器输出端接设置 | 接板子时设成**高阻负载**,不要按 50 Ω 端接 | 按 50 Ω 端接而实际接的是高阻板输入 → 面板值与真实电压差 2 倍;这是台架"差一倍"最常见来源 |
|
||||
| 万用表 AC 档 | 能读到 mV;**记明型号与响应类型**(真 RMS / 均值响应) | 基准就是它;D2 那一步两种表的读数都与基波期望差很多,不记响应类型就说不清是谁错 |
|
||||
| 板子 | 产品构建(`test/board/test_config.h` 四个 `TEST_SUITE_*` 全注释)+ `@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]`,而 D5 故意造 `[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) | 段文件是 D3 / D6 定量档的样本出口;段文件名 `/wave/WAVE<NN>.BIN`(00~99 轮转),**取完立刻改名归档**,否则下一次触发把它盖掉 |
|
||||
| 录波触发路径 | 产品构建里唯一**免改代码**的路径 = UI 菜单第 26 叶「手动录波」确认页(`Drivers/BSP/UI/ui_page_wave_manual.c` → `App/task/ui_task.c:605`) | 保护启动上升沿也算一次触发,但保护判据现处**未标定闭锁**态(AGENTS.md「现状」),别指望它自动来段;bench 定时自抓在双层编译门 `DTU_BENCH` 且 `WAVE_BENCH_AUTOCAP` 非 0 之内(默认 0,`App/task/adc_task.c:153`),开它就走 §8.9 的还原判据 |
|
||||
| 共地 | 发生器 LO/GND 与板 AGND **单点**相连 | 不共地量到的是两地之差;现象见 §8.8 |
|
||||
|
||||
**开机前置门**——接发生器**之前**日志就该长这样,不满足先回 §5 步骤 5.5 查节奏,别往下做:
|
||||
|
||||
- `adc:` 行 10 s 一行,同一心跳里 `frames` 在涨("9 路全 0"要读成"没数据",§4 尾注);`idle=0`、`adc1_err=0`;
|
||||
- `meas:` 的 `v=1`、`seq` 每 10 s 约 +500(200 ms 一帧;不涨 = 发布闸门停);
|
||||
- `cal:` 的 `cl=0xE6` —— **这是来源派生**(V0.1 那五路本就触底,见 `docs/任务规划/数据可信.md` §2.6),既不是本轮战果也不是本轮故障;
|
||||
- 四个位图 `vl` / `cl` / `rail` / `edge` 同一个位序(语义通道枚举 `App/algo/adc_input.h:38`):bit0 `UA`、bit1 `UB`、
|
||||
bit2 `UC`、bit3 `UN`、bit4 `IA`、bit5 `IB`、bit6 `IC`、bit7 `IN`、bit8 `TEMP` → `0xE6` = bit1/2/5/6/7 = `UB/UC/IB/IC/IN`;
|
||||
- **先上电、在基线里看到 `adc: UB` 有正常摆动,再开发生器输出**:贴轨锁存要求该通道"此前出现过中带样本"(释放沿前提,
|
||||
`App/algo/adc_health.c` 的 `adc_health_rail_watch()`),开机就注大信号会让 D5 的锁存**装不上**,测出假不通过。
|
||||
- 开机各有一条 `first:` 与 `phasor(first)` / `freq(first)` 单发行(`App/task/adc_task.c:478` 与 `App/task/adc_task.c:201`
|
||||
的 `tag` 参数),与 10 s 一轮的 `hb` 行是两拨——数心跳时别把 `first` 计进去;
|
||||
- `phasor` 行**只要有数就打**:打印条件只有 `phasor_get() == 0`,不带任何 valid 位(`App/task/adc_task.c:209`)→
|
||||
「phasor 有数」既不说明标定可用、也不说明这一路没在削顶,判读必须配 `vl` / `cl` / `rail` 三个位图一起看;
|
||||
- 注入正弦**不会**点亮 `health:` 或 `sampling fault:` 那几行:`adc_health` 的位只吃序号差、错误计数增量与 tick 差
|
||||
(`App/algo/adc_health.c:91-189`),与波形幅值无关;它们若亮是节拍 / 丢帧问题,别归因给发生器(§8.8 同一条)。
|
||||
- `wave:` 行(`App/task/adc_task.c:782`)基线:`armed=0`、`err=0`、`ovf=0`、`drop=0`,`seg=` 是本开机以来的成段数(**可以是 0**)。
|
||||
本轮每触发一段就核 `seg` +1 且那三个计数不涨;涨了先按 §8.8 的落盘行分诊,别接着往上下注入。
|
||||
|
||||
### 8.3 接线的四条(与直流轮次比,差别只在第 2、3 条)
|
||||
|
||||
1. **注入点与直流轮次完全相同**:§3 清单的**端子侧**焊盘(`U5` 排针,串阻的 1 脚侧)。③ 段 1:1 已由直流轮次实测
|
||||
(斜率偏差 −0.14 %,§6),所以端子侧电压就是引脚电压,不用再算变比。引脚侧仍须外串 10 k(§7 第 4 条)。
|
||||
2. **幅度按峰值核,别看面板上的 Vrms**:1.000 Vrms 正弦峰值已 1.414 V。
|
||||
|
||||
| 侧 | 判据上限(§7 红线 2) | 允许的最大正弦 Vrms | 本节用量 |
|
||||
|---|---|---|---|
|
||||
| TPAFE(`UA` / `UN` / `IA`) | ±2 V 峰值 | 1.414 Vrms | **1.000 Vrms**(峰值 1.414 V) |
|
||||
| STM32(`UB` / `UC` / `IB` / `IC` / `IN`) | 0~3.3 V 单极性 | 无正向意义 | 只在 D5 用:注交流 = **故意触底**,负半周本就钉 0 |
|
||||
|
||||
3. **一次只注一路**(D3 除外,它要 `UA` 与 `UN` 两路同源),换表笔前先关输出。端子侧串阻是 **1 k**(R31 / R28 / R19 / R52 …),
|
||||
STM32 那五路负半周经**片内**钳位 / ESD 路径灌向 VDDA——网表 `docs/硬件平台/V0.1.tel` :228-241 逐条实证这些节点只有
|
||||
「电容 .2 + 串阻 .2 + MCU 脚」三件,**无外置钳位二极管、无隔直电容**,所以这条电流走的是芯片内部路径;
|
||||
1.000 Vrms 时峰值电流约 (1.414 − 0.3) / 1 k ≈ **1.1 mA**(按片内钳位压降 0.3 V 估、仓内无实测,只用来论证量级),
|
||||
但**别往上加幅度**——这种注入只允许出现在 D5 这类"故意削顶"的用例里。
|
||||
4. **电流通道也是注电压**:`IA` 的注入点同样是电压进 R19 端子侧。这一轮证的是"给定引脚电压算得对不对",
|
||||
不是"一次侧 1 A 怎么办"——后者要变比,属 `docs/任务规划/数据可信.md` §5 的端子 / 子板变比确认。
|
||||
|
||||
### 8.4 记录表抬头(每条用例一份,与日志一起归档)
|
||||
|
||||
```text
|
||||
日期 ____ / 操作人 ____ / 板子序列号 ____
|
||||
固件 commit ________(@build 0 错误 0 警告:是 / 否)
|
||||
发生器:型号 ______ 输出端接 = 高阻 / 50 Ω 波形 ____ 频率设定 ____
|
||||
万用表:型号 ______ AC 响应类型 = 真 RMS / 均值 共地方式 = 单点 / 发生器浮地
|
||||
注入点:路名 ____(端子侧 U5 / 引脚侧外串 ____ Ω)
|
||||
[adc_cal] = 未标定(eng=0)—— 必须是这个状态
|
||||
基线(未注时):meas: ua_mv=____ un_mv=____ ia_mv=____ rail=0x____ cl=0x____
|
||||
段文件(走定量档的用例):文件名 ________ 触发方式 = 手动 / 保护 / bench 取回时刻 ________
|
||||
脚本:test/host/wave_read.py(先跑 --selftest:通过 / 否) fft / shape / dump 输出与 plot 图归档在 ________
|
||||
```
|
||||
### 8.5 逐条执行卡
|
||||
|
||||
**每条卡的通用动作**:接好 → 开输出 → 等 ≥30 s(相量窗 + `meas` 200 ms 轨稳定)→ 取**连续 ≥10 行**心跳 →
|
||||
先确认 `frames` 还在涨、`adc1_err=0`,再核数 → 关输出 → 填表。"连续 ≥10 行"不是保守:`meas:` 是
|
||||
**瞬时基波有效值透传**、200 ms 是上送节拍不是平均窗(`docs/任务规划/数据可信.md` §4.5),
|
||||
单行读数带拍频式跳动,必须看一片才能定中枢。
|
||||
|
||||
#### D1 基波幅值(主用例,先做)
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 接法 | 发生器 → `UA`(R31 / `AIN1` / U5.A3)端子侧;万用表 AC 档**并同一注入点**——量真实加到板上的电压,不是面板值 |
|
||||
| 设定 | 正弦、50.000 Hz、**1.000 Vrms**(峰值 1.414 V) |
|
||||
| 主读数 | `meas: ua_mv=`(1 mV) |
|
||||
| 期望与判据 | `ua_mv` ≈ 万用表读数 × 1000,偏差 **≤2 %**(与静态同档) |
|
||||
| 交叉读数 | 104 `0x4011`(短浮点)应 ≈ 万用表读数,单位是**引脚电压域**(`eng=0`,不是二次值) |
|
||||
| 坑 ① | **别用 `phasor(hb)` 比幅值**——×0.1 定点,1 V 只剩 10 格(§4 尾注同一条) |
|
||||
| 坑 ② | 104 每点 0.5 V(电流 0.005 A)变化死区,生效值在 `App/algo/run_par.c` 的 `[run_par]` 副本:1.000 → 1.100 V **不会**上报变化,那是死区不是坏了;连续扫幅度只看 `meas:` |
|
||||
| 坑 ③ | **`cal: ua_mv` 与 `meas: ua_mv` 同名不同物**:前者是当拍瞬时码折算(`App/task/adc_task.c:587`),后者是 200 ms 快照里那一周波的基波有效值(`App/task/adc_task.c:605`),50 Hz 下二者可以差很多——`App/task/adc_task.c:595-599` 的原注就是**勿互为判据**,D1 只认 `meas:` |
|
||||
|
||||
记录表(每条 10 行取中枢):
|
||||
|
||||
| 序号 | 万用表 AC (V) | `meas: ua_mv` | 期望 = 万用表 × 1000 | 偏差 % | 结论 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | | | | | |
|
||||
| 2 | | | | | |
|
||||
| 3 | | | | | |
|
||||
|
||||
**加做(3 min,非判据)**:0.500 / 0.200 Vrms 两点看是否**等比**——不是验精度,是看有没有隐藏的触底或死区。
|
||||
0.2 Vrms 那点 104 必然被死区吞掉,只读 `meas:`。
|
||||
|
||||
#### D2 读数是否只跟基波走(同峰值改波形)
|
||||
|
||||
按**峰值对齐**(不是按 Vrms 对齐):三者峰值都取 1.414 V,发生器面板 Vrms 分别约 正弦 1.000 V、方波 1.414 V、
|
||||
三角 0.816 V。期望的**基波有效值**(全周傅氏基波口径:`App/algo/phasor.c:3` 的系数已预乘 √2/N、`fAmSQ` 直接就是
|
||||
有效值;下表的 20 点整周期 DFT 值就是按固件同一个窗算的):
|
||||
|
||||
| 波形 | 基波有效值 / 峰值 | 峰值 1.414 V 时的期望 `ua_mv` | 真有效值 / 峰值(对照) |
|
||||
|---|---|---|---|
|
||||
| 正弦 | 0.707 | 1000 | 0.707 |
|
||||
| 方波 | 0.904(闭式 4/π/√2 = 0.900) | ≈1278 | 1.000 |
|
||||
| 三角 | 0.578(闭式 8/π²/√2 = 0.573) | ≈817 | 0.583 |
|
||||
|
||||
- 判据:读数 = **基波**期望 ±2 %,**不是**真有效值。方波那栏最直观——真 RMS 表会读 ≈1414 mV、
|
||||
均值响应表读 ≈1570 mV,而板上读 ≈1278 mV,**这正是"这台仪表读的是基波"的证据,不是 bug**;
|
||||
所以抬头里必须写清万用表响应类型(判据文档同一条:拿真有效值表去对会以为差了 10 %)。
|
||||
- 系数的来路:方波基波幅值 = 4/π × 峰值、三角 = 8/π² × 峰值,再除 √2 得基波有效值 → 闭式 **0.900 / 0.573**,
|
||||
20 点整周期 DFT 实算 **0.904 / 0.578**;`docs/任务规划/数据可信.md` §4.6.2 的 D2 行与本节同值,算术以本节为准。
|
||||
|
||||
记录表:
|
||||
|
||||
| 波形 | 峰值设定 (V) | 面板 Vrms | 万用表 AC 实测 | `ua_mv` | 期望 | 偏差 % | 结论 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 方波 | 1.414 | | | | ≈1278 | | |
|
||||
| 三角 | 1.414 | | | | ≈817 | | |
|
||||
|
||||
#### D3 相位(只许比同片内)
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 接法 | `UA`(R31)与 `UN`(R28)**都走 TPAFE**,同幅 1.000 Vrms、同相——发生器单路输出分两路即天然同相;双通道机型则两通道都设 0° |
|
||||
| 读数 | `phasor(hb) C02-UA-F1: Re=.. Im=..` 与 `C03-UN-F1: Re=.. Im=..`,各取 `atan2(Im, Re)` 再比差值 |
|
||||
| 判据(§4.6.2) | **定量档**:同相时同段内两路相位差 ≤2°,把发生器(或 B 通道)改 90° 后差值跟到 ≈90°;**定性档只判跟随**(差值落进 80°~100°,容差写进记录表) |
|
||||
| ⚠ 分辨率 | 出口只有 ×0.1 且向下截断:1.000 Vrms 时 Re/Im 各只有 ±10 格,**一格就是 0.1 V** → 角度量化误差最坏 ≈ atan(0.1 / 0.707) ≈ 8°,**≤2° 今天用串口判不出来** |
|
||||
| 三个档 | ① **段文件定量档(首选,零改代码)**:同一次注入触发一段,`test/host/wave_read.py fft` 重算基波相位,比**同段内** `UA`(表序 0)与 `UN`(表序 3)的相位差——浮点重算,不受 ×0.1 量化限制,≤2° 判得动,动作见下面第 2 段;② **定性档**:没取到段时退而用,同相 ≈0°、改 90° 后差值落进 80°~100° 即算跟随,容差写进记录表;③ **抬倍数退路**:临时把 `log_phasors()` 的倍数改成 ×1000 并改掉行尾 `(x0.1)`,跑完按 §8.9 还原——段文件档已落地,这条基本不必走,要走仍**先问用户** |
|
||||
| 红线 | **只许比同片内**:跨转换器无同刻性(V0.1 硬件缺陷)+ STM32 那五路另有 1 拍乒乓滞后 → `docs/硬件平台/ADC通道映射说明.md` §7 |
|
||||
|
||||
记录表(定性档;定量档记脚本输出,归档口径见 §8.10 第 1 条):
|
||||
|
||||
| 发生器相位设定 | C02-UA `Re` / `Im` | C03-UN `Re` / `Im` | 两路 `atan2` 之差 | 结论 |
|
||||
|---|---|---|---|---|
|
||||
| 0° / 0° | | | | |
|
||||
| 0° / 90° | | | | |
|
||||
|
||||
**段文件定量档的具体动作(D3 首选,零改代码)**
|
||||
|
||||
1. `UA`(R31)与 `UN`(R28)两路接同一发生器输出(同相),注 **1.000 Vrms / 50.000 Hz**;
|
||||
2. 稳定 ≥30 s 后,在 UI「手动录波」确认页触发一次;成段有**秒级**延迟(落盘在 defaultTask,卷空闲后一次心跳搬完整段)
|
||||
→ 等串口 `wave:` 行的 `seg=` +1、`armed` 回 0 再去取文件;
|
||||
3. FTP 取 `/wave/WAVE<NN>.BIN`(本轮那一段的确切号),**取完立刻改名**——100 个号轮转会被覆盖;
|
||||
4. 主机侧跑:`& '.venv\Scripts\python.exe' test/host/wave_read.py fft WAVE<NN>.BIN --all`
|
||||
—— 每个整周波给一行:基波 RMS、相位、2 次与 5 次谐波 RMS(单位 mV,系数镜像 `App/algo/phasor.c`);
|
||||
5. 判据:`UA`(`--ch 0`)与 `UN`(`--ch 3`)在**同一周波**内的相位差——同相注入应 ≈0°,把发生器(或 B 通道)改 90° 后应 ≈90°,
|
||||
容差 ±2°(`docs/任务规划/数据可信.md` §4.6.2 D3);
|
||||
6. ⚠ 段的**绝对**相位与发生器没有锁定关系(触发瞬间固定落在段内点 159),所以**只能比同段内两路的相对差**;
|
||||
跨段比相位、或拿发生器相位当绝对基准都不成立(段内点序见 `docs/子系统设计/故障录波设计说明.md` §6.2);
|
||||
7. 交叉旁证:同一段先 `dump`(逐字段 + 双 CRC16,看封存瞬间的标定与品质快照),再 `plot --ch 0`(CRC 不符或零长度它
|
||||
**必须拒收**而不是画出半张图);板上 `phasor(hb)` 的 `Re` / `Im` 象限应与脚本给的象限一致(×0.1 只够核象限)。
|
||||
|
||||
#### D4 测频(默认 A 档 = 判"如实报不可测")
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 接法与设定 | 同 D1(`UA`,1.000 Vrms / 50.000 Hz) |
|
||||
| 读数 | `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 |
|
||||
| 坑 | 非整 50 Hz 时 `meas:` 会拍频式跳动,属预期 → **扫频读 `freq(hb)`,别读 `meas:`**(`docs/任务规划/数据可信.md` §8 风险表同一条);测频的取源顺序是先 `UA`(相量实例 C02)再退 `IA`(C01)(`App/algo/freq.c:47`)——本卡注 `UA` 走的就是 UA 源;若哪天只注 `IA`,测频会跟着 IA 走,别以为它锁在 UA |
|
||||
|
||||
记录表(A 档):
|
||||
|
||||
| 频率设定 (Hz) | `freq(hb)` f | valid | UI `Hz` | 104 `0x401D` 品质 | 结论 |
|
||||
|---|---|---|---|---|---|
|
||||
| 50.000 | | | | | |
|
||||
|
||||
#### D5 触底与品质路由(负向用例)
|
||||
|
||||
- **判据**(`docs/任务规划/数据可信.md` §4.6.2 D5):给 `UB`(R52 / `AIN2` / U5.B7)注 **DC −1.00 V 保持 ≥5 s**
|
||||
或 **1.000 Vrms / 2.0 Hz** 的正弦 → 负半周钉下轨 → 连续 20 拍同侧后该路贴轨位锁存、`edge` 起沿、104 该路 IV 且值清零。
|
||||
- ⚠ **注 50 Hz 同幅正弦造不出这个锁存,这是实现约束不是固件缺功能**:锁存条件是"连续 `ADC_RAIL_N_RUN = 20` 拍
|
||||
贴同一侧轨带"(`App/algo/adc_health.h:174`),而 `adc_health_rail_watch()` 里**任何一拍回到中带就把两侧计数清零**;
|
||||
1 kHz 下一个 50 Hz 周波只有 20 个点、正半周必然落在中带 → 连续贴底最长 ≈ 209°/360° × 20 ≈ **11 拍 < 20**。
|
||||
- **轨带数值**(`App/algo/adc_cal.c:452` 的 `adc_cal_rail_ths()`,TEMP 不监视):STM32 侧贴底 = 码 ≤ **81**(2 % FS ≈ 65 mV)、
|
||||
贴顶 = 码 ≥ 4013;TPAFE 侧 = ±32112 码(≈ ±4.9 V)→ **TPAFE 侧这一条在 ±2 V 判据上限内根本造不出来**,
|
||||
只有 STM32 那五路能做。
|
||||
- **改法 A(首选,零固件改动)**:发生器切 **DC 输出 −1.00 V** 注 `UB` 端子侧,保持 ≥5 s。期望:`cal:` 的 `rail=0x002`、
|
||||
`edge=0x002`、一条 `[ERR] rail: edge=0x2 sticky=0x2 (code-domain, 20-run+release)`(`App/task/adc_task.c:428`)、
|
||||
104 `0x4012` 该路 IV 且值清零、`adc: UB` 码值贴 0。撤掉注入后 **sticky 位不随输入恢复而清**(锁存语义,
|
||||
只有 `adc_health_rail_init()` / 复位清)→ 复位一次并把"是否随复位清"记进表:这条实测行为是本轮的产出之一。
|
||||
- **改法 B**:发生器 1.000 Vrms 但**频率设 2.0 Hz** → 负半周 250 ms = 250 拍贴底 ≥ 20 → 锁存;
|
||||
顺带证"贴轨检测与工频无关"这条口径。
|
||||
- **50 Hz 档照跑,判据就是"不锁存"**:记下 `adc: UB` 负半周钉 0 而 `rail` 仍 `0x000` —— 这一档证的是
|
||||
"锁存要连续 20 拍同侧"这条实现约束本身,不是失败,也不是战果。
|
||||
- **反向假阳性**:反过来注一个峰值 <65 mV(<46 mVrms)的 50 Hz 小信号,会让 `UB` 一路贴底而**锁存**——
|
||||
那是"信号小到进不了中带",不是削顶。读 `rail` 时要把这条一起排除。
|
||||
- **与 `cl` 区分**:`cl=0xE6` 是**来源派生**的静态触底掩码(`docs/任务规划/数据可信.md` §2.6),D5 要的是动态
|
||||
`rail=` / `edge=`;别拿 `cl` 当本轮战果。
|
||||
|
||||
记录表:
|
||||
|
||||
| 步 | 设定 | `rail` | `edge` | `[ERR] rail:` 行数 | 104 `0x4012` | 复位后 `rail` | 结论 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | 50.000 Hz / 1.000 Vrms 正弦 | | | | | 不适用 | |
|
||||
| 2 | DC −1.00 V(改法 A) | | | | | | |
|
||||
| 3 | 2.0 Hz / 1.000 Vrms(改法 B) | | | | | | |
|
||||
|
||||
**段文件档的两把尺子(现在能把"来源派生 vs 实测"一次核清)**
|
||||
|
||||
`shape` 拿**实测**贴轨判定与段头 `clip_mask` 对表;而段头那一位**来自通道来源、从不实测**(`test/host/wave_read.py`
|
||||
文件头的实证与本卡上面的 `cl=0xE6` 同源,两处互证)→ 三种注入分别配三把尺子:
|
||||
|
||||
| 注入 | 命令 | 期望 |
|
||||
|---|---|---|
|
||||
| 改法 A(`UB` 注 DC −1.00 V 保持 ≥5 s) | `shape WAVE<NN>.BIN --strict` | 实测贴底落进 bit1;段头静态已含 bit1 → 等式成立。**实测冒出段头没有的位**才算失败 = 有别的路被推到轨上,先查接线 |
|
||||
| 极小注入 / 未驱动通道 | 同上再加 `--clip-superset` | 换成"段头覆盖实测"这把尺子:容忍"封存了但本轮没贴轨";实测有、段头没有仍然失败 |
|
||||
| 原接法(50 Hz 正弦注 `UB`) | `shape WAVE<NN>.BIN`(不带 `--strict`) | 只读逐通道贴轨游程:正弦过零必回中带,`--min-run` 默认 4 也可能判不出贴轨——**别把"没判"读成 bug** |
|
||||
|
||||
`--min-run`(连续几点算贴轨)与固件的"连续 20 拍"是**两把不同的尺子**,别互相换算;固件侧结论仍以 `rail:` / `edge=`
|
||||
位图为准(本卡上半段)。
|
||||
|
||||
#### D6 谐波轨(**段文件 `fft` 定量档 + UI 目检旁证**)
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 接法 | 发生器 → `IA`(R19 / `AIN8` / U5.B5)端子侧 |
|
||||
| 设定 | 基波 + 20 % 二次 + 20 % 五次(发生器有谐波叠加 / ARB 模式就用它) |
|
||||
| ⚠ 峰值预算 | 1.000 Vrms 基波 + 两个 20 % 分量的最坏对齐峰值 ≈ 1.414 + 0.283 + 0.283 = **1.98 V,已顶到 ±2 V 判据上限**。所以基波改 **0.800 Vrms**、二次与五次各 0.160 Vrms,最坏峰值 ≈ 1.58 V 留余量;期望值随之换算 |
|
||||
| 主读数 | UI 交流量页 `Ia2` / `Ia5` 两行——**3 位小数 = 1 mV**(`Drivers/BSP/UI/ui_page_ac.c:47`),目检 + 拍照;远端可走 7003 看图截屏 |
|
||||
| 期望与判据 | 期望 ≈ 各分量注入值(0.160 Vrms)±2 % |
|
||||
| 旁证 | `phasor(hb) C04-IA-F2` / `C05-IA-F5` 的 `rms=`:×0.1 只有 1~2 格,**只能证明这根轨不是恒 0**,判不了 ±2 %(§8.1) |
|
||||
| 没有谐波叠加功能时 | 分三次单注:只注 0.160 Vrms / 100 Hz → `Ia2` 有数、`Ia5` 应 ≈0;只注 0.160 Vrms / 250 Hz → `Ia5` 有数、`Ia2` 应 ≈0;再注纯基波 → 两行都 ≈0。这一档证的是"两根轨各认各的次",**不证叠加工况**,结论里要写明 |
|
||||
| 口径 | 这两根轨**目前没有任何下游消费者**(`App/algo/prot_elm.c` 无引用、`meas` 只算基波)→ D6 只证"算得对",不证"有人用";出口归属 `docs/任务规划/数据可信.md` §9 的谐波轨立项 |
|
||||
|
||||
记录表:
|
||||
|
||||
| 注入分量 | 发生器设定 | UI 读数 (V) | 期望 (V) | 偏差 % | `phasor` 旁证 | 结论 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 基波 0.800 Vrms + 二次 0.160 + 五次 0.160 | | `Ia2=` / `Ia5=` | 0.160 / 0.160 | | | |
|
||||
|
||||
**段文件定量档(D6 的谐波现在判得到位)**
|
||||
|
||||
同一次注入(基波 0.800 Vrms + 二次 0.160 + 五次 0.160,峰值预算见上表)触发一段、FTP 取回后跑:
|
||||
|
||||
```text
|
||||
& '.venv\Scripts\python.exe' test/host/wave_read.py fft WAVE<NN>.BIN --ch 4 --all
|
||||
```
|
||||
|
||||
`fft` 逐整周波给**基波 RMS + 2 次与 5 次谐波 RMS**(mV,浮点重算),±2 % 在这条档是定量判据;`--ch` 是段内通道表序,
|
||||
`IA` = 4(表序 `UA UB UC UN IA IB IC IN`,TEMP 不录,`App/algo/wave_ring.h:216`)。UI 目检与 7003 截图降为**旁证**——
|
||||
它证的是"板上 `Ia2` / `Ia5` 与脚本算的是同一件事"(差值应在显示位数之内),不是替代判据。THD 与方和根仍不在本轮,
|
||||
口径见 `docs/任务规划/数据可信.md` §4.6.5。
|
||||
|
||||
### 8.6 顺序与耗时(一次台架 session 跑完)
|
||||
|
||||
| 序 | 做什么 | 预计 | 备注 |
|
||||
|---|---|---|---|
|
||||
| 0 | 前置门 + 基线 30 s | 10 min | §8.2,不满足就停 |
|
||||
| 1 | D1(含 0.5 / 0.2 Vrms 加做) | 15 min | 主用例 |
|
||||
| 2 | D2(方波 / 三角,峰值对齐) | 15 min | 抬头里记万用表响应类型 |
|
||||
| 3 | D6:段文件 `fft` 定量档 + UI 目检旁证(同一次注入触发一段) | 25 min | 命令见 §8.5 的 D6 卡 |
|
||||
| 4 | D3:段文件相对相位档(`UA` / `UN` 同源,再移 90°) | 20 min | 定性档只在没取到段时退用 |
|
||||
| 5 | D4 A 档 | 5 min | 判"如实报不可测" |
|
||||
| 6 | D5:50 Hz 档 + 改法 A + 改法 B + 复位复观(每档各触发一段) | 30 min | **单独一份日志**,判读规则见 §8.7 |
|
||||
| 7 | 同一份接线下把 104 交叉读数复跑一遍 | 15 min | `test/host/iec104_master.py` |
|
||||
| 8 | 取段与脚本产物归档(FTP `get` + `dump` / `fft` / `shape` / `plot`) | 15 min | 每段取完立刻改名,100 号轮转会覆盖 |
|
||||
| — | (需用户点头)D4 B 档 / D3 抬倍数档 | +40 min | 各含一次重编、烧录与还原;段文件落地后 D3 这条基本不必走 |
|
||||
|
||||
合计约 **135 min**(不含"需用户点头"那两档)。取段与跑脚本集中在末尾第 8 行统一做,中间各条只负责"触发 + 让
|
||||
`seg=` +1",免得一次 session 里反复插拔网线。
|
||||
|
||||
### 8.7 `[ERR]` 的判读规则(D5 单独跑、单独归档)
|
||||
|
||||
- D5 会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `@test` 的 forbidden 标记含
|
||||
`[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `@test` 的标记校验当结论**:改人工判读,并在归档文件名与
|
||||
抬头里写明「预期 [ERR]:贴轨锁存负向用例」;§8.6 已把它排在最后单独一轮(`docs/任务规划/数据可信.md` §8 风险表「削顶负向用例被当成固件 bug」行同一条)。
|
||||
- 除这一条 `[ERR] rail:` 之外,**任何 `[ERR]` 都是不通过**;四个 Fault 的崩溃日志也以 `[ERR]` 开头 →
|
||||
出现 Fault 立即停线取现场(`docs/子系统设计/看门狗设计说明.md` §10),别再继续注。
|
||||
- 每次开机 `rail:` 边沿只打一条、`SYSLOG` 也只留痕一次("adc code rail fault" 进 `selftest.log`)——这是设计不是漏报;
|
||||
要看第二次边沿就复位一次。
|
||||
|
||||
### 8.8 失败分诊(先查这一列,别急着改固件)
|
||||
|
||||
| 现象 | 先查 | 依据 |
|
||||
|---|---|---|
|
||||
| 与万用表差约 2 倍 | 发生器输出端接设成了 50 Ω | §8.2 表第 2 行 |
|
||||
| 差约 5.3 倍,或数字带 6553.6 / 1241.2 的痕迹 | 拿错刻度,或把 `adc:` 的码值当 mV 读 | §2 两把尺子、§4「码是什么」 |
|
||||
| `phasor` 行 `rms` 只有 10 格量级 | 正常(×0.1),不是 bug | §4 尾注、§8.1 |
|
||||
| 非整 50 Hz 时 `meas:` 来回跳 | 预期:瞬时透传、200 ms 不是平均窗 → 改读 `freq(hb)` | `docs/任务规划/数据可信.md` §8 风险表 |
|
||||
| 104 读数不动 | 每点 0.5 V / 0.005 A 变化死区(生效值 `App/algo/run_par.c`) | §8.5 的 D1 坑 ② |
|
||||
| 50 Hz 正弦注 `UB` 但 `rail` 不锁存 | **预期**(连续贴底不足 20 拍)→ 改法 A / B | §8.5 的 D5 卡 |
|
||||
| 改 DC 也不锁存 | 释放沿前提没满足:开机后该通道没出现过中带样本 → 先建基线再注入 | `App/algo/adc_health.c`、§8.2 前置门末条 |
|
||||
| 撤掉注入后 `rail` 仍非 0 | 锁存语义(sticky),只 `adc_health_rail_init()` / 复位清 → 复位后仍不清**才是** bug | §8.5 的 D5 改法 A |
|
||||
| 读数带与幅度无关的偏置或漂移 | 共地 / 地环 | §8.2 末行 |
|
||||
| `meas: seq` 不涨 | 发布闸门停 → 先查采集链,别判交流 | §8.2 前置门 |
|
||||
| `health:` 或 `sampling fault:` 亮 | 与注入的波形无关:`adc_health` 的位只吃序号差、错误计数增量与 tick 差(`App/algo/adc_health.c:91-189`)→ 查节拍 / 丢帧,别查发生器 | §8.2 前置门 |
|
||||
| 9 路全 0 | 读作"没数据",不是"输入为零" | §4 尾注 |
|
||||
| `wave_read.py` 报错退出 / 拒绝出图 | **这是正确行为**:CRC 不符或零长度它必须拒收而不是画出东西 → 重取一份、核对是不是搬到一半的半成品;仍拒收就 `dump` 看两处 CRC16 哪个不符 | `test/host/wave_read.py` 文件头 |
|
||||
| `shape --strict` 说实测与段头不等 | 先分清尺子:段头 `CLIPPED` 位来自**通道来源、从不实测**;DC 偏置 / 极小注入 / 未驱动通道应改用 `--clip-superset` | §8.5 的 D5 段文件档 |
|
||||
| 触发后 `seg=` 不涨或 `drop` / `ovf` 涨 | 落盘道在 defaultTask 卷空闲后才搬段,先等几秒;仍不涨查 `wave:` 行三计数与 `/wave` 目录可写(`[ftp]` 写权限) | `App/task/adc_task.c:782` |
|
||||
|
||||
### 8.9 收尾判据(走过临时改动的一条都不许留)
|
||||
|
||||
| 临时改动 | 收尾判据 |
|
||||
|---|---|
|
||||
| D4 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 那行留一句"本轮台架已还原" |
|
||||
| D3 改倍数:`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` 必须为空(产品镜像里这段必须零存在) |
|
||||
| 只动接线与发生器、没动代码 | `git status` 除归档目录外干净 |
|
||||
| 全程 | `test/board/test_config.h` 四个套件宏保持全注释(产品构建);烧录仍须 `-Rst -Run` + 回读校验 |
|
||||
|
||||
### 8.10 归档与回写(跑完必做,缺一算没收尾)
|
||||
|
||||
1. **证据归档** → `docs/测试说明/boot_log_data/`,沿用现有命名惯例(日期_主题_类型.txt):
|
||||
`20261104_ac_d1_meas_log.txt`、`20261104_ac_d5_rail_log.txt`、`20261104_ac_iec104_log.txt`;UI 目检照片同目录
|
||||
(该目录若不收图,就在抬头里写清照片存放位置)。**走段文件定量档的用例另加两样**:取回的段原件(原始字节,
|
||||
`20261104_ac_d3_wave<nn>.BIN`)与脚本输出(`fft` / `shape` / `dump` 三份文本 + `plot` 的图)——定量档的证据本体是
|
||||
**那一段原始码值**,日志只是旁证;段原件一旦覆盖就再也拿不回来,先改名再触发下一段。
|
||||
2. **实测结论回写** → `docs/硬件平台/ADC通道映射说明.md`,在 §8.1(直流轮次)之后**新开一节**——
|
||||
任务卡 `docs/任务规划/数据可信.md` §5 的 DoD 就是这么分的。本文只留"怎么跑",**不抄实测数**。
|
||||
3. **本文回写** → 只在 §8.1 那张表下加一行指针级别的句子("YYYY-MM-DD 已按本节跑通,实测见 …");
|
||||
不在本文建第二份结论表,否则两份必然漂移(`docs/文档规范.md` 同一事实只在一处详述)。
|
||||
4. **判据文档的一致性维护** → 本文 §8 的期望值算术、接法与出口分档已写进
|
||||
`docs/任务规划/数据可信.md` §4.6.2(2026-10-29:D2 的逐波形系数、D5 的 DC / 2 Hz 接法与 50 Hz 档的
|
||||
"不锁存属预期"、D3 与 D6 的"定量档走段文件、定性档走 `phasor(hb)`")。今后两边若再分叉,按开头引导块的
|
||||
两类裁定处理(语义从那节、算术与步骤从本文),**别在本文另立第二份判据表**。
|
||||
5. **销项** → `docs/任务规划/数据可信.md` §2.7 与 §6 卡片表里这一条按登记簿「完成即销项」处理,属 Ask first,
|
||||
须用户明确指示后再做。
|
||||
|
||||
### 8.11 这一轮证不到(别让读数长得像证据)
|
||||
|
||||
`docs/任务规划/数据可信.md` §4.6.5 那张五行表整条适用(端子二次值 / 精度等级 / 跨转换器相位与功率与对称分量 /
|
||||
THD 与谐波方和根 / 温升 EMC 长时间漂移)只适用,本节按出口已落地改三条现状(2026-10-29 录波数据面并入主干之后):
|
||||
**样本出口已成立**(`/wave/WAVE<NN>.BIN` + `test/host/wave_read.py`)→ D3 / D6 走定量档时结论是**形状级**,不再只是标量级;
|
||||
但**板级那一轮对表本身还没跑过**(卡片判据见 `docs/任务规划/故障录波.md`),本轮跑出来的就是第一批实测;
|
||||
段与发生器**没有绝对相位锁定**(触发瞬间只钉在段内点 159)→ 能核的是同段内两路的**相对**相位与极性,
|
||||
全局相角基准仍证不到;一次触发只有 320 点 = 320 ms,**不等于长时间稳定性**。示波器仍旧只作旁证、不进判据。
|
||||
|
||||
再补一条「别把台架做成功能性重复」的边界:**算法本体的精度已经在纯数字域钉住了**——`test/host/phasor/phasor_test.c`
|
||||
的 TC-PHASOR-003 与 TC-PHASOR-004(基波 = 峰值 / √2 容差 1 %、注入相角容差 0.5°)和 TC-PHASOR-006(2 / 3 / 5 次
|
||||
谐波 1 %),以及 `test/host/freq/freq_test.c` 的扫频判据 J1(45~55 Hz 内 ≤0.02 Hz),都是把已知正弦**直接喂进
|
||||
`sample_ring`**,绕过了 `adc_cal` 的折算(那里的幅值是工程量口径,不是引脚 µV)。所以 §8 这一轮真正要证的只有
|
||||
「信号走到 `sample_ring` 之前」那一段:发生器刻度与输出端接、前端串阻与滤波电容的幅频、两片转换器的同刻性、
|
||||
码到 mV 的折算、出口的量化。这一轮过了**不等于**「算法精度已实测验证」,结论里别那么写。
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
## 3. 怎么跑
|
||||
|
||||
```powershell
|
||||
# 全量:33 套单测逐套"编译(带 PDB)→ 插桩运行 → 解析 Cobertura",最后并集 + 比基线
|
||||
# 全量:44 套单测逐套"编译(带 PDB)→ 插桩运行 → 解析 Cobertura",最后并集 + 比基线
|
||||
& '.venv\Scripts\python.exe' scripts/check_test_coverage.py
|
||||
|
||||
# 只跑一套(调试用;子集会跳过"在测文件消失"类回退判定,见 §5)
|
||||
@@ -77,7 +77,9 @@
|
||||
退出码:`0` 全过且无回退 / `1` 有单测失败或覆盖率回退 / `2` 环境错误(无 cl、无 mscoverage、
|
||||
编译失败、XML 缺失)/ `3` 用法错误。全量实测约 **67 s**(33 套 × 编译 + 插桩运行;
|
||||
2026-10-25 实测;16 套时约 25 s、28 套时约 45 s,套件数涨了耗时就涨,别拿旧数当预算)。最新一次实测:
|
||||
2026-10-28 = 41 套 **~105 s**(该钩子同日移入 `stages: [manual]`,由远端 CI 与 `ci_local.py` 的 manual 段强制)。
|
||||
2026-10-28 = 41 套 **~105 s**(该钩子同日移入 `stages: [manual]`,由远端 CI 与 `ci_local.py` 的 manual 段强制);
|
||||
套件数现值 **44 套**(2026-10-29 实测,台账见 `docs/门禁说明/host-unit-tests.md` §6 第 4 条),**44 套这一轮的总闸耗时未重测**,
|
||||
上面那三个耗时都是当时值,别当现值预算。
|
||||
套件数的现值与逐个来历见 `docs/门禁说明/host-unit-tests.md` §6 第 4 条。
|
||||
|
||||
中间产物(exe / PDB / Cobertura XML)一律落 `%TEMP%\dtu_host\<工作树哈希>\coverage\<模块>`
|
||||
@@ -154,7 +156,7 @@
|
||||
宁可红,也不给可疑的数字。
|
||||
6. **只覆盖跑到的进程树。** 一次 `collect` 只看一次运行;没跑到的套件不进分母
|
||||
(全量跑时以"模块未跑到"判 REGRESS),子集运行明确降级(§5)。
|
||||
7. **头文件里的代码归 `.h`。** 统计面含 `.h`(静态内联与被展开的行落在头文件上);当前 33 套
|
||||
7. **头文件里的代码归 `.h`。** 统计面含 `.h`(静态内联与被展开的行落在头文件上);当前 44 套
|
||||
几乎没触及 `.h`,表里看不到它们——不要以为头文件"天生 100%"。
|
||||
8. **依赖 `Microsoft.CodeCoverage.Console.exe`。** 探测顺序:`DTU_MS_COVERAGE`(设了就必须
|
||||
有效、**不静默回退**)→ 各 VS 安装根的 `Common7\IDE\Extensions\Microsoft\CodeCoverage.Console` →
|
||||
@@ -713,7 +715,7 @@ 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` |
|
||||
| UI 侧 | 17 个 `.c` = **1,942 行** | 与 PC 模拟器是**同一份源码**,行为回归已由 `ui-golden`(菜单 8 状态 + 内容页 60 张像素黄金参考)覆盖;再写单测是第三份重复投资 | `ui-verify` skill 五步 + `docs/UI设计/UI_自动化测试流程.md` |
|
||||
| UI 侧 | 17 个 `.c` = **1,942 行** | 与 PC 模拟器是**同一份源码**,行为回归已由 `ui-golden`(菜单 8 状态 + 内容页 66 张像素黄金参考)覆盖;再写单测是第三份重复投资 | `ui-verify` skill 五步 + `docs/UI设计/UI_自动化测试流程.md` |
|
||||
| 任务编排 | `default_task.c`(281)、`adc_task.c`(171)、`net_task.c`(123)、`protect_task.c`(90)、`ui_task.c`(71)、`rs485_task.c`(11) = **747 行** | 每个只有 4~5 个函数,主体是"按顺序调别人";真逻辑在被调模块里(那些已测或在 P1/P2)。给编排层写单测最容易写成"断言我调了几次"的自证 | 真机启动锚点计数 + IWDG `required_mask`(`@test`)+ `docs/架构设计/项目启动顺序管理.md` |
|
||||
| 内核前装配 | `app_main.c`(135) | 被测对象本身就是"`osKernelStart` 之前不许调内核 API"的时序,主机侧无法忠实复现(PC 上没有那个约束) | 同上 + `cubemx-survival` 审计 |
|
||||
|
||||
|
||||
@@ -70,8 +70,9 @@
|
||||
| `TC-BOOTSTAT-030c` | init 后连读 100 次,三类 I2C 调用计数**纹丝不动** | "任意上下文可读"不是口头承诺 |
|
||||
|
||||
> ⚠ 这些号是**主机侧用例号**,不是板端用例号,也不是需求号;族口径沿用
|
||||
> `docs/编号规范.md` 的 TC 族(默认 `check_dangling`),所以**本文就是定义位**:
|
||||
> 新增/删除用例必须同步改本表,否则 `id-check` 校验 3 会报悬空、校验 7 会报孤儿。
|
||||
> `docs/编号规范.md` 的 TC 族(既有号只读),所以**本文就是定义位**:
|
||||
> 新增/删除用例必须同步改本表——`id-check` 已于 2026-10-28 退役,悬空与孤儿**不再有机器警报**,只靠评审;
|
||||
> 新增用例本身不再配号(`docs/编号规范.md` §4.1 一律停发),用语义化测试函数名。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
- **初始化回归**:`App/task/ui_task.c` 已有 `UI port init: OK` / `UI menu init: OK` / `UI net sink: started`
|
||||
打印(见 `@test` 命令示例);将其加入 `-Required` 即可经串口自动回归 UI 初始化。
|
||||
- **屏幕内容回归**:由 LVGL 模拟器像素回归覆盖(文本级断言 + 60 张黄金参考 + 池水位门禁,
|
||||
- **屏幕内容回归**:由 LVGL 模拟器像素回归覆盖(文本级断言 + 66 张黄金参考 + 池水位门禁,
|
||||
见 `docs/UI设计/UI_自动化测试流程.md`);上机屏幕仍需人工目检(见 `测试开发标准流程.md` §1)。
|
||||
|
||||
## 扩展用法
|
||||
@@ -136,7 +136,3 @@
|
||||
`--forbidden "FAIL;HardFault"`(`[FAIL]` 仍会拦截真实断言失败);
|
||||
例:GD5F 只读 `--required "=== GD5F2GQ5UE Tests: 16/16 PASSED"`,破坏性 `68/68`。
|
||||
- **长时间采集**:daemon 全量持续写入 `state/serial.log`(20MB 上限自动截头保尾 10MB),直接读文件即可;或用 `log --grep <标记>` 过滤(2026-10-10 起先过滤后截尾,不必再配 `--tail 0`)。
|
||||
|
||||
## 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -277,9 +277,3 @@ if (s_encode(p_key, ..., v, sizeof(v)) >= 0) {
|
||||
(现有板端套件只测了"文件缺失 → 重建"路径,是本缺陷长期漏网的直接原因)。
|
||||
- 检查是否存在同类"返回长度却按 `== 0` 判成功"的调用点(本次已全库核对 `s_encode`
|
||||
的 4 处调用点,除本缺陷外其余正确)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -650,9 +650,3 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
|
||||
| 引用关系图(文件引用) | `docs/架构设计/引用关系图说明.md`(生成工具 `tools/refgraph`;渲染依赖 Graphviz,§2.8) |
|
||||
| 各模块测试指南 | `docs/测试说明/CH395F_Test_Guide.md`、`GD5F2GQ5UE_Test_Guide.md`、`SD2506_Test_Guide.md`、`FTP_Test_Guide.md` |
|
||||
| 已知硬件陷阱 | `docs/问题记录/CH395F_Trap_Records.md` 等 |
|
||||
|
||||
---
|
||||
|
||||
## 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
+116
-101
@@ -1,35 +1,41 @@
|
||||
# 编号规范:默认**不**用编号
|
||||
|
||||
> **一句话:语义标识是唯一默认形态,编号是例外。** 例外只留给**有脚本按字面量匹配**的那几种
|
||||
> (§4.1 的「允许继续发号」一栏);同一张表里还有一栏是**已停发、存量待清**的族(`编号治理历史.md` §9.1 给清点与批次)。
|
||||
> 除此之外一律写语义短语(§3.3)。
|
||||
> **一句话:语义标识是唯一形态,编号一律停发。** 2026-10-29 用户裁定:**任何族都不再新增编号**,
|
||||
> 连 §4.1 那五个「有脚本按字面量匹配」的存量族也一样——既有号只读引用,族里新增内容写语义行名(§3.3)。
|
||||
> §4.1.1 另列更早停发、存量待清的族(清点见 `编号治理历史.md` §9.1)。
|
||||
>
|
||||
> 这条不是洁癖,是用户 2026-10-27 的明确要求:**「不要用字母加数字的编号,简直太难以理解了,语义标识是最好的」**。
|
||||
> 这条不是洁癖,是用户 2026-10-27 的明确要求:**「不要用字母加数字的编号,简直太难以理解了,语义标识是最好的」**,
|
||||
> 2026-10-29 又收一句:**「啥号都不发」**。
|
||||
> 依据也很硬:规划 / 待办 / 审计 / 差距 / 阶段这些给人看的东西,历史上全发过号,也全都变成了没人认得的代号。
|
||||
> 2026-10-26 起两批清理把编号字面量从 **10,956 压到 8,230 处**,2026-10-27 复测为 **7,828 处**(`编号治理历史.md` §8.1 有口径说明);
|
||||
> 而清掉的号里绝大多数从来没被任何脚本读过一次。沿革与教训留在 `编号治理历史.md` §8.3 与 git log,正文不复述。
|
||||
>
|
||||
> **机器面才是事实源**:现行门禁 `scripts/check_ids_new.py` 的两张**前缀级白名单**(`KNOWN_DASH` / `KNOWN_BARE`)
|
||||
> 是 §4.1 的机器投影;本文与白名单冲突时以白名单为准,回来改本文(详解 `docs/门禁说明/id-check-new.md`)。
|
||||
> 旧的族注册表 / 别名表 / 八项校验脚本已随全库审计退役进 `scripts/archive/id_gate/`,只在手工复查存量时用。
|
||||
> **本文就是唯一事实源**:两道编号门禁已分别于 2026-10-28(全库八项校验)与 2026-10-29(只查新增行)
|
||||
> 退役,脚本、两张前缀白名单与族语料都在 `scripts/archive/id_gate/`,**不再进提交门禁**。
|
||||
> 形态判据只剩本文 §1 的默认答案(一律不发)+ §4.1 存量族表 + 人工评审;留档 `docs/门禁说明/id-check-new.md`
|
||||
> 与 `docs/门禁说明/id-check.md`,手工普查命令与恢复机器面的成本见 §8.5。归档脚本随时能重扫一批改动的形态(`--range A..B`)。
|
||||
|
||||
---
|
||||
|
||||
> **本文范围**:这里只留**现在还生效**的口径。跳号的节(`编号治理历史.md` §2 八条原则 / `编号治理历史.md` §4.2 / `编号治理历史.md` §4.5 / `编号治理历史.md` §6 模块码表 /
|
||||
> `编号治理历史.md` §7 改号与迁移 / `编号治理历史.md` §8.1 基线 / `编号治理历史.md` §8.3 批次 / `编号治理历史.md` §8.4 台账 / `编号治理历史.md` §9 存量清点与换算表)是编号治理的**过程记录**,
|
||||
> 2026-10-28 已整体移入 `scripts/archive/id_gate/编号治理历史.md`——要查历史判据、批次流水、
|
||||
> 「数据可信」任务卡号换算表,去那里;**改现在的做法只看本文**。
|
||||
> 节号沿用旧编号,故意不重排:外部文档与脚本引的是 `§4.1` / `§8.5` 这些号,重排等于制造一批假链接。
|
||||
> **本文范围**:这里只留**现在还生效**的口径。编号治理的**过程记录**(八条设计原则、基线数字、批次流水、
|
||||
> 存量清点与逐号换算表)2026-10-28 已整体移入 `scripts/archive/id_gate/编号治理历史.md`,下文简称
|
||||
> **`编号治理历史.md`**;它的节号沿用旧编号、故意不重排(外部文档与脚本引的是 `§4.1` / `§8.5` 这类号,
|
||||
> 重排等于制造一批假链接)。要查历史判据、批次流水、「数据可信」任务卡号换算表,去那篇;
|
||||
> **改现在的做法只看本文**。
|
||||
|
||||
## 1. 决定要不要发号之前,先问三句
|
||||
## 1. 默认答案:不发号
|
||||
|
||||
| 问自己 | 答"否"的结论 |
|
||||
**2026-10-29 用户裁定:新条目一律不配编号。** 这里原本是三句自问(「有脚本按字面量匹配就允许发」),
|
||||
现在这条路也关掉了——判「能不能发号」不再看形态,答案恒为否。剩下的只是既有号怎么用:
|
||||
|
||||
| 场景 | 怎么做 |
|
||||
|---|---|
|
||||
| 有脚本 / CI / 回归测试要按这个字面量做匹配吗? | **不发号**,写语义短语(§3.3) |
|
||||
| 现有文档或登记表里已经能指代这件事吗? | 引用它,别再发第二个号指同一件事 |
|
||||
| 一年后别人还能在属主登记表查到它的定义吗? | 查不到就别发——"先记着,以后再说"是编号失控的唯一来源 |
|
||||
| 想给新需求 / 新踩坑 / 新用例 / 新待办配个号 | 不发,写语义行名(§3.3);登记表那一行的行名就是标识 |
|
||||
| 新东西确实要被脚本按字面量匹配(比如新的回归脚本键) | **先问用户**(Ask first)。批下来才算例外,顺序见 §4.1 末条 |
|
||||
| 现有文档或登记表里已经能指代这件事 | 引用它,别再发第二个号指同一件事 |
|
||||
| 碰到查不到定义的号 | 删号留事实,不是补一个定义(§10) |
|
||||
|
||||
三条硬规矩(对确实要发的号):
|
||||
三条硬规矩(管既有号的引用与处置):
|
||||
|
||||
- **号不承载语义**:严重度、优先级、模块名、轮次都不写进号里。事实一变,号就开始撒谎。
|
||||
- **号不回收、不复用**:销项 = 删登记行 + 清掉所有引用 + 别名表标 `void`(§5.5)。历史上"销掉的号被
|
||||
@@ -42,7 +48,7 @@
|
||||
|---|---|---|
|
||||
| ① 最好 | **语义短语** | 规划 / 待办 / 缺陷 / 风险 / 决策 / 差距 / 文档内步骤与阶段——**默认形态** |
|
||||
| ② 其次 | **文档名 + 章节号** | 指某篇文档的某节(`docs/网络/网络自愈架构设计.md` §4.10,紧邻写法) |
|
||||
| ③ 最后 | **编号** | 只有 §4.1「允许继续发号」那一栏,且必须能反查到定义 |
|
||||
| ③ 最后 | **编号** | 只指 §4.1 五个存量族的**既有号**(只读),且必须能反查到定义;新条目不在此列 |
|
||||
|
||||
### 3.1 判定口诀
|
||||
**机器读 → 发号;人读 → 写短语。** 拿不准就写短语:短语看不懂还能靠上下文猜,号看不懂只能去查表。
|
||||
@@ -65,62 +71,62 @@
|
||||
|
||||
## 4. 编号空间
|
||||
|
||||
### 4.1 允许用号的族(**穷举**——这张表以外一律不许发号)
|
||||
### 4.1 存量族(**穷举**——2026-10-29 起连这五个族也不再发新号)
|
||||
|
||||
**允许继续发号**——**恰好下面 5 行**,判据 = 有脚本按字面量匹配它(现状实测 2026-10-27 见 `编号治理历史.md` §9.1)。
|
||||
`TC-*` **不在本表**:它的发号已按用户裁定 2026-10-27 停掉(别把那句裁定读成「暂缓」——防误读的三条对照在
|
||||
本节末的框里,清点的来龙去脉在 `编号治理历史.md` §9.1 批次三)。
|
||||
下表**恰好 5 行**,是仓库里仅剩还带编号的族:既有号继续被脚本按字面量匹配、继续被引用,**但族里新增
|
||||
内容不再配号**(写语义行名)。「有脚本按字面量匹配它」这个旧判据自 2026-10-29 起作废——这五族的号是
|
||||
历史遗留的机器把手,不是发号许可。`TC-*` 早两天(2026-10-27)已按同一逻辑停发,清点与来龙去脉在
|
||||
`编号治理历史.md` §9.1 批次三。
|
||||
|
||||
| 族 | 形态 | 属主登记处 | 用途 |
|
||||
| 族 | 形态 | 属主登记处 | 既有号被谁读 / 新写怎么办 |
|
||||
|---|---|---|---|
|
||||
| `TRAP-*` | `TRAP-<域>-<nn>` | `docs/问题记录/*_Trap_Records.md` | 踩坑证词的永久锚点,代码注释反查 |
|
||||
| `FR-*` | 需求 | `docs/产品需求/产品需求设计文档.md` §5 | 需求↔测试追溯 |
|
||||
| `CODE-RULE-<nn>` | 代码规范条款 | `scripts/check_coding_standard.py` | 门禁按条款号报错并统计存量 |
|
||||
| `MENU-STATE-<nn>` | UI 状态机键 | `simulator/tests/check_menu_rows.py` | 像素回归脚本按它取状态行 |
|
||||
| `P-<n>` | **本文的设计原则** | 本文 `编号治理历史.md` §2 | 就是本文自己的条目 |
|
||||
| `TRAP-*` | `TRAP-<域>-<nn>` | `docs/问题记录/*_Trap_Records.md` | 代码注释按号反查踩坑证词;**新踩坑**在域文件里写语义行名,不再顺延号段 |
|
||||
| `FR-*` | 需求 | `docs/产品需求/产品需求设计文档.md` §5 | 需求↔测试追溯的既有键;**新需求**用 §5 表格的行名(语义短语)指代,追溯按既有号跑存量 |
|
||||
| `CODE-RULE-<nn>` | 代码规范条款 | `scripts/check_coding_standard.py` | 门禁按条款号报错并统计存量;**新条款先问用户**——脚本要按号统计,破例得由用户批 |
|
||||
| `MENU-STATE-<nn>` | UI 状态机键 | `simulator/tests/check_menu_rows.py` | 像素回归脚本按它取状态行;**新状态同样先问用户** |
|
||||
| `P-<n>` | 编号治理的设计原则 | `scripts/archive/id_gate/编号治理历史.md` §2(2026-10-28 随沿革归档,本文不再列条目) | **只读存量**:原则已归档,新写条目按 §3.3 写语义短语 |
|
||||
|
||||
> ⚠ **「批次三不做了」只有一层意思:不去清这个族,同时把它的发号口关死。** 它**不是**「TC 族维持原状、
|
||||
> 新用例照旧配号」——维持原状的是**存量号**,关掉的是**发号口**。三条对照,防止往任何一边读过头:
|
||||
> ⚠ **停发只有一层意思:不去清存量号,但发号口关死。2026-10-29 起这句话适用于全部族,不只 `TC-*`。**
|
||||
> 维持原状的是**存量号**,关掉的是**发号口**。四条对照,防止往任何一边读过头:
|
||||
>
|
||||
> - ❌「不做 = 维持原状 = 还能发」——**错**。新用例一律用**语义化测试函数名 + 文档用例表的行名**(§3.3),
|
||||
> 不再配 TC 号;指认**既有**用例仍可引用现存号(旧号只读,不是许可新配)。
|
||||
> - ❌「停发 = 这族要废了,顺手清掉、把号腾出来复用」——**也错**。现存号与它在 `scripts/archive/id_gate/id_registry.json`
|
||||
> 里的登记,是 `check_test_traceability.py` 与 41 套 `run_unit_test.py` **按字面量读的追溯把手**,
|
||||
> 清它等于换机制(`编号治理历史.md` §9.1);号不回收、不复用是 §1 的硬规矩。
|
||||
> - ❌「registry 里 TC 族还带着活游标,所以门禁允许我发」——**不成立:这条缺口已于 2026-10-27 同日关掉**。
|
||||
> 37 个 TC 类族现在**全部** `frozen: true`,其中 **34 个 `TC-HOST-*` 子族的 `cursor` 冻结在裁定日该套件存量
|
||||
> 最高号**(5 个游标低于存量的已抬到水位:`TC-PROTELEM-013→015`、`TC-SYSLOG-108→137`、`TC-ADCHEALTH-011→023`、
|
||||
> `TC-ADCCAL-017→023`、`TC-I104SRV-041→053`;**游标必须是字符串形态**,整型比较会静默不生效)→ 在套件
|
||||
> 登记处写一个比游标更高的 TC 号就是 §8 第 5 项「冻结族越游标」**错误级**,而 `id_known_backlog.json` 台账
|
||||
> 0 条指纹 → 增量拦截等于全量拦提交。探针实测(`check_ids` 的 Checker `_check_frozen`):在 `TC-PROTSET-`
|
||||
> 段存量最高号之上再加一号 → 命中 1 条校验 5;写存量最高号本身 → 零误伤。
|
||||
> **还剩一处空白,别把这条机械化读成无死角**:主族 `TC`(没有专属子族的模块码,如 `TC-ADCIN-*`)的 `cursor`
|
||||
> 仍为 `null`——一个游标表达不了每个模块码的水位,写全族最高号反而放行低号,所以按空白登记(探针实测:
|
||||
> 给无专属子族的模块码(如 `TC-ADCIN-` 段)写一个新号,门禁不响)。这一面由三条兜:① §8 校验 3——新号没进属主登记处 = 悬空引用(错误级);
|
||||
> ② `编号治理历史.md` §9.1 末段的**冻结水位**(只减不增,涨了就要问「这号是谁在停发之后发的」);③ 评审。**所以给这些模块
|
||||
> 补号,即使在文档表格里一并补了定义行,一样违反本裁定。** 要把这面也钉死(按模块码克隆子族,或给族级加
|
||||
> 字面量上限)与「真要清退」同属追溯机制改造,按 `编号治理历史.md` §7.3 单独开卡,别夹在本批里顺手做。
|
||||
> - **唯一的重启入口**:先由用户裁定 → 再回到本节把族放回上面那 7 行、并指出按它做字面量匹配的脚本 →
|
||||
> 才谈得上发号。顺序倒过来就是 `编号治理历史.md` §1.2 那四类事故的由来。
|
||||
> - ❌「§4.1 里的族没被点名停发,所以还能发」——**错**。五族一样不发,新条目写语义行名(§3.3);
|
||||
> 指认**既有**需求 / 踩坑 / 用例仍可引用现存号(旧号只读,不是新配的许可)。
|
||||
> - ❌「停发 = 这族要废了,顺手清掉、把号腾出来复用」——**也错**。现存号是脚本与追溯机制认的把手
|
||||
> (`scripts/check_test_traceability.py` 2026-10-29 实测**只手工跑**:不在任何钩子、`scripts/ci_local.py`
|
||||
> 与远端 CI 里),清它等于换机制(`编号治理历史.md` §9.1);号不回收、不复用是 §1 的硬规矩。
|
||||
> - ❌「`id_registry.json` 里 TC 族带着游标,所以那边允许我发」——**不成立**。37 个 TC 类族 2026-10-27 全部
|
||||
> 登记 `frozen: true`,34 个 `TC-HOST-*` 子族的游标钉在当日各套件存量最高号(5 个低于存量的抬到水位:
|
||||
> `TC-PROTELEM-013→015`、`TC-SYSLOG-108→137`、`TC-ADCHEALTH-011→023`、`TC-ADCCAL-017→023`、
|
||||
> `TC-I104SRV-041→053`;**游标必须是字符串形态**,整型比较会静默不生效)。那是那次裁定的**登记留痕**:
|
||||
> 两道编号门禁退役后没有任何东西再读这份登记——**别把「不报错」读成「允许发号」**。
|
||||
> - **已知空白面(当年登记时就写着)**:主族 `TC`(没有专属子族的模块码,如 `TC-ADCIN-` 段)的 `cursor`
|
||||
> 为 `null`——一个游标表达不了每个模块码的水位,写全族最高号反而放行低号。机器面在的时候它就不响,
|
||||
> 现在连壳都没有:**给这类模块码补号,即使在文档表格里一并补了定义行,一样违反裁定**,评审专门盯。
|
||||
> 要钉死它(按模块码克隆子族)与「真要清退」同属追溯机制改造,按 `编号治理历史.md` §7.3 单独开卡。
|
||||
> - **唯一的例外入口**:先由用户裁定 → 再回到 §4.1 把该族写清「哪个脚本按字面量匹配它」 → 才谈得上
|
||||
> 发号。顺序倒过来就是 `编号治理历史.md` §1.2 那四类事故的由来。
|
||||
|
||||
**已停发、存量待清**(一律**不得再新增引用**;下表除 `TC-*` 外,登记未删只是为了让历史行不报「未登记前缀」,
|
||||
清点见 `编号治理历史.md` §9.1。`TC-*` 是这一栏的**特例**:登记与现存号都要留着,它是机器把手而不是待清对象,见 `编号治理历史.md` §9.1 批次三):
|
||||
**已停发、存量待清**(§4.1 那五族 2026-10-29 起同样停发,区别只在存量保留、不去清;本表这些族连存量引用也要清掉,一律**不得再新增引用**;下表除 `TC-*` 外,`id_registry.json` 里剩的登记只是归档普查的
|
||||
语料残留,**不再当"登记处"读**——那份登记自 2026-10-28 起没有机器读者。清点见 `编号治理历史.md` §9.1。
|
||||
`TC-*` 是这一栏的**特例**:登记与现存号都要留着,现存号仍被追溯脚本按字面量读,不是待清对象
|
||||
(见 `编号治理历史.md` §9.1 批次三):
|
||||
|
||||
| 族 | 状态 | 依据 |
|
||||
|---|---|---|
|
||||
| `TC-*` | **保留现存号、即日停发(机器已冻结)**:34 个 `TC-HOST-*` 子族 `frozen: true`、游标钉在 2026-10-27 存量水位,越游标即 §8 校验 5 错误级;现存号只减不增,新用例改用语义化测试函数名 + 文档表格行名(主族 `TC` 的空白面见上方框) | 用户裁定 2026-10-27「批次三,不坐」;本族是 §4.1 判据唯一真被脚本按字面量读的族,清它等于换追溯机制,详见 `编号治理历史.md` §9.1 批次三 |
|
||||
| `TC-*` | **保留现存号、即日停发**:`id_registry.json` 里 37 个 TC 类族登记 `frozen: true`、34 个 `TC-HOST-*` 子族游标钉在 2026-10-27 存量水位(**该登记 2026-10-29 起无机器读者,只是裁定留痕**);现存号只减不增,新用例改用语义化测试函数名 + 文档表格行名(主族 `TC` 的空白面见上方框) | 用户裁定 2026-10-27「批次三,不坐」;本族是 §4.1 判据唯一真被脚本按字面量读的族,清它等于换追溯机制,详见 `编号治理历史.md` §9.1 批次三 |
|
||||
|
||||
| 族 | 形态 | 存量 | 为什么该清 |
|
||||
|---|---|---|---|
|
||||
| `L<n>-<nn>` | 保护功能号 | **批次二已整族转功能名,即日停发**:活引用 0,只剩一处**原始串口日志**(冻结证据、永久保留)与本文/技能文档的形态举例;属主文档表首格写功能名,不再配号 | 用户 2026-10-27 定调「语义标识是最好的」+ 本轮指令「把能清的族清掉一批」;探针(Checker _check_dangling):写一个新号(字母 L + 段号 + 连字符 + 两位数、从未定义过的形态)→ §8 校验 3 悬空引用**错误级** → 发号口已机器关闭。**不冻结数值游标**:L1/L2/…各段最高号不同,单一数字游标反而放行低号 |
|
||||
| `F-<MOD>-<nn>` | 网络设计条目 | **批次一已转「`编号治理历史.md` §7 条目名」,即日停发**:代码与测试侧 0 处;属主文档 `编号治理历史.md` §7 编号列 13 条仅作历史对照(定义行不算引用) | 同上;探针:写一个新号(F 前缀 + 模块码 + 从未出现过的序号)→ 校验 3 **错误级**;引用旧号命中小写别名表 retired-to-phrase = **只读**,不构成发号许可 |
|
||||
| `L<n>-<nn>` | 保护功能号 | **批次二已整族转功能名,即日停发**:活引用 0,只剩一处**原始串口日志**(冻结证据、永久保留)与本文/技能文档的形态举例;属主文档表首格写功能名,不再配号 | 用户 2026-10-27 定调「语义标识是最好的」+ 本轮指令「把能清的族清掉一批」;发号口此前由"悬空引用"校验关着,**2026-10-29 起只由本表与评审关**。**数值游标不冻结**:L1/L2/…各段最高号不同,单一数字游标反而放行低号 |
|
||||
| `F-<MOD>-<nn>` | 网络设计条目 | **批次一已转「`编号治理历史.md` §7 条目名」,即日停发**:代码与测试侧 0 处;属主文档 `编号治理历史.md` §7 编号列 13 条仅作历史对照(定义行不算引用) | 同上;引用旧号命中别名表 `retired-to-phrase` = **只读**,不构成发号许可 |
|
||||
| `N-*` | 登记簿 `N-<nn>` | 4 处 / 3 个字面量 | 登记簿已于 2026-10-26 全面改语义短语,残留的是漏网的旧引用 |
|
||||
| `TM-*` | 阶段/里程碑 `TM-<code>-<nn>` | 1 处 | 阶段号本就属 §3.3 明令不许写号的那类 |
|
||||
| `T-TEST` | 台架阶段 **裸形**(字母 T 紧跟一位数字) | 活引用 0(批次一清零;余下是本文自述与技能文档的形态举例) | 裸形最危险(同字母多义),且改「台架阶段一」即可 |
|
||||
| `F-*` | 网络设计条目 `F-<MOD>-<nn>` | 动手前 38 处 / 12 个 → **2026-10-27 批次一收口**:正文 9 处(含一个从来没有定义行的 DRV 段孤儿号,删号)+ 主机侧单测注释 1 处改「条目名」,仅属主文档 `编号治理历史.md` §7 历史对照表留 12 条编号 | 代码注释按号回指设计条目,但没有任何脚本匹配它 |
|
||||
|
||||
- **新增族的唯一正当条件**:有脚本要按字面量匹配它。顺序固定:先改 `id_registry.json`(含属主登记处)
|
||||
→ 再写属主文档 → 最后才允许在别处引用。反过来做必然造出悬空号。
|
||||
- **新增族:2026-10-29 起一律不许**,先问用户。真批下来,顺序固定:先改本文 §4.1(写清哪个脚本按字面量
|
||||
匹配它)→ 再写属主文档 → 届时有机器面才动 `scripts/archive/id_gate/id_registry.json` → 最后才允许在别处
|
||||
引用。反过来做必然造出悬空号。
|
||||
- 模块码是既定缩写,事实源 = registry 的族 pattern 与属主文档标题行,**别自创**(`编号治理历史.md` §6)。
|
||||
|
||||
### 4.1.1 已停发的族(别再指,更别再"复用"这些号)
|
||||
@@ -129,36 +135,41 @@
|
||||
|---|---|---|
|
||||
| 登记簿 `N-x` / `S-x` / `T-x`、专项 `A-x`、已删报告的 UI 簇 `U1~U5`(2026-10-26 方案 B) | 语义短语——**登记簿表首格本身就是短语**,如「双转换器无共同采样触发」「CI 远端闭环未验证」 | `scripts/archive/id_gate/id_aliases.json` 标 `retired-to-phrase` |
|
||||
| 网络设计条目 `F-NET/F-DRV/F-MS-<nn>`(12 个,2026-10-27 批次一) | 「`编号治理历史.md` §7 + 条目名」短语,如 `(`编号治理历史.md` §7「全局读即清搁浅事件」)`;条目名的权威清单 = `docs/网络/网络自愈架构设计.md` `编号治理历史.md` §7「条目名」列;那个 DRV 段孤儿号按本节末句**删号留事实**、未补定义 | `scripts/archive/id_gate/id_aliases.json` 标 `retired-to-phrase`(13 条)+属主文档 `编号治理历史.md` §7 编号列仅作历史对照 |
|
||||
| 「数据可信」任务卡号(中文前缀「卡」+ 数字,形态见 `编号治理历史.md` §9.3;2026-10-27 批次五) | §5 表的**标识列直接写任务名**(「端子/子板变比确认」「两点标定与精度验收」),代码注释与文档写语义短语(「标定折算落地」「未标定闭锁」「遥测快照发布」…);**逐号换算表见本文 `编号治理历史.md` §9.3** | ⚠ **机器面空白**:中文前缀不进 `scripts/archive/id_gate/check_ids.py` 语料(族 pattern 全是拉丁形态)→ 门禁咬不到,今天只有本表 + `编号治理历史.md` §9.3 + 评审三条兜;`id_aliases.json` 刻意不登记(登记了机器也看不见,只多一张没人核的表) |
|
||||
| 测试验证层级号(`L0`、`L0.5`、`L1~L3` 这一族,**注意与 UI 设计令牌层 L0、硬件位号 L1~L3、采集链旧层名等同形族无关**;2026-10-28 批次六) | 文档与 CI 步骤名写语义名(静态门禁 / 主机侧纯逻辑单测 / 板端测试套件 / 主机侧回归 / 人工目检);ASCII 短码(static / preflight / commitmsg / size / host-unit / coverage)**只进**日志标签、命令行旗标、CI job id、日志文件名 | ⚠ **同形族警示**:逐号换算表与判族表见 `scripts/archive/id_gate/编号治理历史.md` §10 与 §10.1(撞族六个,动手前先判族);本族虽是拉丁形态但**门禁咬不到**——`id-check` 同日退役、现行 `id-check-new` 只看新增行里的「字母+数字」形态,停发只靠本表 + 归档换算表 + 评审 |
|
||||
| 「数据可信」任务卡号(中文前缀「卡」+ 数字,形态见 `编号治理历史.md` §9.3;2026-10-27 批次五) | §5 表的**标识列直接写任务名**(「端子/子板变比确认」「两点标定与精度验收」),代码注释与文档写语义短语(「标定折算落地」「未标定闭锁」「遥测快照发布」…);**逐号换算表见本文 `编号治理历史.md` §9.3** | ⚠ **这一族从来没有机器面**:两道门禁的语料族 pattern 全是拉丁形态,中文前缀本就不在其中,何况两道都已退役 → 只有本表 + `编号治理历史.md` §9.3 + 评审三条兜;`id_aliases.json` 刻意不登记(没有机器读者,登记等于多一张没人核的表) |
|
||||
| 测试验证层级号(`L0`、`L0.5`、`L1~L3` 这一族,**注意与 UI 设计令牌层 L0、硬件位号 L1~L3、采集链旧层名等同形族无关**;2026-10-28 批次六) | 文档与 CI 步骤名写语义名(静态门禁 / 主机侧纯逻辑单测 / 板端测试套件 / 主机侧回归 / 人工目检);ASCII 短码(static / preflight / commitmsg / size / host-unit / coverage)**只进**日志标签、命令行旗标、CI job id、日志文件名 | ⚠ **同形族警示**:逐号换算表与判族表见 `scripts/archive/id_gate/编号治理历史.md` §10 与 §10.1(撞族六个,动手前先判族);本族虽是拉丁形态但**门禁咬不到**——两道编号门禁已退役(全库校验 2026-10-28、新增行检查 2026-10-29),停发只靠本表 + 归档换算表 + 评审 |
|
||||
| `E-xx` 审计发现、`P0-x`~`P3-x` 审计条目、报告六族 `D/R/F/N/U/S`、复审残号 `S1~S14`/`A-x`/`B1~B10`/`T-x`、模拟器差距 `H-x`/`L-x`、`TM-*` 阶段号、`DP-*`、`gen`/`round`/会话临时号 | 写条目本身("原子快照取数"、"队头阻塞"、"台架日志代次 288"、"第 3 轮台架") | 别名表标 `void`;**部分死号故意不进别名表**,好让门禁继续咬残留引用 |
|
||||
| 规范曾规划却从未启用的前缀族(`DEF`/`WI`/`DR`/`OQ`/`RSK`/`GAP`/`SC`/`GEN`/`MS`/`UL`/`LAT`/`CS` 等) | 同上,写事实 | 无定义,本就不该存在 |
|
||||
|
||||
> 想知道某个号还活着吗:跑 `scripts/archive/id_gate/check_ids.py --top 0`,或直接查 `id_aliases.json`——命中 `void` /
|
||||
> `retired-to-phrase` 就是死号。**看到查不到的号,正确动作是删号留事实,不是补一个定义。**
|
||||
> 想知道某个号还活着吗:查 `id_aliases.json`(命中 `void` / `retired-to-phrase` 就是死号),或手工跑
|
||||
> `scripts/archive/id_gate/check_ids.py --top 0`——**报告制,不影响任何退出码**。**看到查不到的号,
|
||||
> 正确动作是删号留事实,不是补一个定义。**
|
||||
|
||||
### 4.3 冻结前缀(历史快照,正文不回改)
|
||||
族还在注册表里、但属主登记处已删的:**只为让冻结历史行不报"未登记前缀"而保留登记**,游标一律冻结,
|
||||
不得新增、不得复用。
|
||||
族还在 `scripts/archive/id_gate/id_registry.json` 里、属主登记处已删的那批:登记留着只为让归档普查
|
||||
不报"未登记前缀",游标一律冻结,**不得新增、不得复用**。⚠ 2026-10-29 起这份登记**没有机器读者**,
|
||||
它是档案不是闸门——别指望写一个冻结族的号会有人来拦。
|
||||
|
||||
### 4.4 这些**不是**编号——别顺手清,也别当门禁漏洞
|
||||
### 4.4 这些**不是**编号——别顺手清
|
||||
元器件位号 `U1`/`C12`/`R3`/`L5`/`D7`(硬件语料按目录整体排除)、数学与协议记号(`N-1`、测频的正序与
|
||||
负序分量 `S1`/`S2`、`SHA-256`、`RS-485`、`ADC1_IN14`、手册页码)、活表代号 `E0~E19`(网络自愈 §5.2,
|
||||
**无连字符**,与死号 `E-nn` 只差一个连字符)、Socket 状态组合 `U1~U15`(网络自愈 §1.1)、档位词 `P0~P3`。
|
||||
判据只有一条:**带连字符、且能在属主登记表查到定义**才叫编号;查不到的就是普通文本,不是「漏登记的号」。
|
||||
|
||||
> 2026-10-27 补两条实测口径:① **元器件位号目录整体排除**——`tools/netlist_semantics/` 是硬件知识库
|
||||
> (字母 T 紧跟数字在那里是网络变压器位号),已进 `id_registry.json` 的 `excluded_paths`,与 `docs/硬件平台/` 同口径;
|
||||
> ② **唯一已知误判**:`docs/算法/傅氏算法详解.md` 里的数学记号 `N-1`(「一个周波 N 个点」的求和上界)会被
|
||||
> `N-x` 族命中,属可判定引用、不影响两根护栏,**不清**——清了反而破坏公式。
|
||||
> 两条口径来自当年的语料审计,今天判"是不是编号"照它想:① **元器件位号整目录排除**——
|
||||
> `tools/netlist_semantics/` 是硬件知识库(字母 T 紧跟数字在那里是网络变压器位号),与 `docs/硬件平台/`
|
||||
> 同口径(归档普查把它写在 `id_registry.json` 的 `excluded_paths`);② **唯一已知误判**:
|
||||
> `docs/算法/傅氏算法详解.md` 里的数学记号 `N-1`(「一个周波 N 个点」的求和上界)形似 `N-x` 族,
|
||||
> 属可判定的普通文本,**不清**——清了反而破坏公式。
|
||||
|
||||
## 5. 分配、引用、销项
|
||||
|
||||
### 5.1 新增编号
|
||||
先过 §1 三问。确实要发号:① 在 `id_registry.json` 确认族与游标 ② 在属主登记处写定义行 ③ 才允许在
|
||||
别处引用。**没有属主登记处的号不许存在**——门禁的校验 3 就是照这句实现的。
|
||||
**族不在 §4.1 那 5 行之内的,第 ① 步就该走不通**(`TC-*` 已停发:即使在 registry 里查得到它、游标还是活的,
|
||||
也不构成"能发号"的理由,见 §4.1 末框)。
|
||||
### 5.1 新增编号:默认答案是没有
|
||||
|
||||
一律不发(§1、§4.1)。新条目写语义短语(§3.3),登记处的行名就是标识;引用既有号必须能反查到定义、
|
||||
旁边必须带人话(§1 三条硬规矩)。**唯一入口是用户裁定**:某样新东西确实要被脚本按字面量匹配时先问,
|
||||
批下来再按 §4.1 末条的顺序落文档。
|
||||
`scripts/archive/id_gate/id_registry.json` 的族与游标登记**只在恢复机器面时才动**(§8.5):没有脚本读它,
|
||||
写进去也拦不住任何人——**「registry 里查得到」从来不是发号许可**,许可只来自用户裁定。
|
||||
|
||||
### 5.3 代码注释
|
||||
**优先写短语**(`FTP 鉴权`、`监听生命周期模块化`),别写字母号;确需引用活号时必须带属主文档名。
|
||||
@@ -167,7 +178,13 @@
|
||||
|
||||
### 5.5 销项与归档
|
||||
删登记行 + **同步清掉全仓引用**(清引用是销项的一部分,不是"以后再说")+ 别名表标 `void`。
|
||||
冻结的历史(变更记录行、`[示例]` / `[计划]` 行)不回改,门禁对它们已豁免。**现状补充**:2026-10-28 起新文档一律不再设「变更记录」节(`docs/文档规范.md` §5.1),所以这条冻结规则只对**仍保留变更记录表的 5 篇**(同一份清单)与 `scripts/archive/id_gate/` 下的归档文档生效——不是「以后还能往变更记录里写」的理由。**唯一例外**(用户裁定 2026-10-28):这类行里**已停发 / 作废族的示例编号**可改成形态描述(写成「字母紧跟数字」这类说法)——改的是示例的写法,不是历史事实;**保留现存号**族的游标数字与硬件位号举例不在例外内,形态化等于毁证据。
|
||||
冻结的历史(`[示例]` / `[计划]` 行、仍保留变更记录表的文档)不回改。**2026-10-28 起新文档一律不再设
|
||||
「变更记录」节**(`docs/文档规范.md` §5.1——例外清单以那一节为准,本文不复制份数,抄一份就会漂一次)。
|
||||
所以"冻结历史行"只对清单里的文档与 `scripts/archive/id_gate/` 下的归档文档生效,不是「以后还能往变更
|
||||
记录里写」的理由。**唯一例外**(用户裁定 2026-10-28):这类行里**已停发 / 作废族的示例编号**可改成形态
|
||||
描述(写成「字母紧跟数字」这类说法)——改的是示例的写法,不是历史事实;**保留现存号**族的游标数字与
|
||||
硬件位号举例不在例外内,形态化等于毁证据。原先那句「门禁对它们已豁免」随门禁一起失效:
|
||||
现在没有任何门禁会去看这些行。
|
||||
|
||||
### 5.7 语义标识的登记与别名
|
||||
短语就是登记处的行名,不需要别名表;**只有"号转短语"**时才在别名表留一条 `retired-to-phrase`,
|
||||
@@ -175,43 +192,41 @@
|
||||
|
||||
---
|
||||
|
||||
## 8. 门禁(2026-10-28 起:只有一道)
|
||||
## 8. 机器面:2026-10-29 起一道也没有
|
||||
|
||||
**现行门禁 = `id-check-new`**(钩子同名):只查 `git diff` 的**新增行**里有没有未登记形态的
|
||||
「字母+数字」标识;零语料、零维护。判据、白名单与盲区 → `docs/门禁说明/id-check-new.md`。
|
||||
**现行没有编号门禁**:2026-10-29 用户裁定把最后那道只查新增行的 `id-check-new` 也摘了(钩子口径
|
||||
52 → 51,脚本与白名单移入 `scripts/archive/id_gate/check_ids_new.py`,留档 `docs/门禁说明/id-check-new.md`,
|
||||
理由与手工跑法都在那篇)。判据回到本文 §1 的默认答案(一律不发)、§4.1 存量族表与人工评审。
|
||||
|
||||
本节以下(`编号治理历史.md` §8.1~`编号治理历史.md` §8.4)是**已退役**的全库语料审计(`id-check`,八项校验)的留档:
|
||||
脚本与两份语料已整体移入 `scripts/archive/id_gate/`,不再进提交门禁,
|
||||
只在你手工复查「存量编号现状」时才跑。退役理由与判据替换见 §8.5;
|
||||
八项校验一句话版(历史口径):① 未登记前缀 ② 形态不合规 ③ 悬空引用 ④ 重复定义
|
||||
⑤ 冻结族越游标 ⑥ 编号未紧邻文档名 ⑦ 孤儿条目(告警)⑧ 可判定比例 + 两根护栏。
|
||||
全库八项校验(未登记前缀 / 形态不合规 / 悬空引用 / 重复定义 / 冻结族越游标 / 未紧邻文档名 / 孤儿告警 /
|
||||
可判定比例 + 两根护栏)2026-10-28 退役:它查过什么、为什么退、当年清点数字 → `docs/门禁说明/id-check.md`
|
||||
与 `scripts/archive/id_gate/编号治理历史.md`。**本文不抄第二份口径**——那份口径已经数错过一次了。
|
||||
|
||||
### 8.5 门禁退役路线(2026-10-28 裁定)
|
||||
### 8.5 手工普查与恢复机器面
|
||||
|
||||
目标不是"关掉检查",而是把成本从**养 99 族语料**降到**禁止新编号**。三步,每步可独立回滚:
|
||||
**没有门禁不等于没有工具**,两份脚本都还在,随时能跑(它们退成了工具,不是消失了):
|
||||
|
||||
| 步 | 内容 | 状态 |
|
||||
|---|---|---|
|
||||
| ① | 两根护栏(比例基线 / 不可判定绝对数上限)**转报告制**:照打数字,不再影响退出码 | **已完成 2026-10-28** |
|
||||
| ② | 用 **diff 级新号拦截**替换语料审计:只看新增行里命中"字母+数字"形态、且不在白名单里的字面量,FAIL 并提示改成语义短语;白名单 = 前缀级两张表(活族 11 + 器件/协议/点号约 90),基本不增长 | **已完成 2026-10-28**(`scripts/check_ids_new.py` + 钩子 `id-check-new`,与 `id-check` **双跑观察期**;详解 `docs/门禁说明/id-check-new.md`) |
|
||||
| ③ | 退役语料:99 族语料+脚本整体移入 `scripts/archive/id_gate/`(保留可复现性,不再进提交门禁);`check_ids.py` 降为手工报告工具,钩子摘除(口径 52 → 51) | **已完成 2026-10-28**(用户裁定跳过观察期直接做;`docs/门禁说明/id-check-new.md` 是唯一现役编号门禁) |
|
||||
```text
|
||||
scripts/archive/id_gate/check_ids_new.py --range A..B # 只看一段提交的新增行里有没有自造形态
|
||||
scripts/archive/id_gate/check_ids.py --top 0 # 全库存量普查(报告制,不影响退出码)
|
||||
```
|
||||
|
||||
**在 ② 落地之前,校验 1~6 仍是唯一的机械牙**:别因为"两根线不拦了"就顺手写号——
|
||||
新增的未登记前缀照样当场 FAIL(实测:在一个新文件里写一个自造前缀 + 数字,校验 1 与增量拦截双拦)。
|
||||
- **恢复机器面的成本**:把脚本放回 `scripts/`、在 `.pre-commit-config.yaml` 重挂一条钩子、口径 +1,
|
||||
并同批改 `AGENTS.md` 与 `docs/门禁说明/README.md` 两处计数——三处一起做,只挂钩子会在别人提交里露馅。
|
||||
- **为什么走到"一道也没有"**:2026-10-28 退全库八项校验(99 族语料养不起,成本落在每次碰文档的提交上),
|
||||
2026-10-29 退最后那道新增行拦截(用户裁定:它咬形态不咬语义,白名单本身成了维护面)。两道各自拦下过
|
||||
什么、退役当日那次误拦(录波段文件名 `WAVE00.BIN`)→ `docs/门禁说明/id-check-new.md` 与
|
||||
`docs/门禁说明/id-check.md`;四步流水与当时的探针数字 → `scripts/archive/id_gate/编号治理历史.md`。
|
||||
|
||||
---
|
||||
|
||||
## 10. 速查
|
||||
- 看到一个不认识的号 → 先查 `id_aliases.json` 与属主登记表;查不到就**删号留事实**,不要补定义。
|
||||
- 想给待办 / 缺陷 / 差距 / 风险发号 → 不许,写短语(§3.3)。
|
||||
- 想给任何新条目发号(含需求 `FR-*`、踩坑 `TRAP-*`、用例 `TC-*`、待办与风险)→ 不许,写语义行名(§3.3 / §4.1)。
|
||||
- 想知道「还有多少号可清」→ 看 `编号治理历史.md` §9 的存量表与三批计划。
|
||||
- 想给文档内步骤或测试阶段发号 → 不许,写「步骤 3」「阶段 7」(§3.3)。
|
||||
- 想新增前缀族 → 先回答"哪个脚本要按字面量匹配它"(§4.1);答不出就别建。
|
||||
- 想新增前缀族 → 先问用户(Ask first);「有脚本要按字面量匹配它」这个旧判据自 2026-10-29 起不再构成许可(§1)。
|
||||
- 要改号 → `scripts/rename_ids.py` + `编号治理历史.md` §7.3 五步。
|
||||
- 门禁报错读不懂 → `docs/门禁说明/id-check.md`。
|
||||
|
||||
---
|
||||
|
||||
## 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
- 编号形态拿不准 → 本文 §1 的默认答案 + §4.1 那张表;**没有任何机器会替你兜**。两道门禁的历史与退役理由 →
|
||||
`docs/门禁说明/id-check.md`、`docs/门禁说明/id-check-new.md`。
|
||||
- 想让机器帮你扫一批改动,或要恢复机器面 → §8.5。
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@
|
||||
| `TRAP-NET-nn` | **陷阱记录**条目 | 同上 |
|
||||
| `N-nn` | `docs/任务规划/登记簿.md` 的**任务/发现编号**(⚠ 已销项的号不再可查,本文引用处即其最终事实描述) | 该登记簿 |
|
||||
| `D1`~`D7` | 本文**决策记录** | §10 |
|
||||
| `自愈收敛阶段 N` | 2026-10 的分阶段实施标签,**只存在于历史文档与归档**(2026-10-28 清理流水后全仓仅剩 5 篇保留的变更记录)(代码注释已全部改为平实语言);勾选清单已归档(§12)——**勿用它叙述设计,也勿再写回注释** | §12 |
|
||||
| `自愈收敛阶段 N` | 2026-10 的分阶段实施标签,**只存在于历史文档与归档**(2026-10-29 起全仓仅剩 5 篇保留的变更记录真台账)(代码注释已全部改为平实语言);勾选清单已归档(§12)——**勿用它叙述设计,也勿再写回注释** | §12 |
|
||||
| `TSL` / `TSC` / `TSD` | SPI 命令与数据之间的**时序间隔**要求 | `docs/芯片手册/CH395F.md` §7.4 |
|
||||
| `n30` | 压测脚本 `n30_repro.py`(:21 背靠背 STOR 30 轮),自愈回归的**主判据** | `test/host/` |
|
||||
| 沉降窗 / 隔离期 | 释放会话后、重建监听前的**最短等待**(GRACEFUL 100 ms / QUARANTINE 400 ms) | §4.3 |
|
||||
@@ -868,5 +868,5 @@ accept 走 `handle_connect_event`),且 `NET_RECON_STABLE_DEATH` 两边共用
|
||||
`git log --oneline -- docs/网络/N34-自愈审计/`;
|
||||
- 监听生命周期(本文 §1.1~§1.4 / §4.9 / §9~§11 的来源,含 P0~P3 勾选清单与台架验收记录):
|
||||
`git log --oneline -- docs/子系统设计/网络Socket监听生命周期设计说明.md`;
|
||||
- `自愈收敛阶段 N` 只出现在历史文档与归档里(变更记录节已清理,仅 5 篇保留)(代码注释已清理为平实语言)——
|
||||
- `自愈收敛阶段 N` 只出现在历史文档与归档里(变更记录节已清理,全仓只剩 5 篇真台账)(代码注释已清理为平实语言)——
|
||||
**勿再引入**,设计含义一律以本文 §2~§4 为准。
|
||||
|
||||
+13
-11
@@ -1,7 +1,7 @@
|
||||
# 门禁逐项说明(每种门禁一篇)
|
||||
|
||||
> 本目录逐项说明各门禁:**声明的 52 个钩子到底在干什么、拦谁、放谁、什么时候会骗人**——其中 3 根慢钩子带
|
||||
> `stages: [manual]`,一次 `git commit` 只实跑 49 根(口径见下条)。
|
||||
> 本目录逐项说明各门禁:**声明的 51 个钩子到底在干什么、拦谁、放谁、什么时候会骗人**——其中 3 根慢钩子带
|
||||
> `stages: [manual]`,一次 `git commit` 只实跑 48 根(口径见下条)。
|
||||
> 每个门禁一篇,八段固定结构:查什么 / 为什么要它 / 触发面 / 报错怎么读 / 好处 /
|
||||
> **限制与盲区** / 豁免与基线 / 改门禁本身要注意什么。**最后一节是这套文档存在的理由**——
|
||||
> 一份只看"拦了什么"的门禁文档,会让人以为绿就是对的。
|
||||
@@ -9,10 +9,12 @@
|
||||
- 配置事实源:`.pre-commit-config.yaml`。**钩子数的唯一口径 = YAML 解析后的 hook 条目数**,不是文件里的文本行数。
|
||||
自查(打印声明数;加/删一条钩子后,这个数要与 **AGENTS.md 的「共 N 个钩子」**、**本目录 §2 标题**同时对齐):
|
||||
`& '.venv\Scripts\python.exe' -c "import yaml; d=yaml.safe_load(open('.pre-commit-config.yaml', encoding='utf-8')); print(sum(len(r['hooks']) for r in d['repos']))"`
|
||||
→ **52**(2026-10-26:49 → 50 加 `generated-fresh`;2026-10-27:50 → 51 加 `nested-comment`;
|
||||
2026-10-28:51 → 52 加 `id-check-new`、随即 52 → 51 摘掉 `id-check`(退役全库语料审计);2026-10-29:51 → 52 加 `skill-size`(SKILL.md 写侧上限与 reference 指针,详见 [`skill-size.md`](skill-size.md)))。
|
||||
→ **51**(2026-10-26:49 → 50 加 `generated-fresh`;2026-10-27:50 → 51 加 `nested-comment`;
|
||||
2026-10-28:51 → 52 加 `id-check-new`、随即 52 → 51 摘掉 `id-check`(退役全库语料审计);2026-10-29:51 → 52 加 `skill-size`(SKILL.md 写侧上限与 reference 指针,详见 [`skill-size.md`](skill-size.md));
|
||||
同日 52 → **51** 退役 `id-check-new`——只查新增行的编号门禁整体退场,脚本与白名单移入
|
||||
`scripts/archive/id_gate/`,此后**编号形态没有机器面**(留档 [`id-check-new.md`](id-check-new.md)))。
|
||||
⚠ **别用 `grep -c` 去数配置里的声明行**:该文件里有一条**注释掉的**候选钩子(clang-format,第 65 行),
|
||||
文本 grep 会多算 1 = 52——「声明 52 / 文档 51 / 实跑 51」这种三个数不同源的争执就是这么来的。真要 grep,
|
||||
文本 grep 会多算 1(当前口径 51 会数成 52)——「声明 52 / 文档 51 / 实跑 51」这种三个数不同源的争执就是这么来的。真要 grep,
|
||||
用**锚定行首缩进**的模式(它天然跳过以 # 开头的注释行)2026-10-28 实测直接得 **51**(当时口径),与 YAML 口径一致;
|
||||
会多算 1(比口径多 1)的是**不锚定行首、按子串**匹配声明行的写法(那条被注释掉的候选也被它算进去)。
|
||||
⚠ 反过来的坑也真踩过(2026-10-27):在配置**注释里复述那个 grep 字面量**,会让文本 grep 再虚高 1——
|
||||
@@ -39,12 +41,12 @@
|
||||
| **改写型** | 不报错,直接改你的文件 | `trailing-whitespace`、`end-of-file-fixer` | 重新 `git add` 被改的文件再提交(它改的是工作区,不是索引) |
|
||||
| **硬拦截** | 违反即失败,没有存量豁免 | `cppcheck`、`coding-standard`、`doc-links`、`no-mojibake`、`mem-budget`、`ui-golden`、`font-charset`、`lv-conf-sync`、`task-table`、`mem-numbers`、`registration-check`、`cubemx-survival`、`test-suite-switch`、22 个主机单测、官方 5 个、`gitlint` | 只能改代码或改文档;**不要**找豁免开关 |
|
||||
| **增量拦截** | 规则恒生效,但存量钉在基线/台账里,只许减不许增 | `layer-deps`(3 条,2026-10-25 还清 UI 记录 3 条)、`bsp-err`(38 条)、`generated-fresh`(5 处 `docs/images/` 配图存量,脚本内 `BASELINE`)、`nested-comment`(2 处主机侧假件头,`test/host/common/`,脚本内 `BASELINE`) | 报错先分清是**新增**还是**存量搬家**:`--verbose` 看归类,`--prune` 看已还清 |
|
||||
| **只查新增** | 只看本次改动的增量,没有存量基线,也没有台账 | `id-check-new` | 改成语义短语,或把设备名/协议名登记进脚本白名单(行内 `[编号豁免]` 是最后手段) |
|
||||
| **只查新增** | 只看本次改动的增量,没有存量基线,也没有台账 | ~~`id-check-new`~~(**2026-10-29 退役**,脚本在 `scripts/archive/id_gate/`;这一姿态已无现役门禁) | 手工复查时才谈得上:改成语义短语(门禁不再跑,行内 `[编号豁免]` 也随之失效) |
|
||||
|
||||
⚠ 增量拦截最容易误会的一点:**绿灯不等于没问题**,只等于"没有比上次更差"。
|
||||
每个增量拦截门禁的基线数字都写进它自己的文档,读那篇时第一眼看的就是"现在在册多少条"。
|
||||
|
||||
## 2. 索引(52 个 hook → 29 篇,其中 1 篇是已退役门禁的留档 + 1 根只有本表指针(专篇待补)+ 1 根详解在 docs/测试说明/)
|
||||
## 2. 索引(51 个 hook → 29 篇,其中 2 篇是已退役门禁的留档 + 1 根只有本表指针(专篇待补)+ 1 根详解在 docs/测试说明/)
|
||||
|
||||
> 带 `stages: [manual]` 的三根(`cppcheck` / `test-cppcheck` / `test-coverage`)**提交期不跑**,只在远端 CI 的
|
||||
> 「手动阶段门禁」步与 `scripts/ci_local.py` 的 manual 段跑;下表对应两行的「文档」列已标注。
|
||||
@@ -53,8 +55,8 @@
|
||||
|---|---|---|---|---|
|
||||
| [`bsp-err.md`](bsp-err.md) | `bsp-err` | BSP 的对外 API 面:错误通道类型、超时宏命名、必须写返回值、返回值不得裸丢弃 | `Drivers/BSP/**` 或门禁自身 | 已写 |
|
||||
| [`layer-deps.md`](layer-deps.md) | `layer-deps` | 分层方向:`Drivers/BSP/**` 不得 include `App/**` | `Drivers/BSP/**` | 已写 |
|
||||
| [`id-check.md`](id-check.md) | ~~`id-check`~ | **已退役(2026-10-28)**:全库语料的八项校验,留档备查;现行编号门禁见下一行 | 在时有:几乎所有文本文件 | 已写(退役留档) |
|
||||
| [`id-check-new.md`](id-check-new.md) | `id-check-new` | **只查新增行**有没有新造编号(零语料前缀白名单,已停发族只告警) | 任意 md/c/h/py/yaml/txt | 已写 |
|
||||
| [`id-check.md`](id-check.md) | ~~`id-check`~ | **已退役(2026-10-28)**:全库语料的八项校验,留档备查;接班件也在下一行 | 在时有:几乎所有文本文件 | 已写(退役留档) |
|
||||
| [`id-check-new.md`](id-check-new.md) | ~~`id-check-new`~ | **已退役(2026-10-29,用户裁定)**:只查新增行的编号门禁,留档备查(含退役理由与手工跑法);**编号形态此后无机器面** | 在时有:任意 md/c/h/py/yaml/txt | 已写(退役留档) |
|
||||
| [`doc-links.md`](doc-links.md) | `doc-links` | 文档里的路径引用是否存在;`--sections-strict` 另查章节号是否还存在 | 任意 `.md` | 已写 |
|
||||
| [`cppcheck.md`](cppcheck.md) | `cppcheck`、`test-cppcheck` | 静态分析(真机侧与 `test/` 侧两套范围与抑制项) | C/H 源文件 | 已写(`stages: [manual]`) |
|
||||
| [`coding-standard.md`](coding-standard.md) | `coding-standard` | 编码规范 V1.0 的可机械检查子集(命名、头文件保护宏、模块头注释块…) | C/H 源文件 | 已写 |
|
||||
@@ -70,12 +72,12 @@
|
||||
| [`cubemx-survival.md`](cubemx-survival.md) | `cubemx-survival` | CubeMX 重生成会不会把定制静默抹掉(人工目检审计兜底) | `.ioc`、`Src/`、`Inc/` | 已写 |
|
||||
| [`test-suite-switch.md`](test-suite-switch.md) | `test-suite-switch` | 板级套件开关必须互斥;全注释 = 产品构建 | `test/board/test_config.h` | 已写 |
|
||||
| [`font-charset.md`](font-charset.md) | `font-charset` | UI 用到的汉字是否都在子集字体里(缺字 = 白框) | `Drivers/BSP/UI/**`、字体脚本 | 已写 |
|
||||
| [`ui-golden.md`](ui-golden.md) | `ui-golden` | UI 像素回归:菜单 8 状态 + 内容页 60 张黄金参考图比对 | UI 源码、模拟器 | 已写 |
|
||||
| [`ui-golden.md`](ui-golden.md) | `ui-golden` | UI 像素回归:菜单 8 状态 + 内容页 66 张黄金参考图比对 | UI 源码、模拟器 | 已写 |
|
||||
| [`bom-guard.md`](bom-guard.md) | `bom-guard` | 带 BOM 的文件被编辑工具剥掉 BOM(ARMCC 换解码 → 假语法错) | `Drivers/BSP/UI/**` 等名单内文件 | 已写 |
|
||||
| [`setview-check.md`](setview-check.md) | `setview-check` | 定值查看行表与 `prot_set` 枚举序 / `g_prot_set_meta` 上下限是否同一份口径 | `tools/setview_check/**`、`ui_w_setview.*` | 已写 |
|
||||
| (专篇待补,档位与限度正文在 [`../UI设计/UI_自动化测试流程.md`](../UI设计/UI_自动化测试流程.md) §7.3) | `uinav-selftest` | 板端 UI 导航器认屏快检(黄金参考帧分类;完整闭环档=合入前/上板前手工跑,快检不互替) | `scripts/board_ui_nav.py`、`simulator/tests/ref/*.bin` | 指针 |
|
||||
| [`host-unit-tests.md`](host-unit-tests.md) | 22 个 `*-unit-test` | 主机侧单测(同一机制 22 个实例):逻辑层在无硬件的 PC 上编译并跑断言 | 各自被测模块与其测试目录 | 已写 |
|
||||
| 详解在 [`../测试说明/主机单测覆盖率口径.md`](../测试说明/主机单测覆盖率口径.md)(口径 / 分母 / 基线 / 盲区都在那篇,本目录不重复) | `test-coverage` | **覆盖率总闸**:跑全部 41 套主机单测 + 已覆盖行只增不减的回退台账 | `App/`、`Drivers/BSP/`、`test/host/`、脚本与基线 | 已写(在 `docs/测试说明/`;`stages: [manual]`) |
|
||||
| 详解在 [`../测试说明/主机单测覆盖率口径.md`](../测试说明/主机单测覆盖率口径.md)(口径 / 分母 / 基线 / 盲区都在那篇,本目录不重复) | `test-coverage` | **覆盖率总闸**:跑全部 44 套主机单测 + 已覆盖行只增不减的回退台账 | `App/`、`Drivers/BSP/`、`test/host/`、脚本与基线 | 已写(在 `docs/测试说明/`;`stages: [manual]`) |
|
||||
| [`trailing-whitespace.md`](trailing-whitespace.md) | `trailing-whitespace` | 行尾空白(改写型;CubeMX/Keil 生成文件豁免) | 全部 | 已写 |
|
||||
| [`end-of-file-fixer.md`](end-of-file-fixer.md) | `end-of-file-fixer` | 文件末尾必须恰好一个换行(改写型;同豁免) | 全部 | 已写 |
|
||||
| [`check-added-large-files.md`](check-added-large-files.md) | `check-added-large-files` | 别把大文件(日志/镜像/二进制)误提进仓库 | 全部 | 已写 |
|
||||
|
||||
@@ -140,7 +140,3 @@ BSP 约定:命中 38(在册 38 / 新增 0),基线共 38
|
||||
- 加新的 must-use HAL 族:改 `HAL_STATUS_RE`,同时补一条正反例自测。
|
||||
- 改标签名:脚本与基线一次改完(§6 第 9 条),随后 `--emit-baseline` 复核数量不变。
|
||||
- 门禁自己的注释里不要写只有本次会话懂的代号;要引用规范就写`文件.md` + 紧邻编号。
|
||||
|
||||
## 9. 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -88,6 +88,16 @@ Drivers/BSP/X/x.c:42 [CODE-RULE-09] 函数 foo_bar 缺少函数注释(应含
|
||||
但写成 `Malloc(` 就绕过了(同形后缀也一样:`buf_free(` 因词边界不成立而不算违规)。
|
||||
8. CODE-RULE-20 **只管堆分配函数名**:`alloca` 与变长数组(VLA)是**栈**上的坑,不归它;
|
||||
分配不释放(泄漏)、释放后继续用(UAF)也不归它——本条只判"别碰 C 堆"这一件事。
|
||||
9. **CODE-RULE-09 的回看窗口只有 25 行**:脚本 `scripts/check_coding_standard.py:194` 取的是函数定义行
|
||||
往前 25 行(`window = lines[idx-26:idx-1]`),在里面找「函数功能」关键词。于是**函数头注释块一旦写
|
||||
到 25 行以上,关键词就被挤出窗口**——注释本身完全合规,也照样报「函数缺少函数注释块」。2026-10-29
|
||||
一次真实触发(lead 复跑抓到 6 处、已改 3 个生产文件,全部是**纯注释重排、无一行逻辑改动**):录波
|
||||
落盘道与 UI 道上四个带长口径注释的函数(`wave_store_drain`、`s_log_record_edge`、`App/task/ui_task.c`
|
||||
的录波按键 helper、`simulator/main.c` 的合成喂拍 helper)各撞一到两处。**写法上的规避**:把长口径
|
||||
另起一块放在函数注释块**之前**(两块分开),或把细节搬进函数体内——别让「函数功能」那一行离定义行超过 24 行。
|
||||
10. **不带文件参数就是空跑**:脚本 `:321` 是逐 `sys.argv[1:]` 处理的,**不给文件参数时一个字节都不查、
|
||||
直接返回 0**。手工复现这条门禁时把那个 RC=0 读成「过了」是错的——必须显式列出要查的路径(pre-commit
|
||||
里由框架喂 staged 文件,所以钩子本身不会空跑)。同族坑见 `docs/代码规范/pre-commit使用说明.md` §2。
|
||||
|
||||
补录(判据细节,迁移自原手册):
|
||||
|
||||
|
||||
@@ -85,7 +85,3 @@ D 类刻意只校验**紧邻写法**:文档名与 `§` 之间只允许空白
|
||||
|
||||
- 放宽匹配(例如允许裸名引用)前先想清楚:**误报压到 0 是靠"只认紧邻写法"换来的**。
|
||||
- 新增一类豁免,必须同时新增一类误报;把豁免写在 `docs/文档规范.md` 与本节里,别只写进代码。
|
||||
|
||||
## 9. 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -98,7 +98,3 @@ A 通过 + B 通过 ⟹ 字体覆盖"源码在用"与"刻意保留"的每一个
|
||||
|
||||
- 放宽"必需集"来源(比如不再扫某个目录)前先确认该目录的字最终确实会进生成链;
|
||||
- 与 `ui-golden` 是互补关系:本门管"有没有字形",golden 管"像素对不对"。
|
||||
|
||||
## 9. 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -88,30 +88,32 @@
|
||||
3. `include_dirs` 与源文件清单是**手写**的:新依赖一个目录就会假失败(见 §4 第 2 类),反过来,
|
||||
漏了某个真实依赖可能让测试**编过却少编了一个翻译单元**(少测而不自知)。
|
||||
4. **专属钩子不是唯一防线,而「没有专属钩子」也不等于「没被跑」**。2026-10-26 按 git 历史与实测重新
|
||||
清点(2026-10-28 复点:下列数字已按复点值更新;上一版写「16 个模块有专属钩子、另有 12 套、总闸跑 28 套」——三个数都随 2026-10-25 的
|
||||
清点(2026-10-28 复点、2026-10-29 录波三套入库后再复点:下列数字已按复点值更新;上一版写「16 个模块有专属钩子、另有 12 套、总闸跑 28 套」——三个数都随 2026-10-25 的
|
||||
六根新钩子与后续新增套件过期了,正是本目录反复在防的"文档与机器面不同码"):
|
||||
- **22** 根专属钩子 = 有独立 `files:` 触发面、需要**秒级归因**的那个子集;
|
||||
- `test/host/` 下带 `run_unit_test.py` 的套件实测 **41** 套(43 个目录,除 `common` 与
|
||||
`__pycache__`)⇒ **没有专属钩子的是 19 套**:`boot_stats`、`cfg_file_store_mcu`、`console_log`、
|
||||
- `test/host/` 下带 `run_unit_test.py` 的套件实测 **44** 套(46 个目录,除 `common` 与
|
||||
`__pycache__`;两个数分别来自 `check_test_coverage.py --list` 与 `ls -d test/host/*/`,2026-10-29 实测)
|
||||
⇒ **没有专属钩子的是 22 套**:`boot_stats`、`cfg_file_store_mcu`、`console_log`、
|
||||
`ftp_cfg`、`iec104_serv`、`lftpd`、`lftpd_inet`、`lftpd_io`、`net_cfg`、`net_events`、
|
||||
`net_listener`、`net_pending`、`net_select`、`net_socket`、`prot_set_store`、`soe_store`、
|
||||
`sys_log_mcu`、`task_prof`、`ui_sink`;
|
||||
- 这 19 套每次 CI 与 `scripts/ci_local.py` 都由 `test-coverage` **总闸**真跑(该钩子带 `stages: [manual]`:
|
||||
`sys_log_mcu`、`task_prof`、`ui_sink`、`wave_ring`、`wave_store`、`wave_trig`(末三套 2026-10-29 随
|
||||
故障录波入库,按同一条 Lead 裁定不补专属钩子;22 = 44 套 − 22 根专属钩子,逐点数与两处机器源互证);
|
||||
- 这 22 套每次 CI 与 `scripts/ci_local.py` 都由 `test-coverage` **总闸**真跑(该钩子带 `stages: [manual]`:
|
||||
`git commit` 阶段不跑它,这一根与 `cppcheck` / `test-cppcheck` 同属 CI 与 `ci_local.py` 的 manual 段);
|
||||
发现规则是
|
||||
`HOST_TEST_DIR.rglob("run_unit_test.py")`(与 `scripts/ci_local.py` 的主机侧纯逻辑单测同一口径),
|
||||
退出码语义写死「0 = 全部单测通过且覆盖率相对基线没回退 / 1 = 有单测失败或覆盖率回退」⇒
|
||||
**失败照样拦(CI 与 `ci_local.py` 的 manual 段)**。差的只是归因粒度(要在总闸输出里认出是哪套)与耗时(41 套全量比单根慢得多);
|
||||
**失败照样拦(CI 与 `ci_local.py` 的 manual 段)**。差的只是归因粒度(要在总闸输出里认出是哪套)与耗时(44 套全量比单根慢得多);
|
||||
- 要不要给某套补第 23 根,判据现成:pre-commit 4.x 没有 `always: true`,**该模块有没有自己的
|
||||
`files:` 触发面**决定补了会不会真跑;只为凑数加一根,等于把「快归因面」和「全量把关」两件事混成
|
||||
一件,还要让钩子计数再改一次;
|
||||
- **裁定(2026-10-27,Lead):这批无专属钩子套件(裁定当时 18 套,2026-10-28 现值 19 套;含 `test/host/ui_sink`)不补专属钩子,差异按设计说明记录。**
|
||||
- **裁定(2026-10-27,Lead):这批无专属钩子套件(裁定当时 18 套,2026-10-28 为 19 套,2026-10-29 现值 22 套;含 `test/host/ui_sink` 与新增的 `wave_ring` / `wave_store` / `wave_trig`)不补专属钩子,差异按设计说明记录。**
|
||||
理由不是省事:22 根专属钩子存在的唯一意义是**秒级归因**;而 `ui_sink` 改动面小,它的失败面全部
|
||||
落在 `test-coverage` 总闸的必跑路径里,坏了照样在 CI / `ci_local.py` 拦下。再加一根只是多一个必须「声明数 / 文档数 /
|
||||
实跑数」三处同源同步的计数点——本仓 2026-10-26 到 27 一天之内就为 40 / 50 / 51 这三个数不同源
|
||||
返工过两轮。**要读「这套跑没跑」,读总闸输出,不要来数钩子。** 两个方向的空头账一起堵住:
|
||||
① 别把没接线的套件算进钩子账(原写「23 个 / 含 ui-sink」正是这么错的;2026-10-27 复核的准数是
|
||||
22 根 / 40 套 / 18 套无专属钩子——第 41 套 `boot_stats` 当日 11:29 才入库,此后两数是 41 / 19,以本条上面的实测行为准);② 也别把总闸的绿读成「每套都被单独归因过」——它只保证
|
||||
22 根 / 40 套 / 18 套无专属钩子(三个数都是当时值)——第 41 套 `boot_stats` 当日 11:29 才入库,此后两数当时是 41 / 19,现值 44 / 22 以本条上面的实测行为准);② 也别把总闸的绿读成「每套都被单独归因过」——它只保证
|
||||
「全跑过 + 覆盖率没回退」。将来确实要给某套补专属钩子,先过上一条判据,并在**同一笔**里改
|
||||
AGENTS 与门禁索引的计数(口径 = YAML 解析条目数,见 `docs/门禁说明/README.md` §2 首条)。
|
||||
- 总闸的触发面是 `App/`、`Drivers/BSP/`、`test/host/`、覆盖率脚本与基线,**外加门禁自身**
|
||||
@@ -129,8 +131,8 @@
|
||||
- 用例号段:`TC-HOST-<模块码>` 以 `test/host/<目录>/*` 为属主登记处(`docs/编号规范.md` §4.1);
|
||||
**该号段 2026-10-27 起停发新号**(用户裁定「批次三,不坐」):新增用例**不配号**,写语义化测试函数名,
|
||||
现存号只读、不清、不复用。追溯器只双向比对已有集合,**不会**因为新用例没号而报错——别把它当"要带号"。
|
||||
**机器口径同日已落地**:`scripts/archive/id_gate/id_registry.json` 37 个 TC 类族全部 `frozen: true`,34 个子族游标冻结在 2026-10-27 存量最高号,
|
||||
在号段上再加新号 = `id-check` 校验 5 错误级;无专属子族的模块码落在主族 `TC`(游标仍为空),那一面由校验 3 + 冻结水位 + 评审兜住
|
||||
**当时的机器口径**(2026-10-27 落地,如今只剩留痕):`scripts/archive/id_gate/id_registry.json` 37 个 TC 类族登记 `frozen: true`、34 个子族游标冻结在当日存量最高号,
|
||||
当年在号段上再加新号 = `id-check` 校验 5 错误级;无专属子族的模块码落在主族 `TC`(游标仍为空)。**两道编号门禁已退役(2026-10-28 / 29),这些校验都不再跑**,那一面如今只由规范与评审兜住
|
||||
→ `docs/编号规范.md` §4.1 末框与 `scripts/archive/id_gate/编号治理历史.md` §9.1 末段。
|
||||
|
||||
## 8. 改这套门禁本身(新增一个模块的宿主单测)
|
||||
|
||||
+58
-79
@@ -1,102 +1,81 @@
|
||||
# id-check-new(编号门禁·新增行)
|
||||
# `id-check-new` — 编号门禁·新增行(零语料)——**已退役(2026-10-29)**
|
||||
|
||||
> 一句话:**只查这次改动新增的行里有没有新造的编号**,不维护任何族清单。
|
||||
> 它是 `id-check` 全库语料审计的接班件(退役路线见 `docs/编号规范.md` §8.5)。
|
||||
> ⚠ **本文是退役留档,不要照着它去改门禁。**
|
||||
> 2026-10-29 用户裁定:这道门禁整体去掉——钩子从 `.pre-commit-config.yaml` 摘除(YAML 解析口径
|
||||
> 52 → 51),脚本移入 `scripts/archive/id_gate/check_ids_new.py`(仍可手工跑,`--self-test` 保留)。
|
||||
> **此后编号形态没有任何机器面**:判据只剩 `docs/编号规范.md` §1 的默认答案(一律停发)、§4.1 存量族
|
||||
> (穷举)与人工评审。前辈 `id-check`(全库八项校验)已于 2026-10-28 退役 → `docs/门禁说明/id-check.md`;
|
||||
> 两步合起来的路线 → `docs/编号规范.md` §8.5。
|
||||
> 下面 §1~§6 是退役前的原样描述,只在手工复查「这批改动 / 这段历史里有没有野生编号」时用得上。
|
||||
|
||||
## 1. 它在查什么
|
||||
## 1. 它当初在查什么
|
||||
|
||||
拿 `git diff` 的**新增行**,只做三件事:
|
||||
|
||||
| 形态 | 例子(形态描述,不是本仓号) | 判据 |
|
||||
|---|---|---|
|
||||
| 破折形态 | 两字母前缀 + 连字符 + 数字 | 首段不在白名单 → **FAIL**;在「已停发族」→ 只 WARN |
|
||||
| 裸形态 | 大写字母串紧跟数字(外设/器件/点号写法) | 前缀不在白名单 → **FAIL** |
|
||||
| 单字母 + 数字 | 元件位号与层次号同形的那些 | **一律放行**(见 §6 盲区) |
|
||||
| 破折形态 | 两字母前缀 + 连字符 + 数字 | 首段不在 `KNOWN_DASH` → **FAIL**;在 `RETIRED_DASH`(已停发族)→ 只 WARN |
|
||||
| 裸形态 | 大写字母串紧跟数字(外设 / 器件 / 点号写法) | 前缀不在 `KNOWN_BARE` → **FAIL** |
|
||||
| 单字母 + 数字 | `C12` / `U15` / `L3` / `R3` 这类元件位号与层次号同形 | **一律放行**(已知盲区,与旧门禁的"裸形盲区"同源) |
|
||||
|
||||
判据全文只有两张前缀表(`KNOWN_DASH` ≈ 11 条、`KNOWN_BARE` ≈ 90 条)与一张算术排除表
|
||||
(`SIZE`/`LEN`/`MAX` 之类后面跟 `-数字` 是减法不是编号)。**没有族形态正则、没有游标、
|
||||
没有属主登记处、没有比例基线**——这就是它相对 `id-check` 的全部区别。
|
||||
判据全文只有两张前缀表(退役时 `KNOWN_DASH` 11 条、`KNOWN_BARE` 90 余条)与一张算术排除表
|
||||
(`SIZE` / `LEN` / `MAX` 之类后面跟 `-数字` 是减法不是编号)。**没有族形态正则、没有游标、没有属主
|
||||
登记处、没有比例基线**——这就是它相对 `id-check` 的全部区别,也是它当初被选为接班件的理由。
|
||||
|
||||
## 2. 为什么要它(与 id-check 的分工)
|
||||
豁免三档:行内含 `[示例]` / `[计划]` 按冻结口径跳过;确需保留的字面量(如冻结的现场日志)在同一行
|
||||
写 `[编号豁免]`;脚本自身、`scripts/archive/`、`docs/芯片手册/`、`docs/硬件平台/`、`Lib/`、
|
||||
`Middlewares/` 等在排除清单里。**加白名单是唯一的放宽手段**——没有"整库豁免"入口,也没有 `--write`
|
||||
现状洗白(与旧门禁同一条纪律)。
|
||||
|
||||
`id-check` 需要养 99 族语料(`id_registry.json` 87.6 KB + `id_aliases.json` 187 KB);
|
||||
实测它的**单次运行只有 1.8 s**,贵的是"每笔提交都要维持语料自洽"——近 80 笔提交里 61 笔
|
||||
改了语料/门禁/规范。而它真正有用的那部分(拦住"新写一个自造编号")不需要语料:
|
||||
只要知道"这个前缀我从没见过"就够了。
|
||||
## 2. 为什么当初要它
|
||||
|
||||
所以两者的分工是:**`id-check` 管存量一致性(逐步退役),`id-check-new` 管增量不许新增**。
|
||||
观察期内两条**同时跑**,互相兜底;观察期结束后按 `docs/编号规范.md` §8.5 摘掉 `id-check`。
|
||||
`id-check` 要养 99 族语料(`id_registry.json` 87.6 KB + `id_aliases.json` 187 KB):单次运行只有
|
||||
**1.8 s**,贵的是"每笔提交都要维持语料自洽"——近 80 笔提交里 **61 笔**动了语料 / 门禁 / 规范。
|
||||
而它真正有用的那一小块(拦住"新写一个自造编号")根本不需要语料:只要知道"这个前缀我从没见过"。
|
||||
所以 2026-10-28 的分工是:`id-check` 管存量一致性(当日退役),`id-check-new` 管增量不许新增。
|
||||
|
||||
## 3. 触发面
|
||||
落地前测过噪声:拿最近 **80 笔真实提交**回放,未知形态只有 15 种,其中 12 种是设备 / 协议名
|
||||
(已进白名单)、3 种是已停发族(只告警)→ 白名单调好后**回放 0 误报**。
|
||||
|
||||
- `files:` = 任意 `md` / `c` / `h` / `py` / `yaml` / `txt` 或本配置文件本身;`pass_filenames: false`
|
||||
(脚本自己读 `git diff`,不靠钩子传文件名)。
|
||||
- 只读**新增行**:删行、改行里的旧编号不查(那是存量,归 `id-check`;退役后不再有人管,
|
||||
但这正是用户裁定接受的代价)。
|
||||
- 默认查**暂存区**(`git diff --cached`)。手工自查:`--worktree`(未暂存改动)/ `--range A..B`(一段提交)。
|
||||
- 排除面与旧门禁对齐:第三方库、HAL/CMSIS、`docs/芯片手册/`、`docs/硬件平台/`、`docs/images/`、
|
||||
`simulator/tests/ref/`、`ci-artifacts/`、`.dsh/skills/`、`scripts/rename_maps/`、
|
||||
以及二进制/生成物后缀(`.svg` `.png` `.bin` `.map` `.json` 等)。
|
||||
## 3. 退役的理由(这一节是本文的重点)
|
||||
|
||||
## 4. 报错怎么读
|
||||
1. **它咬的是形态,不是语义**:白名单是"形态"级不是"号"级——前缀合法则 `TIM3` 放行、`TIM99` 同样
|
||||
放行;同形多义(历史上 `S3` 三义那类撞车)它照不出来。这与旧门禁 `id-check.md` §6 记的盲区同源,
|
||||
只是范围缩到新增行。
|
||||
2. **白名单就是它的维护面**:每遇到一类合法字面量(新型器件、协议名、固件生成的产物文件名)就要加
|
||||
一条;不加就误拦,加了就把该前缀后头的所有数字永久放宽。2026-10-29 当天就撞过一次:文档里写
|
||||
录波段文件名 `WAVE00.BIN` 被拦,先改写成 `WAVE<NN>.BIN` 绕行、再登记 `WAVE` 前缀(`KNOWN_BARE`
|
||||
最后一次改动)。这类"合法新词撞形态门"是常态,不是意外。
|
||||
3. **默认形态早就是语义短语**(`docs/编号规范.md` §1 与 §3.3;登记簿新条目一律写语义短语,
|
||||
`docs/任务规划/README.md` §0),新造号在评审里本来就看得见,机器面的边际收益低于误拦成本。
|
||||
4. 与 2026-10-28 摘掉全库审计是**同一条判断的延续**:编号体系不再产生价值。留着这道门只让"要不要
|
||||
再登记一个前缀"变成反复出现的小额开销。
|
||||
5. 别往"轻量版"方向找补:改成只告警(退出码恒 0)等于把形态判断变成没人看的滚屏;转
|
||||
`stages: [manual]` 又因为它是 diff 型脚本、脱离提交时机基本扫不到东西。两条都是留着成本、
|
||||
去掉收益,所以整体退役。
|
||||
|
||||
```text
|
||||
[FAIL] 新增行里有 2 处**未登记形态**的字母+数字标识:
|
||||
docs/xxx.md:3 ZZ-9 [示例]
|
||||
→ 语义标识是默认形态:改写成短语(docs/编号规范.md §1);
|
||||
确属设备名/协议名/位号,就把前缀登记进 scripts/check_ids_new.py 的白名单。
|
||||
## 4. 退役后手工怎么用
|
||||
|
||||
```
|
||||
& '.venv\\Scripts\\python.exe' scripts/archive/id_gate/check_ids_new.py --self-test # 正反例自检(5 正例 / 29 反例)
|
||||
& '.venv\\Scripts\\python.exe' scripts/archive/id_gate/check_ids_new.py --range A..B # 复扫一段提交的新增行
|
||||
& '.venv\\Scripts\\python.exe' scripts/archive/id_gate/check_ids_new.py --worktree # 查未提交的改动
|
||||
```
|
||||
|
||||
三种处理:① 改成语义短语(默认);② 确实是设备名/协议名(如新的芯片型号)→ 把前缀加进
|
||||
`KNOWN_BARE` / `KNOWN_DASH`;③ 这一行必须保留字面(如冻结的现场日志)→ 行内写 `[编号豁免]`。
|
||||
⚠ `[示例]` / `[计划]` 行按既有冻结口径**整行跳过**,这也是本门禁的已知口子(§6)。
|
||||
⚠ 上面报错样例那行带 `[示例]`:两条门禁都认这个冻结标记、认不出「这段是文档举例」,
|
||||
所以**在文档里举反例必须带 `[示例]`**,否则举例自己就会被拦(这条就是被自己拦过一次之后补的)。
|
||||
退出码:0 = 通过 / 仅告警;1 = 有新增编号;2 = 环境错误(git 不可用、不在仓库内)。
|
||||
跑不跑都不影响提交。真正还用得上它的场合是**编号清退批次**:拿它当"形态普查"的尺子,比自己临时
|
||||
写正则省事(做法 → `.dsh/skills/id-family-semantic-migration/SKILL.md`)。
|
||||
|
||||
## 5. 好处
|
||||
## 5. 此后靠什么守着
|
||||
|
||||
- **零维护**:加一个设备型号是改一行白名单,不需要登记族、游标、属主文档、基线比例。
|
||||
- **判据窄**:只对"新增"负责,所以不必解释"为什么这个历史号还在"——那类争论在旧门禁里占了大半时间。
|
||||
- **自证成本低**:`--self-test` 内置 5 个正例 + 29 个反例(含 `TC-NET-204`、`TRAP-ADC-01`、
|
||||
`TPAFE5160`、`GD5F2GQ5UE`、`C12`、`SIZE-2`、`GPIO_PIN_5` 等),改脚本后一条命令验证没把牙弄掉。
|
||||
- **落地前测过噪声**:拿最近 80 笔真实提交回放,未知形态只有 15 种,其中 12 种是设备/协议名
|
||||
(已进白名单)、3 种是已停发族(只告警)→ 白名单调好后回放 **0 误报**。
|
||||
- `docs/编号规范.md` §1「默认答案:不发号」+ §4.1 存量族(穷举)+ §3.3 禁发范围——**新增族不再需要
|
||||
登记机器白名单**,那个"唯一口子"随门禁一起消失;答不出"哪个脚本要按字面量匹配它"就别建族。
|
||||
- 评审与销项纪律:`docs/任务规划/README.md` §0(已随销项消失的号永久作废、不得复用)。
|
||||
- ⚠ 要认下丢了什么:**"销项删行后号被重新取走"这类事故当初正是靠机械牙发现的**(2026-10-14 撞号
|
||||
事故 → 语义标识裁定)。此后提交期不再有这类警报,只剩规范与人工核。
|
||||
|
||||
## 6. 限制与盲区(这一节请当正文读)
|
||||
## 6. 改这个脚本本身要注意什么(历史留笔)
|
||||
|
||||
1. **单字母 + 数字一律放行**:元件位号(`C12`/`U15`/`R3`/`D7`)与层次号、裸形族号同形,
|
||||
机械区分不出来 → 新写一个单字母+数字的号**不会**被拦。这是本门禁最大的洞,与旧门禁的
|
||||
「裸形盲区」同源,属显式接受的代价。
|
||||
2. **只查新增行**:把旧编号从 A 行搬到 B 行、或整段重排,算"新增"→ 会拦(因为形态未登记);
|
||||
反之,只要形态在白名单里,新写的也放行——白名单是"形态"级,不是"号"级。
|
||||
3. **`[示例]`/`[计划]` 整行跳过**:想绕过本门禁,写个 `[示例]` 就行。这靠评审纪律,不靠机器。
|
||||
4. **未跟踪文件不在扫描面**(`git diff` 语义):手工在 `git add` 之前跑 `--worktree` 看不到新文件;
|
||||
提交时必已进暂存区,所以门禁场景不受影响。
|
||||
推论:**在 `pre-commit run --all-files`(`ci_local.py` 的静态门禁)下它是空转的**——
|
||||
那种跑法把文件当"全量"传进来,而本脚本只读暂存区 → 暂存区空 → 打印通过。
|
||||
它只在**真实提交**时才有牙;别拿静态门禁全绿当"没有新编号"的证明。
|
||||
5. **算术白名单是启发式**:`SIZE-2`/`LEN-1` 这类被排除;但若某处写着一个「不在白名单的前缀 +
|
||||
连字符 + 数字」,就会误报一次——把前缀加进白名单即可,不构成误拦风险。
|
||||
6. **不校验语义**:白名单里的前缀可以拿来写任何东西(`TIM3` 合法,`TIM99` 也放行)。
|
||||
它只回答"这是不是新造的前缀",不回答"这个号该不该存在"。
|
||||
|
||||
## 7. 豁免与白名单口径
|
||||
|
||||
| 类别 | 放行方式 | 维护动作 |
|
||||
|---|---|---|
|
||||
| 设备 / 芯片 / 模块型号(`TPAFE5160`、`CH395F`、`SD2506`、`GD5F2GQ5UE`) | `KNOWN_BARE` 前缀 | 新器件时加一条 |
|
||||
| 协议 / 标准(`IEC104`、`SHA-256`、`RS-485`、`UTF-8`) | `KNOWN_DASH` / `KNOWN_BARE` | 基本不增长 |
|
||||
| 遥信/遥控点号(`YX`/`YK`/`YT`/`YH`)与保护元件代号(`OC`/`UV`/`OV`/`CP`…) | `KNOWN_BARE` | 协议数据,不是标识符 |
|
||||
| 活族编号(`FR-*`、`TRAP-*`、`TC-*`、`CODE-RULE-*`、`MENU-STATE-*`) | `KNOWN_DASH` | 新增族要先在此登记 |
|
||||
| 已停发族(登记簿/专项/层次/原则等旧族) | `RETIRED_DASH` → **只 WARN** | 不拦提交,提示改语义短语 |
|
||||
| 一次性保留 | 行内 `[编号豁免]` | 提交说明里要交代原因 |
|
||||
|
||||
## 8. 改这个门禁本身要注意什么
|
||||
|
||||
- 改完必跑 `python scripts/check_ids_new.py --self-test`(正例漏拦 / 反例误拦都会红)。
|
||||
- 加白名单是**唯一**放宽手段;没有"整库豁免"入口,也没有 `--write` 现状洗白(与旧门禁同一条纪律)。
|
||||
- 改完必跑 `--self-test`(正例漏拦 / 反例误拦都会红);退役后这条纪律只对手工复查生效。
|
||||
- 别把白名单做成"按字面量枚举"的账本——那就退化成 `id_aliases.json` 的老路(187 KB 维护负担),
|
||||
前缀级枚举才是它便宜的原因。
|
||||
- 与 `id-check` 的关系变更(摘掉旧的)要同批改:本页、`docs/编号规范.md` §8.5、
|
||||
`docs/门禁说明/README.md` 的索引与计数、`AGENTS.md` 的钩子数。
|
||||
- 归档位置的 `REPO_ROOT` 与原位不同(深两级,取 `parents[3]`);再挪目录要一起改。
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
> ⚠ **本文是退役留档,不要照着它去改门禁。**
|
||||
> 2026-10-28 用户裁定:编号体系本身不再产生价值,全库语料审计的维护成本高过收益
|
||||
> → 钩子摘除(52 → 51),脚本与两份语料整体移入 `scripts/archive/id_gate/`;
|
||||
> **现行编号门禁 = `id-check-new`**(只查新增行、零语料)→ `docs/门禁说明/id-check-new.md`。
|
||||
> **接班件 `id-check-new` 也已于 2026-10-29 退役**(只查新增行那道,用户裁定)→ `docs/门禁说明/id-check-new.md`。
|
||||
> 两道编号门禁自此全部退场:**编号形态没有机器面**,判据回到 `docs/编号规范.md` §1 三句自问 + §4.1
|
||||
> 族总表 + 人工评审;归档脚本 `scripts/archive/id_gate/check_ids_new.py` 可随时手工普查形态。
|
||||
> 退役路线与判据替换 → `docs/编号规范.md` §8.5。下面保留退役前的原样描述与实测基线,
|
||||
> 只在手工复查「存量编号现状」时还用得上。
|
||||
|
||||
> 一句话:它管**"号"这个东西不许野生**——每个编号必须属于一个已登记的族、形态合法、
|
||||
> 有定义处、有人引用、引用它的时候说清是哪篇文档的号。
|
||||
> 规范正文 = `docs/编号规范.md`(§1 先问三句、§4.1 允许用号的族〔穷举〕、§8 门禁与实测基线),本门禁是那八项的机械执行。
|
||||
> 规范正文 = `docs/编号规范.md`(§1 默认答案(一律停发)、§4.1 存量族〔穷举〕、§8 门禁与实测基线),本门禁是那八项的机械执行。
|
||||
|
||||
- 配置:`entry: python scripts/archive/id_gate/check_ids.py --strict`,`pass_filenames: false`,
|
||||
`files:` 覆盖 `.md/.c/.h/.py/.yaml/.json/.txt` 与配置文件本身。
|
||||
@@ -205,7 +207,7 @@
|
||||
要指代就写全号并紧邻属主文档名(写法口径见 `docs/文档规范.md` §3.5,判它的是校验 6)。
|
||||
3. ⚠ **反向错误更贵**:为了「少占余量」而把编号改写成语义短语/散文——那正是 §7.6 与这根护栏的动机要防的
|
||||
稀释(比值会被清号动作稀释,绝对数不会)。2026-10-27 凌晨 Lead 给三条道下过这样一条错指令,已撤回;
|
||||
正确口径是:按 `docs/编号规范.md` §1 先问三句,属允许族就用号,**需要新母号时由 Lead 改 `id_registry.json` 游标**。
|
||||
当时的口径是:按 `docs/编号规范.md` §1 先问三句,属允许族就用号(该判据 2026-10-29 作废,全族一律停发),**需要新母号时由 Lead 改 `id_registry.json` 游标**。
|
||||
|
||||
**每批自证没吃余量(可复制的做法,五项式)**:
|
||||
|
||||
@@ -236,8 +238,3 @@
|
||||
3. 更省事的正解是**写注释与文档时一开始就别用裸形**:改用语义短语,或写全号 + 紧邻属主文档名。同日还有一
|
||||
次反向教训:为「省余量」把在册族的号改成散文,那才是真把护栏当敌人(见 §7.7)。
|
||||
4. 附带收益常比余量值钱:这次顺带消掉一个**同形两义**的撞号源(同一裸形既像自造状态号又撞已登记族)。
|
||||
|
||||
|
||||
## 9. 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -94,7 +94,3 @@
|
||||
|
||||
- 往 `ALLOW` 加条目要写理由,并想清楚会不会变成"给上层开后门";能进基线就别进 `ALLOW`。
|
||||
- 还掉一条债就跑 `--prune` 并删掉那一行;基线只减不增,加行等于放弃约束。
|
||||
|
||||
## 9. 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -89,6 +89,15 @@
|
||||
但不会指出模板——那段链由 `generated-fresh` 保证「脚本与产物同一笔提交」。
|
||||
5. 编译器侧的真实警告仍以 `@build` 为准:本闸只拦「会让 ARMCC 报 9-D 的那一类」,
|
||||
编译日志里其他诊断一概不管。
|
||||
6. **仓库根探测必须兼容 linked worktree**:脚本原先用「`.git` 是不是目录」定位仓库根,而 `git worktree`
|
||||
出来的任务工作树里 `.git` 是**一个文件**(写着 `gitdir: …`)不是目录 → 本闸在任何任务树里恒
|
||||
退出码 2「仓库根定位失败」,**静默不可用**。这不是「放过了真问题」,是**根本没跑**——比放过更坏,
|
||||
因为调用方看到红字会以为环境有问题、看到跳过会以为干净。2026-10-29 已把该判据从 `isdir` 改成
|
||||
`exists`(`scripts/check_nested_comment.py:342`,修复归当日提交)。全仓 `scripts/` 只有它这么探根,
|
||||
其余只读闸在任务树里一直正常。**后果要说清楚**:门禁修好之前那批录波改动没被它扫过,lead 手工对
|
||||
改动面逐文件扫了一遍、嵌套命中 0,所以没有放过真问题——但那是**人工兜底,不是门禁功劳**。
|
||||
通用教训:**门禁脚本按 `isdir(.git)` 探根 = 在 worktree 里自杀**;新增只读闸要么走
|
||||
`git rev-parse --show-toplevel`,要么同时接受 `.git` 的文件形态。
|
||||
|
||||
## 7. 豁免与基线(只减不增)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# `ui-golden` — UI 像素回归(菜单 8 状态 + 内容页 60 张黄金参考)
|
||||
# `ui-golden` — UI 像素回归(菜单 8 状态 + 内容页 66 张黄金参考)
|
||||
|
||||
> 一句话:改 UI 源码时,把模拟器增量重编 + 两条 golden 回归串成一次提交门禁,**像素对不上就拦**。
|
||||
> 它是包装器:真正的判据在被包装的两条脚本与参考图里。
|
||||
@@ -11,7 +11,7 @@
|
||||
## 1. 它在查什么
|
||||
|
||||
- 菜单侧:整棵菜单树的行渲染结果(8 个状态)与参考图逐像素比对;
|
||||
- 内容页侧:60 张黄金参考(= 60 个截图态条目,同一内容页的多个状态各一张,其中 2 张是分页页多拍的第 2 屏),逐张与参考图比对;
|
||||
- 内容页侧:66 张黄金参考(= 66 个截图态条目,同一内容页的多个状态各一张:分页页多拍的第 2 屏与确认链/录波的各态都单独计条目),逐张与参考图比对;
|
||||
- 参考图 `simulator/tests/ref/*.bin` **就是判据本身**。
|
||||
|
||||
## 2. 为什么要它(有牙的实测)
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
1. **参考图即判据**:刷新参考图 = 修改判据。评审时必须盯 `simulator/tests/ref/` 的 diff;
|
||||
一旦"顺手更新",此后所有回归都只在和错的基线比。**门禁无法阻止这件事**,只能靠评审。
|
||||
2. 只覆盖 8 个菜单状态 + 60 张内容页黄金参考:未列入的状态(密码错误态、超长文本、缺字渲染、
|
||||
2. 只覆盖 8 个菜单状态 + 66 张内容页黄金参考:未列入的状态(密码错误态、超长文本、缺字渲染、
|
||||
无 SD 卡 / 挂载失败的降级显示)**不会有任何回归**。
|
||||
3. 跑的是**模拟器那份配置**,不是固件那份 —— 两份 `lv_conf.h` 漂移时,回归结论不能外推到固件
|
||||
(所以 `lv-conf-sync` 是本门有效性的前提)。
|
||||
@@ -76,7 +76,3 @@
|
||||
|
||||
- 加新页面 / 新状态时,要同时扩参考图集合,否则新页面**完全没有回归**(不会报错,只是没覆盖);
|
||||
- 触发面 `files:` 每加一类能改像素的入口就补一行,漏一类 = 那一类改动静默不跑回归。
|
||||
|
||||
## 9. 变更记录
|
||||
|
||||
> (本文件不再记流水账:改了什么是正文说了算,改动过程查 git log。)
|
||||
|
||||
@@ -4,7 +4,7 @@ ADC1 采样链路(TIM3 1 kHz 节拍 → ADC1 九通道扫描 → DMA2_Stream4
|
||||
语义映射)的实证陷阱:现象、判读陷阱、定案根因、修复与回归判据。**动 ADC 采样、通道映射或
|
||||
诊断代码之前先读本文。**
|
||||
|
||||
> 编号口径:按 `docs/编号规范.md` 采用 `TRAP-ADC-<nn>`,本域自 01 起连续。编号不复用、不重排。
|
||||
> 编号口径:本域既有条目按 `TRAP-ADC-<nn>` 自 01 起连续编号,**只读引用**;2026-10-29 起**不再新增 TRAP 号**(`docs/编号规范.md` §4.1 一律停发),新踩坑在本文写语义行名。编号不复用、不重排。
|
||||
> 状态图例:✅ 已修复定案 · ⏳ 待修。
|
||||
> 设计侧口径(采样节拍、寄存器配置、铁律)不在本文,见
|
||||
> `docs/子系统设计/ADC采样节拍设计说明.md`;本文只记「踩过的坑」与「怎么判断踩到了」。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修复、回归判据。改驱动/NET 层前先读本文。
|
||||
|
||||
> 编号口径:按 `docs/编号规范.md` 采用 `TRAP-NET-<nn>`,连续 01~21。编号沿革(2026-10-13 全库重排):
|
||||
> 编号口径:本域既有条目 `TRAP-NET-<nn>` 连续 01~21,**只读引用**;2026-10-29 起**不再新增 TRAP 号**(`docs/编号规范.md` §4.1 一律停发),新踩坑在本文写语义行名。编号沿革(2026-10-13 全库重排):
|
||||
> 旧 01~11 不变;旧 14→12、15→13、16→14、17→12(并入)、18→15、20→16、22→17、23→18、24→19、
|
||||
> 25→20、26→21;旧 12/13/19/21 已移出/并入/删除,无条目。旧形 `Trap NN` 为违规裸形已全库迁移;
|
||||
> 旧号只存在于历史提交与旧日志。编号不复用、不重排。
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| 链 | 现状 | 缺什么 |
|
||||
|---|---|---|
|
||||
| **测量链(遥测)** | 104 每 100 ms 直接读相量瞬时值 | 工程量折算、谐波方和根、平均/死区/零漂、品质位 |
|
||||
| **记录链(录波)** | 只有 64 B 定长 SOE 文本记录 | 波形数据面(**2026-10-29 已按 1 kHz 立项**,docs/任务规划/故障录波.md)、COMTRADE 文件、104 文件服务 |
|
||||
| **记录链(录波)** | SOE 文本记录(64 B 定长)**加上**录波段文件:1 kHz / 8 路原始码值 / 单段 320 点、`/wave` 下轮转、PC 侧读段脚本与现场单通道回放页——**代码面 2026-10-29 已交付,板级判据一轮未跑**(docs/任务规划/故障录波.md) | COMTRADE 文件、104 文件服务;以及 4 kHz 全速与 80 点/周波——本波按 1 kHz 起步,该偏离已按 docs/子系统设计/故障录波设计说明.md §2 显式登记,**不是已补齐** |
|
||||
| **就地闭环(开入开出)** | 固件完全没有 | 开入量采集与防抖、遥信变位、遥控出口、跳闸出口 |
|
||||
|
||||
**四个阻断级短板**(不补则送检必挂):
|
||||
@@ -24,7 +24,7 @@
|
||||
1. **工程量折算缺失**——遥测上送的是 ADC 原始码值(数千~数万),点表要求"短浮点、系数 = 1"的工程量;保护定值(A/V)也在跟码值比较,量纲不成立。
|
||||
2. **遥测链缺失**——没有 200 ms 测量轨:无谐波方和根、无 4 点平均、无死区确认、无零漂、无畸变率;死区做在 104 层(架构错位)。
|
||||
3. **运行参数段 0x8020~0x8058 缺失**——死区、变比、零漂门槛、三级负荷报警、录波投退与定值,主站读写全部回"未知地址"。
|
||||
4. **录波与 104 文件传输缺失**——点表明确要求"录波文件存储 ≤ 15 s""104 读文件目录/文件 ≤ 30 s"。
|
||||
4. **录波已按 1 kHz 口径落地(2026-10-29 代码面),104 文件传输仍缺**——点表明确要求"录波文件存储 ≤ 15 s"(该时限未测,见 §4.4)与"104 读文件目录/文件 ≤ 30 s"(未做)。
|
||||
|
||||
**建议顺序**:先做"数据可信"(1 + 2 + 3 + 频率点,纯软件、可主机单测),再做"精度与安全网"(等周波 + 削顶检测 + 派生量),最后单独立项"录波与文件"(最大单块)。开入开出与功率/电度需硬件与产品先拍板,软件先建接口不使能。
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
|
||||
| 环节 | 参考实现 | 本项目 | 差距 |
|
||||
|---|---|---|---|
|
||||
| **采样节拍** | 内总线 4 kHz(80 点/周波),保护取 1/4 → 1 kHz;交流板本地插值对齐 | TIM3 1 kHz 单节拍,无过采样通路 | 录波无数据源;等周波未闭环 |
|
||||
| **采样节拍** | 内总线 4 kHz(80 点/周波),保护取 1/4 → 1 kHz;交流板本地插值对齐 | TIM3 1 kHz 单节拍,无过采样通路 | 录波已有 1 kHz 数据源(2026-10-29 交付代码面,见 docs/子系统设计/故障录波设计说明.md §2),**但仍无 4 kHz 过采样通路**;等周波未闭环 |
|
||||
| **预处理** | 码值 × 每通道系数(定点 2^10)→ 浮点工程量;自产零序 `3I0=Ia+Ib+Ic`;插值异常闭锁 | 码值直通(系数 = 1),无标定、无自产零序 | **量纲缺失**;零序只能靠外部通道 |
|
||||
| **保护相量** | 全周傅氏 15 基波 + 3 二次 + 1 五次,每 1 ms 重算 | 5 实例(IA/UA/UN 基波 + IA 二/五次),每 1 ms 重算 | 实例数按 V0.1 有效通道裁剪(合理),但**无派生量层** |
|
||||
| **测量遥测** | 15 通道 × k=1~9 DFT → 方和根 → 4 点平均 → 死区 2 点确认 → 零漂置零 → 微调 TLV10 → 变比;22 交流 + 2 直流 + 7 功率 | 无遥测链;104 直读相量瞬时值 | **整链缺失** |
|
||||
| **派生量** | 线电压 / 负序 / 正序 / 零序 / 差分 / 直流 / 同期量(`Calf*Vec` 系列 + `CalfAnaDC/Dif`) | 无 | 整层缺失 |
|
||||
| **录波** | 80 点/周波 × 8 周波,突变量启动,COMTRADE 输出(`Wave.c` + `WaveComtrade.c` 共 7 582 行) | 无 | **整链缺失** |
|
||||
| **录波** | 80 点/周波 × 8 周波,突变量启动,COMTRADE 输出(`Wave.c` + `WaveComtrade.c` 共 7 582 行) | 1 kHz × 8 路原始码值单段(2026-10-29 代码面交付,含段文件与 PC 侧读段工具)| 部分补齐:口径偏离(无过采样、无突变量启动、无 COMTRADE),见 docs/子系统设计/故障录波设计说明.md §2 与 §13 |
|
||||
| **记录** | SOE / COS / 跳闸 / 遥测 / 冻结 / 异常(`RecUnit` + `HistoryFile`) | SOE + 动作记录(B9) | 缺 COS/遥测/冻结/异常 |
|
||||
| **上送** | 101/103/104/61850/ModBus,含文件服务与电度 | 104 子集:M_SP/M_DP/M_SP_TB/M_ME_NC/C_SE/C_RD/C_IC/C_CS | 无文件服务、无电度、无定值区 |
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
### 3.3 完全空白
|
||||
|
||||
遥测链 · 派生量(线/序/差分/直流) · 录波与 COMTRADE · 104 文件服务 · 开入量采集 · 遥控出口 · 跳闸出口 · 功率与电度 · 同期 · 小电流选线 · 运行参数段 · 标定 · 等周波 · 削顶检测。
|
||||
遥测链 · 派生量(线/序/差分/直流) · COMTRADE 与 104 文件服务(录波本身已于 2026-10-29 落地 1 kHz 口径)· 开入量采集 · 遥控出口 · 跳闸出口 · 功率与电度 · 同期 · 小电流选线 · 运行参数段 · 标定 · 等周波 · 削顶检测。
|
||||
|
||||
---
|
||||
|
||||
@@ -161,11 +161,11 @@
|
||||
- 地址、类型、默认值以参考表为准逐项对照,**不要凭想象填**。
|
||||
- 规模:**M**(表驱动,机械但量大;104 侧 `set_read/set_write` 已是通用接口,不用改协议)。
|
||||
|
||||
### 4.4 录波与 104 文件传输缺失
|
||||
### 4.4 录波已按 1 kHz 口径落地,COMTRADE 与 104 文件传输仍缺失
|
||||
|
||||
**现状与证据**
|
||||
|
||||
- 全库无录波、无 COMTRADE(关键词扫描零命中)。
|
||||
- 录波已于 2026-10-29 落地(1 kHz / 8 路 / 段文件:`App/algo/wave_ring.c` 与 `App/algo/wave_store.c`);**COMTRADE 与 104 文件传输仍零命中**(本波不做,见 docs/子系统设计/故障录波设计说明.md §13)。本节原写「全库无录波(关键词扫描零命中)」是落地前的现状句,下面几条里 4 kHz 过采样与文件面的差距仍然成立。
|
||||
- 104 只实现 8 种 ASDU,**无文件传输类型**(120~122 及读文件目录扩展),无定值区类型。
|
||||
- 采样通路只有 1 kHz 一条,没有 4 倍过采样。
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
| 等周波 | 同 §3.1 的「等周波采样(软件测频 + 调频闭环)」(⬜) | 补三条实施约束(ARR 联动、开环先行、过渡处理) |
|
||||
| 遥测链 | 复现规划 §3.2(「遥测谐波轨(可选)」,标"可选") | **建议从"可选"升为"必做"**——它是遥测、电度、越限、畸变率的共同底座 |
|
||||
| 派生量 | 复现规划 §3.2(「线/负序/零序合成」,V0.1 不适用) | 建议代码与单测先就绪,V0.2 只改映射 |
|
||||
| 录波 | 复现规划 §3.4(「故障录波(可选)」,⬜) | 补"104 文件服务"这一半(原登记只写录波) |
|
||||
| 录波 | 复现规划 §3.4(「故障录波(可选)」,⬜;2026-10-29 已按 1 kHz 落地代码面,4 kHz 与 COMTRADE 仍 ⬜)| 补"104 文件服务"这一半(原登记只写录波)|
|
||||
| 运行参数段 | 未登记 | **新发现**,建议按语义标识登记("运行参数段 0x8020~0x8058") |
|
||||
| 开入开出 | 未登记 | **新发现**,建议登记("开入量采集与遥信"、"遥控与出口") |
|
||||
| 削顶检测 | 复现规划 §3.5(「采样通道自检」残余,"码值越界待做") | 给出两条具体判据 |
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
| `ci_local.py` | **本地跑与 CI 等价的门禁**(静态门禁 pre-commit 47 根 + **manual 段**(`cppcheck` / `test-cppcheck` / `test-coverage` 三根逐根 `--hook-stage manual`,默认跑)+ 提交信息门禁 + **体积门禁** + 主机侧纯逻辑单测):自动选用装了 pre-commit 的解释器;无 `.map` 时自适应跳过 `mem-numbers` / `mem-budget`(⚠ 那是 SKIP 不是通过,缺口由体积门禁兜住);退出码 0/1/2 与 CI 对齐。CI 定义见 `../.gitea/workflows/ci.yml`,覆盖边界见 `docs/测试说明/测试开发标准流程.md` §9 |
|
||||
| `check_size_gate.py` | **体积门禁的强制路径**:`.map` 只在本地有,钩子无它时自适应 SKIP,于是"没测=通过"。本脚本用**已提交的同码凭证** `scripts/size_attestation.json` 把这条判据变成无需 Keil 也能跑:`--stamp` 只在"`.map` 存在、比全部受管源码新、且未超 92%"时才盖章(已挂在 `ram_baseline.py --update` 里,无新增人工步骤);`--local` 供 ci_local 的体积门禁步;`--ci` 供 runner(同码性 + 按凭证数字复核红线,主干 push 拦红、其余事件 `--warn-only`);`--show` 排障。**口径与边界的唯一正文在本脚本模块 docstring** |
|
||||
| `size_attestation.json` | (被 `check_size_gate.py` 读写)体积同码凭证:受管源码指纹(`git ls-files` 范围内逐文件 sha256 聚合)+ 测量数字与占比 + `.map` 出处。改固件源码后不重测 → 指纹对不上 → 体积门禁与 CI 同时拦红 |
|
||||
| `check_test_coverage.py` | 主机单测**覆盖率**(2026-10-23):mscoverage 动态插桩跑 `test/host/**` 全部 41 套,统计自研生产源码行/函数覆盖(并集)+ 与 `test_coverage_baseline.json` 比回退;`--module/--update-baseline/--md/--json/--list`;挂在 `test-coverage` 钩子上(`stages: [manual]`,**`git commit` 不跑**),入口 = 远端 CI「手动阶段门禁」步 / `scripts/ci_local.py` 的 manual 段(`--coverage` 旗标保留只为兼容旧命令;口径见 `docs/测试说明/主机单测覆盖率口径.md`) |
|
||||
| `check_test_coverage.py` | 主机单测**覆盖率**(2026-10-23):mscoverage 动态插桩跑 `test/host/**` 全部 44 套,统计自研生产源码行/函数覆盖(并集)+ 与 `test_coverage_baseline.json` 比回退;`--module/--update-baseline/--md/--json/--list`;挂在 `test-coverage` 钩子上(`stages: [manual]`,**`git commit` 不跑**),入口 = 远端 CI「手动阶段门禁」步 / `scripts/ci_local.py` 的 manual 段(`--coverage` 旗标保留只为兼容旧命令;口径见 `docs/测试说明/主机单测覆盖率口径.md`) |
|
||||
| `ci_gitlint_range.ps1` | CI 侧**提交信息门禁**:逐条校验本次 push/PR 引入的提交(`--all-files` 跑不到 `commit-msg` stage,故必须单独守) |
|
||||
| `ci_find_python.ps1` | CI 侧 Python 探测(候选顺序须与 `ci_local.py` 同序同步,否则 CI 与本地判定会分叉) |
|
||||
| `ci_runner_bootstrap.ps1` | **本机 runner 运维脚本**(体检 `-Status` / 干跑 `-DryRun` / 部署 `-SetEnv -StartNow`):装"等 Gitea 就绪 + 崩溃自拉起"的看门狗 + 开机计划任务 + 机器级 `DTU_*` |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user