Files
STM32F4-Base/Drivers/BSP/UI/ui_standby.c
T
wandering1 099a50a49e chore(ui/sim): 修 cppcheck 两项风格提示(未用变量 / 可缩作用域)
来源:合并后主树全量 ci_local 复跑,manual 阶段 cppcheck(提交期
不跑)抓出两处——sim_publish_data_frames 遗留未用变量 i;待机屏
sb_value_str 变量 n 作用域可缩进挂单位 if 块内。
验证:cppcheck 定向两文件 Passed;双端构建同码同数
(Code=256588 RO=32036 RW=2088 ZI=152584);菜单 8 态与内容页
74 张(含 rtlive A≠B 30 字节差)全过。
2026-11-13 16:03:01 +08:00

450 lines
16 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 Standby (待机显示页)
* 模块功能:长时间无按键操作时自动切入的默认界面(UA/UB/UC/IA/IB/IC 六路实时值 + 分隔线 + 底部时钟);
* 数值经 ui_data 通道一实时帧 yc[](App 层装配,本文件零 App include),口径同 ui_page_ac.c;
* 屏幕覆盖层模式——进入保存前屏与焦点、ENTER(确认键)还原,其余键忽略;
* 空闲检测 = lv_timer(1s 周期),活动通知由菜单/内容页键事件挂钩
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期: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(引脚档不挂)
*/
/* 头文件包含区 */
#include <string.h>
#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"
/* 私有宏定义区 */
/*
* 布局常量(设备像素;参考 mockups/待机显示页面.jpg):
* 屏幕宽高取令牌 UI_SCREEN_W/H;六路实时值行自 y=28 起、行距 18
* (末行 y=118..130);分隔线 y=132;底部时钟 y=140 居中
*/
#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(UA/UB/UC/IA/IB/IC) */
#define SB_EQ_X 72 /* "=" 号 x */
#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(居中) */
/* 静态变量定义区 */
/* 通道名表 + 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; /* 全屏容器(焦点/键入口 + 自绘载体,无子对象) */
static uint32_t s_timeout_ms; /* 空闲超时阈值 */
static uint32_t s_last_ms; /* 最近一次用户活动时刻(lv_tick ms) */
static bool s_active; /* 是否处于待机屏 */
static lv_obj_t *s_prev_scr; /* 进入待机前的活动屏(ENTER 还原用) */
static lv_obj_t *s_prev_focus; /* 进入待机前组内焦点对象 */
static ui_standby_time_cb s_time_cb;
/* flat 样式(透明、无边框/圆角/内边距,剥离 mono 主题 card)取自共享样式表
* ui_style.c:与 ui_content_page / ui_menu_view 共用同一 const 对象,不再本地重复定义 */
/* 函数定义区 */
/*
* 函数功能:判断两区域是否相交(等价 lv_area_intersect 非空判断;避免依赖 LVGL 私有头)
* 入口参数:a - 区域 1 const lv_area_t *
* b - 区域 2 const lv_area_t *
* 返回值:true - 相交;false - 不相交
*/
static bool sb_areas_intersect(const lv_area_t *a, const lv_area_t *b) {
int32_t x1 = (a->x1 > b->x1) ? a->x1 : b->x1;
int32_t y1 = (a->y1 > b->y1) ? a->y1 : b->y1;
int32_t x2 = (a->x2 < b->x2) ? a->x2 : b->x2;
int32_t y2 = (a->y2 < b->y2) ? a->y2 : b->y2;
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;
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) {
uint32_t 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 *
* text - 文本(栈缓冲,同帧有效) const char *
* y - 放置 y int32_t
*/
static void sb_draw_centered_text(lv_layer_t *layer, const char *text, int32_t y) {
lv_area_t area;
lv_draw_label_dsc_t ld;
area.x1 = 0;
area.x2 = UI_SCREEN_W - 1;
area.y1 = y;
area.y2 = y + UI_TEXT_BAND_H; /* 覆盖字体行高(13)+ 余量 */
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = text;
ld.color = UI_COL_FG;
ld.ofs_x = (UI_SCREEN_W - lv_text_get_width(text, (uint32_t)strlen(text), LV_FONT_DEFAULT, 0)) / 2;
ld.ofs_y = 0;
lv_draw_label(layer, &ld, &area);
}
/*
* 函数功能:待机屏自绘(六路实时值 + 分隔线 + 底部时钟;逐行 culling)
* 入口参数:e - 事件指针 lv_event_t *
* 返回值:无
* 限定条件:事件为 LV_EVENT_DRAW_MAIN,待机模块已初始化
* 函数说明: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);
for (uint32_t i = 0u; i < SB_CH_COUNT; i++) {
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;
strip.y1 = row_y;
strip.y2 = (int32_t)(row_y + SB_PITCH - 1u);
if (!sb_areas_intersect(&strip, &layer->_clip_area)) {
continue; /* 行不在重绘区:跳过(culling) */
}
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = s_ch_name[i];
ld.text_static = 1;
ld.color = UI_COL_FG;
ld.ofs_x = SB_LABEL_X;
ld.ofs_y = 0;
lv_draw_label(layer, &ld, &strip);
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = "=";
ld.text_static = 1;
ld.color = UI_COL_FG;
ld.ofs_x = SB_EQ_X;
ld.ofs_y = 0;
lv_draw_label(layer, &ld, &strip);
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
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;
lv_draw_label(layer, &ld, &strip);
}
/* 分隔线(高 1px) */
{
lv_area_t line;
lv_draw_rect_dsc_t fd;
line.x1 = 8;
line.x2 = UI_SCREEN_W - 9;
line.y1 = SB_LINE_Y;
line.y2 = SB_LINE_Y;
if (sb_areas_intersect(&line, &layer->_clip_area)) {
lv_draw_rect_dsc_init(&fd);
fd.base.layer = layer;
fd.bg_color = UI_COL_FG;
fd.bg_opa = LV_OPA_COVER;
lv_draw_rect(layer, &fd, &line);
}
}
/* 底部时钟(每秒重绘;time_cb 平台提供) */
{
lv_area_t t;
t.x1 = 0;
t.x2 = UI_SCREEN_W - 1;
t.y1 = SB_TIME_Y;
t.y2 = (int32_t)(SB_TIME_Y + UI_TEXT_BAND_H);
if (sb_areas_intersect(&t, &layer->_clip_area)) {
char ts[24];
s_time_cb(ts, sizeof(ts));
sb_draw_centered_text(layer, ts, SB_TIME_Y);
}
}
}
/*
* 函数功能:进入待机屏(保存前屏与焦点 → 聚焦容器并切屏)
* 入口参数:无
* 返回值:无
* 限定条件:UI 任务上下文;未处于待机
*/
static void sb_enter(void) {
lv_group_t *g;
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;
if (g != NULL) {
lv_group_add_obj(g, s_cont); /* 幂等:已在组内则忽略 */
lv_group_focus_obj(s_cont); /* 先获焦再切屏,按键立即由本模块接管 */
}
lv_screen_load(s_scr);
s_active = true;
DBG_INFO("standby: enter");
}
/*
* 函数功能:退出待机屏(还原前屏与焦点;重置空闲计时)
* 入口参数:无
* 返回值:无
* 限定条件:UI 任务上下文;处于待机
*/
static void sb_exit(void) {
if (!s_active) {
return;
}
s_active = false;
s_last_ms = lv_tick_get();
if (s_prev_focus != NULL && lv_obj_is_valid(s_prev_focus)) {
lv_group_focus_obj(s_prev_focus);
}
else {
/* 前焦点已失效(如内容页被销毁):回落菜单容器 */
lv_obj_t *mc = ui_menu_view_container();
if (mc != NULL) {
lv_group_focus_obj(mc);
}
}
if (s_prev_scr != NULL && lv_obj_is_valid(s_prev_scr)) {
lv_screen_load(s_prev_scr);
}
DBG_INFO("standby: exit");
}
/*
* 函数功能:待机屏按键事件(仅 ENTER 退出;其余键忽略但重置空闲计时)
* 入口参数:e - 事件指针 lv_event_t *
* 返回值:无
* 限定条件:容器已获焦
*/
static void sb_key_cb(lv_event_t *e) {
lv_key_t key = (lv_key_t)lv_event_get_key(e);
ui_standby_notify_activity();
if (key == LV_KEY_ENTER) {
sb_exit(); /* 确认键:返回进入前的界面(菜单/内容页原样还原) */
}
}
/*
* 函数功能:空闲检测定时器回调(1s 周期;待机中同时驱动时钟每秒重绘)
* 入口参数:t - 定时器句柄 lv_timer_t *
* 返回值:无
*/
static void sb_idle_timer_cb(lv_timer_t *t) {
(void)t;
if (!s_active && s_timeout_ms > 0u && lv_tick_elaps(s_last_ms) >= s_timeout_ms) {
sb_enter();
}
else if (s_active) {
lv_obj_invalidate(s_cont); /* 底部时钟每秒刷新 */
}
}
/*
* 函数功能:初始化待机模块(建常驻屏/容器 + 空闲检测定时器)
* 入口参数:idle_timeout_ms - 无操作超时阈值(ms;0 = 拒绝初始化) uint32_t
* time_cb - 底部时钟字符串提供回调(不可为 NULL) ui_standby_time_cb
* 返回值:0 - 成功;-1 - 失败(已初始化/参数无效/LVGL 池耗尽) int
*/
int ui_standby_init(uint32_t idle_timeout_ms, ui_standby_time_cb time_cb) {
if (s_scr != NULL || idle_timeout_ms == 0u || time_cb == NULL) {
return -1;
}
s_timeout_ms = idle_timeout_ms;
s_time_cb = time_cb;
s_scr = lv_obj_create(NULL); /* parent=NULL → 新建屏幕(常驻,不随切屏销毁) */
if (s_scr == NULL) {
return -1; /* LVGL 池耗尽 */
}
s_cont = lv_obj_create(s_scr);
if (s_cont == NULL) {
lv_obj_delete(s_scr);
s_scr = NULL;
return -1;
}
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_IGNORE_LAYOUT);
lv_obj_set_size(s_cont, UI_SCREEN_W, UI_SCREEN_H);
lv_obj_add_style(s_cont, &ui_style_flat, 0);
lv_obj_add_flag(s_cont, LV_OBJ_FLAG_CLICK_FOCUSABLE);
lv_obj_add_event_cb(s_cont, sb_key_cb, LV_EVENT_KEY, NULL);
lv_obj_add_event_cb(s_cont, sb_draw_cb, LV_EVENT_DRAW_MAIN, NULL);
s_last_ms = lv_tick_get();
if (lv_timer_create(sb_idle_timer_cb, 1000u, NULL) == NULL) {
lv_obj_delete(s_cont);
lv_obj_delete(s_scr);
s_scr = NULL;
s_cont = NULL;
return -1;
}
DBG_INFO("standby: ready (idle=%ums)", (unsigned)idle_timeout_ms);
return 0;
}
/*
* 函数功能:通知用户活动(重置空闲计时)
* 入口参数:无
* 返回值:无
*/
void ui_standby_notify_activity(void) {
if (s_scr != NULL) {
s_last_ms = lv_tick_get();
}
}
/*
* 函数功能:查询是否处于待机屏
* 入口参数:无
* 返回值:true - 待机中;false - 否
*/
bool ui_standby_is_active(void) {
return s_active;
}
/*
* 函数功能:强制进入待机屏(测试钩子,绕过空闲计时)
* 入口参数:无
* 返回值:无
*/
void ui_standby_force_enter(void) {
sb_enter();
}