typedef struct menu { //定义一个菜单
uint8_t range_from,range_to; //当前显示的项开始及结束序号
uint8_t itemCount;//项目总数
uint8_t selected;//当前选择项
char menuItems[SUBMENUS][17];//菜单项目
struct menu *subMenus[SUBMENUS];//子菜单
struct menu *parent;//上级菜单 ,如果是顶级则为null
int (*func[SUBMENUS])(void);//选择相应项按确定键后执行的函数
float funcValue[SUBMENUS];
uint32_t menuId[SUBMENUS];
}Menu;
/*
* 添加下级单项菜单函数
* menu: 菜单指针
* submenus: 下级单项菜单
* index: 索引值
* return:索引值 error:-1
*/
int Menu_addItem(Menu *menu, Menu * subMenu, uint8_t index) {
if (menu == NULL) {
DEBUGOUT("menu is null");
return -1;
}
if (index > menu->itemCount) {
DEBUGOUT("index too big");
return -2;
}
//menu的下级菜单索引值[i]为subMenu
menu->subMenus[index] = subMenu;
menu->menuId[index+1] = (menu->menuId[0] << 4) | (index + 1);
subMenu->menuId[0] = menu->menuId[index+1];
//subMenu的上级菜单为menu
subMenu->parent = menu;
//printf("menu->id=%02x, submenu->id=%02x\r\n", menu->menuId[0], subMenu->menuId[0]);
return index;
}
这个函数是进行下级菜单的插入,进行菜单的深度插入。
同时,将下级菜单的父级菜单绑定到当前菜单上,这样使用返回的时候,可以跳转到本机菜单。
char Menu_addFunc(Menu * menu, int(*fun)(void), uint8_t index) {
if (menu == NULL) {
DEBUGOUT("menu is null");
return 0;
}
if (index > menu->itemCount) {
DEBUGOUT("index is too big");
return 0;
}
if (fun == NULL) {
DEBUGOUT("function is null");
return 0;
}
//添加调用函数
menu->func[index] = fun;
menu->menuId[index+1] = (menu->menuId[0] << 4) | (index + 1);
return 1;
}
进行参数判断
把函数绑定到菜单的函数列表中
/*
* 返回上级菜单
*
* menu: 菜单指针
*/
void Menu_return(Menu * menu) {
if (menu == 0){
DEBUGOUT("menu is null");
return ;
}
// LCD清屏操作
f_LCD_clear = SET;
if (LCD_TaskFlag & TASK_SET) {
LCD_TaskFlag &= ~TASK_SET;
} else {
//如果有上级菜单的话
if (menu->parent) {
currentMenu = menu->parent;
}
}
}
会去判断
menu->parent
是否存在,如果存在,则跳转到上级菜单。
返回上级菜单,从最低级菜单,一层层返回到第高级菜单上。
如果没有上级菜单,则不进行刷新界面。
/*
* 进入子菜单
* menu: 菜单指针
* index: 子菜单索引
* 如果存在函数,首先运行函数。
*/
void Menu_enter(Menu * menu, uint16_t index) {
// LCD清屏操作
f_LCD_clear = SET;
//索引值大于项目总数 且 不具有下级子菜单
if (index >= menu->itemCount
|| (menu->subMenus[index] == NULL
|| menu->subMenus == NULL)) {
//如果存在函数 执行函数
if (menu->func != NULL && menu->func[index] != NULL) {
LCD_TaskFlag |= TASK_SET;
// 执行函数
(*menu->func[index])();
}
} else {
// 进入子菜单
currentMenu = menu->subMenus[index];
}
}
判断当前选中菜单是否大于总数 && 下级菜单列表是否为空 && 选中菜单项是否有下级菜单
如果不满足,则进入函数选择分支
判断当前选中函数列表是否为空 当前选中函数指针是否为空
如果有可执行函数,置函数标志位LCD_TaskFlag |= TASK_SET;
接下来直接执行函数(*menu->func[index])();
如果不满足上述条件,则说明有下级菜单。
//LCD显示函数重定义
#define LCD_displayStr my_LCD_Display_String
#define LCD_displayStrRev my_LCD_Display_String_Reverse
#define LCD_displayChar my_LCD_Display_Char
#define LCD_displayCharRev my_LCD_Display_Char_Reverse
#define LCD_Clear my_LCD_Display_Clear
这些函数在
display.c
中直接定义,如果需要在LCD
屏幕上实现,则只需要实现这几个函数就可以
void my_LCD_Display_String(unsigned char x, unsigned char y, char *displayStr) {
uint8_t col, len;
for (col = 0;col < strlen(displayStr);col++) {
LCD_Screen[x][y+col] = displayStr[col];
}
}
void my_LCD_Display_String_Reverse(unsigned char x, unsigned char y, char *displayStr) {
uint8_t col, len;
for (col = 0;col < strlen(displayStr);col++) {
LCD_Screen[x][y+col] = displayStr[col];
}
}
void my_LCD_Display_Clear(void) {
uint8_t row;
if (!f_LCD_clear) {
return ;
}
for (row = 0;row < 4;row++) {
my_LCD_Display_String(row, 0, MSG_LCD_NULL);
}
}
声明一个数组
LCD_Screen
,用来存放需要显示的字符,共分为4行16列
显示时,显示LCD_Screen
的数据即可。
#define FOREGROUND_WHITE (FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_RED)
#define BACKGROUND_WHITE (BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_RED)
#define normal_display(x, y) \
do { \
pos.X = 50+y;\
pos.Y = 10+x;\
SetConsoleCursorPosition(hOut, pos); \
SetConsoleTextAttribute(hOut, BACKGROUND_WHITE);\
} while (0)
#define rev_display(x, y) \
do { \
pos.X = 50+y;\
pos.Y = 10+x;\
SetConsoleCursorPosition(hOut, pos); \
SetConsoleTextAttribute(hOut, BACKGROUND_GREEN);\
} while (0)
void LCD_Dispaly(void) {
uint8_t row = 0, col = 0;
for (row = 0;row < 4;row++) {
if (row != currentMenu->selected) {
for (col = 0;col < 16;col++) {
normal_display(row, col);
dchar(LCD_Screen[row][col]);
}
} else {
for (col = 0;col < 16;col++) {
rev_display(row, col);
dchar(LCD_Screen[row][col]);
}
}
}
}
normal_display() 是进行正常显示
rev_display() 是进行反向显示
因为我是在上位机C语言环境下进行调试,所以这个是可以在控制台上输出正向和反向打印。
/*
* 以下程序,可以放置在本文件外部
* 属于本菜单程序实例
*/
//一级菜单
Menu fMenu = {
// form, to, count, selected
0, 3, 4, 0,
// items string
{
"1",
"2",
"3",
"4",
},
};
// 二级菜单
Menu Menu_Level = {
// form, to, count, selected
0, 2, 3, 0,
// items string
{
"11",
"12",
"13",
},
};
Menu Menu_Temp = {
// form, to, count, selected
0, 0, 1, 0,
// items string
{
"21",
},
};
Menu Menu_Alarm = {
// form, to, count, selected
0, 1, 2, 0,
// items string
{
"211",
"212",
},
};
//int LCD_displayLevelHigh(void) {
// char s[20];
// sprintf(s, " %.0f", LCD_DisplayTempValue);
// LCD_displayStr(1, 0, s);
//}
#define GET_LCD_FUNC_NAME(name) (LCD_display##name)
#define LCD_CREATE_DISPLAY_FUNC(name, title, unit) \
static int LCD_display##name(void) { \
char s[20]; \
LCD_displayStr(0, 0, #title); \
sprintf(s, "%.2f %s", LCD_DisplayTempValue, #unit); \
LCD_displayStr(1, 3, s); \
}
LCD_CREATE_DISPLAY_FUNC(LevelHigh, level, mm);
void menu_init(void) {
Menu_addFunc(&Menu_Level, GET_LCD_FUNC_NAME(LevelHigh), 0);
Menu_addItem(&fMenu, &Menu_Level, 0);
Menu_addItem(&fMenu, &Menu_Temp, 1);
Menu_addItem(&fMenu, &Menu_Alarm, 2);
currentMenu = &fMenu;
}
此部分主要是进行菜单的初始化和对第二级菜单的绑定
利用宏,进行方便的创建显示函数,和获取显示函数的名字
将显示函数绑定到指定的菜单项上
currentMenu = &fMenu;
将第一级菜单作为当前菜单进行显示。
currentMenu
是当前可以操作的菜单
#define TASK_SET 0x0001 // 设置界面
#define TASK_ROOT 0x0002 // 登陆用户状态 0:user 1:root
#define TASK_DFP 0x0004 // 显示界面状态(DFP:displayFirstPage) 0:首页 1:菜单项
#define TASK_GPRS 0x0008 // GPRS发送状态 0:不可以发送 1:可以发送
#define TASK_SW 0x0010 // 电磁阀开启状态:0:没有开启 1:开启
#define TASK_LOGIN 0x0020 // 登陆状态 0:未登录 1:已登录
需要有一些显示控制位,来表达当前显示任务处于什么样的显示阶段。
CSDN链接
百度云链接
上:上翻页
下:下翻页
左:返回上级菜单或者退出函数显示
右:进入下级菜单或者进入函数显示
设计上可以参考 simple gui