150 lines
4.4 KiB
C
150 lines
4.4 KiB
C
/*
|
|
* 模块名称:UI Menu (菜单页面构建)
|
|
* 模块功能:基于 lv_menu 复现 DTU-HMI 树形菜单,支持方向键导航与返回
|
|
* 适用平台:STM32F407ZGTx
|
|
* 作者:王建锋
|
|
* 创建日期:2026-08-29
|
|
* 修改记录:
|
|
* 2026-08-29 王建锋 创建初始版本
|
|
*/
|
|
#include "ui_menu.h"
|
|
|
|
#include "lvgl.h"
|
|
|
|
#define DBG_TAG "[UI_MENU]"
|
|
#include "dbg_log.h"
|
|
|
|
/* 菜单节点表(简化自 DTU-HMI menuTab):层级 + 名称 */
|
|
typedef struct {
|
|
uint8_t level; /* 0=根页面, 1..3=子层次 */
|
|
const char *name;
|
|
} ui_menu_node_t;
|
|
|
|
static const ui_menu_node_t g_menu_tab[] = {
|
|
{0, "主界面"},
|
|
{1, "装置信息"},
|
|
{1, "实时数据"},
|
|
{2, "交流量"},
|
|
{2, "直流量"},
|
|
{2, "开入量"},
|
|
{1, "定值参数"},
|
|
{2, "定值"},
|
|
{3, "定值"},
|
|
{3, "遥调"},
|
|
{3, "零压"},
|
|
{3, "电压"},
|
|
{2, "查看"},
|
|
{3, "定值"},
|
|
{3, "遥调"},
|
|
{3, "零压"},
|
|
{3, "电压"},
|
|
{1, "遥测量"},
|
|
{2, "测量值"},
|
|
{2, "遥调系数"},
|
|
{2, "遥测系数"},
|
|
{2, "双字节"},
|
|
{2, "装置遥调"},
|
|
{1, "装置维护"},
|
|
{2, "时间设置"},
|
|
{2, "强制复归"},
|
|
{2, "手动录波"},
|
|
{2, "自动录波"},
|
|
{1, "通讯设置"},
|
|
{2, "通讯参数"},
|
|
{2, "网络配置"},
|
|
{2, "SNTP设置"},
|
|
{1, "记录查询"},
|
|
{2, "SOE记录"},
|
|
{2, "事故记录"},
|
|
{2, "操作记录"},
|
|
{2, "告警记录"},
|
|
{2, "变位告警"},
|
|
{2, "遥信记录"},
|
|
{2, "自检记录"},
|
|
{2, "定值记录"},
|
|
{2, "录波记录"},
|
|
{0, "厂家设置"},
|
|
{1, "元件设置"},
|
|
{1, "恢复默认"},
|
|
{2, "全部恢复"},
|
|
{2, "默认参数"},
|
|
{2, "默认定值"},
|
|
{2, "默认系数"},
|
|
{2, "电压"},
|
|
{2, "元件参数"},
|
|
{1, "版本显示"},
|
|
{1, "装置遥调"},
|
|
{2, "测量遥调"},
|
|
{2, "装置遥调"},
|
|
{2, "直流遥调"},
|
|
{2, "开出遥调"},
|
|
{2, "装置记录"},
|
|
{2, "告警记录"},
|
|
{2, "遥调记录"},
|
|
{2, "信号复归"},
|
|
{1, "版本信息"},
|
|
};
|
|
|
|
/*
|
|
* 函数功能:构建菜单(lv_menu 页面树),复现参考项目菜单结构
|
|
* 入口参数:无
|
|
* 返回值:0 - 成功,其他 - 失败(对象创建失败)
|
|
* 限定条件:ui_port_init() 已成功调用,活动屏幕存在
|
|
* 函数说明:1. 仅构建主页面(根页 + 一级菜单项),子树暂不构建以控制内存占用
|
|
* 2. 文本全部使用静态字符串(不 strdup),减少 LVGL 堆分配
|
|
* 3. 构建中监控 LVGL 池空闲内存,不足时中止而非崩溃
|
|
*/
|
|
int ui_menu_init(void)
|
|
{
|
|
lv_obj_t *menu = lv_menu_create(lv_screen_active());
|
|
if (menu == NULL) {
|
|
return -1;
|
|
}
|
|
lv_obj_set_size(menu, 160, 160);
|
|
lv_menu_set_mode_root_back_button(menu, LV_MENU_ROOT_BACK_BUTTON_ENABLED);
|
|
lv_group_add_obj(lv_group_get_default(), menu);
|
|
|
|
/* 仅构建主页面:根页 + 一级菜单项(静态文本,不建子树,省内存) */
|
|
lv_obj_t *root_page = lv_menu_page_create(menu, NULL);
|
|
if (root_page == NULL) {
|
|
return -1;
|
|
}
|
|
lv_menu_set_page_title_static(root_page, "主界面");
|
|
|
|
lv_mem_monitor_t mon;
|
|
lv_mem_monitor(&mon);
|
|
DBG_INFO("menu build start: total=%u free=%u big=%u",
|
|
(unsigned)mon.total_size,
|
|
(unsigned)mon.free_size, (unsigned)mon.free_biggest_size);
|
|
|
|
uint32_t n = sizeof(g_menu_tab) / sizeof(g_menu_tab[0]);
|
|
for (uint32_t i = 1; i < n; i++) {
|
|
if (g_menu_tab[i].level != 1) {
|
|
continue; /* 只显示一级菜单项,子树暂不构建 */
|
|
}
|
|
|
|
/* 内存警戒:空闲池不足则中止,避免 LVGL 内部 NULL 解引用崩溃 */
|
|
lv_mem_monitor(&mon);
|
|
if (mon.free_biggest_size < 1024) {
|
|
DBG_ERROR("menu mem low: abort at item %u (free=%u big=%u)",
|
|
(unsigned)i, (unsigned)mon.free_size,
|
|
(unsigned)mon.free_biggest_size);
|
|
break;
|
|
}
|
|
|
|
const char *name = g_menu_tab[i].name;
|
|
lv_obj_t *item = lv_menu_cont_create(root_page);
|
|
lv_obj_t *lbl = lv_label_create(item);
|
|
lv_label_set_text_static(lbl, name);
|
|
}
|
|
|
|
lv_menu_set_page(menu, root_page);
|
|
|
|
lv_mem_monitor(&mon);
|
|
DBG_INFO("menu build done: total=%u free=%u big=%u used=%u",
|
|
(unsigned)mon.total_size,
|
|
(unsigned)mon.free_size, (unsigned)mon.free_biggest_size,
|
|
(unsigned)(mon.total_size - mon.free_size));
|
|
return 0;
|
|
}
|