a5142c6d9d
- 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 绿。
248 lines
11 KiB
C
248 lines
11 KiB
C
/*
|
||
* 模块名称:UI Page AC (交流量内容页)
|
||
* 模块功能:交流量叶子节点(node 3)内容页——7 行单页动态列表,数值全部取自 ui_data 实时帧
|
||
* (App 层装配,本文件零 App include):Ua/U0/Ia 走基波相量实例、Ia2/Ia5 走谐波
|
||
* 实例、Hz 走测频新鲜度、第 7 行是**量纲口径说明行**(快照 eng → 未标定/二次值/
|
||
* 一次值,不是测量值,是给上面 6 行的诚实性标注);向框架 ui_content_page 提供
|
||
* 页面描述符(零 LVGL 对象)
|
||
* 为什么不是参考图的 24 行:V0.1 只有 8 路电气量且 5 路削顶无采样源、相量实例只有 5 个,
|
||
* 照抄 24 行 = 17 行 "--" 的假骨架(取舍见 docs/UI设计/页面扩充详细方案.md §3.1.1)
|
||
* 空值口径:IV/NT/RAW/削顶/未发布一律 "--",不显 0.000——把"没有数"或"坏数"显成
|
||
* 一个像实测的数是现场误读的头号来源
|
||
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-09
|
||
* 修改记录:
|
||
* 2026-09-09 王建锋 创建:自 ui_content_page 单体拆出(数据原样迁移,零改动)
|
||
* v2.0 2026-10-25 王建锋 道E task-15 回炉:24 行 Flash 字面量 → 7 行快照真值;帧缓存
|
||
* 按发布序号门控;数值格式化复用 ui_w_editor_fmt_scaled(定点
|
||
* 整数路线,页面侧不引 %f 软浮点打印)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
|
||
#include "ui_page_ac.h"
|
||
|
||
#include "ui_data.h"
|
||
#include "ui_tokens.h"
|
||
#include "ui_w_editor.h"
|
||
|
||
/* 私有宏定义区 */
|
||
|
||
/* 相量实例槽号(快照 ph[] 按写侧 App/algo/phasor.c 的 k_inst[] 序发布,见 ui_data.h 的
|
||
* UI_DATA_PH_MAX 注)。本文件不 include App 头(分层方向),故槽号在这里独立登记一份;
|
||
* 真漂移(实例表插入/重排)的表现是"某行显示另一路的数",防线 = test/host/ui_data 的槽数
|
||
* 与语义一致性断言 + 上机对点(待台架实测) */
|
||
#define AC_PH_IA_F1 0u /* 基波 Ia */
|
||
#define AC_PH_UA_F1 1u /* 基波 Ua */
|
||
#define AC_PH_UN_F1 2u /* 基波 U0(零序取 Un 通道,见 UI_数据供给设计说明.md §4.1) */
|
||
#define AC_PH_IA_F2 3u /* 二次谐波 Ia2(涌流制动监视量) */
|
||
#define AC_PH_IA_F5 4u /* 五次谐波 Ia5 */
|
||
|
||
#define AC_ROW_HZ 5u /* 频率行 */
|
||
#define AC_ROW_CAL 6u /* 量纲口径行(非测量值) */
|
||
#define AC_ROW_N 7u
|
||
#define AC_INVALID "--"
|
||
#define AC_IA_DECIMALS 3u /* 电压/电流小数位 */
|
||
#define AC_HZ_DECIMALS 2u
|
||
/* 不可信品质合集:无效 / 未攒满一周波 / 透传码值 / 自检异常 / 闭锁 / 越程 */
|
||
#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) */
|
||
static const char *const s_names[AC_ROW_N] = {
|
||
"Ua", "U0", "Ia", "Ia2", "Ia5", "Hz", "标定",
|
||
};
|
||
|
||
/* 前三行 → 实例槽映射(不把魔法数散在分支里) */
|
||
static const uint8_t s_ph_row[3] = { AC_PH_UA_F1, AC_PH_UN_F1, AC_PH_IA_F1 };
|
||
|
||
/* 帧缓存:发布序号变化才整帧拷一次(UI_数据供给设计说明.md §5.4 的页面侧约定) */
|
||
static ui_data_rt_t s_rt;
|
||
static uint32_t s_seq;
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:把常量值串安全搬进显示缓冲(替代 strcpy,规范 7.5)
|
||
* 入口参数:a_dst - 目标缓冲(框架 UI_BUF_STRLEN) char *
|
||
* a_lit - 源字面量(编译期串,长度由宏用 sizeof 算好) const char *
|
||
* a_len - 需拷贝字节数(不含 NUL) uint32_t
|
||
* 返回值:无
|
||
* 限定条件:a_dst/a_lit 非 NULL;a_len 超缓冲时截断而不是越界
|
||
* 函数说明:代码规范 7.5 禁 strcpy——这里长度来自**调用点的 sizeof**,写串与算长
|
||
* 在同一个宏里,改串不会忘记改长度(原语式拷贝才是安全的来源,不是人的记性)
|
||
*/
|
||
static void ac_put(char *a_dst, const char *a_lit, uint32_t a_len) {
|
||
if (a_len > ((uint32_t)UI_BUF_STRLEN - 1u)) {
|
||
a_len = (uint32_t)UI_BUF_STRLEN - 1u;
|
||
}
|
||
(void)memcpy(a_dst, a_lit, a_len);
|
||
a_dst[a_len] = (char)0;
|
||
}
|
||
|
||
/* 值串落屏统一走这里:长度由 sizeof 在调用点算,改串不会漏改长度 */
|
||
#define AC_PUT(a_dst, a_lit) ac_put((a_dst), (a_lit), (uint32_t)(sizeof(a_lit) - 1u))
|
||
|
||
/*
|
||
* 函数功能:把快照里的工程量浮点转成定点整数后交共享格式化器输出
|
||
* 入口参数:a_val - 数值(有效值,恒非负;负值按坏数钳 0) float
|
||
* a_decimals - 小数位 0..3 uint8_t
|
||
* a_buf - 输出缓冲 char *
|
||
* a_len - 缓冲长度(含 NUL) uint32_t
|
||
* 返回值:无
|
||
* 限定条件:a_buf 非 NULL
|
||
* 函数说明:本仓 UI 侧统一走"定点整数 + 共享格式化"(ui_w_editor_fmt_scaled,与整定页同一
|
||
* 真源,两处显示口径不可能漂移);页面里不写 %f——MicroLIB 的软浮点打印一旦
|
||
* 被拉进镜像就是几百字节 Flash,而模拟器(宿主 CRT)永远照不出这笔代价
|
||
*/
|
||
static void ac_format(const float a_val, uint8_t a_decimals, char *a_buf, uint32_t a_len) {
|
||
int32_t scale = 1;
|
||
uint8_t i;
|
||
|
||
for (i = 0u; (i < a_decimals) && (i < 3u); i++) {
|
||
scale *= 10;
|
||
}
|
||
/* 有效值不可能为负:出现负数说明上游数据坏了,钳 0 交给品质行解释,比在屏上留一个
|
||
* "-0.001"(现场会读成"真的反向了")好 */
|
||
ui_w_editor_fmt_scaled((a_val > 0.0f) ? (int32_t)((a_val * (float)scale) + 0.5f) : 0,
|
||
a_decimals, a_buf, a_len);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取用最新实时帧(只在发布序号变化时整帧拷)
|
||
* 入口参数:无
|
||
* 返回值:1 - 有可用帧;0 - 首帧未到或本帧无效 uint8_t
|
||
* 限定条件:uiTask 绘制上下文
|
||
* 函数说明:读失败(首帧未到)时清缓存并记住本次序号,避免每次重绘重复尝试;下一帧序号
|
||
* 必然变化 → 自动恢复
|
||
*/
|
||
static uint8_t ac_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;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:开页复位缓存(强制下一次绘制重取最新帧,不沿用上次开页的陈旧帧号)
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
*/
|
||
static void ac_init(void) {
|
||
s_seq = 0u;
|
||
(void)memset(&s_rt, 0, sizeof(s_rt));
|
||
}
|
||
|
||
/*
|
||
* 函数功能:逐行取数(前 5 行相量幅值、第 6 行频率、第 7 行量纲口径)
|
||
* 入口参数:a_row - 全局行下标 0..6 uint16_t
|
||
* a_name - 名输出缓冲(框架 UI_BUF_STRLEN) char *
|
||
* a_value - 值输出缓冲 char *
|
||
* 返回值:无
|
||
* 限定条件:uiTask 绘制上下文;缓冲由框架备好且足够
|
||
* 函数说明:"有数但不可信"与"没有数"在屏上必须同形(一律 "--")——削顶或未攒满一周波时
|
||
* 快照里仍留着上一次的数,照抄等于把坏数当实测送进人眼
|
||
*/
|
||
static void ac_refresh(uint16_t a_row, char *a_name, char *a_value) {
|
||
const char *nm = (a_row < AC_ROW_N) ? s_names[a_row] : "";
|
||
uint32_t n;
|
||
|
||
n = (uint32_t)strlen(nm) + 1u;
|
||
if (n > (uint32_t)UI_BUF_STRLEN) {
|
||
n = (uint32_t)UI_BUF_STRLEN;
|
||
}
|
||
(void)memcpy(a_name, nm, n);
|
||
a_value[0] = (char)0;
|
||
|
||
if (ac_frame_ready() == 0u) {
|
||
AC_PUT(a_value, AC_INVALID);
|
||
return;
|
||
}
|
||
|
||
if (a_row < AC_ROW_HZ) {
|
||
/* 前 5 行:相量实例槽(前三行走基波映射,后两行走二次/五次谐波实例) */
|
||
uint8_t slot = (a_row < 3u) ? s_ph_row[a_row] : (uint8_t)a_row;
|
||
const ui_data_yc_t *p;
|
||
|
||
if (slot >= s_rt.ph_n) {
|
||
AC_PUT(a_value, AC_INVALID); /* 本帧没发布这个实例 = 没有数,不是零 */
|
||
return;
|
||
}
|
||
p = &s_rt.ph[slot];
|
||
if ((p->qds & (uint8_t)AC_Q_BAD) != 0u) {
|
||
AC_PUT(a_value, AC_INVALID);
|
||
} else {
|
||
ac_format(p->val, AC_IA_DECIMALS, a_value, (uint32_t)UI_BUF_STRLEN);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (a_row == AC_ROW_HZ) {
|
||
/* 频率新鲜度单独判:eng=0 期间幅值门恒不成立 → 恒 "--",如实反映"现在测不出" */
|
||
if (s_rt.freq_valid == 0u) {
|
||
AC_PUT(a_value, AC_INVALID);
|
||
} else {
|
||
ac_format(s_rt.freq_hz, AC_HZ_DECIMALS, a_value, (uint32_t)UI_BUF_STRLEN);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/* 第 7 行:量纲口径。eng 真值语义 = 0 引脚电压 / 1 二次值 / 2 一次值;标定闭环后自动
|
||
* 变"二次值",页面零改动(写侧口径见 App/app/ui_data_feed.c 的 feed_eng) */
|
||
if (s_rt.eng == (uint8_t)UI_DATA_ENG_SEC) {
|
||
AC_PUT(a_value, "二次值");
|
||
} else if (s_rt.eng == (uint8_t)UI_DATA_ENG_PRI) {
|
||
AC_PUT(a_value, "一次值");
|
||
} else {
|
||
AC_PUT(a_value, "未标定");
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=ac 绑定引用):
|
||
* 7 行单页列表页(8 行/页 → 恒 1 页);band_h 必须 >0——selectable 页选中行是黑字,
|
||
* 无白带即不可见(同 ui_page_runlog 约定)
|
||
*/
|
||
const content_page_def_t ui_page_ac_desc = {
|
||
.id = "ac",
|
||
.title = "交流量",
|
||
.selectable = 1u,
|
||
.top_y = UI_LIST_TOP,
|
||
.pitch = UI_ROW_PITCH,
|
||
.band_h = UI_ROW_BAND_H,
|
||
.text_dy = 0,
|
||
.left_x = UI_LIST_LEFT_X,
|
||
.right_x = UI_LIST_RIGHT_X,
|
||
.rows_per_page = UI_LIST_ROWS,
|
||
.row_count = AC_ROW_N,
|
||
.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,
|
||
};
|