feat(ui+记录): 整定写路径与四类页落地,五类记录写入方补齐
UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机 OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批 预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block (口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng 反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定 内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按 is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。 记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在 defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE); 事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身 (一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。 为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核, 丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。 为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费 两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过; RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同 码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。 实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B; RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS, protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→ 164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34, 既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、 不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页 连跑两次逐字节一致;字库 diff 空。 顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽 16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前 靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值 全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py 模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。 未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行; LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND), 已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
This commit is contained in:
+166
-42
@@ -96,13 +96,15 @@ flowchart TB
|
||||
F --> SRC
|
||||
end
|
||||
subgraph BSP["Drivers/BSP/UI(表现层)"]
|
||||
D["ui_data.c/h<br/>快照存储 + 只读访问器"]
|
||||
P["ui_page_ac / ui_page_yx / ui_page_setlist ...<br/>只读快照 + 格式化 + 布局"]
|
||||
D["ui_data.c/h<br/>快照存储 + 只读访问器<br/>+ 定值写入队列与状态机"]
|
||||
P["ui_page_ac / ui_page_yx / ui_w_setview+ui_w_setlist ...<br/>只读快照 + 格式化 + 布局(整定页另加写投递)"]
|
||||
P --> D
|
||||
P -- "整定保存:ui_data_param_request(&req)" --> D
|
||||
end
|
||||
T["App/task/ui_task.c<br/>调度:每轮调 feed_tick()"] --> F
|
||||
T --> P
|
||||
F -- "ui_data_publish_rt/par(&帧)" --> D
|
||||
F -- "落定:ui_data_param_drain(&ops),执行侧由 App 注入" --> D
|
||||
```
|
||||
|
||||
**include 方向**(全部合法):
|
||||
@@ -116,13 +118,17 @@ flowchart TB
|
||||
|
||||
**`ui_data.c` 不 include 任何 App 头,也不 include 任何页面头**——它是叶子。
|
||||
|
||||
写路径没有给上表新增任何一行:它走的是**反向调用**(BSP 声明函数表、App 提供实现并按节拍
|
||||
把表传进来,见 §6.2),所以 include 方向不变、`layer-deps` 基线不变。这类"下层定义抽象、
|
||||
上层实现并注入"的第三条腿在 §7.2 的记录页 reader 上已经用过一次,两次都不新增 `ALLOW` 条目。
|
||||
|
||||
### 3.2 三个模块的职责边界
|
||||
|
||||
| 模块 | 位置 | 只做 | 绝不做 |
|
||||
|---|---|---|---|
|
||||
| `ui_data` | `Drivers/BSP/UI/` | 存最新一帧、给只读访问、记发布序号 | 取数、格式化、判断业务含义 |
|
||||
| `ui_data_feed` | `App/app/` | 按节拍从各 App 模块取数、组装、发布;变化检测 | 布局、字符串格式化、LVGL 调用 |
|
||||
| 页面 `ui_page_*` | `Drivers/BSP/UI/` | 读快照、按行表格式化、交给框架绘制 | 直接调 App、自己算业务量 |
|
||||
| `ui_data` | `Drivers/BSP/UI/` | 存最新一帧、给只读访问、记发布序号;**存定值写入请求队列、跑那台状态机**(越界与闭锁的判定按注入的 ops 问,自己一条业务规则都不持有) | 取数、格式化、判断业务含义、碰 `prot_set` 与文件系统 |
|
||||
| `ui_data_feed` | `App/app/` | 按节拍从各 App 模块取数、组装、发布;变化检测;**每轮 drain 一条定值请求并注入执行侧实现** | 布局、字符串格式化、LVGL 调用 |
|
||||
| 页面 `ui_page_*` | `Drivers/BSP/UI/` | 读快照、按行表格式化、交给框架绘制;整态把改动攒在**编辑叠层**里(改动位图 + 工作值,`ui_w_setview.c` 模块级一份——同屏只开一页,与各查看页的行缓存同一假设),只在按"保存"时投递一次 | 直接调 App、自己算业务量、自己判越界(行内上下限只是**镜像**,用于早拦一手;真源在 App,见 §9.4 一致性测试) |
|
||||
|
||||
---
|
||||
|
||||
@@ -188,21 +194,41 @@ typedef struct {
|
||||
|
||||
```c
|
||||
#define UI_DATA_SET_N 40u /* 定值项数(与 prot_set 枚举同序,见 §9.2 一致性测试) */
|
||||
#define UI_DATA_VER 2u /* 契约版本;字段只尾追加、追加即升版(§4.3) */
|
||||
|
||||
typedef struct {
|
||||
uint32_t seq;
|
||||
uint32_t checksum; /* 与 prot_set_checksum() 同值:页面据此判"参数是否变过" */
|
||||
uint8_t valid;
|
||||
uint8_t locked; /* 定值闭锁(校验和自检失败等) */
|
||||
uint8_t locked; /* 定值闭锁(校验和自检失败等)—— 定值**写不进去** */
|
||||
uint8_t bad_valid; /* 越界位图有效 */
|
||||
uint8_t rsv;
|
||||
uint64_t bad_mask; /* 40 位:置 1 = 该定值项越界 */
|
||||
int32_t q10[UI_DATA_SET_N]; /* Q10 原值;页面按行表的小数位换算显示 */
|
||||
} ui_data_par_t;
|
||||
uint8_t cal_block; /* v2 尾追加:未标定闭锁(判据停走)—— 定值**仍可改** */
|
||||
} ui_data_par_t; /* 当前 192 B(v2 前 184 B;尾字节由 8 字节对齐补齐) */
|
||||
```
|
||||
|
||||
**为什么存 Q10 而不是 float**:定值是**整表校验和覆盖的定点值**,快照存浮点会引入"显示值与校验和口径不一致"的二义性。Q10 → 显示值的小数位换算属表现层(§8)。
|
||||
|
||||
**`locked` 与 `cal_block` 是两个事实,屏上是两条通道、两个词,不得合并**(判据口径
|
||||
`docs/任务规划/数据可信.md` §4.3):
|
||||
|
||||
| 字段 | 真源 | 说的是什么事 | 屏上落点 | 还能改定值吗 |
|
||||
|---|---|---|---|---|
|
||||
| `locked` | `prot_set_locked()`(L5-1 校验和自检失配) | 这台装置现在**写不进去** | 整定页保存行值列 `LOCK`,且 ENTER 一律不消费(禁编辑) | ❌ |
|
||||
| `cal_block` | `adc_cal_flags()` 的 `ADC_CAL_F_VALID`(UA/UN/IA 三路,判据侧同源 `App/task/protect_task.c`) | 定值能改,但**改了也不会动作**(判据停走) | 整定页标题右侧「未标定」徽标 | ✅ |
|
||||
|
||||
合成一个词就等于互相冒充:现场看到 `LOCK` 会以为"改了白改"而不去标定,看到「未标定」
|
||||
会以为"这台装置写不进定值"而放弃正当的整定操作。两个字段的取数都在 `App/app/ui_data_feed.c`
|
||||
完成,页面零 App include(分层方向由 `scripts/check_layer_deps.py` 守住)。
|
||||
|
||||
> 【v2 落地注(2026-10-26,道F task-1)】本次只尾追加 `cal_block`,帧长 184 → 192 B;
|
||||
> 组装缓冲 `s_scr` 是 rt/par/info 的联合体,而最大者是 **rt(208 B)**,所以这笔追加
|
||||
> 实际**没多花一字节 RAM**——这条"谁兜住了谁"的关系由主机单测的尺寸断言钉住
|
||||
> (帧长一变,那处注释就是唯一会撒谎的地方)。升 `UI_DATA_VER` 是必须的:版本号是
|
||||
> 页面与写侧唯一的对齐凭据,追加字段不升版 = 老页面按新帧长 memcpy 时静默错位。
|
||||
|
||||
### 4.3 扩展规则
|
||||
|
||||
- **只允许尾部追加字段**,结构体开头保留 `seq/valid`。
|
||||
@@ -249,9 +275,9 @@ typedef struct {
|
||||
| 通道 | 节拍 | 理由 |
|
||||
|---|---|---|
|
||||
| `rt` | **200 ms**(内部计数分频,UI tick 10 ms × 20) | 与遥测链目标刷新率一致;人眼在 160×160 单色屏上感知不到更快 |
|
||||
| `par` | **变化触发**:每 200 ms 比一次 `prot_set_checksum()`,变则重发 | 避免每拍拷 160 B;定值变化是低频事件 |
|
||||
| `par` | **变化触发**:每轮比三个各自独立的变化源——`prot_set_checksum()`(定值改动)、`prot_set_locked()`(自检闭锁)、未标定判定(标定完成),**任一翻转就重发一帧** | 定值变化是低频事件,只盯校验和能省下每拍的 192 B 拷贝;但 `locked` 与 `cal_block` 都**不改动校验和**,只盯校验和会让保存行的 `LOCK` 与标题的「未标定」永远停在旧值上(真机现象:标定做完了徽标还挂着,直到某次定值改动被顺带刷掉——那是把两件不相干的事当同步点) |
|
||||
|
||||
`ui_data_feed_tick()` 每轮调用是**廉价**的:一次 tick 差值判断 + 一次 uint32 比较。真正的组装只在到期时发生。
|
||||
`ui_data_feed_tick()` 每轮调用是**廉价**的:一次 tick 差值判断 + 一次 uint32 比较 + 两次字节比较(`locked`/`cal_block` 上一帧值)。真正的组装只在到期或有源翻转时发生,无变化时零拷贝。
|
||||
|
||||
### 5.3 启动时序
|
||||
|
||||
@@ -301,36 +327,90 @@ static void pg_refresh(uint16_t row, char *name, char *value) {
|
||||
|
||||
### 6.2 请求 / 状态交接(复用 `net_cfg` 范式)
|
||||
|
||||
落地形态(道F task-1 落本层队列与状态机,task-2 落页面侧消费者,2026-10-26):
|
||||
|
||||
```
|
||||
ui_page_setlist(编辑态)
|
||||
│ 用户按"保存"
|
||||
ui_page_view_param / _set / _word / _plate(整定态:123 授权 → attach_edit 绑 set/commit/state)
|
||||
│ 选中"保存"行按 ENTER(组件 ui_w_setlist_key 先过两道门:可写权限 + state()!=LOCK)
|
||||
▼
|
||||
ui_data_param_request(&req) ← BSP 侧只投递,不执行
|
||||
│ (小环形队列,深度 4;满则返回 -1,页面提示"忙")
|
||||
sv_commit():req.mask = 改动位图;req.q10[] 只按 mask 置位的槽换算(显示值 → Q10)
|
||||
▼
|
||||
ui_data_feed_tick()(App 侧,uiTask 上下文)
|
||||
│ 取出请求 → 校验(调 prot_set 的只读接口预检)
|
||||
ui_data_param_request(&req) ← 本层只投递,不执行、不判越界、不碰文件系统
|
||||
│ 静态环形队列,深度 4(UI_DATA_PARAM_QD);满 → -1(页面显 ERR + 页脚"忙")
|
||||
│ 入队 = 168 B 整条拷入(.bss 4 × 168 = 672 B;零堆、零变长拷贝)
|
||||
▼
|
||||
prot_set_set_sealed() / prot_set_set() + prot_set_seal()
|
||||
│ 成功 → prot_set_dirty_mark() → defaultTask 落盘
|
||||
ui_data_feed_tick()(App 侧,uiTask 上下文;先 drain 再比校验和,顺序即判据)
|
||||
│ ui_data_param_drain(&s_param_ops):每轮最多落定一条
|
||||
│ ② ops->locked() == 1 → ERR_LOCKED,**本条丢弃**(不排队等解锁)
|
||||
│ ③ ops->check() 按 mask 逐项整批预检,任一项越界 → ERR_RANGE + err_mask,一字不写
|
||||
│ ④ 预检全过再逐项 ops->apply() → prot_set_set_sealed()(只写 mask 内的 id)→ OK
|
||||
│ 值真的变了 → 同轮重发 par 帧(页面按 seq 失效缓存)
|
||||
▼
|
||||
ui_data_param_state(OK / ERR_RANGE / ERR_LOCKED / ERR_BUSY)
|
||||
│
|
||||
▼
|
||||
ui_page_setlist 显示"保存成功 / 越界:Ⅲ段定值"
|
||||
ui_data_param_state() / ui_data_param_err_mask()(保存行显词 + 越界行反显)
|
||||
```
|
||||
|
||||
**这套"UI 只投递、属主任务执行、状态回读"的结构,与 `net_cfg` 的 `net_cfg_save_request` / `net_cfg_save_state` 完全同构**(`Drivers/BSP/NETCFG/net_cfg.h`),不引入新范式——这是"低耦合"的另一个含义:**项目里同类问题只有一种解法**。
|
||||
四个公开函数(声明 `Drivers/BSP/UI/ui_data.h`、实现 `ui_data.c`,全在本层,零 App include):
|
||||
|
||||
| 函数 | 作用 | 返回值口径 |
|
||||
|---|---|---|
|
||||
| `ui_data_param_request()` | 投递一条请求(页面"保存"的唯一出口) | `0` 已入队;**`mask=0` 的空请求也返 0**,但不占槽位、直接落终态 OK(一个都没改就按保存是正常操作,报 ERR 会让现场以为装置有问题);`-1` 队列满;`-2` 参数无效(NULL,或 `mask` 含 `UI_DATA_SET_N` 以上的位——调用方的编程错误不该落成屏上的"越界") |
|
||||
| `ui_data_param_state()` | 回读写路径状态 | `UI_DATA_PARAM_ST_{IDLE,WAIT,OK,ERR_RANGE,ERR_LOCKED,ERR_BUSY}` |
|
||||
| `ui_data_param_err_mask()` | 最近一条**被本次预检挡下**的项位图 | bit i = 1 → 第 i 项越界;最近终态不是 `ERR_RANGE` 时恒 0 |
|
||||
| `ui_data_param_drain()` | 落定一条(App 装配节拍入口) | `1` 处理并提交了一条;`0` 队列空(状态不动);`-1` ops 或其函数指针为 NULL |
|
||||
|
||||
**"算不算越界"与"现在能不能写"都是 App 侧事实,本层一份都不复制**:执行能力经
|
||||
`ui_data_param_ops_t`(`locked` / `check` / `apply` 三个函数指针)由 `App/app/ui_data_feed.c`
|
||||
**每次调 drain 时注入**,本层不保存副本。原因有两条,都不含糊:① 分层方向硬约束——
|
||||
`Drivers/BSP/**` 不得 include `App/**`(`scripts/check_layer_deps.py` 拦提交),而上下限真源
|
||||
在 `App/algo/prot_set.c` 的 `g_prot_set_meta`;② 注入表**刻意不含任何范围数值**,多传一份
|
||||
上下限就是第二真源、就会漂。同 §7.2 记录页的注入式 reader 一条路子,额外的好处是主机单测
|
||||
可以把三个指针换成桩件,于是**队列与状态机的生产代码**(不是副本)能在 PC 上把"满队列 /
|
||||
越界不改值 / 闭锁拒写 / 正常往返"跑全(`test/host/ui_data/`)。
|
||||
|
||||
**请求形状 = 「位图 + 定长 40 槽」(168 B)而不是「项数 + 变长 id 数组」**:表现层的编辑叠层
|
||||
本就是一张改动位图 + 一张工作值表,本形状与它逐位同构 → 提交侧一次赋值、执行侧逐项按位取,
|
||||
两侧都不必维护第三种表示;定长还让队列退化成 O(1) 静态数组。边界与代价:`mask` 只有 64 位
|
||||
→ 定值项数硬上限 64(`ui_data.c` 有编译期守卫,破 64 是编译失败而不是静默丢项);队列 672 B
|
||||
+ 控制字 16 B 是买"整批原子 + 零变长 + 零堆"的价钱(`docs/内存管理/RAM使用分析与优化方案.md`)。
|
||||
|
||||
> ⚠ **提交侧必须只提交 `mask` 内置位的 id,绝不"从显示值回提全表"**:Q10 存不下 0.95 这类
|
||||
> 返回系数(实际存 972/1024),按显示域往返会把用户没动的项顺手改掉。判据"越界值不落盘"与
|
||||
> "改哪条只落哪条"都建在这条上(`docs/任务规划/UI补齐.md` §2 第 2 行)。
|
||||
> 换算的往返精度因此也**不需要**逐位可逆:没动的行根本不进换算函数。
|
||||
|
||||
**这套结构与 `net_cfg` 的 `net_cfg_save_request` / `net_cfg_save_state` 完全同构**
|
||||
(`Drivers/BSP/NETCFG/net_cfg.h`),不引入新范式——这是"低耦合"的另一个含义:
|
||||
**项目里同类问题只有一种解法**。
|
||||
|
||||
### 6.3 失败语义
|
||||
|
||||
| 返回 | 含义 | 页面表现 |
|
||||
|---|---|---|
|
||||
| `OK` | 已封存并置脏 | 行值刷新,页脚短暂"已保存" |
|
||||
| `ERR_RANGE` | 某项越界 | 高亮越界行(用 `bad_mask`),页脚"越界" |
|
||||
| `ERR_LOCKED` | 定值闭锁中(自检失败) | 页脚"定值闭锁",禁止编辑 |
|
||||
| `ERR_BUSY` | 请求队列满 | 页脚"忙,请重试" |
|
||||
| `ERR_TIMEOUT` | 3 s 未取走 | 页脚"无响应" |
|
||||
落定结果只有 5 个值(`UI_DATA_PARAM_ST_*`),页面侧的**词与位置**由整定视图统一映射,
|
||||
四张整定页共用一套(组件按 `state()` 回调给的值画保存行值列,页面不参与解释):
|
||||
|
||||
| 状态 | 含义 | 保存行值列 | 页面附带动作 | 产出方 |
|
||||
|---|---|---|---|---|
|
||||
| `IDLE` | 本次打开页面从未投递过 | `ENTER` | — | 本层初值 |
|
||||
| `WAIT` | 已投递、队列里还有请求在路上 | `WAIT` | 编辑叠层**保留**(不清稿) | `request` 受理 / `drain` 未排空 |
|
||||
| `OK` | 已逐项写入并置脏 | `OK` | 按 `commit` 位图清掉叠层,让位给新帧 | `drain` 第④步 |
|
||||
| `ERR_RANGE` | 整批预检有任一项越界 → **一字未写** | `ERR` | 用 `ui_data_param_err_mask()` 反显越界行 | `drain` 第③步 |
|
||||
| `ERR_LOCKED` | 投递时定值闭锁 → **本条丢弃** | `LOCK` | 反显同 `ERR_RANGE`;`drain` 不回滚(`prot_set` 无回滚接口,谎称"整批未写"比如实报更坏) | `drain` 第②步 |
|
||||
| `ERR_BUSY` | 投递被拒:队列满 | `ERR` | 页脚"忙,请重试";**在队请求一条不动**,用户直接再按一次保存 | `request` 返 -1 |
|
||||
|
||||
三条实现侧口径,都是原稿没写死的:
|
||||
|
||||
1. **`LOCK` 还有一个与状态机无关的来源**:帧里 `par.locked == 1` 时保存行**直接显 `LOCK`
|
||||
并禁 ENTER**(优先级高于历史结果)。闭锁是持续状态,不该只在"某次投递被拒"那一帧才说一次;
|
||||
禁编辑必须落在键路上,只靠屏上词会让用户白改一轮。
|
||||
2. **`ERR_TIMEOUT`(原稿的"3 s 未取走")本层不产出**——uiTask 每 10 ms 就 drain 一次,
|
||||
真出现"永不落定"是 App 侧没接线的装配缺陷,不是运行期状态。页面若要兜底,可自己按投递
|
||||
时刻计时(属页面本地可选项,当前没做:没有消费者就别先加一条无人走的分支)。
|
||||
3. **状态是全局单值,页面必须锁存显示**:`ui_data_param_state()` 落定后一直停在那个值上,
|
||||
直读会让用户移动一下光标就看到别的页留下的旧结果。整定视图把它锁存成"本页投过一次
|
||||
才显示那次的答案、开页复位成 `ENTER`"(`ui_w_setview.c` 的 `s_disp`)。
|
||||
|
||||
> 词表刻意用 ASCII(`ENTER`/`WAIT`/`OK`/`ERR`/`LOCK`)与 `net_cfg` 保存行同排;
|
||||
> 中文「未标定」徽标是**另一件事**(`cal_block`),见 §4.2 的两源两通道表,不要为了
|
||||
> 省一个词把它并进 `LOCK`。
|
||||
|
||||
---
|
||||
|
||||
@@ -446,6 +526,18 @@ typedef struct {
|
||||
> 2. 固定帧里的定值**不手抄 40 个数字**,而是把 `App/algo/prot_set.c`(零平台依赖的纯算法,双端共享核心)链进模拟器、`prot_set_init()` 后取出厂默认——抄表等于造第二真相(同 `scripts/check_font_charset.py` 的"单一规则源"口径);
|
||||
> 3. `build_date` 用**固定串**而不是 `__DATE__`:否则跨天重建会把装置信息页的黄金参考刷掉,而黄金参考是判据本身。
|
||||
> 4. 原稿设的 `--live` 合成正弦模式**尚未实现**(当前无消费者,等动态刷新真的需要人眼观察时再加)。
|
||||
> 5. **定值写路径在模拟器侧有一份同构的执行实现**(`sim_param_locked/check/apply` +
|
||||
> `sim_param_pump()`):真机那段在 `App/app/ui_data_feed.c` 的 tick 里,而模拟器不链接装配器,
|
||||
> 所以在 `main.c` 里镜像一份——`check()` 用的仍是同一份 `g_prot_set_meta` 真源(链进来的
|
||||
> `App/algo/prot_set.c`),因此"模拟器里越界被拒"与"真机上越界被拒"是同一个判定,不是两份规矩。
|
||||
> 固定帧的 `cal_block` 显式填 1:**真机现在就是**未标定态(`rt.eng` 也按同一条口径钉在引脚档),
|
||||
> 屏上的话必须与本机一致,模拟器才配当像素基准。
|
||||
> 6. 落定与"页面自己发现数据变了"的分工:`sim_param_pump()` 在无头模式排在注键之后、截图之前
|
||||
> 同步排空(真机由每 10 ms 的 tick 排);整定视图另有一条 100 ms 的 LVGL 轮询定时器,
|
||||
> 负责把"主站写进来的新值 / 标定完成 / 落定但不改校验和的 ERR"推上屏。**这条定时器在无头
|
||||
> 回归里一次都不会触发**——`run_headless()` 严禁调 `lv_timer_handler()`(I1+FULL 全屏刷新会
|
||||
> 进渲染死循环),所以黄金像素与它无关;两次连跑逐字节一致即为证据(`simulator/README.md`
|
||||
> 的同条注)。窗口模式才用它。
|
||||
|
||||
### 9.3 与"虚拟注入"的关系
|
||||
|
||||
@@ -469,18 +561,29 @@ typedef struct {
|
||||
|
||||
## 10. 内存与性能
|
||||
|
||||
| 项 | 量 | 说明 |
|
||||
| 项 | 量(`.map` 实测) | 说明 |
|
||||
|---|---|---|
|
||||
| `ui_data` rt 快照 | ≈ 132 B | 9×8 + 位图 + 时钟串 |
|
||||
| `ui_data` par 快照 | ≈ 180 B | 40×4 + 头部 |
|
||||
| 状态/队列 | ≈ 60 B | 请求环 4 条 + 状态字 |
|
||||
| **合计(RW_IRAM1)** | **≈ 380 B** | 当前 RW_IRAM1 余 57,504 B → 无压力 |
|
||||
| 页面行缓存 | 每页 ≈ 132 B(仅数据页) | 页面私有静态,随页数线性;若 6 个数据页 ≈ 800 B |
|
||||
| LVGL 池 | **0** | 快照不进池(不是 LVGL 对象) |
|
||||
| 每 200 ms 拷贝 | ≈ 132 B | 一次 memcpy,微秒级 |
|
||||
| 每帧(10 ms) | 0 次拷贝(比 seq 后只在变化时拷) | 见 §5.4 |
|
||||
| `rt` 帧 | 208 B | 遥测 9 路 + 相量槽带 + 位图 + 时钟串;三帧串行组装用的 `s_scr` 是联合体,**取最大者 = rt** |
|
||||
| `par` 帧 | 192 B | 40×4 Q10 + 头部 + `cal_block`(v2);追加这 8 B 由 rt 兜住,**净增 0**(尺寸关系由主机单测钉住,见 §4.2 落地注) |
|
||||
| `info` 帧 | 随字符串字段定长 | 与 rt 同节拍发布,同样落在联合体里,不额外占 |
|
||||
| `ui_data.o` 全部静态 | `.bss` 1,168 B | 含三帧存储、`s_scr` 联合体、**定值请求队列 4×168 = 672 B + 控制字 16 B** |
|
||||
| `ui_w_setview.o` 静态 | `.bss` 352 B / `.data` 48 B | 四视图行缓存 + **整定编辑叠层 190 B**(`int32_t[40]` + 三张位图 + 锁存态) |
|
||||
| 页面行缓存 | 每页 ≈ 52 B(`ui_page_view_*.o` 各 `.bss` 52 B) | 定值相关页共用一份视图缓存(同屏只开一页),所以四张整定页**没有**再各存一份 192 B |
|
||||
| LVGL 池 | 快照 0 B;轮询定时器一只 | 快照不进池(不是 LVGL 对象);定时器是 LVGL 池对象,只建一次不删 |
|
||||
| 每 200 ms 拷贝 | 一帧(≤208 B),且只在变化时 | 一次 memcpy,微秒级 |
|
||||
| 每轮(10 ms) | 0 次拷贝 | 比 seq / 比三个变化源;见 §5.2、§5.4 |
|
||||
|
||||
**结论**:代价是"几百字节 + 每 200 ms 一次 memcpy",换来的是整个 UI 层的数据可及性。
|
||||
栈侧(同一份 `.map` 的调用图,改前改后各跑一次 `scripts/stack_report.py`):uiTask 静态最坏链
|
||||
**1336 B / 配置 16 KB(余 15,048 B,91%)在接入写路径前后逐字节不变**——新链路自身都不深
|
||||
(`sv_commit` 帧 192 B 含 168 B 请求体、链深 208 B;「未标定」徽标帧 160 B、链深 536 B;按键链
|
||||
`pg_key` 起算 480 B),最坏链仍是 LVGL 建树那条,**没有换人**。这是"页面 commit 会在 uiTask 栈上
|
||||
放 168 B 局部量"这类担心的实测答案,也是本表值得写实测数而不写"约几百字节"的理由。
|
||||
|
||||
**结论**:代价 = 约 1.5 KB 静态 + 每 200 ms 一次 memcpy,换来整个 UI 层的数据可及性与一条
|
||||
可测的写路径。区域水位(RW_IRAM1 / RW_CCM / Flash)**不在本文复制**——那是受管快照,
|
||||
由 `scripts/ram_baseline.py` 从每次 `@build` 的 `.map` 生成,第二处写死就是一份会过期的
|
||||
真源(本仓已有一条门禁在钉这个,口径见 `docs/内存管理/RAM使用分析与优化方案.md` §2.1 与
|
||||
`docs/内存管理/RAM预算台账.md`)。本次新增约 1.5 KB 静态 + 一条 LVGL 池定时器,量级无压力。
|
||||
|
||||
---
|
||||
|
||||
@@ -492,7 +595,7 @@ typedef struct {
|
||||
| **步骤2** | `App/app/ui_data_feed.c` + `ui_task.c` 接线(每轮调 tick) | 串口打印首帧(临时锚点);`@build` 0/0 | 步骤1 |
|
||||
| **步骤3** | 交流量 / 直流量回炉(接快照 + 行表加 `src/dot/unit`) | 模拟器固定帧 golden 更新;台架看真实码值变化 | 步骤2 |
|
||||
| **步骤4** | 遥信量 + 定值/控制字/软压板查看(4 页) | 主站改定值 → 页面 200 ms 内变 | 步骤3 |
|
||||
| **步骤5** | 写路径(请求/状态)+ 整定 4 页 | 就地改定值 → 保存 → 掉电重启 → 值保持 | 步骤4 |
|
||||
| **步骤5** | 写路径(请求/状态)+ 整定 4 页 | 就地改定值 → 保存 → 掉电重启 → 值保持 | 步骤4。**已落**(道F task-1 落本层队列与状态机、task-2 落整定 4 页与模拟器侧,2026-10-26);「掉电重启值保持」只能上板收口,见 §12 判据 6 |
|
||||
| **步骤6** | 记录 reader 注入 + 删 3 条 layer-deps 基线 | `check_layer_deps.py` 基线由 6 → 3 | 可与步骤3 并行 |
|
||||
|
||||
**步骤1~步骤2 是关键路径**:它们不产出任何用户可见效果,但决定后面所有页面是否可信。**不要跳过步骤1 直接改页面**——那会退化成"页面里塞假数据 2.0"。
|
||||
@@ -533,6 +636,26 @@ typedef struct {
|
||||
> 「当前是否已过密码」既不真实也不该被推断。判据侧的过法只有一套(查看密码 = 出厂 000 /
|
||||
> 修改密码 = 123,键序与黄金回归的网口设置编辑态完全一致),口径与纪律见
|
||||
> `docs/UI设计/UI_自动化测试流程.md` §7.5;本节只登记这条事实,避免两份文档各说各话。
|
||||
>
|
||||
> 【落地注·六(写路径消费者已上屏,2026-10-26,道F task-2)】整定 4 页(参数 / 定值 /
|
||||
> 控制字 / 软压板)绑到与查看列**同一批行表、同一个 `ui_w_setlist` 组件**:同一份页面
|
||||
> 描述符按 `ui_content_page_is_editable()` 分派只读 / 可写两态,两态的差别只有绑不绑
|
||||
> `set/commit/state` 三个回调。于是本节契约里原本只是示意图的那条链路第一次真的被按到:
|
||||
> **越界值不落盘**(提交只带改动位图)、**闭锁禁编辑**(`state()` 回 LOCK 或帧里
|
||||
> `locked=1` 就直接挡住 ENTER,而不是只换个屏上词)、**落定要自己上屏**(`ERR_RANGE` 与
|
||||
> `ERR_LOCKED` 都不改校验和,页面收不到新帧,靠的是整定视图那条 100 ms 轮询,见 §9.2 注 6)。
|
||||
>
|
||||
> 这一批还暴露了两条「只有第一个消费者才会暴露」的缺陷,都已修(为什么必现写在
|
||||
> `docs/UI设计/页面扩充详细方案.md` §3.2 落地注):二态行 ENTER 不回绕(写出 opts 之外的
|
||||
> 下标:屏上看着没变、叠层里已是越界值,保存必被预检挡回);「参数帧已就位」原先只在
|
||||
> **绘制路径**里被置位,而按键门读它 → 无头回归里开页后的第一个 ENTER 永远被挡(真机开页
|
||||
> 10 ms 内就重绘,症状只在测试侧出现)。第二条的修法是把「数据就位」与「画过没有」解耦:
|
||||
> 判就位之前先按 seq 做一次 sync(帧没变时只是一次 uint32 比较)。
|
||||
>
|
||||
> 另记一条同批暴露、与本契约无关的事实:菜单树重跑时 `ui_menu_tab.h` 有一行注释
|
||||
> 跟着生成器的模板文字变了 —— 说明 HEAD 上「生成器模板」与「已提交生成物」本就不同步
|
||||
> (改模板那笔没重跑生成器)。生成物以生成器输出为准即可,但这条滞后该补一条门禁,
|
||||
> 已登记为待办「生成器幂等性检查」。
|
||||
|
||||
---
|
||||
|
||||
@@ -545,8 +668,9 @@ typedef struct {
|
||||
| 3 | 交流量页显示真实相量(非写死值) | 台架:注入已知信号 → 页面值随信号变 |
|
||||
| 4 | 未标定时页面明确标识(不把码值当伏特) | 目检 + 文本断言(`eng=0` 时带标记) |
|
||||
| 5 | 首帧前显示 `--` 而非 `0.000` | 模拟器:不 publish 直接开页,断言文本 |
|
||||
| 6 | 定值整定全链闭环 | 就地改 → 保存 → 重启 → 值保持 + 校验和自检通过 + 主站 C_RD 一致 |
|
||||
| 7 | 像素回归稳定 | `ui-golden` 连续两次运行零差异 |
|
||||
| 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 页连跑两次逐字节一致(含整定 9 态);轮询定时器不影响——无头模式不调 `lv_timer_handler()`(§9.2 注 6) |
|
||||
| 9 | 「写不进去」与「改了不动作」两条事实互不冒充 | 反向文本断言(`check_content_pages.py` 的 `ABSENT`:查看页与只读态不得出现「未标定」徽标与「保存」行)+ 台架:制造一次 `locked`(改坏校验和档)目检保存行 `LOCK` 且 ENTER 无反应 |
|
||||
| 8 | 主机单测覆盖数据模块 | `test/host/ui_data` 全绿并挂 pre-commit 钩子 |
|
||||
|
||||
---
|
||||
|
||||
+53
-10
@@ -203,6 +203,12 @@ V0.1 **无直流采样通道**(`ADC_SEM` 只有 8 路电气 + 温度),点
|
||||
|
||||
8 个叶子(整定 4 + 查看 4)共用**一份** `ui_w_setlist` 组件 + **4 张视图表**。查看与整定的差别只是 `edit_capable` 与权限。
|
||||
|
||||
> 【2026-10-26 落地】查看 4 页(道E task-15 批 3)与**整定 4 页(道F task-2)都已落**:
|
||||
八个叶子绑的是**同一批模块**(`view_param` / `view_set` / `view_word` / `view_plate`),
|
||||
两态共用**同一份页面描述符**——差别不写在描述符里,而在 `pg_init` 按 `ui_content_page_is_editable()`
|
||||
选 `ui_w_setview_attach()`(只读)还是 `ui_w_setview_attach_edit()`(可写)。
|
||||
下方 §3.2.6 那张按两种描述符写的表已照此更正。
|
||||
|
||||
#### 3.2.1 40 项四视图划分
|
||||
|
||||
| 视图 | 项数 | 判据 | 页数(8 行/页) |
|
||||
@@ -294,23 +300,48 @@ V0.1 **无直流采样通道**(`ADC_SEM` 只有 8 路电气 + 温度),点
|
||||
|
||||
| 项 | 查看(4 页) | 整定(4 页) |
|
||||
|---|---|---|
|
||||
| 权限 | 无密码 → `UI_PERM_VIEW` | 密码 → 按所输密码授 `VIEW`/`MODIFY` |
|
||||
| `edit_capable` | 0 | 1 |
|
||||
| 页脚 | 只显示页码 | 额外一行"保存"(第 8 行固定为保存行) |
|
||||
| 提交 | — | 整表 `prot_set_set*` + `prot_set_seal()` + 置脏,走请求/状态交接 |
|
||||
| 权限 | 无密码 → `UI_PERM_VIEW` | 密码 → 按所输密码授 `VIEW`/`MODIFY`(123 = 修改、000 = 查看) |
|
||||
| `edit_capable` | 描述符置 1,但 `is_editable()` 为假 → 走只读 attach | 同一份描述符,`is_editable()` 为真 → 走 `attach_edit` |
|
||||
| 组件回调 | `commit = state = NULL` → **不画保存行**、ENTER 不消费(绑了 `key_cb` 也等价于没绑) | 绑 `set/commit/state` → 画保存行、放开编辑 |
|
||||
| 页脚 | 只显示页码 | 额外一行"保存"(挂在**末行之后**,不是固定第 8 行) |
|
||||
| 提交 | — | 只提交**改动位图**内的 id(一次封存、一次校验和),经 `ui_data_param_request()` 投递,App 侧 `drain` 落定 → 见 `docs/UI设计/UI_数据供给设计说明.md` §6.2 |
|
||||
| 越界反显来源 | `par.bad_mask`(装载侧,且 `bad_valid=1` 才参与) | 同上 **+** `ui_data_param_err_mask()`(本次提交被整批预检挡下的项) |
|
||||
| 闭锁 | 无写通路,不需要表达 | `par.locked=1` 或投递回 `ERR_LOCKED` → 保存行显 `LOCK` **且 ENTER 一律不消费**(禁编辑,不是只换个词) |
|
||||
| 未标定 | 不画(查看页像素恒零变化) | 标题右侧「未标定」徽标(`par.cal_block`);与 `LOCK` 是两件事,两源两通道两词 |
|
||||
|
||||
> 保存行占掉一个可见行 → 整定页行数 = 视图项数 + 1,分页随之增加:参数 9 行 2 页、定值 19 行 3 页、控制字 5 行 1 页、软压板 11 行 2 页(合计 8 页)。这是"保存必须整表封签"带来的必然代价,不是设计缺陷。
|
||||
|
||||
> 【2026-10-26 落地实测 + 两条必修缺陷】上述分页与黄金参考逐页对上(`sparam_p2` /
|
||||
> `sset` / `sword_cycle` / `splate` 四条用例的页脚分别是
|
||||
> 「共 2 页 / 共 3 页 / 共 1 页 / 共 2 页」)。**「整表封签」指的是一次保存只封存一次、
|
||||
> 只重算一次校验和,不是「把 40 项全写一遍」**——实际只写用户改过的项:Q10 存不下 0.95 这类
|
||||
> 返回系数(真存 972/1024),从显示值回提全表会把用户没动的项顺手改掉。
|
||||
>
|
||||
> 这一批暴露了两条「只有第一个消费者才会暴露」的缺陷(都已修;写路径口径见
|
||||
> `docs/UI设计/UI_数据供给设计说明.md` §6.2/§6.3):
|
||||
> ① `ui_w_setlist` 的二态行 ENTER **不回绕**——落在最后一项时写出 opts 之外的
|
||||
> 下标,屏上看着「按了没反应」,叠层里却已是越界值,随保存投出去必被预检判 `ERR_RANGE`;
|
||||
> 控制字 4 行与软压板 10 行的出厂值**全是**最后一项(跳闸 / 投入),所以是必现而不是边角。
|
||||
> 本方案落地之前,没有任何页面按过这条键(查看页 `commit=NULL` 走不到)。
|
||||
> ② 编辑态的「参数帧已就位」原先只在**绘制路径**里被置位,而按键门读它 → 无头回归里开页后
|
||||
> 第一个 ENTER 永远被挡(真机开页 10 ms 内就重绘,症状只在测试侧出现)。修法是让
|
||||
> 「数据就位」与「画过没有」解耦:判就位之前先按 seq 做一次 sync(帧未变时只是一次比较)。
|
||||
>
|
||||
> 另记一条生成器侧的事实(不在本道修,已登记为待办「生成器幂等性检查」):菜单树重跑时
|
||||
> `ui_menu_tab.h` 有一行注释跟着变了 —— 说明 HEAD 上生成器的模板文字与已提交的
|
||||
> 生成物本来就**不同步**(改模板那笔没重跑生成器)。生成物是产物,以生成器输出为准即可,
|
||||
> 但这条滞后不补,任何人都可能在不知情的情况下把它 revert 掉。
|
||||
#### 3.2.7 编辑交互(复用网口设置页的模型)
|
||||
|
||||
| 键 | 正常模式 | 编辑模式 |
|
||||
|---|---|---|
|
||||
| ↑↓ | 移选(框架内置) | 当前位 +1 / −1(按项步进) |
|
||||
| ←→ | ← 返回上级(框架) | 移动数字位光标 |
|
||||
| ENTER | 进编辑 / 触发保存行 | 确认本项(写 RAM 副本) |
|
||||
| ENTER | 进编辑 / 触发保存行(**闭锁态 `LOCK` 下一律不消费**) | 确认本项(越界则拒退并打提示,值不进叠层) |
|
||||
| ESC | 关页(框架恒可拦截除外) | 关页(未保存的调整留在 RAM,不落盘) |
|
||||
|
||||
- 权限不足时 ENTER 一律忽略(`ui_content_page_is_editable()` 判)。
|
||||
- 权限不足时 ENTER 一律忽略(`ui_content_page_is_editable()` 判);**权限够但装置写不进去时同样忽略**(`state()` 回 `LOCK` 或帧里 `locked=1`)——前者是「这人不能写」,后者是「这台装置现在写不进」,两件事都必须落在键路上,只改屏上词等于让用户白改一轮。
|
||||
- 编辑中「←」是**移动光标**而不是关页:描述符要置 `key_intercept = 1u`,框架才先把 LEFT 交页面(`simulator/tests/` 的 `sparam_edit` / `sparam_oob` 两条用例走的就是这条路)。
|
||||
- 越界项在保存失败后由 `bad_mask` 高亮(`draw_cb` 画反显)。
|
||||
- 规模:`ui_w_setlist` **M**(约 250 行),`ui_w_editor` **M**(约 180 行)。
|
||||
|
||||
@@ -411,6 +442,12 @@ int run_par_set_deadband(uint8_t yc_idx, float v); /* 范围校验 */
|
||||
| 操作记录 | 定值保存成功 / 强制复归 / 时钟设置 / 清除记录(4 处) | 动作成功后写一条 |
|
||||
| 遥控记录 | `iec104_pts_yk_exec()` 返回处 | 每次执行结果(成功/失败/选择态) |
|
||||
|
||||
> **本表只是页面侧的需求清单**(记录页要消费哪几类),**不是记录层的登记表**:
|
||||
> 5 个 ID 的定名、记录格式、条数与字节上限、写入时机、以及是否真的落地,一律以
|
||||
> `App/log` 侧为准,权威口径 `docs/子系统设计/运行与自检记录设计说明.md`;
|
||||
> 实施进度由记录侧任务回报后回填本文(本道不代记,免得同一件事在两份文档里各说一遍——
|
||||
> 同一事实只在一处详述,见 `docs/文档规范.md`)。
|
||||
|
||||
**`ui_w_soe` 要点**:
|
||||
|
||||
```c
|
||||
@@ -505,6 +542,12 @@ void ui_w_soe_refresh(ui_w_soe_t *inst, uint16_t row, char *name, char *valu
|
||||
|
||||
**本批不改菜单树**——27 个空叶子全在现有 42 节点里,`#page=` 注记逐个补上即可(改 `ui_menu_tree.txt` 后重跑 `python tools/menu_gen/gen_menu.py`)。
|
||||
|
||||
> 【2026-10-26 已改一笔】整定 4 叶(参数 / 定值 / 控制字 / 软压板)已补 `#page=`,
|
||||
> 绑的是与查看列**同一批模块**(一个模块被两个叶子绑定是生成器允许的,`validate_pages()` 按
|
||||
> 集合去重判孤儿页)。重跑生成器后 `UI_PAGE_MAP_COUNT` 12 → 16,
|
||||
> `uvprojx` 与 `CMakeLists.txt` 未变(没新增文件)。
|
||||
> 生成器幂等性的一条滞后见 §3.2.6 落地注末段。
|
||||
|
||||
**唯一建议改动**(可选,需产品拍板):补参考项目的"厂家设置"分支。参考侧该分支有 18 个叶子(元件配置、恢复默认 ×5、交流显示、装置调试 ×8、版本信息)。若采纳,精简为 3 个叶子 + 2 个分组:
|
||||
|
||||
| 建议叶子 | 理由 |
|
||||
@@ -579,10 +622,10 @@ SNTP 客户端 ──────→ 批4 SNTP设置
|
||||
| 2 | 3 个静态页全部接真数据 | 改变输入信号 → 页面值变 |
|
||||
| 3 | 未标定态可见 | `eng=0` 时交流量第 7 行显示"未标定" |
|
||||
| 4 | 无源点显示 `--` | 遥信量无效点、直流量直流通道 |
|
||||
| 5 | 定值整定闭环 | 改 → 保存 → 重启 → 值保持 + 校验和自检通过 + 主站 C_RD 一致 |
|
||||
| 6 | 分层门禁无新增 | `check_layer_deps.py` 退出码 0,基线 6 → 3 |
|
||||
| 7 | 像素回归稳定 | `ui-golden` 连续两次零差异 |
|
||||
| 8 | 每个新页有 golden 参考 | `simulator/tests/ref/` 新增对应 `.bin` |
|
||||
| 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 页连跑两次逐字节一致 |
|
||||
| 8 | 每个新页有 golden 参考 | `simulator/tests/ref/` 新增对应 `.bin`。**整定 4 页已有 9 张**(含保存/越界/落定/回绕各态);参考图入库,改它 = 改判据,须逐页过目 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -50,10 +50,10 @@
|
||||
|
||||
| 页面 | 现有数据源 | 还缺什么 |
|
||||
|---|---|---|
|
||||
| 整定 4 页 | `prot_set` 31 项、`ui_w_editor` | 第 1 项写路径 |
|
||||
| 整定 4 页 | `prot_set` 全部定值项(`App/algo/prot_set.h` 的 `PS_ID_COUNT`,现 40 项)、`ui_w_editor` | 写路径已落,缺可编辑页面 |
|
||||
| 时钟设置 | `sys_clock_set_unix()` | 页面与回读校验 |
|
||||
| 强制复归 | `prot_elm_request_reset()`、`ui_page_action` | 页面与 SOE 留痕 |
|
||||
| 清除记录 | `sys_log` | 删除 API、确认交互 |
|
||||
| 清除记录 | `sys_log` | 页面与确认交互(删除 API 已落) |
|
||||
| 遥测死区 / 系数 | `run_par`、`adc_cal` | 可编辑页面 |
|
||||
| 通讯设置 | 网口配置保存范式可复用 | `[comm]` 段及 104 消费 |
|
||||
| 记录 5 页 | SOE、自检、运行记录已有 reader 先例 | 五类写入方与记录类型 |
|
||||
|
||||
@@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 249,500 B (243.65 KB) | 1,048,576 B (1024 KB) | 23.8% |
|
||||
| RW_IRAM1 | 79,008 B (77.16 KB) | 131,072 B (128 KB) | 60.3% |
|
||||
| ER_IROM1 | 253,220 B (247.29 KB) | 1,048,576 B (1024 KB) | 24.1% |
|
||||
| RW_IRAM1 | 79,992 B (78.12 KB) | 131,072 B (128 KB) | 61.0% |
|
||||
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
|
||||
| **Flash (ROM 合计)** | **250,832 B (245.0 KB)** | 1,048,576 B (1024 KB) | **23.9%** |
|
||||
| **Flash (ROM 合计)** | **254,660 B (248.7 KB)** | 1,048,576 B (1024 KB) | **24.3%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
@@ -15,19 +15,19 @@
|
||||
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|
||||
|---|---|---|---|---|
|
||||
| ER_IROM1 (Flash) | 249,500 B | 1,048,576 B | 23.8% | 参考 |
|
||||
| ER_IROM1 (Flash) | 253,220 B | 1,048,576 B | 24.1% | 参考 |
|
||||
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
|
||||
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
|
||||
| RW_IRAM1 | 79,008 B | 131,072 B | 60.3% | PASS |
|
||||
| RW_IRAM1 | 79,992 B | 131,072 B | 61.0% | PASS |
|
||||
|
||||
| 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 |
|
||||
|---|---|---|---|
|
||||
| 网络 | 13,702 B | 12 KB | ⚠ 超 1.4 KB |
|
||||
| 存储 | 7,056 B | 12 KB | OK |
|
||||
| UI | 32,335 B | 16 KB | ⚠ 超 15.6 KB |
|
||||
| UI | 33,247 B | 16 KB | ⚠ 超 16.5 KB |
|
||||
| RTOS 堆 | 57,376 B | 56 KB | 堆=配额 |
|
||||
| 应用 | 15,422 B | 8 KB | ⚠ 超 7.1 KB |
|
||||
| 其它 | 10,499 B | — | 参考 |
|
||||
| 应用 | 15,481 B | 8 KB | ⚠ 超 7.1 KB |
|
||||
| 其它 | 10,503 B | — | 参考 |
|
||||
<!-- MEM_BUDGET:END -->
|
||||
|
||||
- **门禁**:pre-commit `mem-budget` 钩子(`scripts/ram_baseline.py --gate`)——
|
||||
|
||||
+36
-11
@@ -1,7 +1,7 @@
|
||||
# 运行/自检记录与待机显示设计说明
|
||||
|
||||
> **版本**:v1.1(已实施)
|
||||
> **日期**:2026-09-14
|
||||
> **版本**:v1.2(§4.3 已随七类记录更新;v1.1 的原始设计口径不变)
|
||||
> **日期**:2026-09-14(v1.2 更新 2026-10-26)
|
||||
> **状态**:步骤 1–9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 @test 待上电回归(§8.2)
|
||||
> **关联文档**:`项目架构总览.md` · `STM32F4-Base存储架构说明.md` · `系统墙钟时间维护说明.md` · `UI设计/内容页架构.md`(如存在)· `docs/UI设计/mockups/`(待机显示页面.jpg / 运行记录页.jpg / 自检记录页.jpg / 密码页面实际机器的示例图片.jpg)
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
|---|---|------|
|
||||
| `App/log/sys_log.h` | 共享 | 公共 API + 记录格式常量 + 行拆分内联函数(无 OS 依赖) |
|
||||
| `App/log/sys_log_mcu.c` | MCU | FatFS 后端:待发队列(启动前静态数组 + FreeRTOS 消息队列)、defaultTask 落盘、容量截断、上电/重启分类 |
|
||||
| `sim/sim_syslog.c` | 模拟器 | 宿主文件后端(CWD 下 `sim_run.log` / `sim_selftest.log`),直接追加,`time()` 时间戳 |
|
||||
| `sim/sim_syslog.c` | 模拟器 | 宿主文件后端(CWD 下与七类一一对应的 `sim_*.log` 七份),直接追加,`time()` 时间戳;**没有队列与丢弃**,`syslog_post_quiet()` 在本端等价于 `syslog_post()`(两端差异登记在 `docs/测试说明/模拟器差距清单.md` §2.2) |
|
||||
| `Drivers/BSP/UI/ui_standby.h/.c` | 共享 | 空闲检测 + 待机屏(屏幕覆盖层:保存/还原前屏与焦点) |
|
||||
| `Drivers/BSP/UI/ui_page_runlog.h/.c` | 共享 | 运行记录内容页描述符(动态行,数据源 = sys_log API) |
|
||||
| `Drivers/BSP/UI/ui_page_selftest.h/.c` | 共享 | 自检记录内容页描述符(同上) |
|
||||
@@ -105,14 +105,39 @@
|
||||
|
||||
### 4.3 文件与容量
|
||||
|
||||
| 文件 | 内容 | 上限 | 满时策略 |
|
||||
|---|------|------|---------|
|
||||
| `/log/run.log` | 上电/重启记录 | **1024 条(64KB)** | 删除重建(整卷清空,串口告警);DTU 上电频率低,容量充裕 |
|
||||
| `/log/selftest.log` | 自检故障记录 | **512 条(32KB)** | 同上 |
|
||||
**记录类型 = 日志 ID 本身,一类记录 = `/log` 下一个文件**(记录体内没有类型字段位,
|
||||
理由与逐类判据、写入点清单见 `App/log/README.md`「五类记录与『记录类型 = 日志 ID』」——
|
||||
本节只登记"有哪些文件、各多大"这一层,判据表**不在这里抄第二份**)。
|
||||
|
||||
- 目录 `/log/` 首次落盘前 `f_mkdir`(已存在则忽略);文件名均 8.3 合规。
|
||||
- FTP 视角:卷根下多一个 `LOG` 目录,两个 `.log` 文本文件可直接下载(R5)。
|
||||
- 记录**只增不改不删**(除满时重建与未来"清除记录"功能),保证 FTP 端所见即历史。
|
||||
| 文件 | 记录类型(= `syslog_id_t`) | 上限 | 满时策略 |
|
||||
|---|------|------|---------|
|
||||
| `/log/run.log` | `SYSLOG_RUN` 上电/重启与运行事件 | **1024 条(64KB)** | 删除重建(整卷清空,串口告警);DTU 上电频率低,容量充裕 |
|
||||
| `/log/selftest.log` | `SYSLOG_SELFTEST` 自检故障(有问题才记) | **512 条(32KB)** | 同上 |
|
||||
| `/log/accid.log` | `SYSLOG_ACCIDENT` 事故(跳闸型动作) | **512 条(32KB)** | 同上 |
|
||||
| `/log/alarm.log` | `SYSLOG_ALARM` 保护告警 | **1024 条(64KB)** | 同上 |
|
||||
| `/log/start.log` | `SYSLOG_START` 保护启动(越限电平) | **1024 条(64KB)** | 同上 |
|
||||
| `/log/oper.log` | `SYSLOG_OPER` 操作(改动了装置状态的运维动作) | **512 条(32KB)** | 同上 |
|
||||
| `/log/yk.log` | `SYSLOG_YK` 遥控(104 命令的选择/执行/拒绝) | **256 条(16KB)** | 同上 |
|
||||
|
||||
- 表内上限是**逻辑上限,不是预分配**:文件按追加写自然增长,一条不写就不占簇,到顶才
|
||||
删除重建;七类全满的名义峰值 = 4864 条 × 64 B = **304 KB**(NAND 2 GB,且这些都只在
|
||||
事件边沿写,不构成容量或磨损瓶颈)。
|
||||
- 告警/启动给到 1024 条:一次系统故障会连片触发多个元件,别让最能说明故障过程的两类
|
||||
记录最先被截掉。
|
||||
- `id` 只准**尾部追加**:枚举序 = 两端 `s_paths[]`/`s_caps[]` 的下标,中间插值会让
|
||||
某类记录安静地写进别的文件(不报错、屏上照看照翻),机械防线是
|
||||
`TC-SYSLOG-130`/`131`/`131b`(逐 id 验"哪类落哪个文件、各用各的上限")。
|
||||
- 另有两类**不属于本表**的文件各有各的属主与通道:`/log/wdev.log`(停喂取证,
|
||||
`sys_log_evidence()`,§6)、`/log/soe.log`(二进制 SOE,64 B 定长但**另一套
|
||||
布局与读者**,见本节末尾与 `App/log/README.md` soe 小节)。
|
||||
- 目录 `/log/` 首次落盘前 `f_mkdir`(已存在则忽略);文件名均**严格 8.3**
|
||||
(`FF_USE_LFN=0`:主名 ≤8、扩展 ≤3、不以点开头),逐条机器核
|
||||
`TC-SYSLOG-081`/`130c`。
|
||||
- FTP 视角:卷根下 `LOG` 目录内上述记录文件均为**同一格式**的 64 B 定长文本记录,
|
||||
可直接下载后按固定偏移解析(R5)。
|
||||
- 记录**只增不改不删**;删除只有两条既有途径——满时重建,以及 UI「清除记录」
|
||||
(`syslog_request_clear(id)` 请求 / defaultTask `syslog_drain()` 尾部落地,
|
||||
语义与判据见 `App/log/README.md`,O3 已由此关闭)。
|
||||
|
||||
### 4.4 写路径(单写者,调研结论落地)
|
||||
|
||||
@@ -283,7 +308,7 @@ Forbidden:`FAIL`/`ERROR`/`HardFault`。
|
||||
|---|----|------|
|
||||
| O1 | 断电 <10min 的"上电/重启"歧义 | §5.3 阈值折中,文档声明;如需精确可后续加掉电标志(PVD) |
|
||||
| O2 | 故障"已复归"/持续监测(mockup 语义) | v2:周期自检 + 活动故障状态机 + 恢复记录 |
|
||||
| O3 | "清除记录"菜单项(node 27 #P,现为空框) | v2:UI 动作 = 删除/重建 log 文件(与满时策略同路径) |
|
||||
| O3 | ~~"清除记录"菜单项~~ **已实施(2026-10-26 道F task-4)** | 数据面 = `syslog_request_clear(id)` + `syslog_clear_state(id)`,落地在 defaultTask(唯一写者)的 `syslog_drain()` 尾部;UI 侧页面归记录/维护页批次。细口径见 `App/log/README.md` |
|
||||
| O4 | 待机页电流值接实时数据 | 待交流量标定管线(AC 页占位数据替换)落地后接入 `tpafe5160_snapshot()`(原子快照;旧 `tpafe5160_get_buf()` 已于 2026-10-10 删除,见 `AGENTS.md`) |
|
||||
| — | FAT32 小追加簇开销 | §3 调研结论:写频率极低可接受;容量上限约束增长 |
|
||||
| — | UI 读与落盘并发 | 只读既有内容 + EOF 追加 + FatFS 系统锁,无互斥需求(§4.4) |
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
| 渲染链路 | LVGL I1 渲染 → SDL 窗口(MCU 端为帧缓冲 → RemoDispBus) |
|
||||
| 配置文件引擎 | `App/cfg/cfg_file.c` 共享核心 + `cfg_file_store_sim.c`(宿主文件 `sim_config.ini`) |
|
||||
| `net_*` BSD API 之上的协议 | `ui_net_sink.c`(RemoDispBus 服务端,TCP 7003)与 `net_socket_sim.c`(Winsock 后端) |
|
||||
| 日志记录格式 | `sys_log` 定长记录编解码(`sim_syslog.c` 写宿主文件) |
|
||||
| 日志记录格式 | `sys_log` 定长记录编解码 + 记录删除 + **七类记录**(run / selftest / accid / alarm / start / oper / yk 各一份宿主文件,道F task-10;口径 = `App/log/README.md`「记录类型 = 日志 ID」)。`sim_syslog.c` 写/删宿主文件;删除与投递的**时序**与真机不同,见 §2.2 |
|
||||
|
||||
**不覆盖**:CH395F 真实网络栈、GD5F NAND、SD2506 RTC、TPAFE5160 ADC、RS-485、IWDG、中断/时序相关行为。
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
| **控制台单写者无对应实现** | `simulator/dbg_log.h` 把 `DBG_*` 映射到 `printf`;**不编译** `console_log.c` | 单写者环形缓冲 + defaultTask 唯一发送 | `console_log` 的环形缓冲溢出/丢弃计数逻辑**模拟器测不到**;共享代码若误调 `console_log.h` 声明,模拟器链接失败(已被此约束捕获) | `@test` + 板端串口观测;`docs/子系统设计/控制台单写者日志设计说明.md` §4.1 |
|
||||
| **mtime 粒度不同** | 宿主文件 mtime(**秒级**) | FAT 打包时间(`fdate<<16\|ftime`,**分钟级**) | `cfg_file_poll` 的 `(size, mtime)` 变更令牌在两端触发时机不同;分钟级粒度下的"同分钟内两次改动"在真机可能被判为无变化 | 板端 STORAGE 套件;`配置文件设计说明.md` §变更检测 |
|
||||
| **无应用回调(空操作)** | 模拟器不注册段回调,`cfg_file_poll` 重载后回调为空 | 各段回调真正执行(如网络重配置) | "配置变更 → 触发回调 → 副作用"链路在模拟器上**只验证到写盘**,回调逻辑不覆盖 | `@test`(配置保存 → UI 生效) |
|
||||
| **「清除记录」两端时序不同** | `syslog_request_clear()` 在调用内**同步**删除宿主文件(`sim_syslog.c` 单线程,没有卷锁、没有队列),返回后状态立刻是 `OK`/`FAIL`,`SYSLOG_CLEAR_WAIT` 永不出现 | 非写者上下文只置标志,真正的 `f_unlink` 排在 defaultTask 的 `syslog_drain()` 尾部(卷锁 try:拿不到锁就顺延,见 `App/log/sys_log_mcu.c`) | 「按了按钮 → 下一拍才见结果」这条异步链、卷占用顺延、以及删除与写者重建的交错,在模拟器上**结构上不存在**;UI 页面若把 WAIT 当失败,模拟器上看不出来 | 主机单测 `test/host/sys_log_mcu/`(TC-SYSLOG-110..122:请求侧零盘触点 / 落地只在 drain / 卷占用顺延 / 幂等 / 不误删;TC-SYSLOG-133/133b:清除成功投一条操作记录、清 oper 自己时不投)+ 台架装置维护页目检 |
|
||||
| **两条投递口在宿主端合一** | `syslog_post_quiet()` = `syslog_post()` 的别名(直写宿主文件,没有队列/待发数组),`syslog_post_dropped()` **恒返 0** | 两个口分开且判据不同:quiet 的调用链上不许内联日志行缓冲(protectTask / iec104Task 的静态最坏链硬线),丢弃只累加计数、由 defaultTask 在 `syslog_drain()` 里按差值补留痕 | 五类记录(事故 / 告警 / 启动 / 操作 / 遥控)在模拟器上**永不丢弃**,"队列满丢记录"、"丢了几条"、"投递侧不许打日志"这三件事在模拟器上结构上不可见;UI 若把"投了"当成"落盘了",模拟器看不出来 | 主机单测 `test/host/sys_log_mcu/`(TC-SYSLOG-132 / 134 / 135:队列侧与启动前数组侧各自的计数与零留痕)+ `scripts/stack_report.py` 静态最坏链(真机侧唯一手段) |
|
||||
| **堆后端不同** | `cfg_store_alloc` = 宿主 `malloc`(堆余量以 MB 计) | FreeRTOS 堆(`configTOTAL_HEAP_SIZE` 48 KB,实测最低水位 ~4.8 KB) | **分配失败路径在模拟器上永不触发**——"KB 级分配必然返回 NULL"这类缺陷模拟器照不出来(`AGENTS.md` 已列为铁律) | 内存预算门禁 + 真机 `[HEAP]` 水位告警(S11) |
|
||||
|
||||
### 2.3 硬件专属(模拟器完全无对应)
|
||||
|
||||
Reference in New Issue
Block a user