/* * 模块名称: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; }