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:
@@ -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); /* 再删屏 */
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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.bin> <B.bin>`:开页截 A → 发布第 1 号确定性斜坡帧 → **直接调用** `ui_content_page_auto_poll()`(等价定时器到点)→ 截 B;A≠B 即证明"零按键、新帧落屏"。窗口模式(真机行为仿真)每 200 ms 发一帧斜坡值,屏上数值自己会走。
|
||||
---
|
||||
|
||||
## 6. 写路径(定值整定)
|
||||
|
||||
+14
-3
@@ -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 |
|
||||
|
||||
@@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| 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%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 | 门禁(≤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 | — | 参考 |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
|
||||
+200
-84
@@ -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 <seq> 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) {
|
||||
|
||||
@@ -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
|
||||
# 失败时保留临时截图便于排查
|
||||
|
||||
Reference in New Issue
Block a user