Files
STM32F4-Base/Drivers/BSP/UI/ui_page_ac.c
T
wandering1 a5142c6d9d 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 绿。
2026-11-13 14:23:06 +08:00

248 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称: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,
};