From 5bab248deeac14b0d6235e0fe312bcd9388a5e0a Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Fri, 13 Nov 2026 13:11:42 +0800 Subject: [PATCH 1/3] =?UTF-8?q?UI=20=E5=BE=85=E6=9C=BA=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E9=A1=B5=E6=8E=A5=E5=85=A5=20UA/UB/UC/IA/IB/IC=20=E5=85=AD?= =?UTF-8?q?=E8=B7=AF=E5=AE=9E=E6=97=B6=E5=80=BC=EF=BC=88v1.1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ui_standby.c:v1 占位(6 行恒 0.00A)→ ui_data 通道一按 seq 门控取帧, yc 语义下标 {0,1,2,4,5,6}(跳 UN/IN);无首帧/坏质显 --;定点格式化复用 ui_w_editor_fmt_scaled;单位随 eng(引脚档不挂),口径逐字对齐 ui_page_ac.c - ui_standby.h:头注与修改记录同步 - 文档 §5.2 按新口径改写;§1 R2 改为新需求口径(不再引 mockup);§9 对应 开放问题销项删行;版本 v1.4 验证:模拟器 0 错 0 警 + 菜单行/内容页/编码规范门禁 PASS + Keil 0 错 0 警; --standby 截图双分支实证(UA/IA 显值、UB/UC/IB/IC 显 --)。 --- Drivers/BSP/UI/ui_standby.c | 131 ++++++++++++++++++++-- Drivers/BSP/UI/ui_standby.h | 4 +- docs/内存管理/RAM使用分析与优化方案.md | 6 +- docs/内存管理/RAM预算台账.md | 6 +- docs/子系统设计/运行与自检记录设计说明.md | 28 +++-- scripts/ram_baseline.tsv | 2 +- scripts/size_attestation.json | 22 ++-- 7 files changed, 157 insertions(+), 42 deletions(-) diff --git a/Drivers/BSP/UI/ui_standby.c b/Drivers/BSP/UI/ui_standby.c index 0cd42d20..a175d044 100644 --- a/Drivers/BSP/UI/ui_standby.c +++ b/Drivers/BSP/UI/ui_standby.c @@ -1,6 +1,7 @@ /* * 模块名称:UI Standby (待机显示页) - * 模块功能:长时间无按键操作时自动切入的默认界面(6 路电流值 + 分隔线 + 底部时钟); + * 模块功能:长时间无按键操作时自动切入的默认界面(UA/UB/UC/IA/IB/IC 六路实时值 + 分隔线 + 底部时钟); + * 数值经 ui_data 通道一实时帧 yc[](App 层装配,本文件零 App include),口径同 ui_page_ac.c; * 屏幕覆盖层模式——进入保存前屏与焦点、ENTER(确认键)还原,其余键忽略; * 空闲检测 = lv_timer(1s 周期),活动通知由菜单/内容页键事件挂钩 * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) @@ -8,6 +9,9 @@ * 创建日期:2026-09-14 * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/子系统设计/运行与自检记录设计说明.md §5.2) + * v1.1 2026-11-13 DTU 项目组 v1 占位(6 行电流恒 0.00A)→ UA/UB/UC/IA/IB/IC 实时值: + * ui_data 通道一按 seq 门控取帧;无首帧/坏质显 "--";定点 + * 格式化复用 ui_w_editor_fmt_scaled;单位随 eng(引脚档不挂) */ /* 头文件包含区 */ @@ -16,9 +20,11 @@ #include "ui_standby.h" #include "lvgl.h" +#include "ui_data.h" #include "ui_menu_view.h" #include "ui_style.h" #include "ui_tokens.h" +#include "ui_w_editor.h" #define DBG_TAG "[UI_STANDBY]" #include "dbg_log.h" @@ -27,24 +33,40 @@ /* * 布局常量(设备像素;参考 mockups/待机显示页面.jpg): - * 屏幕宽高取令牌 UI_SCREEN_W/H;6 路电流值行自 y=28 起、行距 18 + * 屏幕宽高取令牌 UI_SCREEN_W/H;六路实时值行自 y=28 起、行距 18 * (末行 y=118..130);分隔线 y=132;底部时钟 y=140 居中 */ -#define SB_CH_COUNT 6u /* 电流通道数 */ +#define SB_CH_COUNT 6u /* 显示通道数(六路主要电力量;yc 下标见 s_ch_yc) */ #define SB_ROW_Y0 28 /* 首数据行顶 y */ #define SB_PITCH 18 /* 行距(相邻行槽顶间距) */ -#define SB_LABEL_X 24 /* 通道名 x(Ib/Ic/...) */ +#define SB_LABEL_X 24 /* 通道名 x(UA/UB/UC/IA/IB/IC) */ #define SB_EQ_X 72 /* "=" 号 x */ -#define SB_VALUE_X 96 /* 数值 x(0.00A;v1 占位,待交流量标定管线接入实时值) */ +#define SB_VALUE_X 96 /* 数值 x(数字+单位 或 "--") */ + +/* 值串缓冲长度:最坏 "2147483.647V" = 12 + NUL,取 16 留余量 */ +#define SB_VAL_BUF_LEN 16u +#define SB_INVALID "--" /* 空值串(同交流量页 AC_INVALID) */ +#define SB_DECIMALS 3u /* 电压/电流小数位(同交流量页 AC_IA_DECIMALS) */ +/* 不可信品质合集:无效 / 未攒满一周波 / 透传码值 / 自检异常 / 闭锁 / 越程 + * (逐字同 ui_page_ac.c 的 AC_Q_BAD——两处判据不可能漂移) */ +#define SB_Q_BAD (UI_DATA_Q_IV | UI_DATA_Q_NT | UI_DATA_Q_RAW | UI_DATA_Q_SB | \ + UI_DATA_Q_BL | UI_DATA_Q_OV) #define SB_LINE_Y 132 /* 分隔线 y(高 1px) */ #define SB_TIME_Y 140 /* 底部时钟放置 y(居中) */ /* 静态变量定义区 */ -/* 通道名与占位数值(Flash;全 ASCII,无需中文字形) */ -static const char *const s_ch_name[SB_CH_COUNT] = { "Ib", "Ic", "Io", "Ia2", "Ib2", "Ic2" }; -static const char *const s_ch_value[SB_CH_COUNT] = { "0.00A", "0.00A", "0.00A", "0.00A", "0.00A", "0.00A" }; +/* 通道名表 + yc[] 语义下标映射(Flash;全 ASCII,无需中文字形)。 + * 下标 = adc_semantic_t 序(UA/UB/UC/UN/IA/IB/IC/IN/TEMP):跳过 UN(3)/IN(7)—— + * 用户点名显示六路(2026-11-13),且 UN 本台架永久无数据,摆一排恒 "--" 的假通道 + * 不如不摆(取舍逻辑同交流量页只显 Ua/U0/Ia,docs/UI设计/页面扩充详细方案.md §3.1.1) */ +static const char *const s_ch_name[SB_CH_COUNT] = { "UA", "UB", "UC", "IA", "IB", "IC" }; +static const uint8_t s_ch_yc[SB_CH_COUNT] = { 0u, 1u, 2u, 4u, 5u, 6u }; + +/* 帧缓存:发布序号变化才整帧拷一次(页面侧约定 = ui_data.h 头注 / UI_数据供给设计说明.md §5.4) */ +static ui_data_rt_t s_rt; +static uint32_t s_seq; static lv_obj_t *s_scr; /* 待机屏(init 时建、常驻不销毁) */ static lv_obj_t *s_cont; /* 全屏容器(焦点/键入口 + 自绘载体,无子对象) */ @@ -75,6 +97,85 @@ static bool sb_areas_intersect(const lv_area_t *a, const lv_area_t *b) { return (x1 <= x2 && y1 <= y2); } +/* + * 函数功能:把常量值串安全搬进显示缓冲(替代 strcpy,规范 7.5;同 ui_page_ac.c 的 ac_put) + * 入口参数:a_dst - 目标缓冲(SB_VAL_BUF_LEN) char * + * a_lit - 源字面量(编译期串,长度由宏用 sizeof 算好) const char * + * a_len - 需拷贝字节数(不含 NUL) uint32_t + * 返回值:无 + * 限定条件:a_dst/a_lit 非 NULL;a_len 超缓冲时截断而不是越界 + */ +static void sb_put(char *a_dst, const char *a_lit, uint32_t a_len) { + if (a_len > (SB_VAL_BUF_LEN - 1u)) { + a_len = SB_VAL_BUF_LEN - 1u; + } + (void)memcpy(a_dst, a_lit, a_len); + a_dst[a_len] = (char)0; +} + +/* 值串落屏统一走这里:长度由 sizeof 在调用点算,改串不会漏改长度 */ +#define SB_PUT(a_dst, a_lit) sb_put((a_dst), (a_lit), (uint32_t)(sizeof(a_lit) - 1u)) + +/* + * 函数功能:取用最新实时帧(只在发布序号变化时整帧拷)——读法同 ui_page_ac.c 的 ac_frame_ready + * 入口参数:无 + * 返回值:1 - 有可用帧;0 - 首帧未到或本帧无效 uint8_t + * 限定条件:UI 任务绘制上下文 + * 函数说明:读失败(首帧未到)时清缓存并记住本次序号,避免每次重绘重复尝试;下一帧序号 + * 必然变化 → 自动恢复。待机 1s 定时器整屏 invalidate 就是数值刷新节拍,不新增定时器 + */ +static uint8_t sb_frame_ready(void) { + uint32_t now = ui_data_rt_seq(); + + if (now != s_seq) { + s_seq = now; + if (ui_data_rt(&s_rt) != 0) { + (void)memset(&s_rt, 0, sizeof(s_rt)); + return 0u; + } + } + return (s_rt.valid != 0u) ? 1u : 0u; +} + +/* + * 函数功能:拼一行值串(数字 + 单位,或 "--") + * 入口参数:a_idx - 显示行下标 0..5 uint32_t + * a_buf - 输出缓冲 char * + * a_len - 缓冲容量(含 NUL) uint32_t + * 返回值:无 + * 限定条件:UI 任务绘制上下文 + * 函数说明:口径同 ui_page_ac.c——无首帧或品质带 SB_Q_BAD 一律 "--"("有数但不可信"与 + * "没有数"同形);数字走共享定点格式化 ui_w_editor_fmt_scaled(3 位小数 = + * 10^3 定点,负值钳 0 交给品质位解释),页面侧不引 %f 软浮点打印。单位随 eng: + * 二次/一次值档 UA/UB/UC 挂 "V"、IA/IB/IC 挂 "A";ENG_PIN 档不挂单位——引脚档 + * 数字是 ADC 引脚电压而非端子电流量,挂单位就是替用户完成没做的换算(交流量页 + * 同档另设量纲口径行标注,待机屏无第 7 行可标,故数字照显、单位缺席) + */ +static void sb_value_str(uint32_t a_idx, char *a_buf, uint32_t a_len) { + const ui_data_yc_t *p; + uint32_t n; + + if ((a_idx >= SB_CH_COUNT) || (sb_frame_ready() == 0u) || + (s_ch_yc[a_idx] >= (uint8_t)UI_DATA_YC_N)) { + SB_PUT(a_buf, SB_INVALID); + return; + } + p = &s_rt.yc[s_ch_yc[a_idx]]; + if ((p->qds & (uint8_t)SB_Q_BAD) != 0u) { + SB_PUT(a_buf, SB_INVALID); + return; + } + ui_w_editor_fmt_scaled((p->val > 0.0f) ? (int32_t)((p->val * 1000.0f) + 0.5f) : 0, + SB_DECIMALS, a_buf, a_len); + if (s_rt.eng != (uint8_t)UI_DATA_ENG_PIN) { + n = (uint32_t)strlen(a_buf); + if ((n + 1u) < a_len) { + a_buf[n] = (a_idx < 3u) ? 'V' : 'A'; + a_buf[n + 1u] = (char)0; + } + } +} + /* * 函数功能:绘制居中单行文本(底部时钟) * 入口参数:layer - 绘制层 lv_layer_t * @@ -100,11 +201,12 @@ static void sb_draw_centered_text(lv_layer_t *layer, const char *text, int32_t y } /* - * 函数功能:待机屏自绘(6 路电流值 + 分隔线 + 底部时钟;逐行 culling) + * 函数功能:待机屏自绘(六路实时值 + 分隔线 + 底部时钟;逐行 culling) * 入口参数:e - 事件指针 lv_event_t * * 返回值:无 * 限定条件:事件为 LV_EVENT_DRAW_MAIN,待机模块已初始化 - * 函数说明:1bpp 极性 white=暗墨——文本/分隔线均白色;数值 v1 占位 0.00A + * 函数说明:1bpp 极性 white=暗墨——文本/分隔线均白色;数值逐行现拼(sb_value_str), + * 待机 1s invalidate 即数值刷新节拍 */ static void sb_draw_cb(lv_event_t *e) { lv_layer_t *layer = lv_event_get_layer(e); @@ -113,6 +215,7 @@ static void sb_draw_cb(lv_event_t *e) { int32_t row_y = SB_ROW_Y0 + (int32_t)i * SB_PITCH; lv_area_t strip; lv_draw_label_dsc_t ld; + char vb[SB_VAL_BUF_LEN]; strip.x1 = 0; strip.x2 = UI_SCREEN_W - 1; @@ -142,8 +245,8 @@ static void sb_draw_cb(lv_event_t *e) { lv_draw_label_dsc_init(&ld); ld.base.layer = layer; - ld.text = s_ch_value[i]; - ld.text_static = 1; + sb_value_str(i, vb, (uint32_t)sizeof(vb)); /* 栈缓冲同帧有效(同底部时钟 ts 模式) */ + ld.text = vb; ld.color = UI_COL_FG; ld.ofs_x = SB_VALUE_X; ld.ofs_y = 0; @@ -197,6 +300,10 @@ static void sb_enter(void) { if (s_active || s_scr == NULL) { return; } + /* 进入即复位帧缓存(同交流量页 ac_init 开页复位理由:不沿用上次的陈旧帧号, + * 强制下一次绘制重取最新帧) */ + s_seq = 0u; + (void)memset(&s_rt, 0, sizeof(s_rt)); s_prev_scr = lv_screen_active(); g = lv_group_get_default(); s_prev_focus = (g != NULL) ? lv_group_get_focused(g) : NULL; diff --git a/Drivers/BSP/UI/ui_standby.h b/Drivers/BSP/UI/ui_standby.h index 0417516d..6128fda4 100644 --- a/Drivers/BSP/UI/ui_standby.h +++ b/Drivers/BSP/UI/ui_standby.h @@ -1,6 +1,7 @@ /* * 模块名称:UI Standby (待机显示页) - * 模块功能:长时间无按键操作时自动切入的默认界面(6 路电流值 + 分隔线 + 底部时钟); + * 模块功能:长时间无按键操作时自动切入的默认界面(UA/UB/UC/IA/IB/IC 六路实时值 + 分隔线 + 底部时钟); + * 数值经 ui_data 通道一实时帧 yc[](口径同 ui_page_ac.c); * 屏幕覆盖层模式——进入保存前屏与焦点、ENTER(确认键)还原,其余键忽略; * 空闲检测 = lv_timer(1s 周期),活动通知由菜单/内容页键事件挂钩 * 适用平台:STM32F407ZGTx / PC 模拟器(两端共用) @@ -8,6 +9,7 @@ * 创建日期:2026-09-14 * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/子系统设计/运行与自检记录设计说明.md §5.2) + * v1.1 2026-11-13 DTU 项目组 6 路电流占位 → UA/UB/UC/IA/IB/IC 实时值(口径同 ui_page_ac.c) */ #ifndef UI_STANDBY_H diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index 011494a0..bb94a8a0 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 287,964 B (281.21 KB) | 1,048,576 B (1024 KB) | 27.5% | -| RW_IRAM1 | 97,024 B (94.75 KB) | 131,072 B (128 KB) | 74.0% | +| ER_IROM1 | 288,148 B (281.39 KB) | 1,048,576 B (1024 KB) | 27.5% | +| RW_IRAM1 | 97,240 B (94.96 KB) | 131,072 B (128 KB) | 74.2% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | -| **Flash (ROM 合计)** | **290,044 B (283.2 KB)** | 1,048,576 B (1024 KB) | **27.7%** | +| **Flash (ROM 合计)** | **290,228 B (283.4 KB)** | 1,048,576 B (1024 KB) | **27.7%** | 各区的容量与常量来源: diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index e9b0430b..042cf628 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -15,16 +15,16 @@ | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | |---|---|---|---|---| -| ER_IROM1 (Flash) | 287,964 B | 1,048,576 B | 27.5% | 参考 | +| ER_IROM1 (Flash) | 288,148 B | 1,048,576 B | 27.5% | 参考 | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | -| RW_IRAM1 | 97,024 B | 131,072 B | 74.0% | PASS | +| RW_IRAM1 | 97,240 B | 131,072 B | 74.2% | PASS | | 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 | |---|---|---|---| | 网络 | 13,958 B | 12 KB | ⚠ 超 1.6 KB | | 存储 | 9,144 B | 12 KB | OK | -| UI | 36,511 B | 16 KB | ⚠ 超 19.7 KB | +| UI | 36,723 B | 16 KB | ⚠ 超 19.9 KB | | RTOS 堆 | 57,376 B | 56 KB | 堆=配额 | | 应用 | 26,609 B | 8 KB | ⚠ 超 18.0 KB | | 其它 | 10,792 B | — | 参考 | diff --git a/docs/子系统设计/运行与自检记录设计说明.md b/docs/子系统设计/运行与自检记录设计说明.md index e42c7a64..8183b410 100644 --- a/docs/子系统设计/运行与自检记录设计说明.md +++ b/docs/子系统设计/运行与自检记录设计说明.md @@ -1,7 +1,7 @@ # 运行/自检记录与待机显示设计说明 -> **版本**:v1.3(§4.4 新增"卷锁 fail-open 边沿补投自检记录"一条,道G task-40;v1.2 为 §4.3 随七类记录更新,v1.1 的原始设计口径不变) -> **日期**:2026-09-14(v1.2 更新 2026-10-26) +> **版本**:v1.4(2026-11-13:§5.2 待机屏从 6 路占位电流改为 UA/UB/UC/IA/IB/IC 六路实时值,§1 R2 按新口径改写、不再引 mockup,§9 对应开放问题销项删行;v1.3 为 §4.4 新增"卷锁 fail-open 边沿补投自检记录"一条,道G task-40;v1.2 为 §4.3 随七类记录更新,v1.1 的原始设计口径不变) +> **日期**:2026-09-14(v1.2 更新 2026-10-26,v1.4 更新 2026-11-13) > **状态**:步骤 1–9 全部完成——双端构建 0/0、`--logtest` PASS、ui-verify 五门禁全绿;真机 /serial-boot-test 待上电回归(§8.2) > **关联文档**:`项目架构总览.md` · `STM32F4-Base存储架构说明.md` · `系统墙钟时间维护说明.md` · `UI设计/内容页架构.md`(如存在)· `docs/UI设计/mockups/`(待机显示页面.jpg / 运行记录页.jpg / 自检记录页.jpg / 密码页面实际机器的示例图片.jpg) @@ -10,7 +10,7 @@ | # | 需求 | 说明 | |---|------|------| | R1 | 密码错误提示 | 用户密钥页输错密码时提示"密码输入错误",并允许重新输入(现状:仅清空重输、无提示) | -| R2 | 待机显示页 | 长时间无操作时默认显示的界面(参考 `mockups/待机显示页面.jpg`:6 路电流值 + 底部时钟);**只有按确认键才进入菜单** | +| R2 | 待机显示页 | 长时间无操作时默认显示的界面:UA/UB/UC/IA/IB/IC 六路电量的实时值 + 底部时钟;**只有按确认键才进入菜单** | | R3 | 运行记录页 | 记录装置上电时间、重启时间(参考 `mockups/运行记录页.jpg`:标题 + 逐条"时间戳 + 事件" + 页脚分页) | | R4 | 自检记录页 | 记录开机自检结果,有问题才记录(参考 `mockups/自检记录页.jpg`) | | R5 | FTP 可读存储 | 记录以**纯文本文件**落盘,FTP(根目录 = FatFS 卷根)可直接下载读取 | @@ -225,19 +225,26 @@ int syslog_get_record(syslog_id_t id, uint32_t idx, char *ts, size_t ts_len - 空闲检测 = `lv_timer`(1s 周期,MCU/模拟器均经 `lv_timer_handler` 驱动,无 OS 依赖); - 活动通知挂钩:`menu_key_cb`、`cp_key_cb` 每收到一个键调 `ui_standby_notify_activity()`(重置计时)。 -**待机屏内容**(参考 mockup,全 ASCII 无需新字体字): +**待机屏内容**(v1.1 起显示实时值,2026-11-13;全 ASCII 无需新字体字): ``` -Ib = 0.00A ← 6 行电流值(v1 占位 0.00A,与 AC/DC 页现状一致; -Ic = 0.00A 待交流量标定管线落地后接 TPAFE5160 实时值) -Io = 0.00A -Ia2 = 0.00A -Ib2 = 0.00A -Ic2 = 0.00A +UA = 0.341 ← 6 行实时值 = ui_data 通道一 yc[](下标 = adc_semantic_t 语义序: +UB = 0.338 UA/UB/UC = 0..2,IA/IB/IC = 4..6;UN/IN 不在待机屏——用户点名 +UC = 0.339 六路,且 UN 本台架永久无数据,摆一排假通道不如不摆) +IA = 0.021 ← 示例为现状 ENG_PIN 引脚电压档:数字照显、不挂单位(挂单位 = +IB = 0.020 替用户完成没做的标定换算,同交流量页引脚档不造第二口径) +IC = 0.021 ← 升二次/一次值档后单位随 eng:UA/UB/UC 挂 V、IA/IB/IC 挂 A ────────────────────── ← 分隔线(y≈132) YYYY-MM-DD HH:MM:SS ← 底部实时时钟(每秒重绘,lv_timer 驱动 invalidate) ``` +- 空值/坏质口径与格式化的唯一真源 = 交流量页(`ui_page_ac.c`):无首帧或品质带 + `IV|NT|RAW|SB|BL|OV` → `--`;3 位小数定点格式化复用 `ui_w_editor_fmt_scaled`(不引 %f)。 +- 帧读取按 `ui_data_rt_seq()` 门控整帧拷(`ui_data.h` 头注);待机 1s 定时器整屏 + invalidate 即数值刷新节拍,未新增定时器。模拟器 `main.c` 自带固定仿真 rt 帧:待机截图 + (`--standby`)即双分支实证——UA/IA 显引脚档数字(0.812 / 0.046,不挂单位)、 + UB/UC/IB/IC 带 `Q_IV|Q_OV` 坏质显 `--`。 + **键语义(用户定案)**:待机屏上**只有 ENTER(确认键)进入菜单**;其余按键忽略(同时重置空闲计时)。 - 实现为**屏幕覆盖层**:进入时保存当前活动屏 + 组内焦点对象,加载待机屏并聚焦其容器;ENTER 时还原前屏与焦点。 - 内容页打开期间超时 → 同样切待机(页面对象保留在底层,ENTER 返回后原样恢复,编辑态不丢)。 @@ -324,7 +331,6 @@ Forbidden:`FAIL`/`ERROR`/`HardFault`。 | O1 | 断电 <10min 的"上电/重启"歧义 | §5.3 阈值折中,文档声明;如需精确可后续加掉电标志(PVD) | | O2 | 故障"已复归"/持续监测(mockup 语义) | v2:周期自检 + 活动故障状态机 + 恢复记录 | | 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) | diff --git a/scripts/ram_baseline.tsv b/scripts/ram_baseline.tsv index 0abfdea3..139cad88 100644 --- a/scripts/ram_baseline.tsv +++ b/scripts/ram_baseline.tsv @@ -21,4 +21,4 @@ date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi 2026-11-09 255160 31204 2080 152152 286364 27.3 96808 73.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5296;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 2026-11-10 255160 31204 2080 152152 286364 27.3 96808 73.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5296;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 2026-11-11 256024 31760 2080 152368 287784 27.4 97024 74.0 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 -2026-11-13 256200 31764 2080 152368 287964 27.5 97024 74.0 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 +2026-11-13 256428 31720 2080 152584 288148 27.5 97240 74.2 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 diff --git a/scripts/size_attestation.json b/scripts/size_attestation.json index 868cf8a6..563832d9 100644 --- a/scripts/size_attestation.json +++ b/scripts/size_attestation.json @@ -1,25 +1,25 @@ { - "built_at": "2026-11-13T11:25:00+08:00", - "fingerprint": "39f4f69a673f97c33cc59b301e723ec8f1e03686f4556ac6abb1a5a6cd438796", + "built_at": "2026-11-13T13:11:29+08:00", + "fingerprint": "eb152c8e20a33b63da91fd3b5e32877b83b581c43d0c4956375ed13b6531db3f", "fingerprint_files": 1479, "fingerprint_source": "git-index", "gated_regions": [ "RW_IRAM1", "RW_CCM" ], - "git_head": "7c505b2457d977adf7750abbb841b0453fd6bfb9", + "git_head": "40a2816a9971928d1ca6fa218921b86dae79bd63", "map": "MDK-ARM/STM32F407-Demo/STM32F407-Demo.map", - "map_mtime": 1794540294, + "map_mtime": 1794542035, "pct": { - "ER_IROM1": 27.46, + "ER_IROM1": 27.48, "RW_CCM": 87.89, - "RW_IRAM1": 74.02, + "RW_IRAM1": 74.19, "UNINIT_CCM": 18.75 }, "regions": { "ER_IROM1": { "max": 1048576, - "size": 287964 + "size": 288148 }, "RW_CCM": { "max": 65280, @@ -27,7 +27,7 @@ }, "RW_IRAM1": { "max": 131072, - "size": 97024 + "size": 97240 }, "UNINIT_CCM": { "max": 256, @@ -37,10 +37,10 @@ "schema": 2, "scope_note": "\u540c\u7801\u51ed\u8bc1\uff1a\u6307\u7eb9\u8986\u76d6\u56fa\u4ef6\u4f53\u79ef\u76f8\u5173\u6e90\u7801\uff0c\u53e3\u5f84\u89c1\u672c\u811a\u672c docstring", "sizes": { - "code": 256200, - "ro_data": 31764, + "code": 256428, + "ro_data": 31720, "rw_data": 2080, - "zi_data": 152368 + "zi_data": 152584 }, "threshold_pct": 92.0, "worktree_dirt_at_stamp": 0, From a5142c6d9d11b751d5ff1390bd815f5202bcdabb Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Fri, 13 Nov 2026 14:23:06 +0800 Subject: [PATCH 2/3] =?UTF-8?q?UI=20=E5=86=85=E5=AE=B9=E9=A1=B5=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E5=8A=A0=E5=AE=9E=E6=97=B6=E9=A1=B5=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E6=9C=BA=E5=88=B6=EF=BC=88=E4=BA=A4=E6=B5=81?= =?UTF-8?q?=E9=87=8F=E9=A1=B5=E5=90=AF=E7=94=A8=EF=BC=8C300ms=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ui_content_page:描述符尾部追加 auto_ms/auto_seq 可选字段(缺省=0/NULL= 旧行为逐位不变);页打开期间框架持一只 lv_timer,仅当发布序号变过才整页 失效(零空转失效),关页即销;新 API ui_content_page_auto_poll - ui_page_ac.c:.auto_ms=300(≥200ms 发布节拍、避 400=2×200 锁相、300= 15×20ms LVGL 整刷周期;最坏屏上滞后 500ms;完整取值账目在代码注释) - simulator:rt 帧装配收口 sim_fill_rt_frame(n=0 冻结帧保 74 张黄金逐字节 一致,n≥1 确定性斜坡);新增 --rtlive 两态自检 + 窗口模式 200ms 节拍发布; check_content_pages.py 增 [rtlive] 零按键 A≠B 落屏断言 - docs/UI设计:供给说明新增 §5.5 自动刷新口径;内容页架构 §3.1/§四/§九 同步 (并修正 s_open 判定旧描述) - 台账:Keil 构建后按固定顺序回填(Code=256,588 / RW_IRAM1 +8B / UI +8B) 验证:模拟器 0错0警;菜单 8 状态 + 内容页 74 张 + rtlive(A≠B 30 字节)PASS (lead 独立复跑 RC=0);Keil 0错0警;mem-budget --gate / mem-numbers --scan 绿。 --- Drivers/BSP/UI/ui_content_page.c | 63 ++++++ Drivers/BSP/UI/ui_content_page.h | 26 +++ Drivers/BSP/UI/ui_page_ac.c | 16 ++ docs/UI设计/UI_数据供给设计说明.md | 20 ++ docs/UI设计/内容页架构.md | 17 +- docs/内存管理/RAM使用分析与优化方案.md | 6 +- docs/内存管理/RAM预算台账.md | 6 +- scripts/ram_baseline.tsv | 2 +- scripts/size_attestation.json | 20 +- simulator/main.c | 284 +++++++++++++++++-------- simulator/tests/check_content_pages.py | 29 ++- 11 files changed, 384 insertions(+), 105 deletions(-) diff --git a/Drivers/BSP/UI/ui_content_page.c b/Drivers/BSP/UI/ui_content_page.c index 7bfc062a..ad0dac6b 100644 --- a/Drivers/BSP/UI/ui_content_page.c +++ b/Drivers/BSP/UI/ui_content_page.c @@ -36,6 +36,9 @@ * id=%u 更可定位);绘制/按键/几何一律未动 * 2026-11-02 DTU 项目组 新增 ui_content_page_current_desc(#69 板端导航身份查询: * 页 id 从"只有串口开/关沿可见"升级为可按帧查询) + * 2026-11-13 DTU 项目组 实时页自动刷新:描述符 auto_ms>0 的页在打开期间由框架 + * 持一只 lv_timer 轮询发布序号(默认通道一 ui_data_rt_seq), + * 序号变过才整页失效;新 API ui_content_page_auto_poll */ /* 头文件包含区 */ @@ -55,6 +58,8 @@ #include "ui_tokens.h" +#include "ui_data.h" + #include "lvgl.h" #define DBG_TAG "[UI_CONTENT_PAGE]" @@ -85,6 +90,8 @@ static uint16_t s_sel; /* 列表页选中行( static uint8_t s_perm; /* 本次授予权限(ui_perm_t;开页时装载,关页重置) */ static lv_obj_t *s_scr; /* 页面屏幕(进入时建、返回时毁) */ static lv_obj_t *s_cont; /* 页面全屏容器(焦点/键入口 + 自绘载体) */ +static lv_timer_t *s_auto_timer; /* 自动失效轮询定时器(仅 auto_ms>0 的页打开期间存在;关页即销) */ +static uint32_t s_auto_seq; /* 上一拍见到的发布序号(开页时装基准,开页本身不多刷一帧) */ /* 容器样式:flat(透明 + 无边框/圆角/内边距)取自共享样式表 ui_style.c—— * 与 ui_menu_view / ui_standby 共用同一 const 对象(原先三处各定义一份逐字相同 @@ -469,6 +476,42 @@ static void cp_key_cb(lv_event_t *e) { } } +/* + * 函数功能:自动刷新轮询一拍(契约见 ui_content_page.h;判定与序号基准只在这里一份) + * 入口参数:无 + * 返回值:无 + * 限定条件:uiTask 上下文;无页打开 / 本页未订阅(auto_ms=0)时空操作 + * 函数说明:不在此拷帧——失效后由框架重绘走页面 refresh 的 seq 门控取新帧(§5.4 + * 页面私有缓存纪律不变),框架对数据内容零感知,只认序号 + */ +void ui_content_page_auto_poll(void) { + const content_page_def_t *d = s_open; + uint32_t now; + + if (d == NULL || d->auto_ms == 0u) { + return; + } + now = (d->auto_seq != NULL) ? d->auto_seq() : ui_data_rt_seq(); + if (now == s_auto_seq) { + return; /* 数据没变:零失效(省电屏带宽纪律) */ + } + s_auto_seq = now; + ui_content_page_invalidate(); /* 序号变过才整页失效 */ +} + +/* + * 函数功能:自动失效轮询定时器回调(薄封装;一切判定在 ui_content_page_auto_poll) + * 入口参数:t - 定时器对象(未用) lv_timer_t * + * 返回值:无 + * 限定条件:lv_timer_handler 内(uiTask 上下文)——与其他 LVGL 调用同线程同串行点 + * 函数说明:standby 期间内容页对象仍在(待机盖屏不关页),此时失效后台容器只是 + * 多一次脏区合并,待机屏自身重绘节律不受影响(实测待机像素回归绿) + */ +static void cp_auto_timer_cb(lv_timer_t *t) { + (void)t; + ui_content_page_auto_poll(); +} + /* * 函数功能:打开指定内容页(建屏 + 切屏 + 焦点置页面容器) * 入口参数:desc - 页面描述符(取自 ui_page_registry_for / ui_page_registry_password) @@ -517,6 +560,19 @@ bool ui_content_page_open(const content_page_def_t *desc, uint8_t perm) { desc->init_cb(); /* 页面模块重置运行态(编辑模式/光标/保存标志等) */ } + /* 自动失效轮询(实时页自动刷新):定时器随页建销,页面模块零定时器。基准取 + * "开页瞬间"的序号——开页首帧绘制本就已取过数,不该再为基线多补一次重绘; + * 之后每拍只在序号变过时失效。创建失败(LVGL 池耗尽)只降级回"按键才重绘" + * 旧行为并留痕,不挡开页(同 ui_w_setview 轮询定时器的降级口径)。 */ + s_auto_timer = NULL; + if (desc->auto_ms != 0u) { + s_auto_seq = (desc->auto_seq != NULL) ? desc->auto_seq() : ui_data_rt_seq(); + s_auto_timer = lv_timer_create(cp_auto_timer_cb, (uint32_t)desc->auto_ms, NULL); + if (s_auto_timer == NULL) { + DBG_ERROR("auto timer create failed (id=%s): key-only refresh fallback", desc->id); + } + } + /* 焦点/键入口:入默认输入组并获焦(先于切屏,保证按键立即由本模块接管) */ lv_group_t *g = lv_group_get_default(); if (g != NULL) { @@ -550,6 +606,13 @@ void ui_content_page_close(void) { DBG_INFO("page close: id=%s", s_open->id); + /* 自动失效定时器随页销毁:留着它,下一拍就会去失效即将释放的 s_cont + * (回调与关页同在 uiTask 串行点,此处删表即绝后患) */ + if (s_auto_timer != NULL) { + lv_timer_delete(s_auto_timer); + s_auto_timer = NULL; + } + lv_obj_delete(s_cont); /* 先删容器 */ lv_obj_delete(s_scr); /* 再删屏 */ diff --git a/Drivers/BSP/UI/ui_content_page.h b/Drivers/BSP/UI/ui_content_page.h index 6b566633..0e44e56a 100644 --- a/Drivers/BSP/UI/ui_content_page.h +++ b/Drivers/BSP/UI/ui_content_page.h @@ -30,6 +30,11 @@ * ui_page_registry.c(菜单树 #page= 注记为唯一真源), * 逻辑层手写 menu_content_page_for() 随之删除; * 描述符新增 id 字段(ASCII 页名,供日志/诊断定位是哪一页) + * 2026-11-13 DTU 项目组 实时页自动刷新:描述符新增 auto_ms/auto_seq 两字段, + * 新 API ui_content_page_auto_poll——框架在页面打开期间持 + * 一只轮询定时器,发布序号变过才整页失效,数据没变零重绘; + * 页面模块不再各自造定时器(周期账见 ui_page_ac.c 的 + * AC_AUTO_MS 注与 UI_数据供给设计说明.md §5.4) */ #ifndef UI_CONTENT_PAGE_H #define UI_CONTENT_PAGE_H @@ -80,6 +85,15 @@ typedef struct { uint16_t (*count_cb)(void); /* 动态行数(记录页) */ void (*draw_cb)(lv_layer_t *layer, const lv_area_t *clip); /* 表单/操作页:自定义内容 */ bool (*key_cb)(lv_key_t key); /* 页面自有键;true = 页已处理 */ + /* —— 自动失效轮询(全可选;auto_ms=0 = 只在按键重绘,未订阅页行为逐位不变)—— + * auto_ms>0:本页打开期间框架持一只 lv_timer(周期 auto_ms ms,uiTask 上下文随 + * lv_timer_handler 走),每拍取发布序号(auto_seq(),NULL = ui_data_rt_seq() + * 即通道一),序号与上一拍不同才整页失效——取数门控仍是页面 refresh 里的 + * seq 判断(UI_数据供给设计说明.md §5.4 的页面私有缓存约定原样有效)。 + * 关页即销定时器;页面模块禁止自造定时器。周期取值账见 ui_page_ac.c 的 + * AC_AUTO_MS 注释(发布者 rt 每 200ms 一帧,见同文 §5.2)。 */ + uint16_t auto_ms; /* 轮询周期(ms);0 = 不启用自动刷新 */ + uint32_t (*auto_seq)(void); /* 新鲜度序号源;NULL = ui_data_rt_seq() */ } content_page_def_t; /* @@ -154,4 +168,16 @@ bool ui_content_page_is_editable(void); */ uint16_t ui_content_page_selected_row(void); +/* + * 函数功能:自动刷新轮询一拍(生产由框架定时器回调调用;无头模拟器 --rtlive 自检 + * 没有定时器节拍、直调本函数顶替——两个入口共用同一份判定,规则不会两套) + * 入口参数:无 + * 返回值:无 + * 限定条件:uiTask 上下文(lv_timer_handler 内或其等价串行点) + * 函数说明:1. 无页打开 / 本页 auto_ms=0:空操作; + * 2. 序号与上一拍相同:不失效——数据没变的一次整屏重绘是纯支出(屏带宽/功耗); + * 3. 序号变了:记下新基准并整页失效,真正取数仍走页面 refresh 的 seq 门控 + */ +void ui_content_page_auto_poll(void); + #endif /* UI_CONTENT_PAGE_H */ diff --git a/Drivers/BSP/UI/ui_page_ac.c b/Drivers/BSP/UI/ui_page_ac.c index 515f5bdf..c7211b93 100644 --- a/Drivers/BSP/UI/ui_page_ac.c +++ b/Drivers/BSP/UI/ui_page_ac.c @@ -50,6 +50,19 @@ #define AC_Q_BAD (UI_DATA_Q_IV | UI_DATA_Q_NT | UI_DATA_Q_RAW | UI_DATA_Q_SB | \ UI_DATA_Q_BL | UI_DATA_Q_OV) +/* 自动刷新轮询周期(ms;框架级机制,字段语义见 ui_content_page.h 的 auto_ms 注)。 + * 取 300 的账(允许区间 250~500;发布者 rt 通道每 200ms 一帧,见 + * UI_数据供给设计说明.md §5.2): + * ① 必须 ≥ 发布节拍 200ms——轮得比发得快只是空转,白耗 uiTask; + * ② 避开 400ms(恰为 2×200):采样拍与发布拍锁相,感知滞后最坏 800ms; + * ③ 300 = 15 × LVGL 刷新周期 20ms(LV_DEF_REFR_PERIOD 两端同值):失效恰好落进 + * 下一次整刷合并,不多触发一帧;最坏屏上滞后 = 200 + 300 = 500ms,对一周波 + * 相量级的电气量读数足够,读表不觉得卡; + * ④ 250 只比 300 早至多 50ms(新帧首先受发布节拍约束),代价是多 20% 空转拍; + * 500 最坏滞后升到 700ms,数值跳变看着像"翻页"。 + * 稳态开销为零:序号没变不失效不重绘(ui_content_page_auto_poll 纪律)。 */ +#define AC_AUTO_MS 300u + /* 静态变量定义区 */ /* 行名表(Flash) */ @@ -228,4 +241,7 @@ const content_page_def_t ui_page_ac_desc = { .rows = NULL, .init_cb = ac_init, .refresh = ac_refresh, + /* 自动刷新(task-2):框架轮询通道一发布序号,来新帧才整页失效——本页不自造 + * 定时器;auto_seq 缺省 = ui_data_rt_seq(),与 ac_frame_ready 的取数门控同源。 */ + .auto_ms = AC_AUTO_MS, }; diff --git a/docs/UI设计/UI_数据供给设计说明.md b/docs/UI设计/UI_数据供给设计说明.md index 70bdebb5..247b44f4 100644 --- a/docs/UI设计/UI_数据供给设计说明.md +++ b/docs/UI设计/UI_数据供给设计说明.md @@ -340,6 +340,26 @@ static void pg_refresh(uint16_t row, char *name, char *value) { > 用 `ui_data_rt_seq()` 门控而不是"row==0 才刷新",是为了避免依赖"框架总从第 0 行开始绘制"这一隐式时序假设——框架换绘制顺序时页面不会静默出错。 +### 5.5 框架级自动失效(实时页不靠按键也有新数) + +§5.4 的 seq 门解决"取数不冗余",不解决"什么时候该重绘":框架旧行为是**按键触发绘制才走 `refresh()`**——`rt` 帧每 200 ms 在发,人停在「交流量」页不动按键,屏上数值就是冻结的。 + +新机制(task-2):`content_page_def_t` 尾部新增两个可选字段,定时器由**框架 `ui_content_page` 统一持有**(开页创建、关页销毁,同时最多一只),页面模块禁止自造轮询定时器: + +| 字段 | 语义 | +|---|---| +| `uint16_t auto_ms` | 0 = 关闭(保持旧行为;指定初始化未写即 0);>0 = 该页打开期间的轮询周期 | +| `uint32_t (*auto_seq)(void)` | 新鲜度来源;NULL(缺省)= `ui_data_rt_seq()`;读 `info` 通道的页面可填 `ui_data_info_seq()` | + +每拍框架调 `ui_content_page_auto_poll()`(回调经 uiTask 主循环 `lv_timer_handler` 触发,与绘制钩子同上下文):**序号变了才整页失效,没变零动作**——不拷帧、不失效、不重绘,稳态开销与旧行为一致;取数门控仍走 §5.4 的页面私有 seq 缓存,失效只是把"下次按键才有机会读新帧"变成"到点就有机会读新帧"。 + +当前口径: + +- **已启用**:`ac` 交流量页 `.auto_ms = AC_AUTO_MS`(300 ms;250~500 ms 允许区间的取值账目详述在 `ui_page_ac.c` 的 `AC_AUTO_MS` 注释——要点:轮询不得快于 200 ms 发布节拍、避开 400 ms 与其锁相、300 = 15 × LVGL 整刷周期 20 ms(两端 `LV_DEF_REFR_PERIOD` 同值),最坏屏上滞后 200 + 300 = 500 ms)。 +- **不迁移**:待机屏 `ui_standby.c`(自持 1 s 整屏定时器 + seq 门控)、整定视图 `ui_w_setview.c`(`sv_poll` 定时器盯 `par` 通道)——两处各自已有节拍,纳入框架只会多一只定时器。 +- **未接入(待 lead/用户裁定)**:`dc`/`yx`/`clock`(读 `rt`)与 `device_info`/`runreport`(读 `info`)仍是"按键才取新数";接入只差描述符一行 `.auto_ms`(info 页另加 `.auto_seq`)。 + +**回归确定性**:无头模式(`--keys`/74 张黄金参考)从不跑 `lv_timer_handler`,定时器不可能在截屏前触发,且仿真仍是"一次发布冻结帧"——黄金参考逐字节不变(`check_content_pages.py` 实证)。自动失效路径的验证走新开关 `--rtlive <键序> `:开页截 A → 发布第 1 号确定性斜坡帧 → **直接调用** `ui_content_page_auto_poll()`(等价定时器到点)→ 截 B;A≠B 即证明"零按键、新帧落屏"。窗口模式(真机行为仿真)每 200 ms 发一帧斜坡值,屏上数值自己会走。 --- ## 6. 写路径(定值整定) diff --git a/docs/UI设计/内容页架构.md b/docs/UI设计/内容页架构.md index e76ec2fa..9a1fe74b 100644 --- a/docs/UI设计/内容页架构.md +++ b/docs/UI设计/内容页架构.md @@ -96,15 +96,19 @@ flowchart TD | `bool ui_content_page_open(id)` | 开页 | ① 查 `s_defs[id]`:NULL → 打 `ERROR` 日志并返回 false(枚举/表漂移防护)② `lv_obj_create(NULL)` 建屏 + 全屏容器(flat 常量样式 + `IGNORE_LAYOUT` + `CLICK_FOCUSABLE`)③ 容器**先**入默认输入组并获焦 ⓸ `lv_screen_load` 切屏 | | `void ui_content_page_close()` | 关页 | ① 焦点**先**回菜单持久容器(防输入组焦点悬空)② 切回菜单屏(菜单屏对象未动,原样还原)③ 再删容器+屏,池内存回落 | | `void ui_content_page_invalidate()` | 整页重绘 | 失效页面容器;供表单/操作页自身状态变化后调用(无页打开时空操作) | -| `bool ui_content_page_is_open()` | 查询 | `s_open != CONTENT_PAGE_COUNT` | +| `void ui_content_page_auto_poll()` | 自动失效轮询 | 供框架定时器(`auto_ms>0` 的页打开期间才存在)每拍调用:发布序号(`auto_seq()`,NULL = `ui_data_rt_seq()`)与上一拍不同才整页失效,没变零动作;无页打开 / `auto_ms=0` 时空操作。模拟器 `--rtlive` 直接调它做确定性验证(机制见 `UI_数据供给设计说明.md` §5.5) | +| `bool ui_content_page_is_open()` | 查询 | `s_open != NULL` | -框架持有的运行状态(全部 4 个变量,约 20 B): +框架持有的运行状态(基础 5 个 + 自动失效 2 个,与 ui_content_page.c 静态块一致): ```c -static content_page_id_t s_open; /* 当前打开的页(CONTENT_PAGE_COUNT = 未打开) */ +static const content_page_def_t *s_open; /* 当前打开页的描述符(NULL = 未打开) */ static uint16_t s_sel; /* 列表页选中行(全局下标,跨页连续;每次进入清零) */ +static uint8_t s_perm; /* 本次开页授予的权限(ui_perm_t;开页时装载,关页重置) */ static lv_obj_t *s_scr; /* 页面屏幕(进入时建、返回时毁) */ static lv_obj_t *s_cont; /* 页面全屏容器(焦点/键入口 + 自绘载体) */ +static lv_timer_t *s_auto_timer; /* 自动失效轮询定时器(仅 auto_ms>0 的页打开期间存在;关页即销) */ +static uint32_t s_auto_seq; /* 上一拍见到的发布序号(开页时装基准,开页本身不多刷一帧) */ ``` ### 3.2 页面生命周期:一次 ENTER → 一次 ESC @@ -249,6 +253,9 @@ typedef struct { uint16_t (*count_cb)(void); /* 动态行数(记录页) */ void (*draw_cb)(lv_layer_t *layer, const lv_area_t *clip); /* 表单/操作页:自定义内容 */ bool (*key_cb)(lv_key_t key); /* 页面自有键;true = 页已处理 */ + /* —— 自动失效轮询(auto_ms=0 = 只在按键重绘,未订阅页行为逐位不变;见 UI_数据供给设计说明.md §5.5)—— */ + uint16_t auto_ms; /* 轮询周期(ms);0 = 不启用自动刷新 */ + uint32_t (*auto_seq)(void); /* 新鲜度序号源;NULL = ui_data_rt_seq() */ } content_page_def_t; ``` @@ -273,6 +280,7 @@ typedef struct { | `rows` | `const cp_row_t *` | 静态数据表(Flash) | 6 行 | 24 行 | 3 行 | NULL | | `fixed_cur_page` / `fixed_total_pages` | `uint8_t` | 信息页页脚固定值 | 10 / 1 | — | — | — | | `refresh` / `count_cb` / `draw_cb` / `key_cb` | 函数指针 | 扩展钩子 | 全 NULL | 全 NULL | 全 NULL | draw_cb + key_cb(余 NULL) | +| `auto_ms` / `auto_seq` | `uint16_t` / 函数指针 | 自动失效轮询周期 / 新鲜度源(`UI_数据供给设计说明.md` §5.5) | 0 / NULL | **300 / NULL** | 0 / NULL | 0 / NULL | 扩展钩子(机制已实现;密码页用 `draw_cb` + `key_cb`,其余 3 页未用): @@ -283,6 +291,8 @@ typedef struct { | `draw_cb` | `void (lv_layer_t *layer, const lv_area_t *clip)` | 绘制链末尾(内置区块之后) | 表单/操作页自定义内容 | | `key_cb` | `bool (lv_key_t key)` | 键分发时(ESC 之后、内置 ↑↓ 之前) | 页面自有键(ENTER 确认等);返回 true = 页已处理 | +> 自动失效(`auto_ms`/`auto_seq`)**不是页面钩子**:定时器、轮询节拍与序号比较全在框架(`ui_content_page_auto_poll`),页面只声明周期与可选序号源;口径见 `UI_数据供给设计说明.md` §5.5。 + ### 4.1 表单/操作页首个应用:密码页(已落地) 实机(淮源电力 F30)已有"用户密钥"页:进入某些子叶面前须先输入密码(实机照片见 @@ -493,3 +503,4 @@ extern const content_page_def_t ui_page_device_info_desc; /* 描述符(Flash | 2026-10-26 | 验证与回归段的页数口径修正为 **27 页**黄金参考(`simulator/tests/ref/` 入库 27 个 `shot_*.bin`,含整定 9 态);上方变更记录里的「11 页」是当时事实不动 | | 2026-10-26 | 门禁常量修正为 5368 B / 5448 B(= 两个回归脚本头部现值);本文件正文表早已是 5368/5448,只这一处残留 2026-10-11 裁剪 LVGL 控件类之前的 4784/4864 | | 2026-10-26 | 验证与回归段的黄金参考页数 **27 → 40 页**(UI 三页落地后 `simulator/tests/ref/` 实数 40 张);上方变更记录里的 11 / 27 是各当时下限,保留 | +| 2026-11-13 | 框架级自动失效(task-2):描述符尾部新增 `auto_ms`/`auto_seq`(当前仅 ac 页启用 300 ms),新增框架 API `ui_content_page_auto_poll()`(§3.1);§3.1 静态块与代码同步(补 `s_perm` 与自动失效 2 变量);回归脚本新增 `--rtlive` 零按键落屏检查(74 张黄金参考逐字节不变,实测 PASS);机制详述在 `UI_数据供给设计说明.md` §5.5 | diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index bb94a8a0..2d585ce8 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 288,148 B (281.39 KB) | 1,048,576 B (1024 KB) | 27.5% | -| RW_IRAM1 | 97,240 B (94.96 KB) | 131,072 B (128 KB) | 74.2% | +| ER_IROM1 | 288,624 B (281.86 KB) | 1,048,576 B (1024 KB) | 27.5% | +| RW_IRAM1 | 97,248 B (94.97 KB) | 131,072 B (128 KB) | 74.2% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | -| **Flash (ROM 合计)** | **290,228 B (283.4 KB)** | 1,048,576 B (1024 KB) | **27.7%** | +| **Flash (ROM 合计)** | **290,712 B (283.9 KB)** | 1,048,576 B (1024 KB) | **27.7%** | 各区的容量与常量来源: diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index 042cf628..4f7872f5 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -15,16 +15,16 @@ | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | |---|---|---|---|---| -| ER_IROM1 (Flash) | 288,148 B | 1,048,576 B | 27.5% | 参考 | +| ER_IROM1 (Flash) | 288,624 B | 1,048,576 B | 27.5% | 参考 | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | -| RW_IRAM1 | 97,240 B | 131,072 B | 74.2% | PASS | +| RW_IRAM1 | 97,248 B | 131,072 B | 74.2% | PASS | | 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 | |---|---|---|---| | 网络 | 13,958 B | 12 KB | ⚠ 超 1.6 KB | | 存储 | 9,144 B | 12 KB | OK | -| UI | 36,723 B | 16 KB | ⚠ 超 19.9 KB | +| UI | 36,731 B | 16 KB | ⚠ 超 19.9 KB | | RTOS 堆 | 57,376 B | 56 KB | 堆=配额 | | 应用 | 26,609 B | 8 KB | ⚠ 超 18.0 KB | | 其它 | 10,792 B | — | 参考 | diff --git a/scripts/ram_baseline.tsv b/scripts/ram_baseline.tsv index 139cad88..a67acea0 100644 --- a/scripts/ram_baseline.tsv +++ b/scripts/ram_baseline.tsv @@ -21,4 +21,4 @@ date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi 2026-11-09 255160 31204 2080 152152 286364 27.3 96808 73.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5296;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 2026-11-10 255160 31204 2080 152152 286364 27.3 96808 73.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5296;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 2026-11-11 256024 31760 2080 152368 287784 27.4 97024 74.0 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 -2026-11-13 256428 31720 2080 152584 288148 27.5 97240 74.2 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 +2026-11-13 256588 32036 2088 152584 288624 27.5 97248 74.2 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 diff --git a/scripts/size_attestation.json b/scripts/size_attestation.json index 563832d9..51d7b7c7 100644 --- a/scripts/size_attestation.json +++ b/scripts/size_attestation.json @@ -1,17 +1,17 @@ { - "built_at": "2026-11-13T13:11:29+08:00", - "fingerprint": "eb152c8e20a33b63da91fd3b5e32877b83b581c43d0c4956375ed13b6531db3f", + "built_at": "2026-11-13T14:23:05+08:00", + "fingerprint": "f44730aca08c9f35b9eec8a83322b342ee21a2280a94ce2c277b5c62a26fb442", "fingerprint_files": 1479, "fingerprint_source": "git-index", "gated_regions": [ "RW_IRAM1", "RW_CCM" ], - "git_head": "40a2816a9971928d1ca6fa218921b86dae79bd63", + "git_head": "5bab248deeac14b0d6235e0fe312bcd9388a5e0a", "map": "MDK-ARM/STM32F407-Demo/STM32F407-Demo.map", - "map_mtime": 1794542035, + "map_mtime": 1794549805, "pct": { - "ER_IROM1": 27.48, + "ER_IROM1": 27.53, "RW_CCM": 87.89, "RW_IRAM1": 74.19, "UNINIT_CCM": 18.75 @@ -19,7 +19,7 @@ "regions": { "ER_IROM1": { "max": 1048576, - "size": 288148 + "size": 288624 }, "RW_CCM": { "max": 65280, @@ -27,7 +27,7 @@ }, "RW_IRAM1": { "max": 131072, - "size": 97240 + "size": 97248 }, "UNINIT_CCM": { "max": 256, @@ -37,9 +37,9 @@ "schema": 2, "scope_note": "\u540c\u7801\u51ed\u8bc1\uff1a\u6307\u7eb9\u8986\u76d6\u56fa\u4ef6\u4f53\u79ef\u76f8\u5173\u6e90\u7801\uff0c\u53e3\u5f84\u89c1\u672c\u811a\u672c docstring", "sizes": { - "code": 256428, - "ro_data": 31720, - "rw_data": 2080, + "code": 256588, + "ro_data": 32036, + "rw_data": 2088, "zi_data": 152584 }, "threshold_pct": 92.0, diff --git a/simulator/main.c b/simulator/main.c index a22032ae..457d8e8f 100644 --- a/simulator/main.c +++ b/simulator/main.c @@ -6,6 +6,8 @@ * 创建日期:2026-08-31 * 修改记录: * v1.0 2026-08-31 王建锋 创建:基于 lv_port_pc_vscode 适配 + * v1.1 2026-11-13 DTU 项目组 task-2:rt 帧装配收口成 sim_fill_rt_frame 唯一装配口 + + * --rtlive 两态自检 + 窗口模式 200ms 节拍重发 live 帧 * * @file main.c * @brief LVGL 模拟器入口 — 160x160 1bpp 单色菜单 UI @@ -26,6 +28,13 @@ * seq 字符:u=↑ d=↓ l=← r=→ e=Enter s=Esc(其余字符忽略) * 每键经 lv_group_send_data 注入(与 keypad 输入设备同路径),驱动 5 帧后取快照 * + * 实时页自动刷新自检模式(无窗口,两态比对,退出码 0=两枪都出图): + * lvgl_sim.exe --rtlive shotA.bin shotB.bin + * 先注键开到目标页拍 A(冻结帧 n=0);随后发布一帧确定性斜坡帧(仿真设备还在 + * 采集,发布序号前进)并直调生产判定体 ui_content_page_auto_poll(无头无定时器 + * 节拍,直调顶替),再拍 B。A 不等于 B 逐字节成立 = 不按任何键,新数据照样刷屏; + * 像素差异断言在 simulator/tests/check_content_pages.py 里做 + * * 配置轮询自检模式(无窗口、进程内闭环、退出码 0=PASS / 非0=FAIL): * lvgl_sim.exe --cfgtest * 验证链:net_cfg_load(只读 INI;旧 NCFG 档迁移已于 2026-10-10 删除) @@ -64,6 +73,7 @@ #include "sim_port.h" #include "ui_menu.h" #include "ui_standby.h" +#include "ui_content_page.h" /* task-2:--rtlive 直调框架自动刷新判定体 */ #include "ui_net_sink.h" #include "net_cfg.h" #include "cfg_file.h" @@ -972,6 +982,123 @@ static void sim_publish_par_frame(void) { s_par_pub_sum = par.checksum; } +/* + * 函数功能:组装一帧 rt 快照——黄金冻结帧(n=0)与 live 斜坡帧(n>=1)共用这唯一 + * 装配口,仿真假数据不存在第二份 + * 入口参数:p_rt - 目标帧(调用方持有,本函数先整帧清零) ui_data_rt_t * + * n - 帧号;0 = 黄金冻结帧(逐字节等于重构前,74 张黄金参考建立在 + * 它上面),>0 = 确定性斜坡帧(三角波 0..5..0 周期 11 帧,无随机, + * 仅 --rtlive 自检与窗口模式节拍使用) uint32_t + * 返回值:无 + * 限定条件:--noclock / --epoch 形态旗标已就位(时钟段读它们) + * 函数说明:随 n 变的只有显示相关字段(相量/遥测/tick/墙钟秒),品质位与遥信位图 + * 原样冻结——模拟的是设备还在采集,不是设备自己变坏了 + */ +static void sim_fill_rt_frame(ui_data_rt_t *p_rt, uint32_t n) { + uint8_t i; + + (void)memset(p_rt, 0, sizeof(*p_rt)); + p_rt->valid = 1u; + p_rt->eng = UI_DATA_ENG_PIN; + p_rt->tick_ms = 0u; + /* 遥测下标 = adc_semantic_t 语义序(UA/UB/UC/UN/IA/IB/IC/IN/TEMP)。 + * 模拟器不 include adc_input.h,故这里用语义序注释对照,不写符号名 */ + p_rt->yc[0].val = 0.8123f; /* UA 基波(引脚电压域) */ + p_rt->yc[0].qds = 0u; + p_rt->yc[1].val = 0.0f; /* UB:V0.1 硬件削顶无采样源 */ + p_rt->yc[1].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; + p_rt->yc[2].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; /* UC 同上 */ + p_rt->yc[3].val = 0.0054f; /* UN 零序电压基波 */ + p_rt->yc[3].qds = 0u; + p_rt->yc[4].val = 0.0456f; /* IA 基波 */ + p_rt->yc[4].qds = 0u; + p_rt->yc[5].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; /* IB */ + p_rt->yc[6].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; /* IC */ + p_rt->yc[7].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; /* IN */ + p_rt->yc[8].val = 2048.0f; /* TEMP:码值(不参与线性标定) */ + p_rt->yc[8].qds = UI_DATA_Q_RAW; + /* 相量实例槽(序 = phasor k_inst[]:IA-F1/UA-F1/UN-F1/IA-F2/IA-F5) */ + p_rt->ph_n = 5u; + p_rt->ph[0].val = 0.0456f; + p_rt->ph[1].val = 0.8123f; + p_rt->ph[2].val = 0.0054f; + p_rt->ph[3].val = 0.0017f; /* IA 二次谐波(涌流制动监视量) */ + p_rt->ph[4].val = 0.0008f; /* IA 五次谐波(选线用) */ + for (i = 0u; (i < UI_DATA_PH_MAX) && (i < p_rt->ph_n); i++) { + p_rt->ph[i].qds = 0u; + } + for (; i < UI_DATA_PH_MAX; i++) { + p_rt->ph[i].qds = UI_DATA_Q_IV; /* 未发布的槽:无效,页面显示 "--" */ + } + p_rt->yx_bits = 0u; /* 固定帧无动作态 */ + p_rt->yx_valid = 0x4043C000ULL; /* YX15~18/23/31 有效位图(与点表同集合) */ + /* 双点位图照写侧口径给(App/protocol/iec104/iec104_pts.c 的 YX_DP_N = 4 → bit0~3)。 + * 这 4 点**不在**上面的有效位图里(V0.1 无开入硬件),所以遥信页对它们打 "--"—— + * 刻意不把 bit0 塞进 yx_valid 让画面多两个汉字好看:那会让黄金参考显示成"这台设备 + * 有开关位置遥信",而真机上没有(屏上的话必须与本机一致,同页脚页码那条裁定) */ + p_rt->yx_dp = 0x0FULL; + p_rt->freq_valid = 0u; /* eng=0 期间幅值门恒失效 → 无新鲜读数 */ + p_rt->freq_hz = 0.0f; + /* 墙钟两态(道F task-3):--noclock 把这台机器装成"RTC 异常/未对时"——只有 + * 这样才照得到时钟设置页的 "--" + "无时钟"徽标形态;面板设过时以面板值为准, + * 这就是页面上"回读证据"的唯一来源(真机那边是 SD2506 回读 + 快照层重发) */ + p_rt->clock_ok = (s_noclock == 0) ? 1u : 0u; + if (s_noclock != 0) { + p_rt->clock[0] = 0; /* 无效时标 = 空串(页面据此显 "--",绝不显 1970) */ + } else if (s_sim_epoch != 0u) { + ui_data_clock_t ck; + + if (ui_data_clock_from_epoch(s_sim_epoch, &ck) == 0) { + (void)ui_data_clock_format(&ck, p_rt->clock, sizeof(p_rt->clock)); + } else { + p_rt->clock[0] = 0; + p_rt->clock_ok = 0u; + } + } else { + (void)snprintf(p_rt->clock, sizeof(p_rt->clock), "%s", "2026-10-25 08:00:00"); + } + + /* live 斜坡(n=0 完全不进本段:黄金像素逐位不变)。三角波避免单调漂移——窗口 + * 模式跑多久数值域都有界;相量实例与对应遥测写同值(镜像 App/app/ui_data_feed.c + * 的同源纪律)。freq 保持 freq_valid=0:eng=0 期间幅值门恒失效,不为了让 Hz 行 + * 看着在动就伪造新鲜读数 */ + if (n != 0u) { + uint32_t k = n % 11u; + uint32_t s = (k <= 5u) ? k : (10u - k); + float f = (float)s; + + p_rt->tick_ms = n * 200u; + p_rt->yc[0].val = 0.8123f + (0.0020f * f); + p_rt->yc[3].val = 0.0054f + (0.0002f * f); + p_rt->yc[4].val = 0.0456f + (0.0004f * f); + p_rt->ph[0].val = p_rt->yc[4].val; + p_rt->ph[1].val = p_rt->yc[0].val; + p_rt->ph[2].val = p_rt->yc[3].val; + p_rt->ph[3].val = 0.0017f + (0.0001f * f); + p_rt->ph[4].val = 0.0008f + (0.0001f * f); + if ((p_rt->clock_ok != 0u) && (p_rt->clock[0] != (char)0) && (s_sim_epoch == 0u)) { + (void)snprintf(p_rt->clock, sizeof(p_rt->clock), "2026-10-25 08:00:%02u", + (unsigned int)(n % 60u)); + } + } +} + +/* + * 函数功能:发布一帧 live 斜坡快照(--rtlive 自检与窗口模式节拍共用;真机对应 + * App/app/ui_data_feed.c 的 200ms 发布拍) + * 入口参数:n - 帧号(>=1;0 是冻结帧,只由 sim_publish_data_frames 开局发一次) uint32_t + * 返回值:无 + * 限定条件:ui_data_init() 之后 + * 函数说明:ui_data_publish_rt 自带发布序号前进——这正是内容页框架自动失效的触发 + * 源(序号变过才整页失效,见 ui_content_page_auto_poll) + */ +static void sim_publish_rt_live(uint32_t n) { + ui_data_rt_t rt; + + sim_fill_rt_frame(&rt, n); + (void)ui_data_publish_rt(&rt); +} + static void sim_publish_data_frames(void) { ui_data_rt_t rt; ui_data_info_t info; @@ -979,68 +1106,10 @@ static void sim_publish_data_frames(void) { ui_data_init(); - (void)memset(&rt, 0, sizeof(rt)); - rt.valid = 1u; - rt.eng = UI_DATA_ENG_PIN; - rt.tick_ms = 0u; - /* 遥测下标 = adc_semantic_t 语义序(UA/UB/UC/UN/IA/IB/IC/IN/TEMP)。 - * 模拟器不 include adc_input.h,故这里用语义序注释对照,不写符号名 */ - rt.yc[0].val = 0.8123f; /* UA 基波(引脚电压域) */ - rt.yc[0].qds = 0u; - rt.yc[1].val = 0.0f; /* UB:V0.1 硬件削顶无采样源 */ - rt.yc[1].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; - rt.yc[2].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; /* UC 同上 */ - rt.yc[3].val = 0.0054f; /* UN 零序电压基波 */ - rt.yc[3].qds = 0u; - rt.yc[4].val = 0.0456f; /* IA 基波 */ - rt.yc[4].qds = 0u; - rt.yc[5].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; /* IB */ - rt.yc[6].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; /* IC */ - rt.yc[7].qds = UI_DATA_Q_IV | UI_DATA_Q_OV; /* IN */ - rt.yc[8].val = 2048.0f; /* TEMP:码值(不参与线性标定) */ - rt.yc[8].qds = UI_DATA_Q_RAW; - /* 相量实例槽(序 = phasor k_inst[]:IA-F1/UA-F1/UN-F1/IA-F2/IA-F5) */ - rt.ph_n = 5u; - rt.ph[0].val = 0.0456f; - rt.ph[1].val = 0.8123f; - rt.ph[2].val = 0.0054f; - rt.ph[3].val = 0.0017f; /* IA 二次谐波(涌流制动监视量) */ - rt.ph[4].val = 0.0008f; /* IA 五次谐波(选线用) */ - for (i = 0u; (i < UI_DATA_PH_MAX) && (i < rt.ph_n); i++) { - rt.ph[i].qds = 0u; - } - for (; i < UI_DATA_PH_MAX; i++) { - rt.ph[i].qds = UI_DATA_Q_IV; /* 未发布的槽:无效,页面显示 "--" */ - } - rt.yx_bits = 0u; /* 固定帧无动作态 */ - rt.yx_valid = 0x4043C000ULL; /* YX15~18/23/31 有效位图(与点表同集合) */ - /* 双点位图照写侧口径给(App/protocol/iec104/iec104_pts.c 的 YX_DP_N = 4 → bit0~3)。 - * 这 4 点**不在**上面的有效位图里(V0.1 无开入硬件),所以遥信页对它们打 "--"—— - * 刻意不把 bit0 塞进 yx_valid 让画面多两个汉字好看:那会让黄金参考显示成"这台设备 - * 有开关位置遥信",而真机上没有(屏上的话必须与本机一致,同页脚页码那条裁定) */ - rt.yx_dp = 0x0FULL; - rt.freq_valid = 0u; /* eng=0 期间幅值门恒失效 → 无新鲜读数 */ - rt.freq_hz = 0.0f; - /* 墙钟两态(道F task-3):--noclock 把这台机器装成"RTC 异常/未对时"——只有 - * 这样才照得到时钟设置页的 "--" + "无时钟"徽标形态;面板设过时以面板值为准, - * 这就是页面上"回读证据"的唯一来源(真机那边是 SD2506 回读 + 快照层重发) */ - rt.clock_ok = (s_noclock == 0) ? 1u : 0u; - if (s_noclock != 0) { - rt.clock[0] = 0; /* 无效时标 = 空串(页面据此显 "--",绝不显 1970) */ - } else if (s_sim_epoch != 0u) { - ui_data_clock_t ck; - - if (ui_data_clock_from_epoch(s_sim_epoch, &ck) == 0) { - (void)ui_data_clock_format(&ck, rt.clock, sizeof(rt.clock)); - } else { - rt.clock[0] = 0; - rt.clock_ok = 0u; - } - } else { - (void)snprintf(rt.clock, sizeof(rt.clock), "%s", "2026-10-25 08:00:00"); - } + sim_fill_rt_frame(&rt, 0u); /* 冻结帧:黄金基准,装配与斜坡逻辑见 sim_fill_rt_frame */ (void)ui_data_publish_rt(&rt); + /* 定值帧:出厂默认 + 封存(幂等),再按 prot_set 现状发一帧(见上面两个函数) */ (void)prot_set_init(); sim_publish_par_frame(); @@ -1153,14 +1222,49 @@ static void sim_param_pump(void) { } } +/* + * 函数功能:把当前屏快照(I1)写成像素文件(run_headless 首枪与 --rtlive 第二枪共用) + * 入口参数:path - 输出文件路径(写入 160x160 I1 原始像素) const char * + * 返回值:0 - 成功;1 - 快照失败或文件打不开 int + * 限定条件:离屏显示与目标屏已就绪(无头上下文内调用) + * 函数说明:I1 缓冲区布局 [palette 8B][pixel data],跳调色板只写像素;快照即整屏 + * 同步重渲,不依赖 lv_timer_handler(无头禁驱动帧循环的纪律不变) + */ +static int snap_to_file(const char *path) { + lv_draw_buf_t *snap = lv_snapshot_take(lv_screen_active(), LV_COLOR_FORMAT_I1); + if (snap == NULL) { + fprintf(stderr, "[shot] snapshot failed\n"); + return 1; + } + + uint32_t snap_w = snap->header.w; + uint32_t snap_h = snap->header.h; + uint32_t pal_bytes = LV_COLOR_INDEXED_PALETTE_SIZE(LV_COLOR_FORMAT_I1) * (uint32_t)sizeof(lv_color32_t); + const uint8_t * px = snap->data + pal_bytes; /* 只读:写文件时不再改动快照 */ + uint32_t px_bytes = (uint32_t)snap->header.stride * (uint32_t)snap_h; + FILE *fp = fopen(path, "wb"); + if (fp == NULL) { + lv_draw_buf_destroy(snap); + fprintf(stderr, "fopen failed: %s\n", path); + return 1; + } + size_t wn = fwrite(px, 1, px_bytes, fp); + fclose(fp); + lv_draw_buf_destroy(snap); + printf("screenshot: %s (%zu bytes, %ux%u I1)\n", path, wn, snap_w, snap_h); + return 0; +} + /* * 函数功能:无窗口离屏渲染(可注入键序列)并导出末帧像素 * 入口参数:keys - 键序列(u=上 d=下 l=左 r=右 e=确认 s=返回;空 = 不注键) const char * * path - 输出文件(写入 160x160 I1 原始像素) const char * * standby - 非 0 = 注键后强制进入待机屏再截图(--standby 模式) int + * path_b - 非 NULL = --rtlive 模式:A 枪出图后发布一帧 live 斜坡帧并直调 + * 框架自动刷新判定体,再拍 B 写到本路径 const char * * 返回值:0 - 成功;非 0 - 失败 int */ -static int run_headless(const char * keys, const char * path, int standby) { +static int run_headless(const char * keys, const char * path, int standby, const char * path_b) { setvbuf(stdout, NULL, _IONBF, 0); lv_init(); lv_delay_set_cb(shot_delay_cb); @@ -1256,28 +1360,24 @@ static int run_headless(const char * keys, const char * path, int standby) { (unsigned)(mon.total_size - mon.free_size), (unsigned)mon.free_size); /* 用 snapshot 把整屏渲进独立 I1 draw_buf(避开 I1+FULL 的渲染死循环) */ - lv_draw_buf_t *snap = lv_snapshot_take(lv_screen_active(), LV_COLOR_FORMAT_I1); - if (snap == NULL) { - fprintf(stderr, "[shot] snapshot failed\n"); - return 1; + int rc = snap_to_file(path); + + if (rc != 0) { + return rc; } - uint32_t snap_w = snap->header.w; - uint32_t snap_h = snap->header.h; - /* I1 缓冲区布局:[palette 8B][pixel data],跳过 palette 只写像素 */ - uint32_t pal_bytes = LV_COLOR_INDEXED_PALETTE_SIZE(LV_COLOR_FORMAT_I1) * (uint32_t)sizeof(lv_color32_t); - const uint8_t * px = snap->data + pal_bytes; /* 只读:写文件时不再改动快照 */ - uint32_t px_bytes = (uint32_t)snap->header.stride * (uint32_t)snap_h; - FILE *fp = fopen(path, "wb"); - if (fp == NULL) { - lv_draw_buf_destroy(snap); - fprintf(stderr, "fopen failed: %s\n", path); - return 1; + /* --rtlive 第二枪:发布前进过序号的 live 斜坡帧后,无头没有定时器节拍, + * 直调生产判定体 ui_content_page_auto_poll 顶替——它就是板端定时器回调里 + * 跑的同一份代码;"数据变过才失效"的判定与真机逐句相同 */ + if (path_b != NULL) { + sim_publish_rt_live(1u); + ui_content_page_auto_poll(); + rc = snap_to_file(path_b); + if (rc != 0) { + return rc; + } + printf("rtlive: frames written (A=%s B=%s)\n", path, path_b); } - size_t n = fwrite(px, 1, px_bytes, fp); - fclose(fp); - lv_draw_buf_destroy(snap); - printf("screenshot: %s (%zu bytes, %ux%u I1)\n", path, n, snap_w, snap_h); return 0; } @@ -1624,7 +1724,7 @@ static int run_logtest(void) { * 函数功能:模拟器入口(解析模式开关 → 窗口模式或无头自检) * 入口参数:argc - 参数个数 int * argv - 参数数组(--screenshot / --keys / --standby / --cfgtest / - * --logtest / --idle / --noclock) char ** + * --logtest / --idle / --noclock / --rtlive) char ** * 返回值:0 - 正常退出或自检 PASS;非 0 - 失败 int */ int main(int argc, char **argv) { @@ -1652,13 +1752,16 @@ int main(int argc, char **argv) { /* 截图/自检模式:--screenshot / --keys / --standby / --cfgtest / --logtest */ for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--screenshot") == 0 && (i + 1) < argc) { - return run_headless("", argv[i + 1], 0); + return run_headless("", argv[i + 1], 0, NULL); } if (strcmp(argv[i], "--keys") == 0 && (i + 2) < argc) { - return run_headless(argv[i + 1], argv[i + 2], 0); + return run_headless(argv[i + 1], argv[i + 2], 0, NULL); } if (strcmp(argv[i], "--standby") == 0 && (i + 1) < argc) { - return run_headless("", argv[i + 1], 1); + return run_headless("", argv[i + 1], 1, NULL); + } + if (strcmp(argv[i], "--rtlive") == 0 && (i + 3) < argc) { + return run_headless(argv[i + 1], argv[i + 2], 0, argv[i + 3]); } if (strcmp(argv[i], "--cfgtest") == 0) { return run_cfgtest(); @@ -1759,6 +1862,11 @@ int main(int argc, char **argv) { /* 配置文件轮询起点(2s 周期,与 MCU netTask 一致) */ uint32_t cfg_last_poll = (uint32_t)SDL_GetTicks(); + /* rt live 节拍(窗口模式实时演示):镜像板端 ui_data_feed 的 200ms 发布拍; + * 无头回归模式根本不走本循环——黄金参考永远建立在冻结帧 n=0 上 */ + uint32_t rt_last_pub = cfg_last_poll; + uint32_t rt_live_n = 0u; + /* 主循环:驱动 LVGL 定时器与刷新 * 注意:SDL 事件(键盘/窗口/退出)由 LVGL SDL 驱动内部 5ms 定时器 * (lv_sdl_window.c 的 sdl_event_handler)统一轮询分发, @@ -1791,6 +1899,14 @@ int main(int argc, char **argv) { cfg_file_poll(); } + /* rt 帧按拍重发(窗口实时演示,板端 feed 同款 200ms 拍):确定性斜坡帧、 + * 无随机;发布序号前进后,实时页由内容页框架自动失效定时器刷屏—— + * 打开交流量页不按键即可看到数值自己走 */ + if ((uint32_t)(now_ms - rt_last_pub) >= 200u) { + rt_last_pub = now_ms; + sim_publish_rt_live(++rt_live_n); + } + /* 驱动 LVGL 定时器与刷新 */ uint32_t sleep_ms = lv_timer_handler(); if (sleep_ms == LV_NO_TIMER_READY) { diff --git a/simulator/tests/check_content_pages.py b/simulator/tests/check_content_pages.py index 0c9dcfa8..d95cd031 100644 --- a/simulator/tests/check_content_pages.py +++ b/simulator/tests/check_content_pages.py @@ -17,6 +17,10 @@ check_content_pages.py — 叶子内容页像素黄金参考回归 + 文本级 另做开页-关页往返池水位门禁:`es`(info)与 `ddeeesll`(密码页,ESC 后 ←← 弹出路上压的 2 个菜单框回根框,完全还原初始态)后 used 须等于 POOL_AFTER_ROUNDTRIP。 +另跑 --rtlive 两态比对,实证「不按任何键,新数据照样刷屏」(task-2 框架自动失效链: +开页拍 A → 仿真发布一帧斜坡帧并直调 ui_content_page_auto_poll → 拍 B,A≠B 才 PASS; +黄金参考本身仍建立在冻结帧上,与本检查互不影响)。 + 黄金参考更新流程(有意改视觉时):先肉眼看图确认新效果正确,再 --update 刷新, 刷新即改基准 → `git diff --stat simulator/tests/ref` 必须逐页过目并在提交正文说明; 最后 regen_ref_png.py 重生成目检 PNG;误报排查见 docs/UI设计/UI_自动化测试流程.md。 @@ -1220,13 +1224,36 @@ def main(): else: print("[rt:%s] PASS 开页-关页往返池水位 %d B" % (rt_name, used)) + # --rtlive 两态比对(task-2):开交流量页拍 A,发布 live 斜坡帧 + 框架判定体再拍 B。 + # A≠B = 「数据在变、页面没被按键过,屏上内容照样更新」成立;黄金参考不受影响。 + rtA = os.path.join(tmp, "rtlive_a.bin") + rtB = os.path.join(tmp, "rtlive_b.bin") + proc = subprocess.run([str(EXE), "--rtlive", "dee", rtA, rtB], + cwd=tmp, capture_output=True, text=True) + if proc.returncode != 0: + ok = False + print("[rtlive] FAIL 退出码 %d stderr=%s" % (proc.returncode, proc.stderr.strip())) + elif "rtlive: frames written" not in proc.stdout: + ok = False + print("[rtlive] FAIL 输出缺标记行:\n%s" % proc.stdout) + else: + a = Path(rtA).read_bytes() + b = Path(rtB).read_bytes() + if len(a) != len(b) or a == b: + ok = False + print("[rtlive] FAIL A/B 异常(lenA=%d lenB=%d equal=%s)——自动失效链断了或斜坡帧没动显示值" + % (len(a), len(b), a == b)) + else: + diff = sum(1 for x, y in zip(a, b) if x != y) + print("[rtlive] PASS 未按键实时数据落屏(A≠B,字节差异 %d)" % diff) + if update: print("PASS: 黄金参考已刷新(%d 页)" % len(PAGES)) shutil.rmtree(tmp, ignore_errors=True) return 0 if ok: - print("PASS: %d 内容页 + 往返池水位全部通过" % len(PAGES)) + print("PASS: %d 内容页 + 往返池水位 + rtlive 全部通过" % len(PAGES)) shutil.rmtree(tmp, ignore_errors=True) return 0 # 失败时保留临时截图便于排查 From a98d4e06e8fa47dcd15ba5d1364651d1769e4b1b Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Fri, 13 Nov 2026 14:51:49 +0800 Subject: [PATCH 3/3] =?UTF-8?q?UI=20=E5=85=B6=E4=BD=99=E4=BA=94=E4=B8=AA?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E9=A1=B5=E6=8E=A5=E5=85=A5=E6=A1=86=E6=9E=B6?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=A4=B1=E6=95=88=EF=BC=88=E6=8C=89=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=8F=98=E5=8C=96=E8=8A=82=E6=8B=8D=E5=88=86=E6=A1=A3?= =?UTF-8?q?=E5=8F=96=E5=80=BC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 描述符各加一行(机制 task-2 已建,本轮零框架代码改动):dc 300ms(电气量与 ac 同账);yx 500ms(遥信变位按 1s 内上屏到位,比 300 档少 40% 稳态重绘); clock 1000ms(快变项只有墙钟秒尾);device_info / runreport 1000ms 并把新鲜度 源换到通道二 .auto_seq = ui_data_info_seq(info 与 rt 同拍 200ms 发布) ——各页取值账目在该页描述符注释 - docs/UI设计:供给说明 §5.5 改六页全接终态(用户裁定 2026-11-13);内容页架构 字段表/内存表/变更记录同步;自动化测试流程补 rtlive 覆盖行;页面扩充方案 追加 74 张现势注(带日期史实行冻结不动) - .dsh/skills/lvgl-simulator:开关表加 --rtlive 行、黄金参考计数 66→74 同步 验证:模拟器 0错0警;菜单 8 状态 + 内容页 74 张 + rtlive PASS(lead 独立复跑 RC=0,池水位 5368/5448B 均不漂移——timer 开页创建关页销毁、分配归还); Keil 0错0警且 Code/RO/RW/ZI 与 task-2 基线逐字节一致(只改常量值);BOM 全补回。 --- .dsh/skills/lvgl-simulator/SKILL.md | 5 +++-- Drivers/BSP/UI/ui_page_clock.c | 7 +++++++ Drivers/BSP/UI/ui_page_dc.c | 5 +++++ Drivers/BSP/UI/ui_page_device_info.c | 9 +++++++++ Drivers/BSP/UI/ui_page_runreport.c | 7 +++++++ Drivers/BSP/UI/ui_page_yx.c | 6 ++++++ docs/UI设计/UI_数据供给设计说明.md | 3 +-- docs/UI设计/UI_自动化测试流程.md | 2 +- docs/UI设计/内容页架构.md | 9 ++++++--- docs/UI设计/页面扩充详细方案.md | 2 +- scripts/size_attestation.json | 8 ++++---- 11 files changed, 50 insertions(+), 13 deletions(-) diff --git a/.dsh/skills/lvgl-simulator/SKILL.md b/.dsh/skills/lvgl-simulator/SKILL.md index c273151a..760fa5b1 100644 --- a/.dsh/skills/lvgl-simulator/SKILL.md +++ b/.dsh/skills/lvgl-simulator/SKILL.md @@ -1,6 +1,6 @@ --- name: lvgl-simulator -description: LVGL PC 模拟器构建/运行/自检入口:build_sim.bat 自动探测本机 VS、lvgl_sim.exe 无头截图与自检开关(--screenshot/--keys/--standby/--cfgtest/--logtest/--idle)、三套回归脚本、两份 lv_conf.h 同步门禁、构建报错速查(SDL2/lv_conf 找不到、全黑、键盘无响应)。 +description: LVGL PC 模拟器构建/运行/自检入口:build_sim.bat 自动探测本机 VS、lvgl_sim.exe 无头截图与自检开关(--screenshot/--keys/--standby/--cfgtest/--logtest/--idle/--rtlive)、三套回归脚本、两份 lv_conf.h 同步门禁、构建报错速查(SDL2/lv_conf 找不到、全黑、键盘无响应)。 whenToUse: 需要构建或运行模拟器、无头截图出图、跑模拟器自检或 UI 回归、修改 simulator/ 目录代码或 simulator/lv_conf.h、排查模拟器构建/运行异常时。UI 改动后的完整回归走 ui-verify skill。 --- @@ -28,12 +28,13 @@ PC 上编译运行与 MCU **同一份** UI 源码(CMake + MSVC + SDL2,x64, | `lvgl_sim.exe --standby shot.bin` | 待机页截图 | | `lvgl_sim.exe --cfgtest` | 配置引擎闭环:加载→轮询→外部改文件重载→外科式保存回验。临时用当前 CWD 的 `sim_config.ini`,**跑完无论成败一律删除**(旧行为连 PASS 路径也会留一份 4000 B 伪造档 → 之后每次运行都被加载 → 引擎按设计拒写超长文档 → 网口设置页保存行变 `ERR`,把像素回归带偏) | | `lvgl_sim.exe --logtest` | syslog 后端闭环(定长记录/时间戳/轮转) | +| `lvgl_sim.exe --rtlive "dee" a.bin b.bin` | 框架自动失效实证:开交流量页截 A → 发布 1 帧确定性斜坡值 → 直接调 `ui_content_page_auto_poll()` → 截 B;退出码 0 且 A≠B = PASS(零按键新帧落屏;无头黄金参考从不走此路径——无头不跑 `lv_timer_handler`) | ⚠ **模拟器按当前工作目录读写 `sim_config.ini`**:目录里残留一份配置就会改变页面内容。`simulator/tests/` 两套回归因此一律在**空临时目录**里跑(`cwd=tmp`);手工跑 `--keys`/`--screenshot` 做对比时,也要先确认当前目录没有 `sim_config.ini`,否则"复现不了别人的 FAIL"。 ## UI 改动回归 → 用 `ui-verify` skill(勿在此零散手跑) -5 步门禁 = 构建 → `check_menu_rows.py`(菜单 8 状态像素 + 池基线)→ `check_content_pages.py`(文本断言 + 66 张黄金参考 + 往返池)→ `/keil-build` → `coding-standard`。 +5 步门禁 = 构建 → `check_menu_rows.py`(菜单 8 状态像素 + 池基线)→ `check_content_pages.py`(文本断言 + 74 张黄金参考 + 往返池 + rtlive)→ `/keil-build` → `coding-standard`。 RemoDispBus 推流回归单独跑:`.venv\Scripts\python.exe simulator/tests/check_remo_sink.py`。 黄金参考 `tests/ref/*.bin` **随仓库入库**(`.gitignore` 在全仓 *.bin 兜底之后又显式反选,2026-10-22 起;缺基准 = 新克隆跑 `ui-golden` 必 FAIL):不入库的只是派生目检图 `ref/*.png`,换机后先 `check_content_pages.py --update` 生成,再 `regen_ref_png.py` 出目检 PNG。 diff --git a/Drivers/BSP/UI/ui_page_clock.c b/Drivers/BSP/UI/ui_page_clock.c index 80a43af4..c02ed998 100644 --- a/Drivers/BSP/UI/ui_page_clock.c +++ b/Drivers/BSP/UI/ui_page_clock.c @@ -385,4 +385,11 @@ const content_page_def_t ui_page_clock_desc = { .refresh = pg_refresh, .draw_cb = pg_draw, .key_cb = pg_key, + /* 自动失效轮询(机制见 ui_content_page.h 的 auto_ms 注;口径见供给说明 §5.5): + * 本页快变项只有墙钟秒尾——rt 帧 200ms 一发,但秒每 1s 才变,轮询快于 1s + * 只会把同一秒多画几遍(seq 随帧走,每拍都触发整页重绘)。取 1000ms: + * 1000 = 5 × 发布节拍 = 50 × LVGL 整刷周期 20ms;最坏屏上滞后 200 + 1000 = + * 1200ms,屏显是墙钟字符串(非自走计数),迟到的是展示不是时刻,不累积漂移。 + * 编辑态无碍:失效只按当前叠层状态重绘,不动编辑数据 */ + .auto_ms = 1000u, }; diff --git a/Drivers/BSP/UI/ui_page_dc.c b/Drivers/BSP/UI/ui_page_dc.c index 834fac41..e3b2c22e 100644 --- a/Drivers/BSP/UI/ui_page_dc.c +++ b/Drivers/BSP/UI/ui_page_dc.c @@ -149,4 +149,9 @@ const content_page_def_t ui_page_dc_desc = { .rows = NULL, .init_cb = dc_init, .refresh = dc_refresh, + /* 自动失效轮询(框架级机制见 ui_content_page.h 的 auto_ms 注;口径见供给说明 §5.5): + * 直流/站温/温度行随 rt 帧每 200ms 推进,可见内容与电气量同拍——周期账目与 + * ac 页同构(300 = 15 × LVGL 整刷周期 20ms,最坏屏上滞后 200 + 300 = 500ms), + * 四点账全文在 ui_page_ac.c 的 AC_AUTO_MS 注释,此处不复述 */ + .auto_ms = 300u, }; diff --git a/Drivers/BSP/UI/ui_page_device_info.c b/Drivers/BSP/UI/ui_page_device_info.c index 7bcbc755..b9cb0a2f 100644 --- a/Drivers/BSP/UI/ui_page_device_info.c +++ b/Drivers/BSP/UI/ui_page_device_info.c @@ -259,4 +259,13 @@ const content_page_def_t ui_page_device_info_desc = { .fixed_total_pages = 1u, .init_cb = di_init, .refresh = di_refresh, + /* 自动失效轮询——通道二(新鲜度源换 info_seq;机制见 ui_content_page.h 的 + * auto_ms/auto_seq 注,口径见供给说明 §5.5):info 与 rt 同拍发布(每 200ms + * 两帧连发,见 App/app/ui_data_feed.c 的 ui_data_feed_tick;App 侧节拍值, + * 本文件不 include App 头,口径引用文档);本页可见内容近静态(型号/版本/ + * 身份),只有 IP、端口等网络事件级变化——取 1000ms 大档:事件上屏最坏 + * 200 + 1000 = 1200ms,对配置生效核对足够;seq 恒随帧走,周期即稳态重绘频率, + * 近静态页没必要快 */ + .auto_ms = 1000u, + .auto_seq = ui_data_info_seq, }; diff --git a/Drivers/BSP/UI/ui_page_runreport.c b/Drivers/BSP/UI/ui_page_runreport.c index f46a8aea..bfb33270 100644 --- a/Drivers/BSP/UI/ui_page_runreport.c +++ b/Drivers/BSP/UI/ui_page_runreport.c @@ -276,4 +276,11 @@ const content_page_def_t ui_page_runreport_desc = { .count_cb = rr_count, .init_cb = rr_init, .refresh = rr_refresh, + /* 自动失效轮询——通道二(.auto_seq = ui_data_info_seq;机制见 ui_content_page.h + * 的 auto_ms/auto_seq 注,口径见供给说明 §5.5):本页是上电计数、复位原因、 + * 104 观测计数与记录条数的汇总,活跃链路(104 轮询、记录追加)下计数秒级 + * 走动,1000ms 档既让读数"看起来在活",又不在稳态多刷(seq 恒随 info 帧 + * 前进,周期即重绘频率;info 与 rt 同拍 200ms,见供给说明 §5.2) */ + .auto_ms = 1000u, + .auto_seq = ui_data_info_seq, }; diff --git a/Drivers/BSP/UI/ui_page_yx.c b/Drivers/BSP/UI/ui_page_yx.c index 9a09312d..d08e0463 100644 --- a/Drivers/BSP/UI/ui_page_yx.c +++ b/Drivers/BSP/UI/ui_page_yx.c @@ -202,4 +202,10 @@ const content_page_def_t ui_page_yx_desc = { .init_cb = pg_init, .count_cb = pg_count, .refresh = pg_refresh, + /* 自动失效轮询(机制见 ui_content_page.h 的 auto_ms 注;口径见供给说明 §5.5): + * 遥信变位是低频事件,屏上可见变化只在 rt 帧(200ms 拍)落地时发生;seq 恒 + * 随帧前进,所以轮询周期就是稳态整页重绘频率——取 500ms 的账:变位"1s 内 + * 上屏"够用(最坏 200 + 500 = 700ms 到位),比电气量档 300ms 少 40% 无内容 + * 变化的重绘;500 = 25 × LVGL 整刷周期 20ms,落整刷边界 */ + .auto_ms = 500u, }; diff --git a/docs/UI设计/UI_数据供给设计说明.md b/docs/UI设计/UI_数据供给设计说明.md index 247b44f4..99f48696 100644 --- a/docs/UI设计/UI_数据供给设计说明.md +++ b/docs/UI设计/UI_数据供给设计说明.md @@ -355,9 +355,8 @@ static void pg_refresh(uint16_t row, char *name, char *value) { 当前口径: -- **已启用**:`ac` 交流量页 `.auto_ms = AC_AUTO_MS`(300 ms;250~500 ms 允许区间的取值账目详述在 `ui_page_ac.c` 的 `AC_AUTO_MS` 注释——要点:轮询不得快于 200 ms 发布节拍、避开 400 ms 与其锁相、300 = 15 × LVGL 整刷周期 20 ms(两端 `LV_DEF_REFR_PERIOD` 同值),最坏屏上滞后 200 + 300 = 500 ms)。 +- **六个实时页全部接入(用户裁定 2026-11-13)**。seq 恒随帧前进,所以轮询周期 = 稳态整页重绘频率——可见内容变化越慢取档越大:`ac`/`dc` 300 ms(电气量随 rt 帧帧帧推进;取值账目:轮询不得快于 200 ms 发布节拍、避开 400 ms 与其锁相、300 = 15 × LVGL 整刷周期 20 ms(两端 `LV_DEF_REFR_PERIOD` 同值),最坏屏上滞后 200 + 300 = 500 ms;全文在 `ui_page_ac.c` 的 `AC_AUTO_MS` 注释);`yx` 500 ms(变位低频事件,按"1 s 内上屏"到位——最坏 700 ms,较 300 档少 40% 稳态重绘);`clock` 1000 ms(快变项只有墙钟秒尾,快于 1 s 的轮询只会把同一秒多画几遍);`device_info`/`runreport` 1000 ms 并显式 `.auto_seq = ui_data_info_seq`(通道二;`info` 与 `rt` 同拍——`ui_data_feed_tick` 每 200 ms 拍两帧连发,见 §5.2)。各页取值账目在该页描述符注释里,本文不复述。 - **不迁移**:待机屏 `ui_standby.c`(自持 1 s 整屏定时器 + seq 门控)、整定视图 `ui_w_setview.c`(`sv_poll` 定时器盯 `par` 通道)——两处各自已有节拍,纳入框架只会多一只定时器。 -- **未接入(待 lead/用户裁定)**:`dc`/`yx`/`clock`(读 `rt`)与 `device_info`/`runreport`(读 `info`)仍是"按键才取新数";接入只差描述符一行 `.auto_ms`(info 页另加 `.auto_seq`)。 **回归确定性**:无头模式(`--keys`/74 张黄金参考)从不跑 `lv_timer_handler`,定时器不可能在截屏前触发,且仿真仍是"一次发布冻结帧"——黄金参考逐字节不变(`check_content_pages.py` 实证)。自动失效路径的验证走新开关 `--rtlive <键序> `:开页截 A → 发布第 1 号确定性斜坡帧 → **直接调用** `ui_content_page_auto_poll()`(等价定时器到点)→ 截 B;A≠B 即证明"零按键、新帧落屏"。窗口模式(真机行为仿真)每 200 ms 发一帧斜坡值,屏上数值自己会走。 --- diff --git a/docs/UI设计/UI_自动化测试流程.md b/docs/UI设计/UI_自动化测试流程.md index 5aa393b0..4aa4e568 100644 --- a/docs/UI设计/UI_自动化测试流程.md +++ b/docs/UI设计/UI_自动化测试流程.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` | ① 文本级断言(字形模板匹配,正向 484 条 + 反向 45 条,独立于 `ref/`)→ ② 74 张截图与 `ref/` 黄金参考**逐字节**比对 + 开页-关页往返池水位 | `PASS: 74 内容页 + 往返池水位全部通过`(文本断言先于像素比对,失败即中止) | +| 2 | `simulator/tests/check_content_pages.py` | ① 文本级断言(字形模板匹配,正向 484 条 + 反向 45 条,独立于 `ref/`)→ ② 74 张截图与 `ref/` 黄金参考**逐字节**比对 + 开页-关页往返池水位 → ③ `--rtlive` 框架自动失效实证(开交流量页截 A → 发布 1 帧确定性斜坡值 → 直调 `ui_content_page_auto_poll()` → 截 B,A≠B 即 PASS;机制见 `UI_数据供给设计说明.md` §5.5) | `PASS: 74 内容页 + 往返池水位 + rtlive 全部通过`(文本断言先于像素比对,失败即中止) | | 3 | `keil-build` skill(`keil_build.py`) | MCU 端编译(UI 源码双端共用,改一端必须过另一端) | 退出码 0 = BUILD_OK | | 4 | `scripts/check_coding_standard.py` | 代码规范 V1.0 机械检查 | PASS | diff --git a/docs/UI设计/内容页架构.md b/docs/UI设计/内容页架构.md index 9a1fe74b..06322b95 100644 --- a/docs/UI设计/内容页架构.md +++ b/docs/UI设计/内容页架构.md @@ -280,7 +280,9 @@ typedef struct { | `rows` | `const cp_row_t *` | 静态数据表(Flash) | 6 行 | 24 行 | 3 行 | NULL | | `fixed_cur_page` / `fixed_total_pages` | `uint8_t` | 信息页页脚固定值 | 10 / 1 | — | — | — | | `refresh` / `count_cb` / `draw_cb` / `key_cb` | 函数指针 | 扩展钩子 | 全 NULL | 全 NULL | 全 NULL | draw_cb + key_cb(余 NULL) | -| `auto_ms` / `auto_seq` | `uint16_t` / 函数指针 | 自动失效轮询周期 / 新鲜度源(`UI_数据供给设计说明.md` §5.5) | 0 / NULL | **300 / NULL** | 0 / NULL | 0 / NULL | +| `auto_ms` / `auto_seq` | `uint16_t` / 函数指针 | 自动失效轮询周期 / 新鲜度源(`UI_数据供给设计说明.md` §5.5) | 1000 / `ui_data_info_seq` | 300 / NULL | 300 / NULL | 0 / NULL | + +> 上表仅列这四页;未列出的实时页也已接入:`yx` 500 ms、`clock` 1000 ms、`runreport` 1000 ms(`ui_data_info_seq`)。六个实时页的取值账与"不迁移"清单见 `UI_数据供给设计说明.md` §5.5。 扩展钩子(机制已实现;密码页用 `draw_cb` + `key_cb`,其余 3 页未用): @@ -433,8 +435,8 @@ extern const content_page_def_t ui_page_device_info_desc; /* 描述符(Flash | 项 | 值 | 说明 | |---|---|---| -| 框架常驻 RAM | ~20 B | `s_open/s_sel/s_scr/s_cont` 4 变量 | -| 开页峰值 | +776 B | 屏 + 容器 + 事件列表 + 常量样式引用 | +| 框架常驻 RAM | ~28 B | `s_open/s_sel/s_perm/s_scr/s_cont/s_auto_timer/s_auto_seq` 7 变量(后 2 个为自动失效) | +| 开页峰值 | +776 B | 屏 + 容器 + 事件列表 + 常量样式引用;启用自动失效的页另占一只 lv_timer 对象(数十字节,关页归还池,2026-11-13 起) | | 关页回落 | 基线 +80 B 一次性残留 | screens 数组高水位 +8、`lv_screen_load` 菜单屏局部 X/Y 样式 +72(LVGL 核心行为,非泄漏;分解见 `菜单页面设计.md` §9.5) | | 池水位门禁 | 5368 B / 5448 B | 菜单基线 / 开页-关页往返(回归脚本常量,2026-10-11 随 LVGL 控件裁剪由 4784/4864 上移;**与池总大小无关**,16 KB 池下实测值不变) | | 描述符/行表/标题 | Flash const | 不进 LVGL 池;每页增量 = 描述符 ~40 B + 行表 + 代码 | @@ -502,5 +504,6 @@ extern const content_page_def_t ui_page_device_info_desc; /* 描述符(Flash | 2026-10-27 | 内容页黄金参考 **47 → 53 页**(通讯设置页落地并绑 `#page=comm`;判据 = `simulator/tests/ref/` 入库 `shot_*.bin` 实数 53 张,`git diff --name-status 31ca4a1f HEAD -- simulator/tests/ref` 实测只有 6 个 `A`、零 `M` ⇒ 存量 47 张逐字节零漂移);本文件的当前态计数已同步 | | 2026-10-26 | 验证与回归段的页数口径修正为 **27 页**黄金参考(`simulator/tests/ref/` 入库 27 个 `shot_*.bin`,含整定 9 态);上方变更记录里的「11 页」是当时事实不动 | | 2026-10-26 | 门禁常量修正为 5368 B / 5448 B(= 两个回归脚本头部现值);本文件正文表早已是 5368/5448,只这一处残留 2026-10-11 裁剪 LVGL 控件类之前的 4784/4864 | +| 2026-11-13 | 五个实时页接入框架自动失效(task-3,用户裁定全接):`dc` 300 / `yx` 500 / `clock` 1000(rt 通道)与 `device_info` / `runreport` 1000 + `.auto_seq = ui_data_info_seq`(info 通道,`info` 与 `rt` 同拍 200 ms)描述符加 `.auto_ms`,取值账在各页注释;§三 字段表与 §六 内存表同步;实测黄金参考 74 张、往返水位 5448 B、菜单基线 5368 B 均不动(无头从不跑 `lv_timer_handler`) | | 2026-10-26 | 验证与回归段的黄金参考页数 **27 → 40 页**(UI 三页落地后 `simulator/tests/ref/` 实数 40 张);上方变更记录里的 11 / 27 是各当时下限,保留 | | 2026-11-13 | 框架级自动失效(task-2):描述符尾部新增 `auto_ms`/`auto_seq`(当前仅 ac 页启用 300 ms),新增框架 API `ui_content_page_auto_poll()`(§3.1);§3.1 静态块与代码同步(补 `s_perm` 与自动失效 2 变量);回归脚本新增 `--rtlive` 零按键落屏检查(74 张黄金参考逐字节不变,实测 PASS);机制详述在 `UI_数据供给设计说明.md` §5.5 | diff --git a/docs/UI设计/页面扩充详细方案.md b/docs/UI设计/页面扩充详细方案.md index 627b0a63..10c832e6 100644 --- a/docs/UI设计/页面扩充详细方案.md +++ b/docs/UI设计/页面扩充详细方案.md @@ -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 页连跑两次逐字节一致——**该下限此后已扩至 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;三次之间未用更新模式写过任何参考图,故这是同口径的传递性证据)** | +| 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;三次之间未用更新模式写过任何参考图,故这是同口径的传递性证据)**。集合其后扩至 74 张——2026-11-13 按同口径跑过 74 张零差异(task-3 五页接自动刷新,无头不跑 `lv_timer_handler`,往返水位 5,448 B / 菜单 5,368 B 均不动) | | 8 | 每个新页有 golden 参考 | `simulator/tests/ref/` 新增对应 `.bin`。**整定 4 页已有 9 张**(含保存/越界/落定/回绕各态);参考图入库,改它 = 改判据,须逐页过目 | --- diff --git a/scripts/size_attestation.json b/scripts/size_attestation.json index 51d7b7c7..4699d8a9 100644 --- a/scripts/size_attestation.json +++ b/scripts/size_attestation.json @@ -1,15 +1,15 @@ { - "built_at": "2026-11-13T14:23:05+08:00", - "fingerprint": "f44730aca08c9f35b9eec8a83322b342ee21a2280a94ce2c277b5c62a26fb442", + "built_at": "2026-11-13T14:51:49+08:00", + "fingerprint": "7bca71501bd2582cd221f9cfea5d4db5d196d3d40a583ff119dfd26841355379", "fingerprint_files": 1479, "fingerprint_source": "git-index", "gated_regions": [ "RW_IRAM1", "RW_CCM" ], - "git_head": "5bab248deeac14b0d6235e0fe312bcd9388a5e0a", + "git_head": "a5142c6d9d11b751d5ff1390bd815f5202bcdabb", "map": "MDK-ARM/STM32F407-Demo/STM32F407-Demo.map", - "map_mtime": 1794549805, + "map_mtime": 1794551787, "pct": { "ER_IROM1": 27.53, "RW_CCM": 87.89,