完成了基础的中文和英文字符显示
This commit is contained in:
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"clangd.arguments": [
|
"clangd.arguments": [
|
||||||
"--compile-commands-dir=build"
|
"--compile-commands-dir=build"
|
||||||
]
|
],
|
||||||
|
"python-envs.defaultEnvManager": "ms-python.python:conda",
|
||||||
|
"python-envs.defaultPackageManager": "ms-python.python:conda",
|
||||||
|
"python-envs.pythonProjects": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ endif()
|
|||||||
|
|
||||||
add_executable(DTU-HMI
|
add_executable(DTU-HMI
|
||||||
src/main.c
|
src/main.c
|
||||||
src/menu.c
|
src/Drv/menu.c
|
||||||
|
src/Drv/lcd.c
|
||||||
|
src/Drv/Ascii.c
|
||||||
src/thread_utils.c
|
src/thread_utils.c
|
||||||
src/remoteDisplay.c
|
src/remoteDisplay.c
|
||||||
src/TCP/tcp.c
|
src/TCP/tcp.c
|
||||||
|
|||||||
70
README.md
70
README.md
@@ -59,76 +59,6 @@ cmake --build .
|
|||||||
pc_hmi # Windows / Linux 下均可
|
pc_hmi # Windows / Linux 下均可
|
||||||
```
|
```
|
||||||
|
|
||||||
## 运行与按键映射
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./pc_hmi
|
|
||||||
```
|
|
||||||
|
|
||||||
在控制台中使用以下按键进行菜单操作:
|
|
||||||
|
|
||||||
- `W` / `w` – 上(对应嵌入式的 `CN_KEY_U`)
|
|
||||||
- `S` / `s` – 下(`CN_KEY_D`)
|
|
||||||
- `A` / `a` – 左(`CN_KEY_L`)
|
|
||||||
- `D` / `d` – 右(`CN_KEY_R`)
|
|
||||||
- `Enter` – 确认 / 进入子菜单(`CN_KEY_ENT`)
|
|
||||||
- `Esc` – 返回上一级(`CN_KEY_ESC`)
|
|
||||||
|
|
||||||
界面中会用 `[*名称*]` 标出当前选中的菜单项,并在底部显示对应的提示信息。
|
|
||||||
|
|
||||||
> 注意:当前只是一个 **最小化演示环境**,菜单树结构与嵌入式工程中的 `g_tMenuModelTab` 不完全一致,但交互方式相仿,可作为移植和调试时的参考。
|
|
||||||
|
|
||||||
## TCP 通信
|
## TCP 通信
|
||||||
|
|
||||||
项目已集成 **跨平台 TCP 模块**(`include/tcp.h` + `src/tcp.c`),可用于与设备或上位机进行 TCP 通信。
|
|
||||||
|
|
||||||
- **Windows**:使用 Winsock2,已自动链接 `ws2_32`。
|
|
||||||
- **Linux**:使用 BSD socket,无需额外库。
|
|
||||||
|
|
||||||
### 使用前初始化
|
|
||||||
|
|
||||||
程序入口已调用 `Tcp_Init()`,退出前会调用 `Tcp_Cleanup()`。若在其它线程或模块中使用 TCP,无需重复初始化。
|
|
||||||
|
|
||||||
### TCP 客户端示例
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "../include/tcp.h"
|
|
||||||
|
|
||||||
/* 连接服务器 */
|
|
||||||
int sock = TcpClient_Connect("192.168.1.100", 502);
|
|
||||||
if (sock == TCP_INVALID_SOCKET) {
|
|
||||||
/* 连接失败 */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 发送数据 */
|
|
||||||
const char *msg = "hello";
|
|
||||||
int sent = TcpClient_Send(sock, msg, strlen(msg));
|
|
||||||
|
|
||||||
/* 接收数据 */
|
|
||||||
char buf[256];
|
|
||||||
int n = TcpClient_Recv(sock, buf, sizeof(buf) - 1);
|
|
||||||
if (n > 0) {
|
|
||||||
buf[n] = '\0';
|
|
||||||
printf("recv: %s\n", buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 关闭连接 */
|
|
||||||
TcpClient_Close(sock);
|
|
||||||
```
|
|
||||||
|
|
||||||
### TCP 服务端示例
|
|
||||||
|
|
||||||
```c
|
|
||||||
int server = TcpServer_Listen(8080);
|
|
||||||
if (server == TCP_INVALID_SOCKET) return;
|
|
||||||
|
|
||||||
int client = TcpServer_Accept(server); /* 阻塞等待客户端 */
|
|
||||||
if (client != TCP_INVALID_SOCKET) {
|
|
||||||
TcpClient_Send(client, "welcome", 7);
|
|
||||||
TcpClient_Close(client);
|
|
||||||
}
|
|
||||||
TcpServer_Close(server);
|
|
||||||
```
|
|
||||||
|
|
||||||
接口说明见 `include/tcp.h`。
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#ifndef __TYPES__H__
|
||||||
|
#define __TYPES__H__
|
||||||
|
|
||||||
#define uint8_t unsigned char
|
#define uint8_t unsigned char
|
||||||
#define uint16_t unsigned short
|
#define uint16_t unsigned short
|
||||||
#define uint32_t unsigned int
|
#define uint32_t unsigned int
|
||||||
@@ -7,4 +10,9 @@
|
|||||||
#define int32_t int
|
#define int32_t int
|
||||||
#define int64_t long long
|
#define int64_t long long
|
||||||
|
|
||||||
|
#define ptr_size_t unsigned long long
|
||||||
|
|
||||||
|
typedef int (*FUNCPTR) ( );
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
16846
src/Drv/Ascii.c
16846
src/Drv/Ascii.c
File diff suppressed because it is too large
Load Diff
24
src/Drv/ascii.h
Normal file
24
src/Drv/ascii.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef __ASCII_H__
|
||||||
|
#define __ASCII_H__
|
||||||
|
|
||||||
|
#include "../../include/types.h"
|
||||||
|
|
||||||
|
/* 8x8 ASCII 点阵表 */
|
||||||
|
extern const uint8_t g_abyASCII88[][8];
|
||||||
|
|
||||||
|
/* 12x6(或 12 列宽)ASCII 点阵表 */
|
||||||
|
extern uint8_t g_abyASCII126[][12];
|
||||||
|
|
||||||
|
/* 16x8 ASCII 点阵表 */
|
||||||
|
extern uint8_t g_abyASCII168[][16];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define UTF8_HZ12_BYTES_PER_CHAR 24
|
||||||
|
#define UTF8_HZ12_NUM_CHARS 4030
|
||||||
|
|
||||||
|
/* 按 Unicode 码点查找点阵,返回 24 字节指针,未找到返回 NULL */
|
||||||
|
const uint8_t* utf8_hz12_get(uint32_t unicode);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* ASCII_FONT_TABLES_H */
|
||||||
156
src/Drv/display.c
Normal file
156
src/Drv/display.c
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#include "menu.h"
|
||||||
|
#include "display.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//========================================== //
|
||||||
|
// 字符串代号常量定义
|
||||||
|
enum _STR_FLAG_ {
|
||||||
|
EN_STR_FLAG_FUNSET = 0, // 投入,退出
|
||||||
|
EN_STR_FLAG_OKCANCEL,
|
||||||
|
EN_VALUE_FLAG_SLOTTYPE, // 板件类型
|
||||||
|
EN_VALUE_FLAG_YESNO, // 是否
|
||||||
|
EN_VALUE_FLAG_CHN_NUM, // 中文数字
|
||||||
|
EN_VALUE_FLAG_YXSET, // 遥信设置
|
||||||
|
EN_VALUE_FLAG_YKSTEP, // 遥控步骤
|
||||||
|
EN_VALUE_FLAG_YKACTION, // 遥控操作类型
|
||||||
|
EN_VALUE_FLAG_CHK, // 自检信息
|
||||||
|
EN_VALUE_FLAG_BEUSED, // 是否使用
|
||||||
|
EN_VALUE_FLAG_PORTTYPE, // 端口类型
|
||||||
|
EN_VALUE_FLAG_MAPNAME, // 转发表名称
|
||||||
|
EN_VALUE_FLAG_RECTITLE, // 记录类型表
|
||||||
|
EN_VALUE_FLAG_BHINFO, // 保护动作/复归
|
||||||
|
EN_VALUE_FLAG_BHSET, // 保护动作/复归
|
||||||
|
EN_VALUE_FLAG_OKCANCEL, // 确定/取消
|
||||||
|
EN_VALUE_FLAG_FILETYPE, // 文件类型
|
||||||
|
EN_VALUE_FLAG_ROLE, // 备份角色
|
||||||
|
EN_VALUE_FLAG_COMMSTATE, // 通讯状态
|
||||||
|
EN_VALUE_FLAG_ALARM, // 告警状态
|
||||||
|
EN_VALUE_FLAG_YXTYPE, // 遥信类型
|
||||||
|
EN_STR_FLAG_MENUALAUTO,
|
||||||
|
EN_STR_FLAG_LINKBREAK,
|
||||||
|
EN_STR_FLAG_DONEUNDONE,
|
||||||
|
EN_STR_FLAG_ACTBACK,
|
||||||
|
EN_STR_FLAG_RESUMEBREAK,
|
||||||
|
EN_STR_FLAG_USESTOP,
|
||||||
|
EN_STR_FLAG_BAUDRATE,
|
||||||
|
EN_STR_FLAG_VERIFY,
|
||||||
|
EN_STR_FLAG_PROTOL,
|
||||||
|
EN_STR_FLAG_RECORD,
|
||||||
|
EN_STR_FLAG_MESSAGE,
|
||||||
|
EN_STR_FLAG_COM,
|
||||||
|
EN_STR_FLAG_MODE,
|
||||||
|
EN_STR_FLAG_COMBAUD,
|
||||||
|
EN_STR_FLAG_DATABIT,
|
||||||
|
EN_STR_FLAG_STOPBIT,
|
||||||
|
EN_STR_FLAG_COMTYPE,
|
||||||
|
EN_STR_FLAG_SAMEV,
|
||||||
|
EN_STR_FLAG_PT,
|
||||||
|
EN_STR_FLAG_CT,
|
||||||
|
EN_STR_FLAG_TRANSTYPE,
|
||||||
|
EN_STR_FLAG_LINEPHS,
|
||||||
|
EN_STR_FLAG_RETOUT,
|
||||||
|
EN_STR_FLAG_YXDI,
|
||||||
|
EN_STR_FLAG_YKPUL,
|
||||||
|
EN_STR_FLAG_WAVE,
|
||||||
|
EN_STR_FLAG_MEMTYPE,
|
||||||
|
EN_STR_FLAG_CHANNEL,
|
||||||
|
EN_STR_FLAG_POWERTYPE,
|
||||||
|
EN_STR_FLAG_ZEROTYPE,
|
||||||
|
EN_STR_FLAG_YCCOMMTYPE,
|
||||||
|
EN_STR_FLAG_YXCOMMTYPE,
|
||||||
|
EN_STR_FLAG_CTRLWORD,
|
||||||
|
EN_STR_FLAG_BUSNAME,
|
||||||
|
EN_STR_FLAG_UNITSHOWMODE,
|
||||||
|
EN_STR_FLAG_BAYMODE,
|
||||||
|
EN_STR_FLAG_BASEANA,
|
||||||
|
EN_STR_FLAG_BASEPHASE,
|
||||||
|
EN_STR_FLAG_BAYCODE,
|
||||||
|
EN_STR_FLAG_SET_NUM,
|
||||||
|
EN_STR_FLAG_DCTYPE,
|
||||||
|
EN_STR_FLAG_DCINTYPE,
|
||||||
|
EN_STR_FLAG_DWINTYPE,
|
||||||
|
EN_STR_FLAG_PTCT,
|
||||||
|
EN_STR_FLAG_HARMOTYPE,
|
||||||
|
EN_STR_FLAG_YXSIN_DI, //设置双点遥信
|
||||||
|
EN_STR_FLAG_SHOWANATYPE,
|
||||||
|
EN_STR_FLAG_ANAPOLARTYPE, // 交流通道极性
|
||||||
|
EN_STR_FLAG_WIRDIAGRAMTYPE // 增加主接线图类型识别.modified by zhanggl 111010
|
||||||
|
|
||||||
|
} enumStrType;
|
||||||
|
|
||||||
|
// 主菜单定义
|
||||||
|
//============================================================
|
||||||
|
// byMenuClass; 菜单分级标志(最大级数不能超过3级);
|
||||||
|
// byName[50]; 菜单字符串;
|
||||||
|
// byTip[36]; 菜单提示文本;
|
||||||
|
// byAttrib; 菜单属性,设置菜单特殊显示效果;
|
||||||
|
// wPassword; 访问密码,0x0000表示没有密码;
|
||||||
|
// wPara; 菜单执行函数参数;
|
||||||
|
// pfnWinProc; 界面执行函数指针;
|
||||||
|
// 如果CN_HAVE_HIDE_MENU 为 TRUE ,0 级菜单的最后一组菜单为隐藏菜单
|
||||||
|
//===========================================================
|
||||||
|
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 },
|
||||||
|
};
|
||||||
174
src/Drv/display.h
Normal file
174
src/Drv/display.h
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#ifndef __DISPLAY__H__
|
||||||
|
#define __DISPLAY__H__
|
||||||
|
|
||||||
|
#include "../../include/types.h"
|
||||||
|
|
||||||
|
|
||||||
|
// 菜单常量表定义
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t byClass; // 菜单分级标志;
|
||||||
|
uint8_t byName[50]; // 菜单字符串;
|
||||||
|
uint8_t byTip[50]; // 菜单提示文本;
|
||||||
|
uint8_t byAttrib; // 菜单属性,设置菜单特殊显示效果;
|
||||||
|
uint16_t wPassword; // 访问密码,0x0000表示没有密码;
|
||||||
|
uint16_t wPara; // 菜单执行函数参数;
|
||||||
|
FUNCPTR pfnWinProc; // 界面执行函数指针;
|
||||||
|
}tagMenuModel,*tagPMenuModel;
|
||||||
|
|
||||||
|
//菜单密码设置
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#define CN_COP_PWD (321) // 厂家密码:可修改保护元件配置、查看内存等
|
||||||
|
#define CN_USER_PWD (700) // 用户超级密码:可用于修改普通密码、定值,预设等
|
||||||
|
#define CN_SUPER_PWD (620)
|
||||||
|
|
||||||
|
|
||||||
|
enum _REV_TYPE_ // 复归类型
|
||||||
|
{
|
||||||
|
EN_REV_FREE, // 非强制复归
|
||||||
|
EN_REV_FORCE // 强制复归
|
||||||
|
};
|
||||||
|
|
||||||
|
enum _SET_SIDE_TYPE_ // 定值类别
|
||||||
|
{
|
||||||
|
EN_SIDE_START = 0, // 备用侧
|
||||||
|
EN_SIDE_BASIC, // 基本信息
|
||||||
|
EN_SIDE_DEVINF, // 装置参数
|
||||||
|
EN_SIDE_COP, // 内部定值
|
||||||
|
EN_SIDE_MATRIX, // 内部定值 出口矩阵
|
||||||
|
EN_SIDE_ALL, // 全侧
|
||||||
|
EN_SIDE_HIGH, // 高压侧
|
||||||
|
EN_SIDE_MED1, // 中压侧
|
||||||
|
EN_SIDE_MED2, // 中压侧
|
||||||
|
EN_SIDE_LOW1, // 低压1侧
|
||||||
|
EN_SIDE_LOW2, // 低压2侧
|
||||||
|
EN_SIDE_LK, // 电抗器
|
||||||
|
EN_SIDE_Z, // Z变
|
||||||
|
EN_SIDE_DEF, // 自定义
|
||||||
|
EN_SIDE_NONE = 0xFF // 不存在
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Const_Soft.h 常量固定部分
|
||||||
|
// ============================================================================
|
||||||
|
// 软压板类型
|
||||||
|
enum _SOFT_TYPE_NUMBER
|
||||||
|
{
|
||||||
|
EN_SOFT_PRO = 0, // 保护功能软压板
|
||||||
|
EN_SOFT_GOOSE, // GOOSE软压板
|
||||||
|
EN_SOFT_MU, // MU软压板
|
||||||
|
EN_SOFT_SWITCH, // 刀闸强制软压板
|
||||||
|
EN_SOFT_BAK, // 备用软压板
|
||||||
|
//----------------------------------------------//
|
||||||
|
EN_SOFT_TYPE_END // 软压板类型总数目,不可改动
|
||||||
|
};
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//实时数据类型标志
|
||||||
|
//============================================================================
|
||||||
|
enum _MEA_TYPE_{
|
||||||
|
//保护交流量
|
||||||
|
EN_MEA_RLY, // 保护交流量1
|
||||||
|
EN_MEA_RLY2, // 保护交流量2
|
||||||
|
EN_MEA_RLY3, // 保护交流量3
|
||||||
|
EN_MEA_ANA, // 保护测量量1
|
||||||
|
EN_MEA_ANA2, // 保护测量量2
|
||||||
|
EN_MEA_ANA3, // 保护测量量3
|
||||||
|
//遥测
|
||||||
|
EN_MEA_AC, // 遥测交流量
|
||||||
|
EN_MEA_DC, // 保护直流量
|
||||||
|
EN_MEA_SYN, // 保护谐波量
|
||||||
|
EN_MEA_POWER, // 保护电能量
|
||||||
|
EN_MEA_DD, // 电度量
|
||||||
|
EN_MEA_JLYC,
|
||||||
|
EN_MEA_GEAR, // 保护档位量
|
||||||
|
EN_MEA_TQ, // 保护同期量
|
||||||
|
EN_MEA_INPUT1, // 保护交流量1
|
||||||
|
EN_MEA_INPUT2, // 保护交流量2
|
||||||
|
EN_MEA_INPUT3, // 保护交流量3
|
||||||
|
EN_INPUT_RLY_ALL, //开入加遥信
|
||||||
|
EN_INPUT_RLY_FAULT,
|
||||||
|
EN_INPUT_RLY_OTHER,
|
||||||
|
EN_INPUT_BS_ALL,
|
||||||
|
EN_INPUT_BS_FAULT,
|
||||||
|
EN_INPUT_BS_OTHER,
|
||||||
|
|
||||||
|
EN_MEA_ADJ, // 遥测微调系数
|
||||||
|
EN_MEA_YX, // 遥信状态
|
||||||
|
EN_OUTPUT_TRIP, // 保护出口调试
|
||||||
|
EN_OUTPUT_SIGN, // 信号出口调试
|
||||||
|
EN_MEA_LS, // 联锁信号.zhanggl
|
||||||
|
EN_MEA_SCRLY // 顺控配置信号.zhanggl
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum _INDEX_VALUE_TYPE_{
|
||||||
|
|
||||||
|
EN_SOFT_SET,
|
||||||
|
EN_FIGURE_SET
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// 菜单常量表相关常量
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//交流量菜单对应wParameter执行菜单参数(应用于菜单常量表)
|
||||||
|
//============================================================================
|
||||||
|
enum _ANA_PARA_ {
|
||||||
|
EN_ANA_0 = 1,
|
||||||
|
EN_ANA_1
|
||||||
|
};
|
||||||
|
|
||||||
|
//============================================================================
|
||||||
|
//开入量菜单对应wParameter执行菜单参数(应用于菜单常量表)
|
||||||
|
//============================================================================
|
||||||
|
enum _INPUT_PARA_ {
|
||||||
|
EN_INPUT_0 = 1,
|
||||||
|
EN_INPUT_1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum _ANA_TYPE_
|
||||||
|
{
|
||||||
|
EN_TYPE_DIF_CURRENT=0 ,
|
||||||
|
EN_TYPE_UNIT_CURRENT ,
|
||||||
|
EN_TYPE_UNIT_VOLTAGE
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
enum _NO_USER_PASSWORD_{
|
||||||
|
EN_NO_USER_PWD = 0x55
|
||||||
|
};
|
||||||
|
enum _FACTORY_PASSWORD_{
|
||||||
|
EN_FACTORY_PASSWORD = 0x55
|
||||||
|
};
|
||||||
|
|
||||||
|
// 记录类型
|
||||||
|
enum _REC_TYPE_
|
||||||
|
{
|
||||||
|
EN_ACT_REC = 0, // 事件记录
|
||||||
|
EN_ALM_REC, // 告警记录
|
||||||
|
EN_CHK_REC, // 自检记录
|
||||||
|
EN_SOE_REC, // SOE记录
|
||||||
|
EN_COS_REC, // COS记录
|
||||||
|
EN_LOCK_REC, // 瞬时闭锁记录
|
||||||
|
EN_OVER_REC, // 越限记录
|
||||||
|
EN_START_REC, // 启动记录
|
||||||
|
EN_RUN_REC, // 运行记录
|
||||||
|
EN_INPUT_REC, // 开入变位记录
|
||||||
|
EN_ONOFF_REC, // 保护投退记录
|
||||||
|
EN_OPT_REC, // 保护操作记录
|
||||||
|
EN_YK_REC, // 保护遥控记录
|
||||||
|
EN_SC_REC, // 装置顺控记录
|
||||||
|
EN_SCSTEPINFO_REC, // 装置顺控单步记录
|
||||||
|
EN_FAULT_REC, // 事故报告记录
|
||||||
|
EN_ACTWAVE_REC, // 动作录波信息记录
|
||||||
|
EN_STARTWAVE_REC, // 启动录波信息记录
|
||||||
|
EN_HANDWAVE_REC, // 手动录波信息记录
|
||||||
|
EN_FAULT_NO, // 故障序号
|
||||||
|
EN_ALL_REC = 0xFF, // 所有记录
|
||||||
|
EN_NO_REC = 0xFFFF // 无效记录
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
34
src/Drv/key.c
Normal file
34
src/Drv/key.c
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#include "key.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct{ // 按键控制结构
|
||||||
|
uint8_t byUsrFlg; // 用户标志,0x55标志 有显示已刷新,供远程显示用
|
||||||
|
uint8_t byKeyValid; // 有效标志 <见 _KEY_VALID_FLAG_ 定义>
|
||||||
|
uint8_t byKeyValue; // 键值
|
||||||
|
uint8_t bUseRkey; //是否启用远程按键
|
||||||
|
}tagRKeyCtrl;
|
||||||
|
|
||||||
|
tagRKeyCtrl g_tRemoteKey; //远程按键
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t Key_Read()
|
||||||
|
{
|
||||||
|
uint8_t byKeyTmp;
|
||||||
|
|
||||||
|
byKeyTmp = KEY_NONE;
|
||||||
|
|
||||||
|
if(EN_KEY_FLAG_NEW == g_tRemoteKey.byKeyValid )
|
||||||
|
{
|
||||||
|
byKeyTmp = g_tRemoteKey.byKeyValue;
|
||||||
|
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return byKeyTmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
31
src/Drv/key.h
Normal file
31
src/Drv/key.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef __KEY_H__
|
||||||
|
#define __KEY_H__
|
||||||
|
|
||||||
|
#include "../../include/types.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define KEY_U (0x02) // 上 32
|
||||||
|
#define KEY_D (0x40) // 下 16
|
||||||
|
#define KEY_L (0x10) // 左 8
|
||||||
|
#define KEY_R (0x08) // 右 4
|
||||||
|
#define KEY_ENT (0x20) // 确认 1
|
||||||
|
#define KEY_ESC (0x01) // 取消 2
|
||||||
|
#define KEY_F1 (0x04) // F1
|
||||||
|
#define KEY_F2 (0x80) // F2
|
||||||
|
#define KEY_ADD (0x82)
|
||||||
|
#define KEY_SUB (0x84)
|
||||||
|
#define KEY_NONE (0) // (无)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enum _KEY_VALID_FLAG_ { // 按键是否有效标志
|
||||||
|
EN_KEY_FLAG_NULL =0, // 无新按键
|
||||||
|
EN_KEY_FLAG_NEW, // 有新按键
|
||||||
|
EN_KEY_FLAG_SAM, // 采样过程中(未确定)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t Key_Read();
|
||||||
|
|
||||||
|
#endif
|
||||||
336
src/Drv/lcd.c
Normal file
336
src/Drv/lcd.c
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
#include "lcd.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include "ascii.h"
|
||||||
|
|
||||||
|
tagScreenControl g_tCVsr; // 当前界面结构指针
|
||||||
|
tagDspAttrib g_tDspAttrib; // 显示属性
|
||||||
|
|
||||||
|
void Lcd_Init(void)
|
||||||
|
{
|
||||||
|
/* 清空显存 */
|
||||||
|
memset(g_tCVsr.pwbyLCDMemory, 0, sizeof(g_tCVsr.pwbyLCDMemory));
|
||||||
|
/* 设置默认颜色 */
|
||||||
|
g_tCVsr.wFontColor = LCD_COLOR_WHITE;
|
||||||
|
g_tCVsr.wBackColor = LCD_COLOR_BLACK;
|
||||||
|
/* 设置默认字体 */
|
||||||
|
//g_tCVsr.pwLibHZ = (uint16_t*)HZK12;
|
||||||
|
g_tCVsr.wGBFontWidth = 14;
|
||||||
|
g_tCVsr.wGBFontHeight = 12;
|
||||||
|
|
||||||
|
/* 设置默认ASCII字体 */
|
||||||
|
g_tCVsr.pbyLibAscii = g_abyASCII126[0];
|
||||||
|
g_tCVsr.wASCIIFontWidth = 8;
|
||||||
|
g_tCVsr.wASCIIFontHeight = 12;
|
||||||
|
g_tDspAttrib.rowSpace = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 填充矩形区域 */
|
||||||
|
void Lcd_FillRect(uint16_t lx, uint16_t ty, uint16_t rx, uint16_t by, uint32_t dwRGB)
|
||||||
|
{
|
||||||
|
uint16_t h, l, err; /* 行/列循环变量;CheckPosi 返回值(本函数未使用) */
|
||||||
|
uint32_t dwRRGB; /* 保存进入前的字体色,用于退出时恢复 */
|
||||||
|
|
||||||
|
dwRRGB = g_tCVsr.wFontColor;
|
||||||
|
g_tCVsr.wFontColor = dwRGB;
|
||||||
|
|
||||||
|
/* 按行扫描矩形区域,逐点用当前颜色绘制(__Lcd_PixelFT 使用当前字体色作为像素色) */
|
||||||
|
for ( h = ty; h <= by; h++ )
|
||||||
|
{
|
||||||
|
for ( l = lx; l <= rx; l++ )
|
||||||
|
{
|
||||||
|
Lcd_SetPixel(l, h, g_tCVsr.wFontColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lcd_SetPixel(uint16_t x, uint16_t y, uint16_t color)
|
||||||
|
{
|
||||||
|
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 一个字节一个像素点 */
|
||||||
|
g_tCVsr.pwbyLCDMemory[y * LCD_LINE_SIZE + x] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Lcd_GetPixel(uint16_t x, uint16_t y)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 一个字节一个像素点 */
|
||||||
|
return g_tCVsr.pwbyLCDMemory[y * LCD_LINE_SIZE + x];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// 功能说明 : 在指定屏幕坐标处显示一个 ASCII 字符
|
||||||
|
// 设计说明 : 从 ASCII 字库中取出点阵, 按当前显示属性(正显/反显、旋转 90° 与否)
|
||||||
|
// 逐点调用画点函数进行显示
|
||||||
|
// 参数说明 : byScreen - 屏幕号
|
||||||
|
// x, y - 字符左上角基准坐标(若 Rotate!=0, 实际显示会旋转 90°)
|
||||||
|
// byAscii - 要显示的 ASCII 码
|
||||||
|
// bTR - TRUE : 透明显示, 点阵为 0 时保留原背景
|
||||||
|
// FALSE : 非透明, 点阵为 0 时用背景色重绘
|
||||||
|
// 返回说明 : 0 - 正常
|
||||||
|
// -1 - Y 方向越界
|
||||||
|
// -2 - X 方向越界
|
||||||
|
//==============================================================================
|
||||||
|
inline uint16_t Lcd_Pub_Ascii(uint16_t x, uint16_t y, uint8_t byAscii)
|
||||||
|
{
|
||||||
|
uint8_t i, j;
|
||||||
|
uint8_t byLine, *pbyFontLib;
|
||||||
|
uint16_t on_color, off_color;
|
||||||
|
|
||||||
|
/* 从 ASCII 字库中取得当前字符的点阵数据首地址
|
||||||
|
每个字符占用 wASCIIFontHeight 个字节, 按行存储 */
|
||||||
|
pbyFontLib = &g_tCVsr.pbyLibAscii[byAscii * g_tCVsr.wASCIIFontHeight];
|
||||||
|
|
||||||
|
/* 边界检查:根据旋转与字符宽高判断是否越界 */
|
||||||
|
if (0 == g_tDspAttrib.Rotate) {
|
||||||
|
if ((x + g_tCVsr.wASCIIFontWidth) > LCD_SIZE_X) return (uint16_t)-2; /* X 越界 */
|
||||||
|
if ((y + g_tCVsr.wASCIIFontHeight) > LCD_SIZE_Y) return (uint16_t)-1; /* Y 越界 */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* 旋转后宽高互换, 重新按宽高做边界检查 */
|
||||||
|
if ((x + g_tCVsr.wASCIIFontHeight) > LCD_SIZE_X) return (uint16_t)-2; /* X 越界 */
|
||||||
|
if ((y + 1) < g_tCVsr.wASCIIFontWidth) return (uint16_t)-1; /* Y 越界(向上旋转) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 根据正显/反显选择“点阵为 1/0 时用的颜色” */
|
||||||
|
if (0 == g_tDspAttrib.style) {
|
||||||
|
/* 正显:1 = 前景色,0 = 背景色 */
|
||||||
|
on_color = g_tCVsr.wFontColor;
|
||||||
|
off_color = g_tCVsr.wBackColor;
|
||||||
|
} else {
|
||||||
|
/* 反显:1 = 背景色,0 = 前景色 */
|
||||||
|
on_color = g_tCVsr.wBackColor;
|
||||||
|
off_color = g_tCVsr.wFontColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < g_tCVsr.wASCIIFontHeight; j++)
|
||||||
|
{
|
||||||
|
byLine = pbyFontLib[j]; /* 第 j 行的 8bit 点阵 */
|
||||||
|
|
||||||
|
for (i = 0; i < g_tCVsr.wASCIIFontWidth; i++)
|
||||||
|
{
|
||||||
|
uint8_t bit_on = ((byLine << i) & 0x80) != 0;
|
||||||
|
uint16_t color;
|
||||||
|
uint16_t px, py;
|
||||||
|
|
||||||
|
if (bit_on)
|
||||||
|
{
|
||||||
|
color = on_color;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
color = off_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == g_tDspAttrib.Rotate)
|
||||||
|
{
|
||||||
|
/* 不旋转: (x+i, y+j) */
|
||||||
|
px = x + i;
|
||||||
|
py = y + j;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* 旋转 90°: 将 (i,j) 映射到 (x+j, y-i) */
|
||||||
|
px = x + j;
|
||||||
|
py = y - i;
|
||||||
|
}
|
||||||
|
Lcd_SetPixel(px, py, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 UTF-8 字节流中解析下一个字符的 Unicode 码点,并返回该字符占用的字节数。
|
||||||
|
*
|
||||||
|
* UTF-8 编码规则简要:
|
||||||
|
* - 1 字节:0xxxxxxx(ASCII,0x00..0x7F)
|
||||||
|
* - 2 字节:110xxxxx 10xxxxxx(U+0080..U+07FF)
|
||||||
|
* - 3 字节:1110xxxx 10xxxxxx 10xxxxxx(U+0800..U+FFFF,含常用汉字)
|
||||||
|
* - 4 字节:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx(U+10000..,本函数不处理)
|
||||||
|
*
|
||||||
|
* @param utf8 指向当前 UTF-8 字节的指针(可含多字节序列)
|
||||||
|
* @param out_unicode 输出该字符的 Unicode 码点(U+0000..U+FFFF)
|
||||||
|
* @return 该字符占用的字节数 1/2/3;0 表示结束、无效或无法解析
|
||||||
|
*/
|
||||||
|
static int utf8_next(const unsigned char *utf8, uint32_t *out_unicode)
|
||||||
|
{
|
||||||
|
unsigned char c = utf8[0];
|
||||||
|
|
||||||
|
/* 字符串结束:'\0' 不算一个可解析字符,返回 0 表示“无更多字符” */
|
||||||
|
if (c == 0) {
|
||||||
|
*out_unicode = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------------------------------------------
|
||||||
|
* 1 字节:ASCII(U+0000..U+007F)
|
||||||
|
* 格式:0xxxxxxx,即 c < 0x80。码点等于该字节的数值。
|
||||||
|
* ----------------------------------------------------------------------- */
|
||||||
|
if (c < 0x80) {
|
||||||
|
*out_unicode = c;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------------------------------------------
|
||||||
|
* 2 字节:U+0080..U+07FF(如拉丁扩展、希腊文等)
|
||||||
|
* 格式:首字节 110xxxxx(0xC0..0xDF),次字节 10xxxxxx(0x80..0xBF)。
|
||||||
|
* 码点 = (首字节低 5 位)<<6 | (次字节低 6 位) = (c&0x1F)<<6 | (utf8[1]&0x3F)。
|
||||||
|
* ----------------------------------------------------------------------- */
|
||||||
|
if ((c & 0xE0) == 0xC0) {
|
||||||
|
if (utf8[1] == 0)
|
||||||
|
return 0; /* 首字节后无后续字节,非法 UTF-8 序列 */
|
||||||
|
*out_unicode = (uint32_t)((c & 0x1F) << 6 | (utf8[1] & 0x3F));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------------------------------------------
|
||||||
|
* 3 字节:U+0800..U+FFFF(含常用汉字、日韩等)
|
||||||
|
* 格式:首字节 1110xxxx(0xE0..0xEF),后两字节均为 10xxxxxx。
|
||||||
|
* 码点 = (首字节低 4 位)<<12 | (第2字节低6位)<<6 | (第3字节低6位)。
|
||||||
|
* 例如 “你” 的 UTF-8 为 E4 BD A0 → U+4F60。
|
||||||
|
* ----------------------------------------------------------------------- */
|
||||||
|
if ((c & 0xF0) == 0xE0) {
|
||||||
|
if (utf8[1] == 0 || utf8[2] == 0)
|
||||||
|
return 0; /* 缺少第二或第三字节,非法序列 */
|
||||||
|
*out_unicode = (uint32_t)((c & 0x0F) << 12 | (utf8[1] & 0x3F) << 6 | (utf8[2] & 0x3F));
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4 字节(U+10000 及以上)或非法首字节(如 10xxxxxx 单独出现):本实现不解析 */
|
||||||
|
*out_unicode = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lcd_Pub_UTF8(uint16_t x, uint16_t y, uint32_t unicode )
|
||||||
|
{
|
||||||
|
const uint8_t *bitmap = utf8_hz12_get(unicode);
|
||||||
|
uint16_t word = 0;
|
||||||
|
if (bitmap == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (uint8_t j = 0; j < g_tCVsr.wGBFontHeight; j++)
|
||||||
|
{
|
||||||
|
word = (uint16_t)((bitmap[j*2] << 8) | bitmap[j*2+1]);
|
||||||
|
for (uint8_t i = 0; i < g_tCVsr.wGBFontWidth; i++)
|
||||||
|
{
|
||||||
|
if ((word >> (15 - i)) & 1)
|
||||||
|
{
|
||||||
|
Lcd_SetPixel(x + i, y + j, g_tCVsr.wFontColor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lcd_SetPixel(x + i, y + j, g_tCVsr.wBackColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在指定坐标起逐字显示 UTF-8 字符串,支持汉字(UTF-8 字库)、ASCII 与换行。
|
||||||
|
* 字符串以 \\0 结尾;遇换行符 0x0A 则换到下一行;本行放不下时擦除本行剩余部分。
|
||||||
|
*
|
||||||
|
* @param x, y 起始坐标(首个字符左上角)
|
||||||
|
* @param pcString UTF-8 字符串
|
||||||
|
* @return 0 成功;-1 起始 X 越界;-2 起始 Y 越界;<0 其它错误(如找不到换行导致换行失败)
|
||||||
|
*/
|
||||||
|
int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString)
|
||||||
|
{
|
||||||
|
uint16_t bakx, baky; /* 当前行行首坐标,换行时 x 回到 bakx */
|
||||||
|
uint32_t unicode; /* utf8_next 解析出的当前字符码点 */
|
||||||
|
uint16_t index = 0; /* 当前字符在 pcString 中的字节下标 */
|
||||||
|
int8_t err;
|
||||||
|
|
||||||
|
bakx = x;
|
||||||
|
baky = y;
|
||||||
|
|
||||||
|
/* 起始坐标合法性:至少能放下一个 ASCII 宽、一行汉字高 */
|
||||||
|
if (x > LCD_SIZE_X - g_tCVsr.wASCIIFontWidth)
|
||||||
|
return -1;
|
||||||
|
if (y >= LCD_SIZE_Y - g_tCVsr.wGBFontHeight)
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
while (pcString[index] != 0x0)
|
||||||
|
{
|
||||||
|
/* 解析当前字符:n = 占用字节数(1=ASCII,2/3=多字节),unicode = 码点 */
|
||||||
|
int n = utf8_next(pcString + index, &unicode);
|
||||||
|
if (n <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* ---------- 多字节字符(如汉字):n=2 或 3,用 UTF-8 字库绘制 ---------- */
|
||||||
|
if (n > 1)
|
||||||
|
{
|
||||||
|
/* 本行剩余宽度放不下一个汉字:向后查找换行符并换行 */
|
||||||
|
if (x > LCD_SIZE_X - g_tCVsr.wGBFontWidth)
|
||||||
|
{
|
||||||
|
/*擦除本行剩余部分*/
|
||||||
|
for(uint16_t j = y; j < y + g_tCVsr.wGBFontHeight; j++)
|
||||||
|
{
|
||||||
|
for(uint16_t i = x; i < LCD_SIZE_X; i++)
|
||||||
|
{
|
||||||
|
Lcd_SetPixel(i, j, g_tCVsr.wBackColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lcd_Pub_UTF8(x, y, unicode);
|
||||||
|
x += g_tCVsr.wGBFontWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* ---------- 单字节字符(ASCII):n=1 ---------- */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (unicode == 0x0a)
|
||||||
|
{
|
||||||
|
/* 换行符:x 回到行首,y 下移一行高度 + 行距 */
|
||||||
|
x = bakx;
|
||||||
|
y += g_tCVsr.wGBFontHeight + g_tDspAttrib.rowSpace;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* 控制字符(0x00..0x0F 且非 0x0A)不绘制,仅跳过 */
|
||||||
|
if (unicode >= 0x10)
|
||||||
|
{
|
||||||
|
/* 本行放不下一个 ASCII 时,向后查找换行符并换行 */
|
||||||
|
if (x > LCD_SIZE_X - g_tCVsr.wASCIIFontWidth)
|
||||||
|
{
|
||||||
|
/*擦除本行剩余部分*/
|
||||||
|
for(uint16_t j = y; j < y + g_tCVsr.wASCIIFontHeight; j++)
|
||||||
|
{
|
||||||
|
for(uint16_t i = x; i < LCD_SIZE_X; i++)
|
||||||
|
{
|
||||||
|
Lcd_SetPixel(i, j, g_tCVsr.wBackColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lcd_Pub_Ascii(x, y, (uint8_t)unicode);
|
||||||
|
x += g_tCVsr.wASCIIFontWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index += n; /* 已处理完当前字符,下标移到下一字符 */
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t Lcd_ShowTest(uint16_t x, uint16_t y, uint8_t *pcString)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
55
src/Drv/lcd.h
Normal file
55
src/Drv/lcd.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef __LCD__H__
|
||||||
|
#define __LCD__H__
|
||||||
|
|
||||||
|
#include "../../include/types.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 单色液晶屏幕 160*160
|
||||||
|
一个 byte 代表一个像素点
|
||||||
|
0xFF 代表白色
|
||||||
|
0x00 代表黑色
|
||||||
|
*/
|
||||||
|
#define LCD_SIZE_X 160
|
||||||
|
#define LCD_SIZE_Y 160
|
||||||
|
|
||||||
|
|
||||||
|
#define LCD_LINE_SIZE LCD_SIZE_X
|
||||||
|
|
||||||
|
#define LCD_DISPLAYMEMORYSIZE (LCD_SIZE_X * LCD_SIZE_Y)
|
||||||
|
|
||||||
|
#define LCD_COLOR_WHITE 0xFF
|
||||||
|
#define LCD_COLOR_BLACK 0x00
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t pwbyLCDMemory[LCD_DISPLAYMEMORYSIZE]; //定义显存
|
||||||
|
uint16_t wFontColor; // 字体颜色
|
||||||
|
uint16_t wBackColor; // 字符显示背景颜色
|
||||||
|
uint16_t wGBFontWidth; // 汉字字体宽度
|
||||||
|
uint16_t wGBFontHeight; // 汉字字体高度
|
||||||
|
uint16_t wASCIIFontWidth; // 字符字体宽度
|
||||||
|
uint16_t wASCIIFontHeight; // 字符字体高度
|
||||||
|
uint16_t *pwLibHZ; // 汉字库地址
|
||||||
|
uint8_t *pbyLibAscii; // ASCII库地址
|
||||||
|
} tagScreenControl;
|
||||||
|
|
||||||
|
/* 当前界面/显存(lcd.c 中定义,供 remoteDisplay 等模块读取显存) */
|
||||||
|
extern tagScreenControl g_tCVsr;
|
||||||
|
|
||||||
|
typedef struct { // 显示属性数据结构
|
||||||
|
uint16_t style:1; // 显示方式 <0=正常显示; 1=返显>
|
||||||
|
uint16_t StringDirect:1; // 字符显示方向<0=横向显示, 1=竖向显示>
|
||||||
|
uint16_t fillZero:1; // 10进制数显示前面是否补0。0=不补0,1=补0;
|
||||||
|
uint16_t Rotate:1; // 字符是否旋转显示,目前只支持逆时针转90度
|
||||||
|
uint16_t res:6; // 保留
|
||||||
|
uint16_t rowSpace:4; // 行距
|
||||||
|
}tagDspAttrib, *tagPDspAttrib;
|
||||||
|
|
||||||
|
|
||||||
|
void Lcd_Init(void);
|
||||||
|
void Lcd_SetPixel(uint16_t x, uint16_t y, uint16_t color);
|
||||||
|
uint16_t Lcd_GetPixel(uint16_t x, uint16_t y);
|
||||||
|
int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString);
|
||||||
|
int8_t Lcd_ShowTest(uint16_t x, uint16_t y, uint8_t *pcString);
|
||||||
|
#endif
|
||||||
178
src/Drv/menu.c
Normal file
178
src/Drv/menu.c
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#include "menu.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "lcd.h"
|
||||||
|
#include "key.h"
|
||||||
|
|
||||||
|
/* 简单的静态菜单树:
|
||||||
|
*
|
||||||
|
* 层级 0: [运行界面] [定值设置] [查看数据]
|
||||||
|
* │ │
|
||||||
|
* 层级 1: (子菜单...) (子菜单...)
|
||||||
|
*/
|
||||||
|
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; // 访问密码,0x0000表示没有密码;
|
||||||
|
uint16_t wPara; // 菜单执行函数参数;
|
||||||
|
FUNCPTR pfnWinProc; // 界面执行函数指针;
|
||||||
|
|
||||||
|
uint16_t wPos; // 当前菜单位置,相对于本级菜单。
|
||||||
|
uint16_t wNum; // 下级菜单项数
|
||||||
|
uint16_t wSPosX; // 下级菜单起始坐标
|
||||||
|
uint16_t wSPosY; // 下级菜单起始坐标
|
||||||
|
uint16_t wEPosX; // 下级菜单对角坐标
|
||||||
|
uint16_t wEPosY; // 下级菜单对角坐标
|
||||||
|
}tagMenuItem,*tagPMenuItem;
|
||||||
|
|
||||||
|
// 显示控制结构
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
tagPMenuItem ptMenuCur; // 菜单当前指针
|
||||||
|
|
||||||
|
uint8_t byLayer; // 显示层
|
||||||
|
uint8_t bySide; // 用于显示定值侧别
|
||||||
|
|
||||||
|
uint16_t wGroup; // 最大页面数
|
||||||
|
uint16_t wPage; // 当前页面数
|
||||||
|
uint16_t wRes; // RES
|
||||||
|
uint16_t wPassword; // 输入密码
|
||||||
|
|
||||||
|
uint16_t wPara; // 菜单参数
|
||||||
|
uint16_t wItemN; // 当前菜单对应显示项数
|
||||||
|
uint16_t wPos; // 数据定位
|
||||||
|
uint16_t wBaseNo; // 相位基准(debug)
|
||||||
|
|
||||||
|
uint8_t byNumOfPage; // 每页显示项数
|
||||||
|
uint8_t byCount; // 通用计数
|
||||||
|
uint8_t byAdjustX;
|
||||||
|
uint8_t bGetSet; // 上电召定值标志
|
||||||
|
uint8_t bLightByNewRec; // 由新记录点亮背光灯.modified by zhanggl.111109
|
||||||
|
|
||||||
|
unsigned bPwdOK:1; // 密码状态
|
||||||
|
unsigned bFirst:1; // 第一次进入界面<是/否>
|
||||||
|
unsigned bChanged:1; // 数据是否被修改
|
||||||
|
unsigned bModFlag:1; // 是否处于修改状态
|
||||||
|
unsigned bEdit:1; // 重入标志
|
||||||
|
unsigned bFlash:1; // 闪烁标志
|
||||||
|
unsigned bRunLayer:1; // 运行层标志
|
||||||
|
unsigned bitOutput:1;
|
||||||
|
unsigned bShowMode:1; // F1功能键切换同轴显示模式
|
||||||
|
unsigned bShowCursor:1; // F2功能键切换游标显示模式
|
||||||
|
unsigned bRes:1;
|
||||||
|
}tagDspCtrl,*tagPDspCtrl;
|
||||||
|
|
||||||
|
|
||||||
|
tagDspCtrl g_tDspCtrl; // 显示控制全局结构
|
||||||
|
|
||||||
|
|
||||||
|
static MenuItem g_menuRun;
|
||||||
|
static MenuItem g_menuSet;
|
||||||
|
static MenuItem g_menuSee;
|
||||||
|
static MenuItem g_menuSet1;
|
||||||
|
static MenuItem g_menuSet2;
|
||||||
|
|
||||||
|
void Menu_Init()
|
||||||
|
{
|
||||||
|
Lcd_Init();
|
||||||
|
|
||||||
|
/* 初始化显示控制结构 */
|
||||||
|
memset(&g_tDspCtrl, 0, sizeof(g_tDspCtrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu_Show_0Level()
|
||||||
|
{
|
||||||
|
tagPMenuItem ptIndex; /* 遍历 0 级菜单链表用的指针(从表头开始依次后移) */
|
||||||
|
uint16_t wSPosX; /* 当前要显示的 0 级菜单项左上角 X 坐标(屏幕坐标) */
|
||||||
|
uint16_t wSPosY; /* 0 级菜单“下陷框”左上角 Y 坐标 */
|
||||||
|
uint16_t wEPosX; /* 0 级菜单“下陷框”右下角 X 坐标 */
|
||||||
|
uint16_t wEPosY; /* 0 级菜单“下陷框”右下角 Y 坐标 */
|
||||||
|
uint16_t wLoop; /* 循环计数:第几个 0 级菜单项(从 0 开始) */
|
||||||
|
uint8_t byName[50]; /* 临时缓冲区:存放当前 0 级菜单项名称 */
|
||||||
|
uint8_t byInterval; /* 每个 0 级菜单在 X 方向上的间隔宽度(用于平均分布) */
|
||||||
|
uint8_t byLeftMove; /* 当前 0 级菜单整体向左平移的像素数(超出一屏时产生“滚动”效果) */
|
||||||
|
uint8_t by0LevelNum; /* 实际参与显示的 0 级菜单个数(是否显示“隐藏/显示”项会影响个数) */
|
||||||
|
|
||||||
|
|
||||||
|
Lcd_ShowStr(16, 20, (uint8_t*)"你好!\nabcsadfasdfasdfsdfasdf"); /* 你好! */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void MenuProc_See_AppInfo()
|
||||||
|
{
|
||||||
|
printf("MenuProc_See_AppInfo\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuProc_See_YC()
|
||||||
|
{
|
||||||
|
printf("MenuProc_See_YC\n");
|
||||||
|
}
|
||||||
|
void MenuProc_Set_Value()
|
||||||
|
{
|
||||||
|
printf("MenuProc_Set_Value\n");
|
||||||
|
}
|
||||||
|
void MenuProc_Set_Soft()
|
||||||
|
{
|
||||||
|
printf("MenuProc_Set_Soft\n");
|
||||||
|
}
|
||||||
|
void MenuProc_Cfg_Time()
|
||||||
|
{
|
||||||
|
printf("MenuProc_Cfg_Time\n");
|
||||||
|
}
|
||||||
|
void MenuProc_Cfg_RevEvent()
|
||||||
|
{
|
||||||
|
printf("MenuProc_Cfg_RevEvent\n");
|
||||||
|
}
|
||||||
|
void MenuProc_Cfg_ManualWave()
|
||||||
|
{
|
||||||
|
printf("MenuProc_Cfg_ManualWave\n");
|
||||||
|
}
|
||||||
|
void MenuProc_See_Input()
|
||||||
|
{
|
||||||
|
printf("MenuProc_See_Input\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuProc_See_Set()
|
||||||
|
{
|
||||||
|
printf("MenuProc_See_Set\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuProc_See_Soft()
|
||||||
|
{
|
||||||
|
printf("MenuProc_See_Soft\n");
|
||||||
|
}
|
||||||
|
void MenuProc_YX_SetCommType()
|
||||||
|
{
|
||||||
|
printf("MenuProc_YX_SetCommType\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuProc_YX_SetWidth()
|
||||||
|
{
|
||||||
|
printf("MenuProc_YX_SetWidth\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuProc_YX_SetTwin()
|
||||||
|
{
|
||||||
|
printf("MenuProc_YX_SetTwin\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int Menu_MapKey(int ch)
|
||||||
|
{
|
||||||
|
if (ch == 'w' || ch == 'W') return KEY_U;
|
||||||
|
if (ch == 's' || ch == 'S') return KEY_D;
|
||||||
|
if (ch == 'a' || ch == 'A') return KEY_L;
|
||||||
|
if (ch == 'd' || ch == 'D') return KEY_R;
|
||||||
|
if (ch == '\r' || ch == '\n') return KEY_ENT;
|
||||||
|
if (ch == 27) return KEY_ESC; /* ESC */
|
||||||
|
return KEY_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
89
src/Drv/menu.h
Normal file
89
src/Drv/menu.h
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#ifndef __MENU_H__
|
||||||
|
#define __MENU_H__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PC 端菜单模拟环境
|
||||||
|
*
|
||||||
|
* 目标:
|
||||||
|
* - 用纯 C 语言在控制台模拟嵌入式 HMI 的菜单逻辑
|
||||||
|
* - 键值映射:W/S/A/D/Enter/Esc → 上/下/左/右/确认/退出
|
||||||
|
* - 结构设计尽量贴近原工程的 g_tMenuCtrl / Menu_Route / Menu_Show_Proc
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct MenuItem MenuItem;
|
||||||
|
|
||||||
|
struct MenuItem {
|
||||||
|
const char *name; /* 菜单名称 */
|
||||||
|
const char *tip; /* 底部提示信息 */
|
||||||
|
|
||||||
|
MenuItem *higher; /* 上级菜单 */
|
||||||
|
MenuItem *lower; /* 下级第一个菜单 */
|
||||||
|
MenuItem *before; /* 同级上一个 */
|
||||||
|
MenuItem *behind; /* 同级下一个 */
|
||||||
|
|
||||||
|
uint8_t level; /* 菜单级别:0/1/2/... */
|
||||||
|
uint8_t pos; /* 在同级菜单中的序号(从 0 开始) */
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MenuItem *head0; /* 0 级菜单头结点 */
|
||||||
|
MenuItem *current; /* 当前选中的菜单结点 */
|
||||||
|
} MenuCtrl;
|
||||||
|
|
||||||
|
/* 初始化一棵简单的测试菜单树 */
|
||||||
|
void Menu_Init();
|
||||||
|
void Menu_Show_0Level();
|
||||||
|
|
||||||
|
/* 非功能键处理 */
|
||||||
|
void Menu_NonPfunc();
|
||||||
|
void MenuProc_AllInf_Default();
|
||||||
|
void MenuProc_Para_Default();
|
||||||
|
void MenuProc_Set_Default();
|
||||||
|
void MenuProc_Resume_Soft();
|
||||||
|
void MenuProc_Cfg_CellDef();
|
||||||
|
void MenuProc_Cfg_ShowAnaType();
|
||||||
|
void MenuProc_Dbg_XuYX();
|
||||||
|
void MenuProc_Dbg_XuYC();
|
||||||
|
void MenuProc_Dbg_XuEvent();
|
||||||
|
void MenuProc_Dbg_Relay();
|
||||||
|
void MenuProc_See_VersionBoard();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void MenuProc_See_AppInfo();
|
||||||
|
void MenuProc_Set_Value();
|
||||||
|
void MenuProc_Set_Soft();
|
||||||
|
void MenuProc_Cfg_Time();
|
||||||
|
void MenuProc_Cfg_RevEvent();
|
||||||
|
void MenuProc_Cfg_ManualWave();
|
||||||
|
void MenuProc_Cfg_ClrRec();
|
||||||
|
void MenuProc_Cfg_ComPara();
|
||||||
|
void MenuProc_Cfg_EditIP();
|
||||||
|
void MenuProc_Cfg_EditSntp();
|
||||||
|
void MenuProc_Cfg_CellConf();
|
||||||
|
|
||||||
|
void MenuProc_See_RecSOE();
|
||||||
|
void MenuProc_See_RecAct();
|
||||||
|
void MenuProc_See_RecOpt();
|
||||||
|
void MenuProc_See_RecAlm();
|
||||||
|
void MenuProc_See_RecStart();
|
||||||
|
void MenuProc_See_RecYK();
|
||||||
|
void MenuProc_See_RecChk();
|
||||||
|
void MenuProc_See_RecRun();
|
||||||
|
void MenuProc_See_RecFault();
|
||||||
|
|
||||||
|
void MenuProc_See_YC();
|
||||||
|
void MenuProc_See_Input();
|
||||||
|
void MenuProc_See_Set();
|
||||||
|
void MenuProc_See_Soft();
|
||||||
|
void MenuProc_YC_SetSqValue();
|
||||||
|
void MenuProc_YC_SetAdjCoe();
|
||||||
|
void MenuProc_YX_SetCommType();
|
||||||
|
void MenuProc_YX_SetWidth();
|
||||||
|
void MenuProc_YX_SetTwin();
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef PC_HMI_TCP_H
|
#ifndef __TCP__H__
|
||||||
#define PC_HMI_TCP_H
|
#define __TCP__H__
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 跨平台 TCP 通信模块
|
* 跨平台 TCP 通信模块
|
||||||
@@ -9,8 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include "../../include/types.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
|
|||||||
104
src/main.c
104
src/main.c
@@ -31,7 +31,7 @@ static int getch(void)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* 菜单:MenuItem、MenuCtrl、Menu_InitPC、Menu_LoopStep、Menu_MapKey、KEY_* */
|
/* 菜单:MenuItem、MenuCtrl、Menu_InitPC、Menu_LoopStep、Menu_MapKey、KEY_* */
|
||||||
#include "menu.h"
|
#include "Drv/menu.h"
|
||||||
/* TCP:Tcp_Init/Cleanup、TcpServer_Listen/Accept/Close、TcpClient_Send/Recv/Close */
|
/* TCP:Tcp_Init/Cleanup、TcpServer_Listen/Accept/Close、TcpClient_Send/Recv/Close */
|
||||||
#include "TCP/tcp.h"
|
#include "TCP/tcp.h"
|
||||||
/* 线程:Thread_Create、Thread_Join、thread_handle_t */
|
/* 线程:Thread_Create、Thread_Join、thread_handle_t */
|
||||||
@@ -39,6 +39,42 @@ static int getch(void)
|
|||||||
/* 远程显示 / TCP 服务器线程:server_thread_arg_t、tcp_server_thread_fn */
|
/* 远程显示 / TCP 服务器线程:server_thread_arg_t、tcp_server_thread_fn */
|
||||||
#include "remoteDisplay.h"
|
#include "remoteDisplay.h"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* 启动 TCP 服务与服务器线程(Tcp_Init + 创建线程 + 短暂等待就绪)
|
||||||
|
* port: 监听端口(如 7070)
|
||||||
|
* out_server_sock: 输出监听 socket,供主线程退出时 TcpServer_Close
|
||||||
|
* out_server_quit: 输出退出标志,主线程置 1 通知服务器线程退出
|
||||||
|
* out_server_th: 输出线程句柄,供主线程 Thread_Join
|
||||||
|
* 返回:0 成功,1 失败(已调用 Tcp_Cleanup)
|
||||||
|
* ---------------------------------------------------------------------------- */
|
||||||
|
static int StartTcpServerThread(uint16_t port,
|
||||||
|
int *out_server_sock,
|
||||||
|
volatile int *out_server_quit,
|
||||||
|
thread_handle_t *out_server_th)
|
||||||
|
{
|
||||||
|
server_thread_arg_t server_arg;
|
||||||
|
|
||||||
|
if (Tcp_Init() != 0) {
|
||||||
|
fprintf(stderr, "Tcp_Init failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_server_sock = TCP_INVALID_SOCKET;
|
||||||
|
*out_server_quit = 0;
|
||||||
|
server_arg.port = port;
|
||||||
|
server_arg.p_server_sock = out_server_sock;
|
||||||
|
server_arg.p_quit = out_server_quit;
|
||||||
|
|
||||||
|
if (Thread_Create(tcp_server_thread_fn, &server_arg, out_server_th) != 0) {
|
||||||
|
fprintf(stderr, "Thread_Create(server) failed\n");
|
||||||
|
Tcp_Cleanup();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Sleep(200);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* 程序入口
|
* 程序入口
|
||||||
* ---------------------------------------------------------------------------- */
|
* ---------------------------------------------------------------------------- */
|
||||||
@@ -50,62 +86,22 @@ int main(void)
|
|||||||
system("chcp 65001 > nul");
|
system("chcp 65001 > nul");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* 初始化网络库(Windows 下必须最先调用,否则 Winsock 不可用) */
|
int server_sock;
|
||||||
if (Tcp_Init() != 0) {
|
volatile int server_quit;
|
||||||
fprintf(stderr, "Tcp_Init failed\n");
|
thread_handle_t server_th;
|
||||||
|
|
||||||
|
|
||||||
|
printf("开始初始化菜单树...\n");
|
||||||
|
Menu_Init(); /* 初始化菜单树(运行界面/定值设置/查看数据等) */
|
||||||
|
Menu_Show_0Level();
|
||||||
|
printf("PC 端 HMI 菜单模拟启动(TCP 服务在单独线程,端口 7003)。\n");
|
||||||
|
/* 7003 为 RemoDispBus 默认端口,与 remo_disp_server.py 一致 */
|
||||||
|
if (StartTcpServerThread(7003, &server_sock, &server_quit, &server_th) != 0)
|
||||||
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
while(1) {
|
||||||
|
|
||||||
/* 主线程持有的监听 socket:由服务器线程在 Listen 成功后回写,退出时主线程用来 Close 以唤醒 Accept */
|
|
||||||
int server_sock = TCP_INVALID_SOCKET;
|
|
||||||
/* 退出标志:主线程置 1 后,服务器线程在下一轮循环或 Accept 返回后退出 */
|
|
||||||
volatile int server_quit = 0;
|
|
||||||
/* 传给服务器线程的参数(端口、回写 socket 的指针、退出标志指针) */
|
|
||||||
server_thread_arg_t server_arg = {
|
|
||||||
.port = 7070, /* TCP 监听端口 */
|
|
||||||
.p_server_sock = &server_sock, /* 服务器线程将监听 socket 写回此处 */
|
|
||||||
.p_quit = &server_quit, /* 主线程通过此处通知服务器线程退出 */
|
|
||||||
};
|
|
||||||
|
|
||||||
thread_handle_t server_th; /* 服务器线程句柄,用于后续 Thread_Join 等待其结束 */
|
|
||||||
if (Thread_Create(tcp_server_thread_fn, &server_arg, &server_th) != 0) { /* 创建并启动服务器线程 */
|
|
||||||
fprintf(stderr, "Thread_Create(server) failed\n");
|
|
||||||
Tcp_Cleanup(); /* 创建失败则清理网络库后退出 */
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
Sleep(200); /* Windows:等待 200ms,让服务器线程完成 Listen 并写回 server_sock */
|
|
||||||
#else
|
|
||||||
usleep(200000); /* Linux:同上,200ms = 200000 微秒 */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
MenuCtrl ctrl; /* 菜单控制结构:当前选中项、0 级头节点等 */
|
|
||||||
int ch, key; /* ch:键盘原始字符;key:映射后的 KEY_U/KEY_D/KEY_ENT 等 */
|
|
||||||
|
|
||||||
Menu_InitPC(&ctrl); /* 初始化菜单树(运行界面/定值设置/查看数据等) */
|
|
||||||
printf("PC 端 HMI 菜单模拟启动(TCP 服务在单独线程,端口 7070)。\n");
|
|
||||||
printf("--------------------------------------------------\n");
|
|
||||||
Menu_LoopStep(&ctrl, KEY_NONE); /* 用 KEY_NONE 触发一次重绘,显示当前菜单 */
|
|
||||||
|
|
||||||
while (1) { /* 主循环:处理键盘并驱动菜单,直到用户按 Q 退出 */
|
|
||||||
printf("\n按键(W/S/A/D/Enter/Esc),Q 退出:");
|
|
||||||
#ifdef _WIN32
|
|
||||||
ch = _getch(); /* Windows:无回显读键 */
|
|
||||||
#else
|
|
||||||
ch = getch(); /* Linux:上面实现的 getch() */
|
|
||||||
#endif
|
|
||||||
if (ch == 'q' || ch == 'Q') { /* 用户选择退出 */
|
|
||||||
printf("\n正在退出...\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
key = Menu_MapKey(ch); /* 将字符映射为菜单键(W→KEY_U 等) */
|
|
||||||
if (key == KEY_NONE) { /* 非菜单键则忽略 */
|
|
||||||
printf(" (忽略)\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
Menu_LoopStep(&ctrl, key); /* 根据按键更新当前菜单项并重绘 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server_quit = 1; /* 通知服务器线程退出:下一轮 while 或 Accept 返回后会结束循环 */
|
server_quit = 1; /* 通知服务器线程退出:下一轮 while 或 Accept 返回后会结束循环 */
|
||||||
|
|||||||
164
src/menu.c
164
src/menu.c
@@ -1,164 +0,0 @@
|
|||||||
#include "menu.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* 简单的静态菜单树:
|
|
||||||
*
|
|
||||||
* 层级 0: [运行界面] [定值设置] [查看数据]
|
|
||||||
* │ │
|
|
||||||
* 层级 1: (子菜单...) (子菜单...)
|
|
||||||
*/
|
|
||||||
|
|
||||||
static MenuItem g_menuRun;
|
|
||||||
static MenuItem g_menuSet;
|
|
||||||
static MenuItem g_menuSee;
|
|
||||||
static MenuItem g_menuSet1;
|
|
||||||
static MenuItem g_menuSet2;
|
|
||||||
|
|
||||||
void Menu_InitPC(MenuCtrl *ctrl)
|
|
||||||
{
|
|
||||||
memset(&g_menuRun, 0, sizeof(g_menuRun));
|
|
||||||
memset(&g_menuSet, 0, sizeof(g_menuSet));
|
|
||||||
memset(&g_menuSee, 0, sizeof(g_menuSee));
|
|
||||||
memset(&g_menuSet1, 0, sizeof(g_menuSet1));
|
|
||||||
memset(&g_menuSet2, 0, sizeof(g_menuSet2));
|
|
||||||
|
|
||||||
/* 0 级菜单 */
|
|
||||||
g_menuRun.name = "运行界面";
|
|
||||||
g_menuRun.tip = "显示运行状态";
|
|
||||||
g_menuRun.level = 0;
|
|
||||||
g_menuRun.pos = 0;
|
|
||||||
|
|
||||||
g_menuSet.name = "定值设置";
|
|
||||||
g_menuSet.tip = "进入定值设置菜单";
|
|
||||||
g_menuSet.level = 0;
|
|
||||||
g_menuSet.pos = 1;
|
|
||||||
|
|
||||||
g_menuSee.name = "查看数据";
|
|
||||||
g_menuSee.tip = "查看测量值和记录";
|
|
||||||
g_menuSee.level = 0;
|
|
||||||
g_menuSee.pos = 2;
|
|
||||||
|
|
||||||
/* 0 级链表关系 */
|
|
||||||
g_menuRun.before = &g_menuSee;
|
|
||||||
g_menuRun.behind = &g_menuSet;
|
|
||||||
|
|
||||||
g_menuSet.before = &g_menuRun;
|
|
||||||
g_menuSet.behind = &g_menuSee;
|
|
||||||
|
|
||||||
g_menuSee.before = &g_menuSet;
|
|
||||||
g_menuSee.behind = &g_menuRun;
|
|
||||||
|
|
||||||
/* 1 级菜单:定值设置下的两个子菜单 */
|
|
||||||
g_menuSet1.name = "保护定值";
|
|
||||||
g_menuSet1.tip = "编辑保护定值";
|
|
||||||
g_menuSet1.level = 1;
|
|
||||||
g_menuSet1.pos = 0;
|
|
||||||
g_menuSet1.higher = &g_menuSet;
|
|
||||||
|
|
||||||
g_menuSet2.name = "通信参数";
|
|
||||||
g_menuSet2.tip = "编辑通信参数";
|
|
||||||
g_menuSet2.level = 1;
|
|
||||||
g_menuSet2.pos = 1;
|
|
||||||
g_menuSet2.higher = &g_menuSet;
|
|
||||||
|
|
||||||
g_menuSet1.before = &g_menuSet2;
|
|
||||||
g_menuSet1.behind = &g_menuSet2;
|
|
||||||
|
|
||||||
g_menuSet2.before = &g_menuSet1;
|
|
||||||
g_menuSet2.behind = &g_menuSet1;
|
|
||||||
|
|
||||||
g_menuSet.lower = &g_menuSet1;
|
|
||||||
|
|
||||||
/* 初始化控制结构 */
|
|
||||||
ctrl->head0 = &g_menuRun;
|
|
||||||
ctrl->current = &g_menuRun;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 控制台绘制当前菜单状态 */
|
|
||||||
static void Menu_Draw(const MenuCtrl *ctrl)
|
|
||||||
{
|
|
||||||
const MenuItem *cur = ctrl->current;
|
|
||||||
const MenuItem *p;
|
|
||||||
|
|
||||||
printf("\n==== PC HMI 菜单模拟 ====\n");
|
|
||||||
printf("使用 W/S/A/D/Enter/Esc 操作,Ctrl+C 退出。\n\n");
|
|
||||||
|
|
||||||
/* 显示 0 级菜单 */
|
|
||||||
printf("[0级菜单]: ");
|
|
||||||
p = ctrl->head0;
|
|
||||||
if (!p) {
|
|
||||||
printf("(空)\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const MenuItem *start = p;
|
|
||||||
do {
|
|
||||||
if (p == cur)
|
|
||||||
printf(" [*%s*] ", p->name);
|
|
||||||
else
|
|
||||||
printf(" %s ", p->name);
|
|
||||||
p = p->behind;
|
|
||||||
} while (p && p != start);
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
/* 若当前有下级,则显示 1 级菜单 */
|
|
||||||
if (cur->lower) {
|
|
||||||
printf(" 子菜单:\n");
|
|
||||||
p = cur->lower;
|
|
||||||
start = p;
|
|
||||||
do {
|
|
||||||
printf(" - %s\n", p->name);
|
|
||||||
p = p->behind;
|
|
||||||
} while (p && p != start);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 提示信息 */
|
|
||||||
printf("\n提示: %s\n", cur->tip ? cur->tip : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu_LoopStep(MenuCtrl *ctrl, int key)
|
|
||||||
{
|
|
||||||
MenuItem *cur = ctrl->current;
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case KEY_U:
|
|
||||||
if (cur->before)
|
|
||||||
cur = cur->before;
|
|
||||||
break;
|
|
||||||
case KEY_D:
|
|
||||||
if (cur->behind)
|
|
||||||
cur = cur->behind;
|
|
||||||
break;
|
|
||||||
case KEY_L:
|
|
||||||
if (cur->higher)
|
|
||||||
cur = cur->higher;
|
|
||||||
break;
|
|
||||||
case KEY_R:
|
|
||||||
case KEY_ENT:
|
|
||||||
if (cur->lower)
|
|
||||||
cur = cur->lower;
|
|
||||||
printf("\n>>> 进入 [%s] 功能(模拟用户层)...\n", cur->name);
|
|
||||||
break;
|
|
||||||
case KEY_ESC:
|
|
||||||
if (cur->higher)
|
|
||||||
cur = cur->higher;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrl->current = cur;
|
|
||||||
Menu_Draw(ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Menu_MapKey(int ch)
|
|
||||||
{
|
|
||||||
if (ch == 'w' || ch == 'W') return KEY_U;
|
|
||||||
if (ch == 's' || ch == 'S') return KEY_D;
|
|
||||||
if (ch == 'a' || ch == 'A') return KEY_L;
|
|
||||||
if (ch == 'd' || ch == 'D') return KEY_R;
|
|
||||||
if (ch == '\r' || ch == '\n') return KEY_ENT;
|
|
||||||
if (ch == 27) return KEY_ESC; /* ESC */
|
|
||||||
return KEY_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
56
src/menu.h
56
src/menu.h
@@ -1,56 +0,0 @@
|
|||||||
#ifndef PC_HMI_MENU_H
|
|
||||||
#define PC_HMI_MENU_H
|
|
||||||
|
|
||||||
/*
|
|
||||||
* PC 端菜单模拟环境
|
|
||||||
*
|
|
||||||
* 目标:
|
|
||||||
* - 用纯 C 语言在控制台模拟嵌入式 HMI 的菜单逻辑
|
|
||||||
* - 键值映射:W/S/A/D/Enter/Esc → 上/下/左/右/确认/退出
|
|
||||||
* - 结构设计尽量贴近原工程的 g_tMenuCtrl / Menu_Route / Menu_Show_Proc
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef struct MenuItem MenuItem;
|
|
||||||
|
|
||||||
struct MenuItem {
|
|
||||||
const char *name; /* 菜单名称 */
|
|
||||||
const char *tip; /* 底部提示信息 */
|
|
||||||
|
|
||||||
MenuItem *higher; /* 上级菜单 */
|
|
||||||
MenuItem *lower; /* 下级第一个菜单 */
|
|
||||||
MenuItem *before; /* 同级上一个 */
|
|
||||||
MenuItem *behind; /* 同级下一个 */
|
|
||||||
|
|
||||||
uint8_t level; /* 菜单级别:0/1/2/... */
|
|
||||||
uint8_t pos; /* 在同级菜单中的序号(从 0 开始) */
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
MenuItem *head0; /* 0 级菜单头结点 */
|
|
||||||
MenuItem *current; /* 当前选中的菜单结点 */
|
|
||||||
} MenuCtrl;
|
|
||||||
|
|
||||||
/* 初始化一棵简单的测试菜单树 */
|
|
||||||
void Menu_InitPC(MenuCtrl *ctrl);
|
|
||||||
|
|
||||||
/* 处理一帧输入,并在控制台绘制菜单 */
|
|
||||||
void Menu_LoopStep(MenuCtrl *ctrl, int key);
|
|
||||||
|
|
||||||
/* 键值定义:与嵌入式逻辑对应的抽象键 */
|
|
||||||
enum {
|
|
||||||
KEY_NONE = 0,
|
|
||||||
KEY_U,
|
|
||||||
KEY_D,
|
|
||||||
KEY_L,
|
|
||||||
KEY_R,
|
|
||||||
KEY_ENT,
|
|
||||||
KEY_ESC,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 从键盘输入字符映射到 KEY_* 抽象键(W/S/A/D/Enter/Esc) */
|
|
||||||
int Menu_MapKey(int ch);
|
|
||||||
|
|
||||||
#endif /* PC_HMI_MENU_H */
|
|
||||||
|
|
||||||
@@ -1,53 +1,214 @@
|
|||||||
/*
|
/*
|
||||||
* remoteDisplay.c - TCP 服务器线程实现
|
* remoteDisplay.c - TCP 服务器线程实现
|
||||||
* 逻辑:等待客户端连接,循环接收消息并原样回发(echo),直到客户端断开或主线程请求退出
|
* 实现 RemoDispBus 协议:解析 remo_disp_server.py 的请求,返回 lcd 显存数据等。
|
||||||
|
* 帧格式: [TAG][cmd][len_hi][len_lo][data][crc],CRC = data 区逐字节异或低 8 位。
|
||||||
|
* 客户端 TAG=0xAA,设备回复 TAG=0xBB。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "TCP/tcp.h"
|
|
||||||
#include "remoteDisplay.h"
|
#include "remoteDisplay.h"
|
||||||
|
#include "Drv/lcd.h"
|
||||||
|
#include "TCP/tcp.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* RemoDispBus 协议常量(与 remo_disp_server.py 一致) */
|
||||||
|
#define TAG_CLIENT 0xAAu
|
||||||
|
#define TAG_DEVICE 0xBBu
|
||||||
|
#define CMD_KEEPLIVE 0
|
||||||
|
#define CMD_INIT 1
|
||||||
|
#define CMD_KEY 2
|
||||||
|
#define CMD_LCDMEM 3
|
||||||
|
|
||||||
|
/* 计算 data 区 CRC:逐字节异或取低 8 位 */
|
||||||
|
static uint8_t calc_crc(const uint8_t* data, unsigned int len)
|
||||||
|
{
|
||||||
|
uint8_t crc = 0;
|
||||||
|
while (len--)
|
||||||
|
crc ^= *data++;
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 从 recv 缓冲区解析一帧: [TAG][cmd][len_hi][len_lo][data...][crc]
|
||||||
|
* 成功返回 1 并设置 *p_cmd, *p_data, *p_data_len;失败返回 0。
|
||||||
|
* *p_consume 返回本帧总字节数(含头尾),调用方从缓冲区移除。 */
|
||||||
|
static int parse_frame(const uint8_t* buf, unsigned int buf_len,
|
||||||
|
uint8_t* p_cmd, const uint8_t** p_data, unsigned int* p_data_len,
|
||||||
|
unsigned int* p_consume)
|
||||||
|
{
|
||||||
|
if (buf_len < 5 || buf[0] != TAG_CLIENT)
|
||||||
|
return 0;
|
||||||
|
unsigned int data_len = ((unsigned int)buf[2] << 8) | buf[3];
|
||||||
|
unsigned int frame_len = 4 + data_len + 1; /* 头4 + data + crc */
|
||||||
|
if (buf_len < frame_len)
|
||||||
|
return 0;
|
||||||
|
uint8_t crc = calc_crc(buf + 4, data_len);
|
||||||
|
if (crc != buf[4 + data_len])
|
||||||
|
return 0;
|
||||||
|
*p_cmd = buf[1];
|
||||||
|
*p_data = buf + 4;
|
||||||
|
*p_data_len = data_len;
|
||||||
|
*p_consume = frame_len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 回复帧最大长度:头4 + 最大 payload(4+LCD_DISPLAYMEMORYSIZE) + crc1 */
|
||||||
|
#define REMO_REPLY_BUF_SIZE (4 + 4 + LCD_DISPLAYMEMORYSIZE + 1)
|
||||||
|
|
||||||
|
/* 构造设备回复帧并发送: [TAG_DEVICE][cmd][len_hi][len_lo][data][crc],一次性发送 */
|
||||||
|
static int send_reply(int client, uint8_t cmd, const uint8_t* data, unsigned int data_len)
|
||||||
|
{
|
||||||
|
uint8_t buf[REMO_REPLY_BUF_SIZE];
|
||||||
|
unsigned int total = 4 + data_len + 1;
|
||||||
|
|
||||||
|
if (total > REMO_REPLY_BUF_SIZE)
|
||||||
|
return -1;
|
||||||
|
buf[0] = TAG_DEVICE;
|
||||||
|
buf[1] = cmd & 0xFF;
|
||||||
|
buf[2] = (data_len >> 8) & 0xFF;
|
||||||
|
buf[3] = data_len & 0xFF;
|
||||||
|
if (data_len)
|
||||||
|
memcpy(buf + 4, data, data_len);
|
||||||
|
buf[4 + data_len] = calc_crc(data, data_len);
|
||||||
|
|
||||||
|
return TcpClient_Send(client, (const char*)buf, total) == (int)total ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 处理 CMD_LCDMEM:请求 data 为 4 字节大端起始地址;回复 [4B 地址][显存数据] */
|
||||||
|
static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int req_len)
|
||||||
|
{
|
||||||
|
unsigned int start_addr = 0;
|
||||||
|
|
||||||
|
printf("handle_cmd_lcdmem\n");
|
||||||
|
if (req_len >= 4)
|
||||||
|
start_addr = ((unsigned int)req_data[0] << 24) | ((unsigned int)req_data[1] << 16)
|
||||||
|
| ((unsigned int)req_data[2] << 8) | req_data[3];
|
||||||
|
|
||||||
|
/* 回复 payload = [4B 地址大端][显存],Python 取 payload[4:] 为位图 */
|
||||||
|
uint8_t payload[4 + LCD_DISPLAYMEMORYSIZE];
|
||||||
|
payload[0] = (start_addr >> 24) & 0xFF;
|
||||||
|
payload[1] = (start_addr >> 16) & 0xFF;
|
||||||
|
payload[2] = (start_addr >> 8) & 0xFF;
|
||||||
|
payload[3] = start_addr & 0xFF;
|
||||||
|
|
||||||
|
unsigned int copy_len = LCD_DISPLAYMEMORYSIZE;
|
||||||
|
if (start_addr < LCD_DISPLAYMEMORYSIZE) {
|
||||||
|
unsigned int offset = start_addr;
|
||||||
|
copy_len = LCD_DISPLAYMEMORYSIZE - offset;
|
||||||
|
memcpy(payload + 4, g_tCVsr.pwbyLCDMemory + offset, copy_len);
|
||||||
|
} else {
|
||||||
|
copy_len = 0;
|
||||||
|
}
|
||||||
|
send_reply(client, CMD_LCDMEM, payload, 4 + copy_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 处理 CMD_INIT:回复宽、高、显存大小(与 Python 端约定一致时可扩展) */
|
||||||
|
static void handle_cmd_init(int client)
|
||||||
|
{
|
||||||
|
uint8_t data[8];
|
||||||
|
data[0] = (LCD_SIZE_X >> 8) & 0xFF;
|
||||||
|
data[1] = LCD_SIZE_X & 0xFF;
|
||||||
|
data[2] = (LCD_SIZE_Y >> 8) & 0xFF;
|
||||||
|
data[3] = LCD_SIZE_Y & 0xFF;
|
||||||
|
data[4] = (LCD_DISPLAYMEMORYSIZE >> 24) & 0xFF;
|
||||||
|
data[5] = (LCD_DISPLAYMEMORYSIZE >> 16) & 0xFF;
|
||||||
|
data[6] = (LCD_DISPLAYMEMORYSIZE >> 8) & 0xFF;
|
||||||
|
data[7] = LCD_DISPLAYMEMORYSIZE & 0xFF;
|
||||||
|
send_reply(client, CMD_INIT, data, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 处理 CMD_KEY:可选转交菜单,此处仅回 ACK */
|
||||||
|
static void handle_cmd_key(int client, const uint8_t* data, unsigned int len)
|
||||||
|
{
|
||||||
|
(void)data;
|
||||||
|
(void)len;
|
||||||
|
send_reply(client, CMD_KEY, (const uint8_t*)NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 处理 CMD_KEEPLIVE:保活,回空 */
|
||||||
|
static void handle_cmd_keeplive(int client)
|
||||||
|
{
|
||||||
|
send_reply(client, CMD_KEEPLIVE, (const uint8_t*)NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
void tcp_server_thread_fn(void* arg)
|
void tcp_server_thread_fn(void* arg)
|
||||||
{
|
{
|
||||||
server_thread_arg_t* ctx = (server_thread_arg_t*)arg; /* 强转为参数结构体 */
|
server_thread_arg_t* ctx = (server_thread_arg_t*)arg;
|
||||||
int server_sock = TcpServer_Listen((uint16_t)ctx->port); /* 在指定端口创建监听 socket */
|
int server_sock = TcpServer_Listen((uint16_t)ctx->port);
|
||||||
if (server_sock == TCP_INVALID_SOCKET) { /* 监听失败(端口占用或权限等) */
|
if (server_sock == TCP_INVALID_SOCKET) {
|
||||||
fprintf(stderr, "[TCP Server] TcpServer_Listen(%d) failed\n", ctx->port);
|
fprintf(stderr, "[TCP Server] TcpServer_Listen(%d) failed\n", ctx->port);
|
||||||
return; /* 线程直接退出,主线程通过 server_sock 仍为 INVALID 可知失败 */
|
return;
|
||||||
}
|
}
|
||||||
*ctx->p_server_sock = server_sock; /* 回写监听 socket,主线程可用来关闭以结束 Accept 阻塞 */
|
*ctx->p_server_sock = server_sock;
|
||||||
printf("[TCP Server] 监听端口 %d,等待客户端连接...\n", ctx->port);
|
printf("[TCP Server] RemoDispBus 监听端口 %d,等待客户端连接...\n", ctx->port);
|
||||||
|
|
||||||
char recv_buf[1024]; /* 接收缓冲区,用于收客户端数据并回发 */
|
#define REMO_BUF_SIZE 4096
|
||||||
|
uint8_t recv_buf[REMO_BUF_SIZE];
|
||||||
|
unsigned int recv_len = 0;
|
||||||
|
|
||||||
while (!*ctx->p_quit) { /* 主线程未请求退出时一直循环 */
|
/* ========== 外层循环:主线程未请求退出时,持续等待并接受新客户端 ========== */
|
||||||
int client = TcpServer_Accept(server_sock); /* 阻塞等待一个客户端连接 */
|
while (!*ctx->p_quit) {
|
||||||
if (client == TCP_INVALID_SOCKET) { /* Accept 失败(含:主线程已关闭 server_sock) */
|
/* 阻塞等待一个客户端连接;主线程关闭 server_sock 时 Accept 会失败并返回 INVALID */
|
||||||
if (*ctx->p_quit) /* 若是因退出请求导致,则跳出循环 */
|
int client = TcpServer_Accept(server_sock);
|
||||||
|
if (client == TCP_INVALID_SOCKET) {
|
||||||
|
if (*ctx->p_quit)
|
||||||
break;
|
break;
|
||||||
continue; /* 否则忽略本次错误,继续下一次 Accept */
|
continue;
|
||||||
}
|
}
|
||||||
printf("[TCP Server] 客户端连接成功\n");
|
printf("[TCP Server] 客户端连接成功\n");
|
||||||
/* 只要该客户端还连着,就循环等待消息并回发 */
|
recv_len = 0; /* 新连接对应新的接收缓冲区,避免混用上一连接的残留数据 */
|
||||||
|
|
||||||
|
/* ========== 内层循环:处理当前连接上的 RemoDispBus 请求,直到断开或退出 ========== */
|
||||||
while (!*ctx->p_quit) {
|
while (!*ctx->p_quit) {
|
||||||
printf("[TCP Server] 等待客户端消息\n");
|
/* ----- 1. 若缓冲区中已有一条完整且校验通过的帧,则解析并分发处理 ----- */
|
||||||
memset(recv_buf, 0, sizeof(recv_buf));
|
uint8_t cmd;
|
||||||
int n = TcpClient_Recv(client, recv_buf, sizeof(recv_buf) - 1);
|
const uint8_t* pdata;
|
||||||
|
unsigned int data_len, consume;
|
||||||
|
if (parse_frame(recv_buf, recv_len, &cmd, &pdata, &data_len, &consume)) {
|
||||||
|
switch (cmd) {
|
||||||
|
case CMD_KEEPLIVE:
|
||||||
|
handle_cmd_keeplive(client);
|
||||||
|
break;
|
||||||
|
case CMD_INIT:
|
||||||
|
handle_cmd_init(client);
|
||||||
|
break;
|
||||||
|
case CMD_KEY:
|
||||||
|
handle_cmd_key(client, pdata, data_len);
|
||||||
|
break;
|
||||||
|
case CMD_LCDMEM:
|
||||||
|
handle_cmd_lcdmem(client, pdata, data_len);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
send_reply(client, cmd, (const uint8_t*)NULL, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* 从缓冲区头部移除已消费的 consume 字节,避免重复解析同一帧 */
|
||||||
|
memmove(recv_buf, recv_buf + consume, recv_len - consume);
|
||||||
|
recv_len -= consume;
|
||||||
|
continue; /* 继续检查是否还有完整帧,避免不必要的 recv */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----- 2. 缓冲区中尚无完整帧:先防止溢出,再收一批数据 ----- */
|
||||||
|
if (recv_len >= REMO_BUF_SIZE - 256) {
|
||||||
|
recv_len = 0; /* 异常情况:数据过多且始终不成帧,清空缓冲区防止越界 */
|
||||||
|
}
|
||||||
|
int n = TcpClient_Recv(client, (char*)(recv_buf + recv_len), REMO_BUF_SIZE - recv_len - 1);
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
printf("recv: %s\n", recv_buf);
|
recv_len += (unsigned int)n;
|
||||||
TcpClient_Send(client, recv_buf, (size_t)n);
|
printf("recv_len = %d\n", recv_len);
|
||||||
} else {
|
} else {
|
||||||
/* n==0 表示对方关闭连接,n<0 表示出错,退出本连接循环 */
|
/* n==0 表示对方正常关闭连接,n<0 表示读取出错;均退出本连接循环 */
|
||||||
printf("[TCP Server] 客户端关闭连接\n");
|
printf("[TCP Server] 客户端关闭连接\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* 当前客户端处理结束,关闭该连接;外层循环继续 Accept 下一个客户端 */
|
||||||
TcpClient_Close(client);
|
TcpClient_Close(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
TcpServer_Close(server_sock); /* 关闭监听 socket */
|
TcpServer_Close(server_sock);
|
||||||
*ctx->p_server_sock = TCP_INVALID_SOCKET; /* 通知主线程 socket 已关闭,避免重复 Close */
|
*ctx->p_server_sock = TCP_INVALID_SOCKET;
|
||||||
printf("[TCP Server] 已退出\n");
|
printf("[TCP Server] 已退出\n");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user