# DTU-HMI PC 端 HMI 菜单逻辑模拟程序,纯 C 实现,支持菜单树、LCD 显示、TCP 远程显示(RemoDispBus 协议)及 UTF-8 汉字库。 --- ## 快速开始 ```powershell mkdir build cd build cmake .. cmake --build . ``` 生成可执行文件 `DTU-HMI.exe`(Windows)或 `DTU-HMI`(Linux)。 --- ## 目录结构 ``` DTU-HMI/ ├── CMakeLists.txt ├── gen_utf8_hz12.py # 12×12 UTF-8 汉字库生成脚本 ├── remo_disp_server.py # 远程显示 Python 服务端 ├── include/ │ └── types.h ├── src/ │ ├── main.c │ ├── thread_utils.c/h │ ├── remoteDisplay.c/h │ ├── TCP/tcp.c, tcp.h │ └── Drv/ │ ├── menu.c/h │ ├── display.c/h │ ├── lcd.c/h │ ├── Ascii.c/h │ └── utf8_hz12_data.c/h # 由脚本生成 └── build/ ``` --- ## 环境要求 - **CMake** 3.10+ - **编译器**:Windows 默认 MSVC;Linux 需 GCC/Clang - **编码**:源文件 UTF-8,CMake 已配置 MSVC `/utf-8` --- ## 构建步骤 ### Windows ```powershell mkdir build cd build cmake .. cmake --build . ``` ### Linux / macOS ```bash mkdir build cd build cmake .. cmake --build . ``` --- ## TCP 通信 程序监听端口 **7003**(RemoDispBus 默认)。使用 `remo_disp_server.py` 连接后可实时查看 LCD 显存画面。协议支持 `CMD_INIT`、`CMD_LCDMEM`、`CMD_KEY`、`CMD_KEEPLIVE`。 --- ## 菜单系统 ### 1. 概述 菜单由**静态表** `g_tMenuModelTab` 定义,经 `Menu_Main_Creat_01` 构建为**可遍历树** `g_tMenuItem[]`。每个结点有 `ptHigher/ptLower/ptBefore/ptBehind` 四个指针,同级首尾成环。 ### 2. 数据结构 #### 2.1 静态菜单定义(tagMenuModel) ```c typedef struct { uint8_t byClass; // 菜单分级标志 0/1/2/3 uint8_t byName[50]; // 菜单字符串 uint8_t byTip[50]; // 菜单提示文本 uint8_t byAttrib; // 菜单属性 uint16_t wPassword; // 访问密码,0x0000 表示无密码 uint16_t wPara; // 菜单执行函数参数 FUNCPTR pfnWinProc; // 界面执行函数指针 } tagMenuModel, *tagPMenuModel; /* 注意:表定义时顺序不能乱,需从 0 级开始,一级一级按顺序写入 */ const tagMenuModel g_tMenuModelTab[] = { ... }; ``` #### 2.2 菜单遍历树(tagMenuItem) ```c /* 每个菜单包含:一、上下前后等级关系;二、属性与内容;三、显示坐标 */ typedef struct _MENU_ITEM_ { struct _MENU_ITEM_ *ptHigher; // 上级菜单指针 struct _MENU_ITEM_ *ptLower; // 下级菜单指针 struct _MENU_ITEM_ *ptBefore; // 同级上方指针 struct _MENU_ITEM_ *ptBehind; // 同级下方指针 uint8_t byClass; uint8_t byName[50]; uint8_t byTip[50]; uint8_t byAttrib; uint16_t wPassword; uint16_t wPara; FUNCPTR pfnWinProc; uint16_t wPos, wNum; uint16_t wSPosX, wSPosY, wEPosX, wEPosY; } tagMenuItem, *tagPMenuItem; tagMenuItem g_tMenuItem[300]; /* 所有菜单存储于此数组 */ ``` ### 3. 菜单构建示例 #### 3.1 静态表(g_tMenuModelTab) ```c const tagMenuModel g_tMenuModelTab[] = { { 0, " ", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 1, "装置信息", "查看装置信息", 0, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_AppInfo }, { 1, "实时数据", "装置实时数据", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 2, "交流量", "查看遥测交流量", EN_MEA_AC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC }, { 2, "直流量", "查看遥测直流量", EN_MEA_DC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC }, { 2, "遥信量", "查看遥信开入量", EN_INPUT_RLY_ALL, 0x0000, EN_INPUT_0, (FUNCPTR)MenuProc_See_Input }, { 1, "参数定值", "保护参数查看与修改", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 2, "整定", "整定装置保护参数", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 3, "参数", "查看设备参数定值", EN_FIGURE_SET, CN_USER_PWD, EN_SIDE_BASIC, (FUNCPTR)MenuProc_Set_Value }, { 3, "定值", "设置装置数值定值", EN_FIGURE_SET, CN_USER_PWD, EN_SIDE_DEF, (FUNCPTR)MenuProc_Set_Value }, { 3, "控制字", "设置装置控制字", EN_SOFT_SET, CN_USER_PWD, EN_SIDE_DEF, (FUNCPTR)MenuProc_Set_Value }, { 3, "软压板", "设置软压板", 0, CN_USER_PWD, EN_SOFT_PRO, (FUNCPTR)MenuProc_Set_Soft }, { 2, "查看", "查看装置保护参数", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 3, "参数", "设置设备参数定值", EN_FIGURE_SET, 0x0000, EN_SIDE_BASIC, (FUNCPTR)MenuProc_See_Set }, { 3, "定值", "查看数值型定值", EN_FIGURE_SET, 0x0000, EN_SIDE_DEF, (FUNCPTR)MenuProc_See_Set }, { 3, "控制字", "查看控制字定值", EN_SOFT_SET, 0x0000, EN_SIDE_DEF, (FUNCPTR)MenuProc_See_Set }, { 3, "软压板", "查看软压板", 0, 0x0000, EN_SOFT_PRO, (FUNCPTR)MenuProc_See_Soft }, { 1, "三遥设置", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 2, "遥测死区", "设置遥测量死区门槛", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YC_SetSqValue }, { 2, "遥测系数", "设置遥测量微调系数", EN_MEA_ADJ, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YC_SetAdjCoe }, { 2, "遥信类型", "设置遥信类型", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetCommType }, { 2, "遥信防抖", "设置遥信防抖时间", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetWidth }, { 2, "双点遥信", "设置双点遥信虚端子", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetTwin }, { 1, "装置维护", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 2, "时钟设置", "设置系统时钟", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_Time }, { 2, "强制复归", "可复归未返回事件", EN_REV_FORCE, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_RevEvent }, { 2, "手动录波", "启动手动录波", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ManualWave }, { 2, "清除记录", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ClrRec }, { 1, "通讯参数", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 2, "通讯设置", "外部通讯设置", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ComPara }, { 2, "网口设置", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_EditIP }, { 2, "SNTP设置", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_EditSntp }, { 1, "记录查询", "查看各种装置记录", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 2, "SOE记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecSOE }, { 2, "事故记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecAct }, { 2, "操作记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecOpt }, { 2, "保护告警", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecAlm }, { 2, "保护启动", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecStart }, { 2, "遥控记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecYK }, { 2, "自检记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecChk }, { 2, "运行记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecRun }, { 2, "运行报告", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecFault }, { 0, "厂家设置", "设置装置相关参数", 0, CN_COP_PWD, 0, (FUNCPTR)Menu_NonPfunc }, { 1, "元件配置", "配置元件配置", 0, CN_COP_PWD, EN_FACTORY_PASSWORD,(FUNCPTR)MenuProc_Cfg_CellConf }, { 1, "恢复默认", "恢复默认元件定值参数", 0, CN_COP_PWD, 0, (FUNCPTR)Menu_NonPfunc }, { 2, "全部恢复", "全部参数恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_AllInf_Default }, { 2, "默认参数", "当前参数恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Para_Default }, { 2, "默认定值", "当前定值区恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Set_Default }, { 2, "软压板", "当前软压板恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Resume_Soft }, { 2, "元件配置", "元件配置恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Cfg_CellDef }, { 1, "交流显示", "交流显示方式设置", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Cfg_ShowAnaType }, { 1, "装置调试", "调试装置", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc }, { 2, "虚拟遥信", "设置虚拟遥信值", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuYX }, { 2, "交流虚遥测", "设置虚拟交流遥测值", EN_MEA_AC, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC }, { 2, "直流虚遥测", "设置虚拟直流遥测值", EN_MEA_DC, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC }, { 2, "电度虚遥测", "设置虚拟电度遥测值", EN_MEA_POWER, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC }, { 2, "动作虚事件", "设置虚拟动作事件", EN_ACT_REC, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuEvent }, { 2, "告警虚事件", "设置虚拟告警事件", EN_ALM_REC, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuEvent }, { 2, "动作出口", "进入此菜单保护退出", EN_OUTPUT_TRIP, CN_COP_PWD, EN_INPUT_0, (FUNCPTR)MenuProc_Dbg_Relay }, { 2, "信号出口", "进入此菜单保护退出", EN_OUTPUT_SIGN, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_Relay }, { 1, "版本信息", "查看板件版本信息", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_See_VersionBoard }, }; ``` #### 3.2 解析后的树形结构 根据上表,`Menu_Main_Creat_01` 解析后的菜单树为: ```text 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.清除记录 2.通讯参数 2.通讯设置 2.网口设置 2.SNTP设置 } 1.记录查询 { 2.SOE记录 2.事故记录 2.操作记录 2.保护告警 2.保护启动 2.遥控记录 2.自检记录 2.运行记录 2.运行报告 } } 0.厂家设置 { 1.元件配置 1.恢复默认 { 2.全部恢复 2.默认参数 2.默认定值 2.软压板 2.元件配置 } 1.交流显示 1.装置调试 { 2.虚拟遥信 2.交流虚遥测 2.直流虚遥测 2.电度虚遥测 2.动作虚事件 2.告警虚事件 2.动作出口 2.信号出口 } 1.版本信息 } ``` #### 3.3 单例解析示例:直流量 以 `{2,"直流量","查看遥测直流量",EN_MEA_DC,0x0000,EN_ANA_0,(FUNCPTR)MenuProc_See_YC}` 为例,构建后该菜单项的指针与属性为: ```text { ptHigher = 1.实时数据; ptLower = NULL; ptBefore = 2.交流量; ptBehind = 2.遥信量; byClass = 2; byName = 直流量; byTip = 查看遥测直流量; byAttrib = EN_MEA_DC; wPassword = 0x0000; wPara = 0; pfnWinProc = MenuProc_See_YC; } ``` #### 3.4 同级首尾成环 同级菜单中,首尾通过 `ptBefore/ptBehind` 相连形成环。例如 2 级子菜单: ```text 2.虚拟遥信 2.交流虚遥测 2.直流虚遥测 2.电度虚遥测 2.动作虚事件 2.告警虚事件 2.动作出口 2.信号出口 ``` 其中: - `2.虚拟遥信` 的 `ptBefore` 指向 `2.信号出口`(首的前一个是尾) - `2.信号出口` 的 `ptBehind` 指向 `2.虚拟遥信`(尾的后一个是首) ### 4. 构建流程(Menu_Main_Creat_01) 按 `g_tMenuModelTab` 的定义顺序逐项处理,通过比较 `byCurClass` 与 `byNextClass` 设置每个菜单的层级指针。 #### 情况 1:byCurClass < byNextClass(下一项更深,进入子菜单) ```text ptCurrent ptNextNode (当前) (下一项,更深一级) │ │ │ ptLower ──────────────────►│ │◄───────────────── ptHigher │ │ │ 该级尾不变 新一级的 首=尾=ptNextNode ``` #### 情况 2:byCurClass == byNextClass(同级,并列) ```text ptCurrent ─── ptBehind ──► ptNextNode │ │ │ ptBefore ◄───────────┤ │ │ └──── ptHigher (同) ────────┘ 该级 尾 更新为 ptNextNode ``` #### 情况 3:byCurClass > byNextClass(下一项更浅,回到上层) ```text ... byCurClass 级 ... byNextClass 级 ... ptLast[byNextClass] 已存在 │ │ ptBehind ──► ptNextNode(新) │ │ │◄── ptBefore ────┤ │ ptHigher = 该级尾的 ptHigher 同时:从 byCurClass 到 byNextClass+1 各级首尾成环 ptLast[级]──►ptFirst[级],ptFirst[级]──►ptLast[级] ``` #### 最后:各级首尾成环 表遍历完后,从 **0 级到当前结点所在级**,把该级首尾连成环: ```text ptFirst[级] ◄──────────────► ptLast[级] │ │ └──── ptBehind ───────────────┘ ◄──────── ptBefore ────────────┘ ```