三条道在同一棵树上交付,故合并成**一次权威构建**(@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,交台架道补。
9.0 KiB
开机账本单元测试说明(主机侧)
模块: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(RTOS 与内存铁律、[ERR]通道口径)、测试开发标准流程.md(测试分层与门禁总览)、主机单测覆盖率口径.md(覆盖率总闸如何收编本套)、../子系统设计/系统墙钟时间维护说明.md§13.1(同一块 70 B 用户 SRAM 的占用分配表,本模块占 0..7)、../子系统设计/IEC104上行设计说明.md(同一张运行报告页的另一半数据源:104 观测计数)
1. 为什么要单独做这一套
被测量的用途是回答"这台装置上了多少次电"。这类账本错的时候不像错:屏幕上永远 有一个数字,格式正常、量级正常,没有任何一行日志会说它是假的。四条只能靠构造才能 复现的失效路径:
- I2C 读失败被当成"没有记录":一次总线抖动就把计数当 0 重来,从此从 1 往上数。 更坏的是读失败还顺手回写——那会把可能完好无损的旧记录覆盖掉,故障从"这次读不到" 升级成"永久少一笔账"。
- "本模块首次启用"与"RTC 电池域掉过电"被合并成一态:这两件事的文案完全不同 (前者是"账本从这一版固件开始",后者是"换过电池,历史没了")。合并就是拿前者 冒充后者。
- 只信 magic、不信校验和:电池欠压造成的坏区常是整片全 0 或整片全 FF,magic 那一关自然过不去;但更常见的失效是个别字节翻转——那时 magic 可能仍然是好的, 而次数会错 256 倍。这种数字最容易被人当成真的。
- 到顶回绕而不是饱和:回绕成个位数会被读成"刚上电几次";停在上限则一眼能看出 到头了。
真机只能覆盖"一切正常"那一条: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. 怎么跑
& '.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水位看。