完成了,菜单内容的初级显示
This commit is contained in:
397
README.md
397
README.md
@@ -1,64 +1,375 @@
|
||||
# PC_HMI – 本地 HMI 菜单逻辑的 PC 端模拟
|
||||
# DTU-HMI
|
||||
|
||||
本目录提供一个 **纯 C 语言的控制台程序**,在 PC 上模拟嵌入式 HMI 的菜单交互逻辑,方便调试和理解 `Menu_Route` / `Menu_Show_Proc` 的行为。
|
||||
PC 端 HMI 菜单逻辑模拟程序,纯 C 实现,支持菜单树、LCD 显示、TCP 远程显示(RemoDispBus 协议)及 UTF-8 汉字库。
|
||||
|
||||
## 环境搭建
|
||||
---
|
||||
|
||||
### 1. 安装 CMake
|
||||
|
||||
1. 访问 CMake 官网下载页:[`https://cmake.org/download/`](https://cmake.org/download/)
|
||||
2. 下载适用于 Windows 的安装包(通常为 `cmake-*-windows-x86_64.msi`),安装时**勾选将 CMake 添加到 PATH**。
|
||||
3. 安装完成后打开终端(PowerShell / CMD),执行:
|
||||
|
||||
```powershell
|
||||
cmake --version
|
||||
```
|
||||
|
||||
能看到版本信息即表示安装成功。
|
||||
|
||||
### 2. 编译器与编码说明
|
||||
|
||||
- Windows 下默认使用 **Visual Studio / MSVC** 工具链进行编译。
|
||||
- 源文件采用 **UTF‑8 编码并包含中文注释**,`CMakeLists.txt` 中已经为 MSVC 打开 `/utf-8` 选项,无需额外配置即可正常编译。
|
||||
|
||||
|
||||
## 目录结构
|
||||
|
||||
- `CMakeLists.txt` – CMake 构建配置
|
||||
- `include/menu.h` – 菜单数据结构与接口声明
|
||||
- `include/tcp.h` – TCP 通信接口(客户端/服务端)
|
||||
- `src/menu.c` – 简单菜单树与菜单调度逻辑(PC 版)
|
||||
- `src/main.c` – 主程序入口,从键盘读取按键并驱动菜单
|
||||
- `src/tcp.c` – TCP 实现(Windows Winsock / Linux socket)
|
||||
|
||||
## 构建步骤
|
||||
|
||||
### 1. Windows 示例(当前工程默认方式)
|
||||
|
||||
在仓库根目录下执行(注意路径中有空格时要加引号):
|
||||
## 快速开始
|
||||
|
||||
```powershell
|
||||
mkdir build
|
||||
cd "D:\Code\DTU 程序\DTU-HMI\build"
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
### 2. 通用(Linux / macOS 等)
|
||||
生成可执行文件 `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 ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
生成的可执行文件名为:
|
||||
|
||||
```text
|
||||
pc_hmi # Windows / Linux 下均可
|
||||
```
|
||||
---
|
||||
|
||||
## 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 ────────────┘
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user