修改了菜单按键的逻辑,使菜单可以根据按键动态跳转,但是还没有实现对应的操作逻辑

This commit is contained in:
2026-03-12 18:01:57 +08:00
parent fb1a28df00
commit c2ce221691
13 changed files with 4241 additions and 457 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ extern uint8_t g_abyASCII168[][16];
#define UTF8_HZ12_BYTES_PER_CHAR 24
#define UTF8_HZ12_NUM_CHARS 4030
#define UTF8_HZ12_NUM_CHARS 7038
/* 按 Unicode 码点查找点阵,返回 24 字节指针,未找到返回 NULL */
const uint8_t* utf8_hz12_get(uint32_t unicode);

View File

@@ -1,34 +1,46 @@
/* 远程按键驱动模块:
* 按键数据由外部(如远程显示、串口等)写入 g_tRemoteKey
* 本模块通过 Key_Read() 供菜单等上层逻辑读取。
*/
#include "key.h"
tagRKeyCtrl g_tRemoteKey; /* 远程按键控制结构 */
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()
/******************************************************************************
* 函数名: Key_Init
* 功能: 按键模块初始化,将远程按键控制结构体复位为默认状态
* 参数: 无
* 返回: 无
* 说明: 清除有效标志、键值置 KEY_NONE、关闭远程按键使能
*****************************************************************************/
void Key_Init(void)
{
uint8_t byKeyTmp;
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NULL; /* 无新按键 */
g_tRemoteKey.byKeyValue = KEY_NONE; /* 键值清零 */
g_tRemoteKey.bUseRkey = 1; /* 不启用远程按键 */
}
/******************************************************************************
* 函数名: Key_Read
* 功能: 读取当前按键值(消费式读取)
* 参数: 无
* 返回: 按键值KEY_U/KEY_D/KEY_L/KEY_R/KEY_ENT/KEY_ESC/KEY_F1/KEY_F2 等),
* 无按键时返回 KEY_NONE
* 说明: 若存在新按键则返回键值并清除有效标志;每次调用最多消费一个按键事件
*****************************************************************************/
uint8_t Key_Read(void)
{
uint8_t byKeyTmp; /* 本次读取到的键值 */
byKeyTmp = KEY_NONE;
if(EN_KEY_FLAG_NEW == g_tRemoteKey.byKeyValid )
if (EN_KEY_FLAG_NEW == g_tRemoteKey.byKeyValid)
{
byKeyTmp = g_tRemoteKey.byKeyValue;
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NULL;
byKeyTmp = g_tRemoteKey.byKeyValue; /* 取出键值 */
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NULL; /* 消费后清除标志 */
}
return byKeyTmp;
}

View File

@@ -25,7 +25,15 @@ enum _KEY_VALID_FLAG_ { // 按键是否有效标志
EN_KEY_FLAG_SAM, // 采样过程中(未确定)
};
typedef struct{ // 按键控制结构
uint8_t byUsrFlg; // 用户标志0x55标志 有显示已刷新,供远程显示用
uint8_t byKeyValid; // 有效标志 <见 _KEY_VALID_FLAG_ 定义>
uint8_t byKeyValue; // 键值
uint8_t bUseRkey; //是否启用远程按键
}tagRKeyCtrl;
uint8_t Key_Read();
extern tagRKeyCtrl g_tRemoteKey; //远程按键
uint8_t Key_Read(void);
void Key_Init(void);
#endif

View File

@@ -1,7 +1,12 @@
#include "lcd.h"
#include <string.h>
#include "ascii.h"
#include <math.h>
#ifdef DEBUG
#include <stdio.h>
#endif
tagScreenControl g_tCVsr; // 当前界面结构指针
tagDspAttrib g_tDspAttrib; // 显示属性
@@ -14,16 +19,17 @@ void Lcd_Init(void)
g_tCVsr.wBackColor = LCD_COLOR_BLACK;
/* 设置默认字体 */
//g_tCVsr.pwLibHZ = (uint16_t*)HZK12;
g_tCVsr.wGBFontWidth = 14;
/* 字体的大小需要调试 */
g_tCVsr.wGBFontWidth = 13;
g_tCVsr.wGBFontHeight = 12;
/* 设置默认ASCII字体 */
g_tCVsr.pbyLibAscii = g_abyASCII126[0];
g_tCVsr.wASCIIFontWidth = 8;
g_tCVsr.wASCIIFontWidth = 7;
g_tCVsr.wASCIIFontHeight = 12;
g_tDspAttrib.rowSpace = 2;
}
void Lcd_SetPixel(uint16_t x, uint16_t y, uint16_t color)
void Lcd_SetPixel(uint16_t x, uint16_t y, uint8_t color)
{
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
{
@@ -86,7 +92,7 @@ void Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bo
* 2. 实际绘制区域为 [wXStart, wXEnd) × [wYStart, wYStart + wWidth)
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
*****************************************************************************/
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint16_t color)
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint8_t color)
{
uint16_t wYEnd = wYStart + wWidth; /* 计算水平线在 Y 方向的结束位置 = 起始 Y + 线宽 */
@@ -114,7 +120,7 @@ void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWid
* 2. 实际绘制区域为 [wXStart, wXStart + wWidth) × [wYStart, wYEnd)
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
*****************************************************************************/
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint16_t color)
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint8_t color)
{
uint16_t wXEnd = wXStart + wWidth;
@@ -146,7 +152,7 @@ void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWid
* 2. 其它情况使用类 Bresenham 算法,从两端向中间对称绘制,并按线宽加粗
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
*****************************************************************************/
void Lcd_Line(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend, uint16_t wWidth, uint16_t color)
void Lcd_Line(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend, uint16_t wWidth, uint8_t color)
{
uint16_t wDX; /* X 方向差值(水平偏移量的绝对值) */
uint16_t wDY; /* Y 方向差值(垂直偏移量的绝对值) */
@@ -226,7 +232,6 @@ void Lcd_Line(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend
wDecision = (wDY >> 1); /* 初始化判别值为一半的 wDY */
for (; wCury <= wNexty; ) /* 从两端向中间画,直到在 Y 方向相遇 */
{
/* 累积误差超过一条“格子高度”时,说明需要在 X 方向跨一格 */
if (wDecision >= wDY)
{
@@ -343,7 +348,7 @@ 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;
uint8_t on_color, off_color;
/* 从 ASCII 字库中取得当前字符的点阵数据首地址
每个字符占用 wASCIIFontHeight 个字节, 按行存储 */
@@ -379,7 +384,7 @@ inline uint16_t Lcd_Pub_Ascii(uint16_t x, uint16_t y, uint8_t byAscii)
for (i = 0; i < g_tCVsr.wASCIIFontWidth; i++)
{
uint8_t bit_on = ((byLine << i) & 0x80) != 0;
uint16_t color;
uint8_t color;
uint16_t px, py;
if (bit_on)
@@ -423,7 +428,7 @@ inline uint16_t Lcd_Pub_Ascii(uint16_t x, uint16_t y, uint8_t byAscii)
* @param out_unicode 输出该字符的 Unicode 码点U+0000..U+FFFF
* @return 该字符占用的字节数 1/2/30 表示结束、无效或无法解析
*/
static int utf8_next(const unsigned char *utf8, uint32_t *out_unicode)
uint8_t utf8_next(const unsigned char *utf8, uint32_t *out_unicode)
{
unsigned char c = utf8[0];
@@ -477,8 +482,12 @@ 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)
{
#ifdef DEBUG
printf("Lcd_Pub_UTF8: unicode = %d, bitmap = NULL\n", unicode);
#endif
return;
}
for (uint8_t j = 0; j < g_tCVsr.wGBFontHeight; j++)
{
word = (uint16_t)((bitmap[j*2] << 8) | bitmap[j*2+1]);
@@ -509,8 +518,7 @@ 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;
uint8_t n = 0;
bakx = x;
baky = y;
@@ -519,11 +527,10 @@ int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString)
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);
n = utf8_next(pcString + index, &unicode);
if (n <= 0)
break;

View File

@@ -24,12 +24,12 @@
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; // 字符字体高度
uint8_t wFontColor; // 字体颜色
uint8_t wBackColor; // 字符显示背景颜色
uint8_t wGBFontWidth; // 汉字字体宽度
uint8_t wGBFontHeight; // 汉字字体高度
uint8_t wASCIIFontWidth; // 字符字体宽度
uint8_t wASCIIFontHeight; // 字符字体高度
uint16_t *pwLibHZ; // 汉字库地址
uint8_t *pbyLibAscii; // ASCII库地址
} tagScreenControl;
@@ -48,15 +48,16 @@ typedef struct { // 显示属性数据结构
void Lcd_Init(void);
void Lcd_SetPixel(uint16_t x, uint16_t y, uint16_t color);
void Lcd_SetPixel(uint16_t x, uint16_t y, uint8_t color);
uint16_t Lcd_GetPixel(uint16_t x, uint16_t y);
void Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y, uint32_t color);
void Lcd_MeiTouPic(uint16_t wYStart, uint16_t wWidth);
void Lcd_ButtonPush(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y);
void Lcd_Invert(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend);
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint16_t color);
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint16_t color);
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint8_t color);
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint8_t color);
uint8_t utf8_next(const unsigned char *utf8, uint32_t *out_unicode);
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

View File

@@ -4,6 +4,7 @@
#include <string.h>
#include "lcd.h"
#include "display.h"
#include "key.h"
/* 简单的静态菜单树:
*
@@ -28,13 +29,13 @@
uint16_t wPos; /* 当前菜单在本级中的位置 */
uint16_t wNum; /* 下级菜单的总项数 */
uint16_t wSPosX; // 下级菜单起始坐标
uint16_t wSPosY; // 下级菜单起始坐标
uint16_t wEPosX; // 下级菜单对角坐标
uint16_t wEPosY; // 下级菜单对角坐标
uint16_t wSPosX; /*下级菜单框 x 轴的起始坐标*/
uint16_t wSPosY; /*下级菜单框 y 轴的起始坐标*/
uint16_t wEPosX; /*下级菜单框 x 轴的结束坐标*/
uint16_t wEPosY; /*下级菜单框 y 轴的结束坐标*/
}tagMenuItem,*tagPMenuItem;
// 显示控制结构
// 显示控制结构
typedef struct
{
tagPMenuItem ptMenuCur; // 菜单当前指针
@@ -73,15 +74,15 @@ typedef struct
typedef struct
{ // 菜单控制结构
uint8_t by0LevelNum; // 0级菜单项数
uint8_t byLeftMove; // 菜单左移长度
uint8_t bySecPage; // 第二页菜单标志
uint8_t by0LevelNum; // 0级菜单项数
uint8_t byLeftMove; // 菜单左移长度
uint8_t bySecPage; // 第二页菜单标志
FUNCPTR fnExecute; // 执行函数指针
tagPMenuItem ptHead; // 菜单链表头指针
tagPMenuItem ptCurrent; // 菜单链表当前指针
tagPMenuItem ptCurrent; /* 菜单链表当前指针 */
tagPMenuItem ptRoute[4]; // 0-3级菜单的指针路径
tagPMenuItem ptCurBak; // 菜单链表指针备份
tagPMenuItem ptCurBak; /* ptCurrent 的备份通过比较ptCurBak和ptCurrent是否相等来判断是否发生移动 */
tagPMenuItem pt0Level; // 0级菜单链表指针备份
tagPMenuItem ptSeeSetSoft; // 查看定值菜单结点
@@ -92,8 +93,8 @@ typedef struct
tagPMenuItem ptSetValPara; // 整定定值菜单结点
}tagMenuCtrl,*tagPMenuCtrl;
tagDspCtrl g_tDspCtrl; // 显示控制全局结构
tagMenuCtrl g_tMenuCtrl; // 菜单全局控制结构
tagDspCtrl g_tDspCtrl; /* 显示控制全局结构 */
tagMenuCtrl g_tMenuCtrl; /* 菜单全局控制结构 */
tagMenuItem g_tMenuItem[300]; // 菜单链表空间定义
@@ -242,6 +243,28 @@ void Menu_Main_Creat(void)
ptFirst[wLoop1]->ptBefore = ptLast[wLoop1];
}
}
uint8_t utf8_len_cal(uint8_t *str)
{
uint8_t strLen = 0;
uint32_t unicode;
uint8_t index = 0;
uint8_t n = 0;
while (str[index] != '\0')
{
n = utf8_next(str + index, &unicode);
if (n > 1)
{
strLen += 2;
}
else
{
strLen++;
}
index += n;
}
return strLen;
}
/******************************************************************************
* 函数名: Menu_charLenCal
* 功能: 1.计算指定级菜单下所有子项的最大显示长度
@@ -262,6 +285,7 @@ uint8_t Menu_charLenCal(uint8_t bylevel, uint8_t *byMenuNum, tagPMenuItem *ptFir
{
uint16_t wLoop;
uint8_t byStrLen; /* 当前菜单项名称长度(含可能追加的右箭头) */
uint8_t utf8Len;
uint8_t byMaxLen; /* 同级菜单中的最大显示长度 */
/* 记录子级首项,供后续遍历与布局使用 */
@@ -277,19 +301,19 @@ uint8_t Menu_charLenCal(uint8_t bylevel, uint8_t *byMenuNum, tagPMenuItem *ptFir
for (wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++)
{
/* 计算当前菜单项名称长度 */
utf8Len = utf8_len_cal(ptIndex[bylevel]->byName);
byStrLen = (uint8_t)strlen((const char *)ptIndex[bylevel]->byName);
/* 若有下级且名称末尾尚无右箭头,则追加 '\x10' 并更新长度 */
if ((ptIndex[bylevel]->ptLower != NULL) && ('\x10' != ptIndex[bylevel]->byName[byStrLen - 1]))
{
ptIndex[bylevel]->byName[byStrLen] = '\x10';
ptIndex[bylevel]->byName[byStrLen + 1] = '\0';
byStrLen += 1;
utf8Len += 1;
}
if (byMaxLen < byStrLen)
if (byMaxLen < utf8Len)
{
byMaxLen = byStrLen;
byMaxLen = utf8Len;
}
#ifdef DEBUG
printf("计算%d级菜单位置:%s,长度%d\n", bylevel, ptIndex[bylevel]->byName, byMaxLen);
@@ -319,7 +343,7 @@ void Menu_Sub2PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptInd
uint16_t wTemp;
uint8_t byMaxLen;
uint8_t byMaxNum;
uint8_t byMenuPos;
uint16_t byMenuPos;
uint8_t byItemNum;
uint8_t byMenuNum[4];
@@ -392,55 +416,74 @@ void Menu_Sub2PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptInd
}
}
/*
* 功能:根据当前菜单层级 bylevel计算该层级所有子菜单如二级菜单的矩形框位置。
* 主要负责:
* - 统计下一级菜单的最大字符长度用于确定菜单框的宽度X 方向)
* - 根据上一级菜单条目的位置以及当前条目在本列中的位置,计算菜单框的起始/结束 Y 坐标
* - 处理菜单项超过一屏时的翻页显示逻辑,保证菜单框不超出屏幕边界
*
* 参数说明:
* bylevel :当前要计算的菜单层级(例如 1 表示一级菜单2 表示二级菜单)
* ptFirst :保存各层级“同级菜单链表首节点”的指针数组
* ptIndex 在遍历过程中使用的“当前菜单指针数组”ptIndex[bylevel] 表示当前层级正在处理的菜单项
*/
void Menu_Sub1PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex)
{
uint16_t wSPosX;
uint16_t wSPosY;
uint16_t wEPosX;
uint16_t wEPosY;
uint8_t byMenuNum[4];
uint8_t byMaxLen;
uint8_t byMaxNum;
uint16_t byMenuPos;
uint8_t byItemNum;
uint16_t wTemp;
uint16_t wSPosY; // 当前层级菜单框的纵向起始坐标Start Y
uint16_t wEPosY; // 当前层级菜单框的纵向结束坐标End Y
uint8_t byMenuNum[4]; // 各层级菜单项数量的临时缓存数组,供长度/行数计算使用
uint8_t byMaxLen; // 下一级菜单条目中,最长标题的字符数,用来确定菜单框宽度
uint8_t byMaxNum; // 在一屏竖直方向上最多能显示多少条菜单项
uint16_t byMenuPos; // 当前菜单在本层菜单链表中的序号位置(用于计算其在屏幕中的纵向位置)
uint8_t byItemNum; // 当前参与计算的“菜单项数量”(根据语境,既可能是当前层,也可能是子菜单层)
uint16_t wTemp; // 用于中间计算时的临时变量(例如交换起止坐标等)
byMaxNum = (MENU_YMAX) / LINE_HEIGHT; // 页内纵向可显示菜单数
byMaxNum = (MENU_YMAX) / LINE_HEIGHT; // 在当前屏幕高度内,竖直方向上一页最多能显示菜单
ptIndex[bylevel] = ptFirst[bylevel];
for (uint16_t wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++)
ptIndex[bylevel] = ptFirst[bylevel]; // 从本层级菜单链表的首节点开始遍历
for (uint16_t wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++) // 遍历当前层级的所有菜单项(以最大项数为上限防止死循环)
{
if ((ptIndex[bylevel]->ptLower) != NULL) // 当前1级菜单有下级菜单
if ((ptIndex[bylevel]->ptLower) != NULL) // 如果当前菜单项存在下一级菜单(即有子菜单需要显示)
{
// 统计下一级菜单的条目数和每个条目的字符长度,返回该下一级中最长标题的字符数
// 同时,把各层级菜单数量写入 byMenuNum[] 中,用于后面计算 Y 坐标和分页
byMaxLen = Menu_charLenCal(bylevel, byMenuNum, ptFirst, ptIndex); // 计算2级菜单项数及字符数
/*X坐标计算*/
ptIndex[bylevel]->wSPosX = ptIndex[bylevel - 1]->wEPosX; // 2级菜单矩形框横坐标起始
ptIndex[bylevel]->wEPosX = ptIndex[bylevel]->wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD; // 2级菜单矩形框横坐标终止
/* X 坐标计算:二级(或更深层级)菜单的矩形框紧挨着上一级菜单的右侧展开 */
ptIndex[bylevel]->wSPosX = ptIndex[bylevel - 1]->wEPosX; // 2级菜单矩形框横坐标起始 = 上一级菜单矩形框的结束 X
ptIndex[bylevel]->wEPosX = ptIndex[bylevel]->wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD; // 2级菜单矩形框横坐标终止 = 起始 X + 最大字符宽度 + 额外边框间距
byMenuPos = ptIndex[bylevel]->wPos; // 1级菜单在菜单列中的位置
byItemNum = byMenuNum[bylevel]; // 1级菜单项数
byMenuPos = ptIndex[bylevel]->wPos; // 当前菜单在本层菜单链表中的逻辑位置(第几项)
byItemNum = byMenuNum[bylevel]; // 本层菜单项数
// 当本层菜单总数超过一屏,且当前高亮菜单已经滚动到非第一页时,
// 需要对菜单在当前页中的显示位置进行折算,实现“↑/↓”翻页效果。
if ((byItemNum > byMaxNum) && (byMenuPos >= byMaxNum)) // 1级菜单项数大于一页且1级菜单当前位置不在第一页
{
byMenuPos = byMaxNum - (byItemNum - byMenuPos); /* 第一页尾显示↓ 第二页头显示↑ */
}
// 根据“上一级菜单框的起始 Y 坐标”和“当前菜单位于本层中的序号”,
// 计算出当前子菜单框在 Y 方向上的起始位置,使子菜单在对应父菜单项的右侧对齐。
wSPosY = ptIndex[bylevel - 1]->wSPosY + (byMenuPos - 1) * LINE_HEIGHT; // 计算2级菜单框起始坐标
byItemNum = byMenuNum[bylevel + 1]; // 2级菜单项数
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算2级菜单框终止坐标
byItemNum = byMenuNum[bylevel + 1]; // 下一级(子菜单项数
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算2级菜单框终止坐标 = 起始 Y + 所有子项高度 + 边框间距
#ifdef DEBUG
printf("%d %d %d %d \n", bylevel, byItemNum, wSPosY, wEPosY);
#endif
// 情况 1子菜单框整体没有超过屏幕的最大 Y 边界,直接采用计算结果
if (wEPosY < MENU_YMAX) /* 菜单框没有超出边界 */
{
ptIndex[bylevel]->wSPosY = wSPosY; // 2级菜单矩形框纵起始坐标定位
ptIndex[bylevel]->wEPosY = wEPosY; // 2级菜单矩形框纵终止坐标定位
}
else /* 菜单框超出边界 */
else /* 情况 2子菜单框在向下展开时超出了屏幕底部需要整体向上“提”一段 */
{
// 反向计算:尝试让子菜单从当前高亮项“往上展开”,
// 使最后一项与当前父菜单项对齐,从而避免超出底部。
wEPosY = wSPosY - (byItemNum - 1) * LINE_HEIGHT - MENU_YADD;
if ((wEPosY > LINE_HEIGHT) && (wEPosY < MENU_YMAX)) // 菜单向上不越限
{
@@ -497,6 +540,7 @@ void Menu_Sub1PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptInd
* 3. 下拉框宽度由 Menu_charLenCal 返回的 byMaxLen 与字宽 CN_WITDTH 决定
* 4. 若下拉框超出屏幕顶部wEPosY < CN_YMAX则 wEPosY 取屏高,需分页显示
* 5. 2 级及以下子菜单坐标由 Menu_Sub1PosCal 递归计算
* 注意:当前还有一个 bug没有检查坐标的上下界导致按照上下界清屏会清不干净
*****************************************************************************/
void Menu_PositionCal(tagPMenuItem ptMenuHead)
{
@@ -521,18 +565,21 @@ void Menu_PositionCal(tagPMenuItem ptMenuHead)
if (ptIndex[0]->ptLower != NULL) /* 有下级菜单 */
{
/* 计算 1 级子项最大长度,并为其名称追加右箭头;更新 ptFirst/ptIndex/byMenuNum */
byMaxLen = Menu_charLenCal(0, byMenuNum, ptFirst, ptIndex);
/* 左上角坐标计算 */
/* 1 级下拉框左上角:按 0 级序号横向定位,纵向从屏底向上预留多行高度 */
ptIndex[0]->wSPosX = (byMenuNum[0] - 1) * byInterval;
ptIndex[0]->wSPosY = LCD_SIZE_Y - LINE_HEIGHT - byMenuNum[1] * LINE_HEIGHT - MENU_YADD;
/* 右下角坐标计算 */
/* 下拉框右下角 X起始 + 最大字符数×字宽 + 边距 */
ptIndex[0]->wEPosX = ptIndex[0]->wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD;
/* 下拉框右下角 Y与 0 级菜单底边齐平,若超出屏顶则取屏高(需分页) */
ptIndex[0]->wEPosY = LCD_SIZE_Y - LINE_HEIGHT;
#ifdef DEBUG
printf("Menu_PositionCal: wLoop = %d, byMaxLen = %d, ptIndex[0]->wEPosX = %d, ptIndex[0]->wEPosY = %d\n", wLoop, byMaxLen, ptIndex[0]->wEPosX, ptIndex[0]->wEPosY);
#endif
/* 递归计算 2 级及以下子菜单的显示坐标 */
Menu_Sub1PosCal(1, ptFirst, ptIndex);
@@ -563,9 +610,9 @@ void Menu_PositionCal(tagPMenuItem ptMenuHead)
void Menu_BoundaryBox(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y)
{
/* 填充按钮内部区域Y 从 top_y+1 到 bottom_y-1X 从 left_x+1 到 right_x-1 */
for (uint16_t y = top_y + 1; y < bottom_y; y++)
for (uint16_t y = top_y; y < bottom_y; y++)
{
for (uint16_t x = left_x + 1; x < right_x; x++)
for (uint16_t x = left_x; x < right_x; x++)
{
Lcd_SetPixel(x, y, g_tCVsr.wBackColor); /* 设置按钮内部像素为前景色,实现实心效果 */
}
@@ -708,7 +755,6 @@ else if (byMenuNum > 20)
* 函数名: Menu_Show_Other
* 功能: 显示除 0 级以外的其它级别菜单1 级 / 2 级 / 3 级)的下拉列表
* 参数: bylevel - 要显示的菜单层级1 表示 1 级菜单2 表示 2 级菜单等)
* byLeftMove - 菜单整体在 X 方向的左移像素数(用于横向滚动或对齐)
* 返回: 无
* 说明: 1. 使用 g_tMenuCtrl.ptRoute[bylevel] 获取本级菜单框坐标,并应用水平偏移
* 2. 调用 Menu_BoundaryBox 绘制当前级菜单的背景边框矩形
@@ -724,11 +770,9 @@ void Menu_Show_Other(uint8_t bylevel)
uint16_t wPosX; /* 菜单文本显示的起始 X 坐标 */
uint16_t wPosY; /* 菜单文本显示的起始 Y 坐标 */
uint16_t byMenuNum; /* 本级菜单项总数,用于控制循环次数 */
uint16_t byMenuPos; /* 当前菜单项在本级中的序号1..byMenuNum */
uint8_t byName[50]; /* 临时缓冲区,用于拷贝菜单名称 */
/* 1. 基于当前层级路径结点,取得本级菜单框坐标并根据 byLeftMove 做水平偏移 */
/* 1. 获取当前层级路径结点,用于绘制本级菜单的背景边框矩形 */
ptIndex = g_tMenuCtrl.ptRoute[bylevel];
/* 绘制本级菜单的背景边框矩形 */
Menu_BoundaryBox(ptIndex->wSPosX, ptIndex->wSPosY, ptIndex->wEPosX, ptIndex->wEPosY);
@@ -739,14 +783,16 @@ void Menu_Show_Other(uint8_t bylevel)
ptIndex = ptRoute; /* 从选中项开始向后遍历同级菜单 */
/* 3. 逐项绘制菜单文字,并对选中项所在行做反显处理 */
wPosX = ptIndex->wSPosX + 4; /* 文本相对左边框右移 4 像素,预留内边距 */
wPosX = g_tMenuCtrl.ptRoute[bylevel]->wSPosX + 4; /* 文本相对左边框右移 4 像素,预留内边距 */
for (uint16_t index = 0; index < g_tMenuCtrl.ptRoute[bylevel]->wNum; index++)
{
wPosY = g_tMenuCtrl.ptRoute[bylevel]->wSPosY + (ptIndex->wPos - 1) * LINE_HEIGHT + 3; /* 行顶坐标 + 行高 * 行号 + 3 像素微调 */
memcpy(byName, ptIndex->byName, 50); /* 将菜单名称拷贝到临时缓存 */
Lcd_ShowStr(wPosX, wPosY, byName); /* 在计算出的坐标位置显示菜单字符串 */
if (ptRoute == ptIndex) /* 若当前绘制项为“选中项” */
#ifdef DEBUG
printf("Menu_Show_Other: wPosX = %d, wPosY = %d, byName = %s\n", wPosX, wPosY, byName);
#endif
if(ptRoute == ptIndex) /* 若当前绘制项为“选中项” */
{
/* 对该行对应的矩形区域执行反显,用于高亮当前选择 */
/* x轴起始位置+2结束位置-2产生内嵌的感觉 */
@@ -774,12 +820,222 @@ void Menu_Show_0Level()
Lcd_MeiTouPic(16, 2 );
Lcd_ShowStr(16, 20, (uint8_t*)"当前模式: 无模式" );
}
/******************************************************************************
* 函数名: Menu_Show_Proc
* 功能: 根据当前菜单状态刷新 0~3 级菜单显示(增量刷新或整体刷新)
* 情况一0 级路径发生变化(如 ESC 退回或跨列切换):需要整体刷新
* 情况二0 级路径未变,但当前结点发生变化
1. 同级内上下移动: 仅重画本级下拉菜单current->byClass-1 对应的菜单框索引)
2. 进层: 仅重画本级下拉菜单current->byClass-1 对应的菜单框索引)
3. 退层:清屏并重画 0~3 级菜单(这是最简单的操作,但是比较耗费资源)
所以1. 2. 的逻辑可以合并为仅重画本级下拉菜单current->byClass-1 对应的菜单框索引)
* 参数: 无(依赖全局 g_tMenuCtrl、g_tDspCtrl、g_tCVsr
* 返回: 无
* 说明: 该函数后续可以改进退层的逻辑,减少计算量
* 注意: 超出屏幕范围的逻辑还没有处理
*****************************************************************************/
void Menu_Show_Proc(void)
{
uint8_t bNeedFresh; /* 是否需要整体刷新 0~3 级菜单1=是0=否) */
#ifdef DEBUG
printf("Menu_Show_proc进入\n");
#endif
bNeedFresh = 0; /* 默认不整体刷新,仅增量更新 */
if (g_tDspCtrl.bFirst) /* 第一次进入菜单层标志 */
{
g_tDspCtrl.bFirst = 0; /* 清除首次标志 */
bNeedFresh = 1; /* 强制整体刷新 */
}
/* 情况一0 级路径发生变化(如 ESC 退回或跨列切换) */
if (g_tMenuCtrl.pt0Level != g_tMenuCtrl.ptRoute[0])
{
bNeedFresh = 1; /* 需要整体刷新 */
Lcd_FillRect(0, MENU_YMIN, LCD_SIZE_X, MENU_YMAX, g_tCVsr.wBackColor); /* 清除 1~3 级菜单区域 */
g_tMenuCtrl.pt0Level = g_tMenuCtrl.ptRoute[0]; /* 更新 0 级路径起点 */
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent; /* 更新备份指针 */
}
else if (g_tMenuCtrl.ptCurBak != g_tMenuCtrl.ptCurrent) /* 情况二0 级路径未变,但当前结点发生变化 */
{
if (g_tMenuCtrl.ptCurrent->byClass >= g_tMenuCtrl.ptCurBak->byClass) /* 2.1 同级内上下移动 或者 进层*/
{
#ifdef DEBUG
printf("同级内上下移动 或 进层:从 %d 级到 %d 级\n", g_tMenuCtrl.ptCurBak->byClass, g_tMenuCtrl.ptCurrent->byClass);
#endif
Menu_Show_Other(g_tMenuCtrl.ptCurrent->byClass - 1); /* 仅重画本级下拉菜单byClass-1 对应 1~3 级菜单框索引) */
/*由于刚开是已经判断了 0 层没有发生变化,因此 ptCurrent->byClass 一定是大于 1 的,所以不需要判断*/
}
else /* 2.2 级别减小(退层,如从 2 级退回 1 级) */
{
#ifdef DEBUG
printf("退层:从 %d 级退回 %d 级\n", g_tMenuCtrl.ptCurBak->byClass, g_tMenuCtrl.ptCurrent->byClass);
#endif
Lcd_FillRect(0, MENU_YMIN, LCD_SIZE_X, MENU_YMAX, g_tCVsr.wBackColor); /* 擦除 1~3 级区域(保留 0~32 像素标题栏) */
bNeedFresh = 1;
}
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent; /* 无论何种情况,更新备份指针为最新当前结点 */
}
/* 情况三:需要整体刷新 0~3 级菜单 */
if (bNeedFresh > 0)
{
Menu_Show_0Level(); /* 显示 0 级菜单 */
for (uint8_t index = 0; index < g_tMenuCtrl.ptCurrent->byClass; index++) /* 依次显示 1 级、2 级、3 级(直到当前层级) */
{
Menu_Show_Other(index);
}
}
#ifdef DEBUG
printf("Menu_Show_proc结束\n");
#endif
}
/******************************************************************************
* 函数名: Menu_Route
* 功能: 菜单层按键调度:读键并更新当前菜单结点与路径,必要时刷新显示
* 参数: 无
* 返回: 无
* 说明: 1. 首次进入时只调用 Menu_Show_Proc 显示当前路径
* 2. 上/下键同级移动;左键退上一级或上一列 0 级;右键/确认进入下级或执行叶子结点
* 3. ESC 在 1 级退回“主界面”并重算布局;在 2~3 级仅退上一级
* 4. 键值使用 key.h 中的 KEY_U/KEY_D/KEY_L/KEY_R/KEY_ENT/KEY_ESC/KEY_F1/KEY_F2
*****************************************************************************/
void Menu_Route(void)
{
tagPMenuItem ptCurrent; /* 当前光标所在菜单结点(可能被按键修改) */
tagPMenuItem ptHead; /* 0 级菜单头结点,用于 ESC 退回与路径重置 */
tagPMenuItem ptIndex; /* 路径回溯时的遍历指针 */
tagPMenuItem *ptRoute; /* 指向 g_tMenuCtrl.ptRoute便于按层级写入路径 */
uint8_t byKeyVal; /* 本次读到的键值KEY_U/KEY_D/KEY_L/KEY_R/KEY_ENT/KEY_ESC 等) */
uint8_t bNeedFresh; /* 本次按键是否导致需要重绘菜单1=是0=否) */
bNeedFresh = 0; /* 默认不刷新,仅当按键改变 ptCurrent 时置 1 */
ptCurrent = g_tMenuCtrl.ptCurrent; /* 取出当前选中的菜单结点 */
ptHead = g_tMenuCtrl.ptHead; /* 取出 0 级头结点 */
ptRoute = g_tMenuCtrl.ptRoute; /* 取出路径数组指针,用于更新 ptRoute[0..3] */
if (g_tDspCtrl.bFirst) /* 第一次进入菜单层:不读键,直接按当前路径刷新显示 */
{
Menu_Show_Proc(); /* 内部会清 bFirst 并绘制 0~当前级菜单 */
}
else /* 非首次:读键并根据键值更新 ptCurrent */
{
byKeyVal = Key_Read(); /* 从 Key_Read 获取键值(无键时为 KEY_NONE */
switch (byKeyVal)
{
case KEY_F1: /* F1 当前项目未使用,忽略 */
break;
case KEY_F2: /* F2 同上 */
break;
case KEY_U: /* 上键切换到同级上一项ptBefore */
ptCurrent = ptCurrent->ptBefore;
bNeedFresh = 1;
break;
case KEY_D: /* 下键切换到同级下一项ptBehind */
ptCurrent = ptCurrent->ptBehind;
bNeedFresh = 1;
break;
case KEY_L: /* 左键:退到上一级路径结点,若已在 0 级则退到上一列 0 级再进其下级 */
ptCurrent = ptRoute[ptCurrent->byClass - 1]; /* 先退到上一级路径结点 */
if (0 == ptCurrent->byClass) /* 若退完后是 0 级,则再退一列或两列并进下级 */
{
if (1 == ptCurrent->wPos) /* 当前在第 1 列:退两列再进下级 */
{
ptCurrent = ptCurrent->ptBefore->ptBefore;
ptCurrent = ptCurrent->ptLower;
}
else /* 其它列:退一列再进下级 */
{
ptCurrent = ptCurrent->ptBefore;
ptCurrent = ptCurrent->ptLower;
}
}
bNeedFresh = 1;
break;
case KEY_R: /* 右键与确认键统一处理 */
case KEY_ENT:
if (ptCurrent->ptLower != NULL) /* 有下级:进入下一层,光标移到下级首项 */
{
ptCurrent = ptCurrent->ptLower;
bNeedFresh = 1;
}
else /* 无下级(叶子结点):执行该菜单项绑定的处理函数 */
{
if (ptCurrent->pfnWinProc != NULL)
{
ptCurrent->pfnWinProc(); /* 如 MenuProc_See_AppInfo、MenuProc_See_YC 等 */
}
}
break;
case KEY_ESC: /* 退出键 */
if (1 == ptCurrent->byClass) /* 当前在 1 级:整体退出到“主界面”,重置 0 级路径与布局 */
{
g_tMenuCtrl.pt0Level = ptHead; /* 0 级路径起点设为头结点 */
g_tMenuCtrl.ptRoute[0] = ptHead; /* 路径[0] 指向 0 级头 */
g_tMenuCtrl.ptCurrent = ptHead->ptLower; /* 当前结点设为 0 级第一个子项 */
ptCurrent = g_tMenuCtrl.ptCurrent;
g_tMenuCtrl.ptCurBak = ptCurrent; /* 备份当前结点 */
g_tMenuCtrl.ptRoute[1] = ptCurrent; /* 路径[1] 指向该 1 级项 */
g_tMenuCtrl.byLeftMove = 0; /* 左移偏移清零 */
return; /* 直接返回,本次不再调用 Menu_Show_Proc由下次 Menu_Route 或外部刷新) */
}
else
{ /* 当前在 2 或 3 级:仅退到上一级路径结点 */
ptCurrent = ptRoute[ptCurrent->byClass - 1];
bNeedFresh = 1;
}
break;
default: /* 其它键值(含 KEY_NONE忽略 */
break;
}
}
if (bNeedFresh) /* 若按键导致菜单位置变化,则更新全局并刷新显示 */
{
g_tMenuCtrl.ptCurrent = ptCurrent; /* 更新当前选中的菜单项 */
if (g_tMenuCtrl.ptCurBak != ptCurrent) /* 当前菜单项相对上次备份有变化,需更新路径数组 */
{
ptIndex = g_tMenuCtrl.ptCurrent;
for (uint16_t index = 0; index < MENU_MAX_ITEM; index++) /* 从当前结点沿 ptHigher 回溯到 0 级 */
{
if (NULL == ptIndex->ptHigher) /* 无上级(不应出现,保险起见退到 ptBefore */
{
ptIndex = ptIndex->ptBefore;
}
else /* 有上级:先回到上级,再将该级路径记录到 ptRoute[byClass] */
{
ptIndex = ptIndex->ptHigher;
g_tMenuCtrl.ptRoute[ptIndex->byClass] = ptIndex;
}
if (0 == ptIndex->byClass) /* 已回溯到 0 级,结束 */
{
break;
}
}
g_tMenuCtrl.ptRoute[g_tMenuCtrl.ptCurrent->byClass] = ptCurrent; /* 当前所在层路径指向新结点 */
}
#ifdef DEBUG
printf("Menu_Route: g_tMenuCtrl.ptCurrent.byClass = %d\n", g_tMenuCtrl.ptCurrent->byClass);
printf("Menu_Route: g_tMenuCtrl.ptCurBak.byClass = %d\n", g_tMenuCtrl.ptCurBak->byClass);
for (uint16_t index = 0; index < 4; index++)
{
printf("Menu_Route: g_tMenuCtrl.ptRoute[%d].byName = %s\n", index, g_tMenuCtrl.ptRoute[index]->byName);
}
#endif
Menu_Show_Proc(); /* 按新路径刷新 0~当前级菜单显示 */
}
}
void Menu_Init(void)
{
Lcd_Init();
/* 初始化显示控制结构 */
memset(&g_tDspCtrl, 0, sizeof(g_tDspCtrl));
g_tDspCtrl.bFirst = 1;
Menu_0LevelNumCal(); /* 统计 0 级菜单项个数 */
Menu_Main_Creat(); /* 创建菜单树 */
@@ -787,14 +1043,14 @@ void Menu_Init(void)
Menu_PositionCal(g_tMenuCtrl.ptHead); /* 菜单框位置计算 */
g_tMenuCtrl.ptRoute[0] = &g_tMenuItem[0]; // 0级路径初始化
g_tMenuCtrl.ptCurrent = g_tMenuCtrl.ptHead->ptLower; // 当前指针初始化
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent;
g_tMenuCtrl.pt0Level = g_tMenuCtrl.ptRoute[0]; /* 供 Menu_Show_Proc 判断 0 级路径是否变化 */
g_tMenuCtrl.ptCurrent = g_tMenuCtrl.ptHead->ptLower; /* 菜单当前指针初始化为0级第一个子项 */
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent; /* 菜单备份指针初始化 */
g_tMenuCtrl.ptRoute[1] = g_tMenuCtrl.ptCurrent; // 1级路径初始化
g_tMenuCtrl.ptRoute[2] = g_tMenuCtrl.ptCurrent; // 1级路径初始化
g_tMenuCtrl.ptRoute[3] = g_tMenuCtrl.ptCurrent; // 1级路径初始化
g_tMenuCtrl.byLeftMove = 0;
g_tMenuCtrl.bySecPage = 0;
Menu_Show_0Level(); /* 显示 0 级菜单 */
Menu_Show_Other(0);
}

View File

@@ -12,43 +12,25 @@
#include <stdint.h>
#include "lcd.h" /* MENU_YMAX 依赖 LCD_SIZE_Y */
#define CN_HEIGHT 12 // 菜单汉字高
#define CN_HEIGHT 12 /* 菜单汉字高 */
#define CN_ROWSPACE 2 // 菜单字符行间隔
#define LINE_HEIGHT (CN_HEIGHT + CN_ROWSPACE) // 字符行间隔
#define MENU_XADD 7 // 菜单框横坐标增加
#define MENU_YADD 4 // 菜单框纵坐标增加
#define MENU_WITDTH 7 // 菜单汉字13×12点阵
#define MENU_XADD 4 // 菜单框横坐标增加
#define MENU_YADD 4 // 菜单框纵坐标增加
#define MENU_WITDTH 7 /*ASCII字体宽度 7因此单个字节显示的宽度是 7 个像素点*/
#define MENU_YMIN 0 // 菜单Y坐标的最小值
#define MENU_YMAX (LCD_SIZE_Y - 16) // 菜单Y坐标的最大值
#define MENU_YMIN 0 // 菜单 Y 坐标的最小值 顶部边界 0
#define MENU_YMAX (LCD_SIZE_Y - 12) // 菜单 Y 坐标的最大值 底部边界 LCD_SIZE_Y - 16
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_Route(void);
void Menu_Show_Proc(void);
/* 非功能键处理 */
void Menu_NonPfunc();

View File

@@ -30,56 +30,18 @@ static int getch(void)
}
#endif
/* 菜单MenuItem、MenuCtrl、Menu_InitPC、Menu_LoopStep、Menu_MapKey、KEY_* */
#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 */
#include "thread_utils.h"
/* 远程显示 / 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;
}
#include "Drv/key.h"
#include "thread_utils.h"
/* ----------------------------------------------------------------------------
* 程序入口
* ---------------------------------------------------------------------------- */
int main(void)
{
uint8_t count = 0;
#ifdef _WIN32
/* Windows将控制台代码页设为 UTF-8避免中文乱码如“监听”“退出”等 */
@@ -89,18 +51,36 @@ int main(void)
int server_sock;
volatile int server_quit;
thread_handle_t server_th;
server_thread_arg_t server_arg;
server_sock = TCP_INVALID_SOCKET;
server_quit = 0;
server_arg.port = 7003;
server_arg.p_server_sock = &server_sock;
server_arg.p_quit = &server_quit;
printf("开始初始化菜单树...\n");
Menu_Init(); /* 初始化菜单树(运行界面/定值设置/查看数据等) */
Key_Init(); /* 初始化按键 */
printf("PC 端 HMI 菜单模拟启动TCP 服务在单独线程,端口 7003\n");
/* 7003 为 RemoDispBus 默认端口,与 remo_disp_server.py 一致 */
if (StartTcpServerThread(7003, &server_sock, &server_quit, &server_th) != 0)
if (StartTcpServerThread(&server_th, &server_arg) != 0)
{
return 1;
}
while(1) {
while(1)
{
Menu_Route();
Sleep(20);
count++;
if(count > 50)
{
count = 0;
Menu_Show_Proc();
}
}
server_quit = 1; /* 通知服务器线程退出:下一轮 while 或 Accept 返回后会结束循环 */

View File

@@ -8,11 +8,10 @@
#include <stdio.h>
#include <string.h>
#include "remoteDisplay.h"
#include "Drv/lcd.h"
#include "TCP/tcp.h"
#include "Drv/key.h"
#include "remoteDisplay.h"
/* RemoDispBus 协议常量(与 remo_disp_server.py 一致) */
#define TAG_CLIENT 0xAAu
@@ -81,7 +80,6 @@ static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int
{
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];
@@ -94,11 +92,14 @@ static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int
payload[3] = start_addr & 0xFF;
unsigned int copy_len = LCD_DISPLAYMEMORYSIZE;
if (start_addr < 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 {
}
else
{
copy_len = 0;
}
send_reply(client, CMD_LCDMEM, payload, 4 + copy_len);
@@ -122,9 +123,11 @@ static void handle_cmd_init(int client)
/* 处理 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);
#ifdef DEBUG
printf("handle_cmd_key: 0x%02X\n", data[0]);
#endif
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NEW;
g_tRemoteKey.byKeyValue = data[0];
}
/* 处理 CMD_KEEPLIVE保活回空 */
@@ -145,14 +148,15 @@ void tcp_server_thread_fn(void* arg)
printf("[TCP Server] RemoDispBus 监听端口 %d等待客户端连接...\n", ctx->port);
#define REMO_BUF_SIZE 4096
uint8_t recv_buf[REMO_BUF_SIZE];
uint8_t recv_buf[4096];
unsigned int recv_len = 0;
/* ========== 外层循环:主线程未请求退出时,持续等待并接受新客户端 ========== */
while (!*ctx->p_quit) {
/* 阻塞等待一个客户端连接;主线程关闭 server_sock 时 Accept 会失败并返回 INVALID */
int client = TcpServer_Accept(server_sock);
if (client == TCP_INVALID_SOCKET) {
if (client == TCP_INVALID_SOCKET)
{
if (*ctx->p_quit)
break;
continue;
@@ -161,13 +165,16 @@ void tcp_server_thread_fn(void* arg)
recv_len = 0; /* 新连接对应新的接收缓冲区,避免混用上一连接的残留数据 */
/* ========== 内层循环:处理当前连接上的 RemoDispBus 请求,直到断开或退出 ========== */
while (!*ctx->p_quit) {
while (!*ctx->p_quit)
{
/* ----- 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) {
if (parse_frame(recv_buf, recv_len, &cmd, &pdata, &data_len, &consume))
{
switch (cmd)
{
case CMD_KEEPLIVE:
handle_cmd_keeplive(client);
break;
@@ -191,14 +198,17 @@ void tcp_server_thread_fn(void* arg)
}
/* ----- 2. 缓冲区中尚无完整帧:先防止溢出,再收一批数据 ----- */
if (recv_len >= REMO_BUF_SIZE - 256) {
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)
{
recv_len += (unsigned int)n;
printf("recv_len = %d\n", recv_len);
} else {
}
else
{
/* n==0 表示对方正常关闭连接n<0 表示读取出错;均退出本连接循环 */
printf("[TCP Server] 客户端关闭连接\n");
break;
@@ -212,3 +222,27 @@ void tcp_server_thread_fn(void* arg)
*ctx->p_server_sock = TCP_INVALID_SOCKET;
printf("[TCP Server] 已退出\n");
}
/* ----------------------------------------------------------------------------
* 启动 TCP 服务与服务器线程Tcp_Init + 创建线程 + 短暂等待就绪)
* port: 监听端口(如 7070
* out_server_sock: 输出监听 socket供主线程退出时 TcpServer_Close
* out_server_quit: 输出退出标志,主线程置 1 通知服务器线程退出
* out_server_th: 输出线程句柄,供主线程 Thread_Join
* 返回0 成功1 失败(已调用 Tcp_Cleanup
* ---------------------------------------------------------------------------- */
int StartTcpServerThread(thread_handle_t *out_server_th, server_thread_arg_t *io_server_arg)
{
if (Tcp_Init() != 0) {
fprintf(stderr, "Tcp_Init failed\n");
return 1;
}
if (Thread_Create(tcp_server_thread_fn, io_server_arg, out_server_th) != 0) {
fprintf(stderr, "Thread_Create(server) failed\n");
Tcp_Cleanup();
return 1;
}
Sleep(200);
return 0;
}

View File

@@ -1,5 +1,9 @@
#ifndef REMOTE_DISPLAY_H
#define REMOTE_DISPLAY_H
#ifndef __REMOTEDISPLAY_H
#define __REMOTEDISPLAY_H
#include "thread_utils.h"
/*
* 远程显示 / TCP 服务器相关接口
@@ -14,8 +18,7 @@ typedef struct {
volatile int* p_quit; /* 主线程置 1 通知线程退出 */
} server_thread_arg_t;
/* TCP 服务器线程函数:在独立线程中监听端口并处理客户端收/发 */
void tcp_server_thread_fn(void* arg);
int StartTcpServerThread(thread_handle_t *out_server_th, server_thread_arg_t *io_server_arg);
#endif /* REMOTE_DISPLAY_H */
#endif