完成了基础的中文和英文字符显示

This commit is contained in:
2026-03-08 21:30:46 +08:00
parent f45b571162
commit 9da748efb8
19 changed files with 5645 additions and 12905 deletions

File diff suppressed because it is too large Load Diff

24
src/Drv/ascii.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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 字节0xxxxxxxASCII0x00..0x7F
* - 2 字节110xxxxx 10xxxxxxU+0080..U+07FF
* - 3 字节1110xxxx 10xxxxxx 10xxxxxxU+0800..U+FFFF含常用汉字
* - 4 字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxxU+10000..,本函数不处理)
*
* @param utf8 指向当前 UTF-8 字节的指针(可含多字节序列)
* @param out_unicode 输出该字符的 Unicode 码点U+0000..U+FFFF
* @return 该字符占用的字节数 1/2/30 表示结束、无效或无法解析
*/
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 字节ASCIIU+0000..U+007F
* 格式0xxxxxxx即 c < 0x80。码点等于该字节的数值。
* ----------------------------------------------------------------------- */
if (c < 0x80) {
*out_unicode = c;
return 1;
}
/* -----------------------------------------------------------------------
* 2 字节U+0080..U+07FF如拉丁扩展、希腊文等
* 格式:首字节 110xxxxx0xC0..0xDF),次字节 10xxxxxx0x80..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含常用汉字、日韩等
* 格式:首字节 1110xxxx0xE0..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=ASCII2/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;
}
}
/* ---------- 单字节字符ASCIIn=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
View 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
View 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
View 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

View File

@@ -1,5 +1,5 @@
#ifndef PC_HMI_TCP_H
#define PC_HMI_TCP_H
#ifndef __TCP__H__
#define __TCP__H__
/*
* 跨平台 TCP 通信模块
@@ -9,8 +9,7 @@
*/
#include <stddef.h>
#include <stdint.h>
#include "../../include/types.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>

View File

@@ -31,7 +31,7 @@ static int getch(void)
#endif
/* 菜单MenuItem、MenuCtrl、Menu_InitPC、Menu_LoopStep、Menu_MapKey、KEY_* */
#include "menu.h"
#include "Drv/menu.h"
/* TCPTcp_Init/Cleanup、TcpServer_Listen/Accept/Close、TcpClient_Send/Recv/Close */
#include "TCP/tcp.h"
/* 线程Thread_Create、Thread_Join、thread_handle_t */
@@ -39,6 +39,42 @@ static int getch(void)
/* 远程显示 / TCP 服务器线程server_thread_arg_t、tcp_server_thread_fn */
#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");
#endif
/* 初始化网络库Windows 下必须最先调用,否则 Winsock 不可用) */
if (Tcp_Init() != 0) {
fprintf(stderr, "Tcp_Init failed\n");
int server_sock;
volatile int server_quit;
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;
}
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 返回后会结束循环 */

View File

@@ -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;
}

View File

@@ -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 */

View File

@@ -1,53 +1,214 @@
/*
* 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 <string.h>
#include "TCP/tcp.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)
{
server_thread_arg_t* ctx = (server_thread_arg_t*)arg; /* 强转为参数结构体 */
int server_sock = TcpServer_Listen((uint16_t)ctx->port); /* 在指定端口创建监听 socket */
if (server_sock == TCP_INVALID_SOCKET) { /* 监听失败(端口占用或权限等) */
server_thread_arg_t* ctx = (server_thread_arg_t*)arg;
int server_sock = TcpServer_Listen((uint16_t)ctx->port);
if (server_sock == TCP_INVALID_SOCKET) {
fprintf(stderr, "[TCP Server] TcpServer_Listen(%d) failed\n", ctx->port);
return; /* 线程直接退出,主线程通过 server_sock 仍为 INVALID 可知失败 */
return;
}
*ctx->p_server_sock = server_sock; /* 回写监听 socket主线程可用来关闭以结束 Accept 阻塞 */
printf("[TCP Server] 监听端口 %d等待客户端连接...\n", ctx->port);
*ctx->p_server_sock = server_sock;
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); /* 阻塞等待一个客户端连接 */
if (client == TCP_INVALID_SOCKET) { /* Accept 失败(含:主线程关闭 server_sock */
if (*ctx->p_quit) /* 若是因退出请求导致,则跳出循环 */
/* ========== 外层循环:主线程未请求退出时,持续等待并接受新客户端 ========== */
while (!*ctx->p_quit) {
/* 阻塞等待一个客户端连接;主线程关闭 server_sock 时 Accept 会失败并返回 INVALID */
int client = TcpServer_Accept(server_sock);
if (client == TCP_INVALID_SOCKET) {
if (*ctx->p_quit)
break;
continue; /* 否则忽略本次错误,继续下一次 Accept */
continue;
}
printf("[TCP Server] 客户端连接成功\n");
/* 只要该客户端还连着,就循环等待消息并回发 */
recv_len = 0; /* 新连接对应新的接收缓冲区,避免混用上一连接的残留数据 */
/* ========== 内层循环:处理当前连接上的 RemoDispBus 请求,直到断开或退出 ========== */
while (!*ctx->p_quit) {
printf("[TCP Server] 等待客户端消息\n");
memset(recv_buf, 0, sizeof(recv_buf));
int n = TcpClient_Recv(client, recv_buf, sizeof(recv_buf) - 1);
/* ----- 1. 若缓冲区中已有一条完整且校验通过的帧,则解析并分发处理 ----- */
uint8_t cmd;
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) {
printf("recv: %s\n", recv_buf);
TcpClient_Send(client, recv_buf, (size_t)n);
recv_len += (unsigned int)n;
printf("recv_len = %d\n", recv_len);
} else {
/* n==0 表示对方关闭连接n<0 表示出错,退出本连接循环 */
/* n==0 表示对方正常关闭连接n<0 表示读取出错;均退出本连接循环 */
printf("[TCP Server] 客户端关闭连接\n");
break;
}
}
/* 当前客户端处理结束,关闭该连接;外层循环继续 Accept 下一个客户端 */
TcpClient_Close(client);
}
TcpServer_Close(server_sock); /* 关闭监听 socket */
*ctx->p_server_sock = TCP_INVALID_SOCKET; /* 通知主线程 socket 已关闭,避免重复 Close */
TcpServer_Close(server_sock);
*ctx->p_server_sock = TCP_INVALID_SOCKET;
printf("[TCP Server] 已退出\n");
}
}