Files
STM32F4-Base/Drivers/BSP/UI/ui_standby.c
T
wandering1 a6cdebde62 文档体系重组与 D-7 一致性门禁落地,清理芯片手册孤儿图片
- D-6 目录规范化:docs 根目录散落文档归入 UI设计/子系统设计/架构设计/审查记录/
  环境与部署/网络 等子目录,去掉目录名空格,重命名 嵌入式C语言代码规范_V1.0.md
- D-7 三档一致性门禁:新增 scripts/check_doc_links.py(链接存在性)、
  scripts/gen_task_table.py(FreeRTOS 任务表数值)、scripts/check_mem_numbers.py
  (内存/Flash 体积),均支持 --check/--write/--diff,pre-commit 钩子同步注册
- D-5 文档索引:新建 docs/README.md,新建 docs/文档规范.md 约束目录命名与链接写法
- D-1 芯片手册瘦身:删除 1069 张无引用孤儿图片(77.06 MB),
  docs/芯片手册 120 MB → 41 MB,已引用图片零断链
- 同步 C 源码注释中的文档路径引用(D-6 重组后),构建 0 错误 0 警告
2026-09-26 00:51:53 +08:00

357 lines
12 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 (待机显示页)
* 模块功能:长时间无按键操作时自动切入的默认界面(6 路电流值 + 分隔线 + 底部时钟);
* 屏幕覆盖层模式——进入保存前屏与焦点、ENTER(确认键)还原,其余键忽略;
* 空闲检测 = lv_timer(1s 周期),活动通知由菜单/内容页键事件挂钩
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本(设计见 docs/子系统设计/运行与自检记录设计说明.md §5.2)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_standby.h"
#include "lvgl.h"
#include "ui_menu_view.h"
#define DBG_TAG "[UI_STANDBY]"
#include "dbg_log.h"
/* 私有宏定义区 */
/*
* 布局常量(设备像素;参考 mockups/待机显示页面.jpg):
* 6 路电流值行自 y=28 起、行距 18(末行 y=118..130);分隔线 y=132;底部时钟 y=140 居中
*/
#define SB_SCREEN_W 160 /* 屏幕宽(px,与 ui_port.h / 模拟器一致) */
#define SB_SCREEN_H 160 /* 屏幕高(px) */
#define SB_CH_COUNT 6u /* 电流通道数 */
#define SB_ROW_Y0 28 /* 首数据行顶 y */
#define SB_PITCH 18 /* 行距(相邻行槽顶间距) */
#define SB_LABEL_X 24 /* 通道名 x(Ib/Ic/...) */
#define SB_EQ_X 72 /* "=" 号 x */
#define SB_VALUE_X 96 /* 数值 x(0.00A;v1 占位,待交流量标定管线接入实时值) */
#define SB_LINE_Y 132 /* 分隔线 y(高 1px) */
#define SB_TIME_Y 140 /* 底部时钟放置 y(居中) */
/* 静态变量定义区 */
/* 通道名与占位数值(Flash;全 ASCII,无需中文字形) */
static const char *const s_ch_name[SB_CH_COUNT] = { "Ib", "Ic", "Io", "Ia2", "Ib2", "Ic2" };
static const char *const s_ch_value[SB_CH_COUNT] = { "0.00A", "0.00A", "0.00A", "0.00A", "0.00A", "0.00A" };
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 主题给普通 lv_obj 附加的 card 样式(同 ui_content_page.c) */
static const lv_style_const_prop_t sb_flat_props[] = {
{ .prop = LV_STYLE_BG_OPA, .value = { .num = LV_OPA_TRANSP } },
{ .prop = LV_STYLE_BORDER_WIDTH, .value = { .num = 0 } },
{ .prop = LV_STYLE_RADIUS, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_LEFT, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_RIGHT, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_TOP, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_BOTTOM, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_ROW, .value = { .num = 0 } },
{ .prop = LV_STYLE_PAD_COLUMN, .value = { .num = 0 } },
LV_STYLE_CONST_PROPS_END
};
LV_STYLE_CONST_INIT(sb_style_flat, sb_flat_props);
/* 函数定义区 */
/*
* 函数功能:判断两区域是否相交(等价 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);
}
/*
* 函数功能:绘制居中单行文本(底部时钟)
* 入口参数: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 = SB_SCREEN_W - 1;
area.y1 = y;
area.y2 = y + 15; /* 覆盖字体行高(13)+ 余量 */
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = text;
ld.color = lv_color_white();
ld.ofs_x = (SB_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);
}
/*
* 函数功能:待机屏自绘(6 路电流值 + 分隔线 + 底部时钟;逐行 culling)
* 入口参数:e - 事件指针 lv_event_t *
* 返回值:无
* 限定条件:事件为 LV_EVENT_DRAW_MAIN,待机模块已初始化
* 函数说明:1bpp 极性 white=暗墨——文本/分隔线均白色;数值 v1 占位 0.00A
*/
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;
strip.x1 = 0;
strip.x2 = SB_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 = lv_color_white();
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 = lv_color_white();
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;
ld.text = s_ch_value[i];
ld.text_static = 1;
ld.color = lv_color_white();
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 = SB_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 = lv_color_white();
fd.bg_opa = LV_OPA_COVER;
lv_draw_rect(layer, &fd, &line);
}
}
/* 底部时钟(每秒重绘;time_cb 平台提供) */
{
lv_area_t t;
t.x1 = 0;
t.x2 = SB_SCREEN_W - 1;
t.y1 = SB_TIME_Y;
t.y2 = (int32_t)(SB_TIME_Y + 15);
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;
}
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, SB_SCREEN_W, SB_SCREEN_H);
lv_obj_add_style(s_cont, &sb_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();
}