feat(UI/采集/104): 通讯设置页 + 开机账本 + adc_cal 可诊断化——三车道同树一次构建入库
三条道在同一棵树上交付,故合并成**一次权威构建**(@build 0 错 0 警,Code=235236 RO=26308 RW=1572
ZI=136924)后同笔入库;台账已按该构建回填(`ram_baseline.py --update` + `--write`、`chec
k_mem_numbers.py
--write`),凭证与实际 .map 同源。归属如下:
· **通讯设置页**(道G task-9):新增 `Drivers/BSP/UI/ui_page_comm.{c,h}`(通道六注入,与死
区页同骨架同纪
律),`#page=comm` 绑定使 bound **21 → 22**、内容页黄金 **47 → 53**(旧 47 页字节零变化,
新增六态含
WAIT/OK 两张);`iec104_comm` 脏位 + store_save(**无事件位**)挂 defaultTask 同点位
轮询;`port_104` 由
宏改走 getter、`IEC104_SERV_PORT` 删除;字体 +听/监(185 字全覆)。**实机链已闭环**:FTP 外改
[comm]
的 ca/port → 复位 → 串口出现 `section 'comm' (2 keys)` 与 `i104: listening o
n 9004` → 还原 2404,全程无
FAIL ⇒「重启即生效 + 端口真值」不是推断是实测。上限核对 6/8 段、2/16 键,超限现象由 TC-CONFIG-056/057
钉死(注册 -1 + `comm init: FAIL` 锚 + getter 回 ROM),**不抬宏**。
· **开机账本**(道F task-12):新增 `App/util/boot_stats.{c,h}`——上电次数落 SD2506 用户
SRAM(magic +
小端计数 + 版本 + 异或校验),一次上电 = 1 读 + ≤1 写,**零 NAND、零卷锁、零配置段**;四态互斥
UNKNOWN / FRESH / CONTINUED / RESTARTED(BLF=1 才算电池域掉过电),**读失败绝不回写**;
文案口径「自 RTC
电池周期起」,**禁写终身次数**。复位原因优先级链从 `app_main.c` 的 if-else 收进一份真源(串口与屏幕同
源);104 四个只增不清计数 + `iec104_serv_get_stats()` 留在 serv、未塞进 link(会被 open
/close 洗掉)。
运行时长按用户裁定 = **只报本次开机以来**,零新增代码。新套件 `test/host/boot_stats` 21/21,五处注入
**全部有牙**(含「漂一字节被校验和挡下而不是报 256 倍错账」)。
· **adc_cal 可诊断化**(道E task-43):三个 volatile 计数 + `adc_cal_get_stats()`
/ `adc_cal_note_persisted()`
(水位**盖写非自增**,因为一次 save 覆盖整档,数 save 会留假未落盘);「差额 × 脏位」三态语义唯一落在
`adc_cal.h`,其中「差额 > 0 且脏位恒 0」= 绕过语义 setter 的静默丢失;TC-ADCCAL-019~023。
三条**必须留档的自纠**(都是执行道自己报的,我不替他们润色):
① stdint 那条断链是**宿主套件抓到、通讯道先修**,采集道承认自己是重复添加者并删掉自己那份;
② 「exit 2 不是证据,断言变红才是」——采集道第一版反证注入了未声明标识符导致编译错,差点当成功;
③ 套件汇总行原写 `s_total/s_total`,失败时会打印「21/21 PASSED (failed=1)」这种自相矛盾的行,已改。
另修:`check_registration.py` 的 `SIM_EXEMPT` 加一行豁免 `App/util/boot_stats.c
`(依赖 I2C 与 RCC 位掩码,
MCU 专属;核过 `sys_clock.c`/`sha256.c` 同形态先例),注册反向校验 exit 1 → 0。发号 TC-IEC1
04-034、
TC-UIDATA-029 已实抬游标(`TC-IEC104-033→034`、`TC-UIDATA-026→029`)。
门禁:id-check --strict 与 plain 均 rc=0、doc-links --all rc=0、注册校验 0、cppche
ck / coding-standard /
bom-drift 0;iec104 179/179、ui_data 295/295、boot_stats 21/21、adc_cal 11
7/117、run_par 67/67;菜单 8 状态
池 5368 不变、内容页 53/53。**已知遗留**:内容页数 47 → 53 的全仓文本同步尚未做(另卡跟踪),本笔只保
证代码与黄金一致;面板注键真机那一腿缺 CLI,交台架道补。
This commit is contained in:
@@ -129,6 +129,7 @@ docs/
|
||||
|---|---|
|
||||
| [`看门狗单元测试说明.md`](测试说明/看门狗单元测试说明.md) | Task Watchdog 判定状态机(位图 / 掩码 / 节流 / tick 回绕),39 用例 |
|
||||
| [`系统时钟单元测试说明.md`](测试说明/系统时钟单元测试说明.md) | sys_clock 时间换算 / 星期 Zeller / 30 天惰性再基准化 / RTC 故障降级,100 断言点 |
|
||||
| [`开机账本单元测试说明.md`](测试说明/开机账本单元测试说明.md) | boot_stats 上电次数四态(不可信 / 首次启用 / 延续 / 电池域掉过电)/ SRAM 记录校验与格式版本 / I2C 失败分支 / 复位原因优先级链,21 断言点 |
|
||||
| [`配置文件单元测试说明.md`](测试说明/配置文件单元测试说明.md) | cfg_file INI 解析 / 值编解码 / load-save-poll 契约 / 超长守卫,149 断言点(含抓出的外科手术保存死码 P0) |
|
||||
| [`ADC输入层单元测试说明.md`](测试说明/ADC输入层单元测试说明.md) | adc_input 语义通道映射 / ADC1 顺序扫描(**Rank↔通道判据**)/ poll 契约 / HAL 故障路径,70 用例 |
|
||||
| [`相量层单元测试说明.md`](测试说明/相量层单元测试说明.md) | phasor 全周傅氏系数表 / 幅值·相位 / 谐波抑制与提取 / 频偏 / 采样环 / 双区交换,57 用例 |
|
||||
|
||||
@@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 260,004 B (253.91 KB) | 1,048,576 B (1024 KB) | 24.8% |
|
||||
| RW_IRAM1 | 80,920 B (79.02 KB) | 131,072 B (128 KB) | 61.7% |
|
||||
| ER_IROM1 | 261,544 B (255.41 KB) | 1,048,576 B (1024 KB) | 24.9% |
|
||||
| RW_IRAM1 | 81,072 B (79.17 KB) | 131,072 B (128 KB) | 61.9% |
|
||||
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
|
||||
| **Flash (ROM 合计)** | **261,540 B (255.4 KB)** | 1,048,576 B (1024 KB) | **24.9%** |
|
||||
| **Flash (ROM 合计)** | **263,116 B (256.9 KB)** | 1,048,576 B (1024 KB) | **25.1%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
@@ -512,9 +512,17 @@ vApplicationAssertFailed` 共 103 行),而 `vApplicationAssertFailed` 钩子
|
||||
记账 208 = D360)与 `[ERR]→NVIC_SystemReset` 这类终点路径,真实运行不会把环走满;它
|
||||
也不算 ISR 上下文保存(约 100 B)与间接调用(§4.0.6 盲区条同源)。所以静态链适合当
|
||||
**硬线**(可复现、无噪声、机器可判:margin ≥ max(1024 B, cfg×30%)),不适合当实际余量;
|
||||
实际余量看 §4.0.2 的串口 `[STACK]` 水位。台架判据「栈水位不劣于上一轮」必须加容差或
|
||||
改拿本节的静态硬线(「栈水位判据加容差或改静态最坏链当硬线」卡 = task-32,与本节和
|
||||
§4.0.2 互指)。
|
||||
实际余量看 §4.0.2 的串口 `[STACK]` 水位。**台架栈判据已定稿(task-32,2026-10-27)——三档分权**:
|
||||
静态 margin 是**唯一拦提交项**(提交时由 `scripts/stack_report.py` 守,见 §4.0.6);运行时水位只做
|
||||
**趋势告警**:同镜像 ≥3 圈取最大值与基线轮最大值互比,容差 **tol = 160 B**(来源 = 实测同镜像最大摆动
|
||||
80 B × 2,再向上取整到 16 B 格;登记与改数规则见 `scripts/stack_tolerance.tsv`——**只有台架重测后才能
|
||||
回填,被这把尺考核的人不得当场决定尺子的松紧**);运行时唯一能升级成 FAIL 的条件是绝对底线
|
||||
`free_min < 25% cfg`;**采集健康门不过 = VOID(exit 2)**,不红不绿也不进台账——按码聚合的人不会把
|
||||
「端口没开」记成「又崩了一次栈」。判据与门都可执行:`scripts/stack_gate.py`(`--selftest` 14 条),它是
|
||||
**台架侧门禁**(每轮台架跑完必跑;Lead 裁定 2026-10-27:不接 pre-commit——提交时既无设备也无当轮日志,
|
||||
挂上去只会得到「永远 SKIP」或「拿上一轮日志判这一轮的码」,后者正是体积门禁那条假凭证的同族)。
|
||||
口径正文:`docs/测试说明/启动日志原子性复验流程.md` §5(三档表与门的细节都在那节)。
|
||||
本节口径与本文的运行时水位小节、静态硬线小节互指(同文件内引用,不再另标编号,避免跨文档串号)。
|
||||
|
||||
**取证纪律(本节方法的可复用模板)**:凡跨树/跨构建引用数字,必须同时钉**产物路径 +
|
||||
Linker run 时刻**——commit hash 不是稳定身份(重写正文即换 hash),tree hash + 父提交
|
||||
|
||||
@@ -15,19 +15,19 @@
|
||||
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|
||||
|---|---|---|---|---|
|
||||
| ER_IROM1 (Flash) | 260,004 B | 1,048,576 B | 24.8% | 参考 |
|
||||
| ER_IROM1 (Flash) | 261,544 B | 1,048,576 B | 24.9% | 参考 |
|
||||
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
|
||||
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
|
||||
| RW_IRAM1 | 80,920 B | 131,072 B | 61.7% | PASS |
|
||||
| RW_IRAM1 | 81,072 B | 131,072 B | 61.9% | PASS |
|
||||
|
||||
| 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 |
|
||||
|---|---|---|---|
|
||||
| 网络 | 13,702 B | 12 KB | ⚠ 超 1.4 KB |
|
||||
| 存储 | 7,072 B | 12 KB | OK |
|
||||
| UI | 34,085 B | 16 KB | ⚠ 超 17.3 KB |
|
||||
| UI | 34,188 B | 16 KB | ⚠ 超 17.4 KB |
|
||||
| RTOS 堆 | 57,376 B | 56 KB | 堆=配额 |
|
||||
| 应用 | 15,537 B | 8 KB | ⚠ 超 7.2 KB |
|
||||
| 其它 | 10,523 B | — | 参考 |
|
||||
| 应用 | 15,552 B | 8 KB | ⚠ 超 7.2 KB |
|
||||
| 其它 | 10,553 B | — | 参考 |
|
||||
<!-- MEM_BUDGET:END -->
|
||||
|
||||
- **门禁**:pre-commit `mem-budget` 钩子(`scripts/ram_baseline.py --gate`)——
|
||||
|
||||
@@ -191,6 +191,30 @@
|
||||
getter 合法带 ca∈1..65535 / port∈1024..65535,带外自愈回工厂默认 1/2404
|
||||
(adc_cal 合法带哨兵同款;getter 不打日志,省 iec104Task 深链栈)。不挂段回调、
|
||||
不新增事件位、不走 SYS_EVT_NET_RECONFIG(热切换代价见 §8,原「V0.1 固定 1」口径作废)。
|
||||
- **面板编辑(道G task-9,2026-10-27)**:「通讯参数 → 通讯设置」页(`#page=comm`,
|
||||
`Drivers/BSP/UI/ui_page_comm.c`)经**通道六注入**(`ui_data_comm_ops_t`,理由与
|
||||
通道五同构:分层 / 模拟器 / 「写值+置脏」原子性)编辑 ca/port;合法带由执行侧
|
||||
现给(面板粗守与 setter 守卫同一出口,抄数即第二真源)。保存行 ENTER →
|
||||
WAIT/OK 锁存判定 = `iec104_comm_dirty_peek()`,落盘 = defaultTask 每轮无条件
|
||||
`iec104_comm_store_save()`(run_par/prot_set 同款 B7 脏位形态,**无事件位**,
|
||||
延续上条 D5 口径)。屏上 OK 只证明**落盘完成**,不证明"已在新值上运行"——
|
||||
生效时刻仍按上条(ca 重启 / port 重 bind)。容量核对:生产占 **6/8 段、
|
||||
[comm] 2/16 键**,超限现象 = 注册返 -1 → `comm init: FAIL` 启动锚 + getter
|
||||
回 ROM 默认,语义由 cfg_file 宿主套 TC-CONFIG-056/057 钉住,不抬宏。
|
||||
|
||||
- **观测计数(只读 getter,2026-10-27 道G task-12)**:`App/task/iec104_serv.c` 另有四枚
|
||||
**只增不清**的计数,经 `iec104_serv_get_stats()`(声明在 `App/task/iec104_serv.h`)对外只读:
|
||||
`conn_up`(主站接入次数)/ `apdu_tx`(成功发出的 APDU 帧数)/ `tx_fail`(发送短写或失败次数)/
|
||||
`rx_bytes`(收到的**字节**数,不是帧数——帧边界在 link 层重组,serv 层看不见)。口径是
|
||||
**上电以来、跨重连不清零**,给页面用时那一栏要写「本次开机以来」,与运行时长同一类。
|
||||
⚠ 与本节三枚**控制量**(`s_liveness_ctr` / `s_send_fail_run` / `s_conn_dead`,为"要不要拆链"
|
||||
服务、会被复位、只有 8 位)无关,拿它们当统计数字必错。为什么留在 serv.c 而不并进通用
|
||||
统计模块、也不放进 `iec104_link_t`(实例是文件级 static,喂数据侧拿不到指针,且
|
||||
`link_open/link_close` 会把"上电以来"洗成"本次连接"):104 的账本归 104。getter
|
||||
**任意上下文可读**(不取锁、不读时刻、不碰 socket;四枚 u32 单字原子,跨字段不保证同一
|
||||
瞬间,四枚之间没有"合起来才成立"的判据),到顶饱和不回绕。**「总召次数 / 上送 I 帧数」
|
||||
不在这里**:那是 link 层事件,serv 照不到,需要它得去
|
||||
`App/protocol/iec104/iec104_link.c` 加——本卡未做,别在页面上摆一个数却没有产生它的地方。
|
||||
|
||||
## 6. RAM / 栈预算(2026-10-12 实测)
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# 开机账本单元测试说明(主机侧)
|
||||
|
||||
> **模块**:Boot Stats(`App/util/boot_stats.c` + `App/util/boot_stats.h`)
|
||||
> **测试类型**:**主机侧纯逻辑单元测试**(L0.5 层)——在 PC 上离线验证"上电次数"这块
|
||||
> RTC 用户 SRAM 账本的**四态判定、记录校验、I2C 失败分支、幂等与饱和**,以及复位原因
|
||||
> 优先级链的唯一出处。零硬件、零烧录、毫秒级。
|
||||
> **状态**:✅ 已落地(21 断言点,MSVC `/W4` 与 cppcheck 均 0 告警)
|
||||
> **日期**:2026-10-27(道G task-12)
|
||||
> **关联文档**:
|
||||
> [`../../AGENTS.md`](../../AGENTS.md)(RTOS 与内存铁律、`[ERR]` 通道口径)、
|
||||
> [`测试开发标准流程.md`](测试开发标准流程.md)(测试分层与门禁总览)、
|
||||
> [`主机单测覆盖率口径.md`](主机单测覆盖率口径.md)(覆盖率总闸如何收编本套)、
|
||||
> [`../子系统设计/系统墙钟时间维护说明.md`](../子系统设计/系统墙钟时间维护说明.md)
|
||||
> §13.1(同一块 70 B 用户 SRAM 的**占用分配表**,本模块占 0..7)、
|
||||
> [`../子系统设计/IEC104上行设计说明.md`](../子系统设计/IEC104上行设计说明.md)
|
||||
> (同一张运行报告页的另一半数据源:104 观测计数)
|
||||
|
||||
---
|
||||
|
||||
## 1. 为什么要单独做这一套
|
||||
|
||||
被测量的用途是回答"这台装置上了多少次电"。这类账本**错的时候不像错**:屏幕上永远
|
||||
有一个数字,格式正常、量级正常,没有任何一行日志会说它是假的。四条只能靠构造才能
|
||||
复现的失效路径:
|
||||
|
||||
1. **I2C 读失败被当成"没有记录"**:一次总线抖动就把计数当 0 重来,从此从 1 往上数。
|
||||
更坏的是读失败还顺手回写——那会把可能完好无损的旧记录**覆盖掉**,故障从"这次读不到"
|
||||
升级成"永久少一笔账"。
|
||||
2. **"本模块首次启用"与"RTC 电池域掉过电"被合并成一态**:这两件事的文案完全不同
|
||||
(前者是"账本从这一版固件开始",后者是"换过电池,历史没了")。合并就是拿前者
|
||||
冒充后者。
|
||||
3. **只信 magic、不信校验和**:电池欠压造成的坏区常是整片全 0 或整片全 FF,magic
|
||||
那一关自然过不去;但更常见的失效是**个别字节翻转**——那时 magic 可能仍然是好的,
|
||||
而次数会错 256 倍。这种数字最容易被人当成真的。
|
||||
4. **到顶回绕而不是饱和**:回绕成个位数会被读成"刚上电几次";停在上限则一眼能看出
|
||||
到头了。
|
||||
|
||||
真机只能覆盖"一切正常"那一条:SRAM 完好、I2C 通、计数往下加。上面四条在板端**构造不
|
||||
出来**(要真的把 I2C 拉死、真的换电池、真的翻转一个字节),而主机侧只要给假件注入一个
|
||||
返回值。这是本套存在的全部理由。
|
||||
|
||||
---
|
||||
|
||||
## 2. 用例清单(本文是这些号的**定义位**)
|
||||
|
||||
跑法见 §4。断言全部只盯结构体字段与假 SRAM 的字节,不依赖打印。
|
||||
|
||||
| 号 | 判据 | 为什么是这一条 |
|
||||
|---|---|---|
|
||||
| `TC-BOOTSTAT-001` | SRAM 成片全 1(magic 过不去)且 RTC 未报欠压 ⇒ 判 FRESH、计数从 1 起、落盘一笔 | 首次启用与掉电必须分开;第一格是 1 不是 0 |
|
||||
| `TC-BOOTSTAT-002` | 合法记录 count=7 ⇒ 判 CONTINUED、count=8,且回写后介质里也是 8 | 账要真的写回去,不是只写在 RAM |
|
||||
| `TC-BOOTSTAT-002b` | 介质不动、只重开被测模块 ⇒ 继续往上数(9) | 这就是"跨掉电保持"的本体 |
|
||||
| `TC-BOOTSTAT-003` | 记录无效且 BLF 置位 ⇒ 判 RESTARTED,绝不判 FRESH | 换过电池要说换过电池 |
|
||||
| `TC-BOOTSTAT-004` | 读失败 ⇒ 判 UNKNOWN、计数归零显示、**零次回写**,假 SRAM 逐字节不变 | 读不到不许覆盖账本 |
|
||||
| `TC-BOOTSTAT-004b` | 同一状态里 `ready=1` 且 `seq=UNKNOWN` | "不可信"必须与"零次""还没初始化"三种都能分辨 |
|
||||
| `TC-BOOTSTAT-005` | 写失败 ⇒ 本次仍报数但 `write_failed=1` | 这条账只会少 1,不会重复计 |
|
||||
| `TC-BOOTSTAT-006` | 同一次开机连调两次 init ⇒ 只 +1、只写一笔 | bring-up 重试不能把一次上电数成两次 |
|
||||
| `TC-BOOTSTAT-007` | magic 与版本都好、中段漂一位 ⇒ 被校验和挡下,报 FRESH 而不是错账 | 挡住"256 倍的像真数字" |
|
||||
| `TC-BOOTSTAT-008` | 格式版本不是当前值 ⇒ 整条作废并按当前版本重写(校验同步重算) | 将来改布局时旧记录不能错位读 |
|
||||
| `TC-BOOTSTAT-009` | 计数已达上限 ⇒ 不再自增、不再写回 | 饱和而不是回绕 |
|
||||
| `TC-BOOTSTAT-010` | BLF 读不到但记录有效 ⇒ 仍判 CONTINUED、`rtc_blf=0` | 读不到佐证不该把好账判坏 |
|
||||
| `TC-BOOTSTAT-011` | SRAM 整片全 0(另一种擦除形态)⇒ FRESH 且第一格是 1 | 出厂态与掉电态的字节形态不同,两态都要认 |
|
||||
| `TC-BOOTSTAT-020` | `IWDG \| SFT` ⇒ IWDG | 崩溃钩子常以软件复位收尾,两态同置时报看门狗才有价值 |
|
||||
| `TC-BOOTSTAT-020b` | `WWDG \| SFT` ⇒ WWDG;单独 SFT ⇒ SFT | 同上,且不能把纯软件复位误判成狗 |
|
||||
| `TC-BOOTSTAT-020c` | `POR \| PIN` ⇒ POR;`PIN \| BOR` ⇒ PIN;只有 BOR ⇒ BOR;全零 ⇒ UNKNOWN | 全零必须承认"不知道",不许猜一个 |
|
||||
| `TC-BOOTSTAT-020d` | 每个编码都有可读串、全纯 ASCII、未知编码回落 `unknown` | 无 BOM 文件里字符串必须 ASCII;开机极早期不能因编码挡启动 |
|
||||
| `TC-BOOTSTAT-020e` | 快照暴露的就是 `g_rcc_csr_latched` 里那次复位原因 | 串口与屏幕不可能各说一套(链已收成一份真源) |
|
||||
| `TC-BOOTSTAT-030` | 未 init ⇒ 整表零且 `ready=0` | 页面该显示占位,而不是"上电 0 次" |
|
||||
| `TC-BOOTSTAT-030b` | `boot_stats_get(NULL)` 无害 | 观测口不该因调用者手滑而炸 |
|
||||
| `TC-BOOTSTAT-030c` | init 后连读 100 次,三类 I2C 调用计数**纹丝不动** | "任意上下文可读"不是口头承诺 |
|
||||
|
||||
> ⚠ 这些号是**主机侧用例号**,不是板端用例号,也不是需求号;族口径沿用
|
||||
> `docs/编号规范.md` 的 TC 族(默认 `check_dangling`),所以**本文就是定义位**:
|
||||
> 新增/删除用例必须同步改本表,否则 `id-check` 校验 3 会报悬空、校验 7 会报孤儿。
|
||||
|
||||
---
|
||||
|
||||
## 3. 桩的边界
|
||||
|
||||
| 头 | 取真实还是桩 | 原因 |
|
||||
|---|---|---|
|
||||
| `stub/main.h` | **桩** | 只给六个 `RCC_CSR_*RSTF` 位掩码;位号照抄 CMSIS(BOR25 / PIN26 / POR27 / SFT28 / IWDG29 / WWDG30)。真实 `main.h` 会拉整棵 HAL 头树,PC 上编不动。缺宏 ⇒ 编译直接失败,不会出现"桩悄悄放过"。 |
|
||||
| `sd2506.h` / `bsp_err.h` / `boot_stats.h` / `app_main.h` | **真实头** | 都不依赖 HAL 类型,用真头才不会出现"假件与真契约漂移" |
|
||||
| `sd2506_read_sram` / `sd2506_write_sram` / `sd2506_read_ctr1` | **假件(本套实现)** | 一片 70 B 假 SRAM + 三个可注入的错误码;地址越界按真驱动契约返 `BSP_ERR_PARAM` |
|
||||
| `g_rcc_csr_latched` | 本套给出定义 | 真定义在 `App/app/app_main.c`,本套不编译它 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 怎么跑
|
||||
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' test/host/boot_stats/run_unit_test.py
|
||||
```
|
||||
|
||||
退出码 0=PASS / 1=测试失败 / 2=环境错误。汇总行是 `s_passed/s_total`——**分子必须是
|
||||
真过了几条**;曾经写成 `s_total/s_total`,那种情况下失败时会打印
|
||||
"21/21 PASSED (failed=1)" 这种自相矛盾的串,等于把门禁的失败显示成成功。
|
||||
|
||||
---
|
||||
|
||||
## 5. 注入反证台账(这套断言有没有牙)
|
||||
|
||||
逐个注入、跑套件、逐字节还原(还原后复跑全绿):
|
||||
|
||||
| 注入的退化写法 | 变红的号 | 说明 |
|
||||
|---|---|---|
|
||||
| 读失败改成"当成读出一片空白"继续往下 | `TC-BOOTSTAT-004`、`TC-BOOTSTAT-004b` | 会回写、把完好账本覆盖掉 |
|
||||
| 合并 FRESH 与 RESTARTED(不看 BLF) | `TC-BOOTSTAT-003` | 拿首次上电冒充 RTC 掉电 |
|
||||
| 只判 magic、丢掉校验和 | `TC-BOOTSTAT-007` | 256 倍错账照发 |
|
||||
| 去掉饱和判据(改成回绕) | `TC-BOOTSTAT-009` | 到顶变小数字 |
|
||||
| BLF 读失败就早退并把记录判坏 | `TC-BOOTSTAT-010` | 判定顺序写反的代价 |
|
||||
|
||||
> ⚠ 第一条**最初是一处假变异**:注入的是未声明标识符,套件 `exit 2`(编译错),
|
||||
> 看着也"红"。那不算证据——**断言变红才是**。上表全部用能编过、能跑起来的退化写法重做过。
|
||||
|
||||
---
|
||||
|
||||
## 6. 主机侧测不出、必须上板的部分
|
||||
|
||||
如实登记,别在文档里当成已验证:
|
||||
|
||||
- **真掉电保持**:断电 ≥5 min 再上电,`boot_count` 应 +1 且旧值保持(同时留证 BLF
|
||||
与电池电压)——主机侧的"假 SRAM 不动"只是模拟,不能证明电池域真的保住了。
|
||||
- **换电池**:应落 `RESTARTED` 而不是 `FRESH`。
|
||||
- **复位原因对照**:IWDG 触发 → 快照 = IWDG;软复位 → SFT;掉电上电 → POR/BOR。
|
||||
三者与串口那条 `reset cause:` 必须同源(同一份判定链,见 `TC-BOOTSTAT-020e`)。
|
||||
- **写点不落在喂狗点**:`boot_stats_init()` 在内核启动前执行,主机侧无法验证"不占
|
||||
喂狗预算",需板端结合 `[STACK]`/`task_wd` 水位看。
|
||||
@@ -351,6 +351,38 @@ UA 无幅值落 IA → 0.0020 Hz。
|
||||
6. **表只许尾部追加**:`k_inst[]`(相量实例)、`prot_elm` 实例表、定值元数据表——
|
||||
枚举序 = 数组序 = 校验和覆盖序,插入或重排等于改校验和与 104 点表。
|
||||
|
||||
### 10.1 标定折算 `adc_cal`:谁写 RAM、谁落盘、什么时机(道G task-43)
|
||||
|
||||
这一小节存在的理由很具体:**此前全仓没有任何一处写清"k/b 改完为什么会丢"**,连写卡片的人都猜错过一次
|
||||
(以为 `cfg_file_poll()` 的 2 s 轮询能兜住未落盘的改动——方向恰好相反)。三条事实链,每条可复算:
|
||||
|
||||
| 问题 | 唯一答案 | 代码落点 |
|
||||
|---|---|---|
|
||||
| 谁是 k/b 的真源 | `[adc_cal]` / `[adc_cal_ofs]` 两段的引擎 RAM 副本(`App/algo/adc_cal.c`),别处不得再存一份系数 | `s_kcfg` / `s_bcfg`;`App/algo/run_par.h` 头注释「不另存一份系数」 |
|
||||
| 谁能写 RAM | **只有 run_par 的两支语义 setter** `run_par_cal_k_set()` / `run_par_cal_b_set()`——"写值 + 置脏"成对发生(task-7 落地,lead 裁②消除两份置脏逻辑);`adc_cal_set()` 本身**只写 RAM、不置脏、不落盘、不返回"稍后会存"** | `App/algo/run_par.c` 语义 setter 内 `adc_cal_set()` 之后才 `s_dirty = 1u` |
|
||||
| 谁落盘、什么时机 | 只有 defaultTask(配置引擎属主)的 `run_par_store_save()`:iec104Task 主循环 `run_par_dirty_peek()` → 置 `SYS_EVT_RUN_PAR_SAVE` → 属主 `cfg_file_save()` 整档外科保存 → 成功清脏,失败留脏下轮重试 | `App/algo/run_par.c` 落盘函数;本模块自身零文件 IO |
|
||||
|
||||
**`cfg_file_poll()` 不是保存路径**:它的方向是**盘 → RAM 的重载**(外部改档 → 沉降去抖 → 重载,见
|
||||
`App/cfg/cfg_file.h` 同族契约与 `docs/子系统设计/配置文件设计说明.md`)。拿它"兜住"未落盘的改值,
|
||||
等于把 FTP 侧的旧档倒灌回 RAM,比不兜更坏。**任何"改了就等于存了"的意图式陈述一律按本表改写**
|
||||
(`docs/代码规范/pre-commit使用说明.md` §2.5 第二条)。
|
||||
|
||||
**"改了没落盘"现在是可诊断事实**(第②项;先例是 `App/fs/vol_lock.h` 的观测计数设计:只计数、
|
||||
不取锁、不在调用点打日志、不打 `[ERR]`)。三个观测计数的**定义只在 `App/algo/adc_cal.h` 一处**,
|
||||
本文只讲怎么读:
|
||||
|
||||
| `set_ok − persisted_set_ok` | `run_par_dirty_peek()` | 定性 |
|
||||
|---|---|---|
|
||||
| 0 | 0 | 盘与 RAM 齐(或压根没改过)——健康 |
|
||||
| >0 | 1 | 改了、还没到落盘时机——正常在途 |
|
||||
| >0 | 0 | **改了且永远不会落盘**:有写者绕过语义 setter 直调 `adc_cal_set()` |
|
||||
|
||||
`persisted_set_ok` 是**水位**而不是计数:`run_par_store_save()` 成功路径上盖写为当时的 `set_ok`
|
||||
(一次 save 覆盖整档、可能对应多次逐通道整定,自增会留下"假未落盘";回归锁 = TC-ADCCAL-021)。
|
||||
两个盲区写在这里,别当成"看得见全部":① 本层刻意不挂 cfg_file 回调,**盘 → RAM 的覆盖不产生任何计数**,
|
||||
未落盘的改值被一次重载无声吞掉照不到;② 三个数之间没有共同时刻,定性只按上表**成对读**,
|
||||
需要时刻请在消费者侧自盖并冠 `obs_` 前缀(先例:喂狗点单写者 latch)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 资源足迹(改容量前先看这里)
|
||||
@@ -365,6 +397,7 @@ UA 无幅值落 IA → 0.0020 Hz。
|
||||
| `adc_input.c` `s_stm32_raw[16]` + `s_tpafe_raw[8]` | 48 B | — |
|
||||
| `adc_input.c` `s_stm32_dma[2×9]`(`uint16_t`) | 36 B | 必须在 `RW_IRAM1`(DMA2 不可达 CCM)→ `docs/子系统设计/ADC采样节拍设计说明.md` §4.3 |
|
||||
| `adc_health.c` 内部状态 | ≈ 40 B | — |
|
||||
| `adc_cal.c` 三个观测计数(`volatile uint32_t`) | **12 B** | 只增不清零、不参与判定;定义与读法见 §10.1 |
|
||||
| 运行期栈 `phasor_run()` 的 `win[20]` | 80 B / 帧 | 抬高 `PHASOR_N` 直接抬栈 |
|
||||
| 运行期栈 `freq_run()` 局部 | ≈ 30 B / 帧 | 无大局部数组 |
|
||||
|
||||
@@ -386,6 +419,7 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分
|
||||
| `prot_set` | 范围校验、all-or-nothing 装载、封存-自检-闭锁、Q10 往返、元数据表自检、blob 往返与损坏拒绝、换代拒绝、`uint64_t` bad_mask | `test/host/prot_set/` |
|
||||
| `prot_elm` | 滞回与定时限、欠量方向、投退、闭锁联动、时基回绕、位图聚合、测频接线与"必不启动值" | `test/host/prot_elm/` |
|
||||
| `freq` | J1~J6(§7.2)+ 直流与谐波抑制 + UA→IA 源回退 + 守卫与幂等 | `test/host/freq/` |
|
||||
| `adc_cal` / `run_par` | Q16 无舍入与合法带含端点、坏文件/坏段/坏零点回退、真 cfg 引擎往返、未标定闭锁单点,以及 §10.1 那三态观测计数(含"一次 save 覆盖多次整定"的回归锁) | `test/host/adc_cal/`、`test/host/run_par/`(用例清单在两个套件文件头,本文不复制条数) |
|
||||
|
||||
命令与对应 pre-commit 钩子名见 `App/algo/README.md` §4(钩子清单的属主是
|
||||
`.pre-commit-config.yaml`,用例清单的属主是各 `test/host/<模块>/` 与上表细则文档——本文不复制数字,
|
||||
|
||||
Reference in New Issue
Block a user