UI 内容页框架加实时页自动刷新机制(交流量页启用,300ms)

- 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 绿。
This commit is contained in:
2026-11-13 14:23:06 +08:00
parent 5bab248dee
commit a5142c6d9d
11 changed files with 384 additions and 105 deletions
+63
View File
@@ -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); /* 再删屏 */
+26
View File
@@ -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 */
+16
View File
@@ -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,
};