产品是面向用户的,用户需要的仅仅是功能以及有好的交互界面,多级菜单在其中扮演着重要的角色,有限的按键实现复杂的界面,层层相扣,标志法对于菜单的管理相当费劲,多了自己都会被自己绕晕...下面介绍一种还算不错的多级菜单是实现方式
这个框架本身不复杂,小伙伴们可以直接看源码,有什么问题,可以一起交流
STM32F407ZGT6
ST7789 LCD屏(SPI1,复用开发板NRF24L01接口)
3个按键 PC7、PC8、PC9
实现起来比较简单,直接进入代码部分,我使用的是3个按键,功能分别是:上翻(增大数值)、下翻(减小数值)、确认
先来定义一个结构体,结构体中包含了5个成员
1、cur:当前的菜单索引号
2、left:左按键对应的索引号
3、OK:确认件对应的索引号
4、right:右键对应的索引号
5、当前索引对应要执行的菜单界面(或功能)
typedef struct
{
uint8_t cur; //当前索引号
uint8_t left; //left 的索引号
uint8_t ok; //OK 的索引号
uint8_t right; //right 的索引号
void (*current_operation)(void); //当前执行的函数
} KEY_TABLE;
再定义一个关于按键键值,菜单索引号的结构体:
typedef struct
{
uint16_t key_value;
uint8_t key_new[3];
uint8_t key_left;
uint8_t key_right;
uint8_t key_ok;
uint8_t func_index;
uint8_t func_index_last;
} Menu_Para;
为每一个子菜单分配上翻、下翻索引值
typedef struct
{
uint8_t Down_index;
uint8_t Up_index;
uint8_t Sys_Down_index;
uint8_t Sys_Up_index;
uint8_t Com_Down_index;
uint8_t Com_Up_index;
uint8_t Game_Down_index;
uint8_t Game_Up_index;
uint8_t Sys_DisModeDown_index;
uint8_t Sys_DisModeUp_index;
uint8_t Sys_NetModeDown_index;
uint8_t Sys_NetModeUp_index;
} MenuSerch_HandleTypeDef;
利用上面第一个提到的结构体,只需要把对应的索引号填入即可,按键按下根据索引号去查找需要跳转的界面
const KEY_TABLE k_table[] =
{
{0, 2, 3, 1, (*MainMenuTask)}, //0---开机主菜单
{1, 2, 3, 1, (*MenuSearchDown)}, //1---1级菜单下翻
{2, 2, 3, 1, (*MenuSearchUP)}, //2---1级菜单上翻
{3, 0, 3, 3, (*MenuSelect)}, //3---1级菜单确认
{4, 0, 4, 4, (*Psd_Auth_Process)}, //4---2级密码认证菜单
{5, 7, 8, 6, (*SysMenu_Process)}, //5---2级系统设置菜单,未使用
{6, 7, 8, 6, (*Sys_SearchDown)}, //6---2级系统设置下翻
{7, 7, 8, 6, (*Sys_SearchUP)}, //7---2级系统设置上翻
{8, 7, 8, 6, (*SubMenu_SysSelect)}, //8---2级系统设置菜单确认
{9, 11, 12, 10, (*ComMenu_Process)}, //9---1级通讯菜单明细显示,未使用
{10, 11, 12, 10, (*Com_SearchDown)}, //10---2级通讯设置下翻
{11, 11, 12, 10, (*Com_SearchUP)}, //11---2级通讯设置上翻
{12, 3, 12, 12, (*SubMenu_ComSelect)}, //12---1级通讯菜单选择确认
{13, 15, 3, 14, (*GamesMenu_Process)}, //13---2级系统设置菜单,未使用
{14, 15, 3, 14, (*Game_SearchDown)}, //14---2级系统设置下翻
{15, 15, 3, 14, (*Game_SearchUP)}, //15---2级系统设置上翻
{16, 3, 8, 6, (*SubMenu_GameSelect)}, //16---2级系统设置菜单确认
{17, 18, 5, 17, (*Current_RangeDown)}, //17--3级系统设置电流范围设置--减小电流范围
{18, 18, 5, 17, (*Current_RangeUP)}, //18---3级系统设置电流范围设置--增大电流范围
{19, 20, 5, 19, (*Voltage_RangeDown)}, //19--3级系统设置电流范围设置--减小电流范围
{20, 20, 5, 19, (*Voltage_RangeUP)}, //20---3级系统设置电流范围设置--增大电流范围
{21, 22, 5, 21, (*DisMode_SearchDown)}, //21--3级系统设置灯光模式下翻
{22, 22, 5, 21, (*DisMode_SearchUP)}, //22---3级系统设置灯光模式上翻
{23, 24, 5, 23, (*Lightness_RangeDown)}, //23--3级系统设置灯光亮度设置--增大亮度
{24, 24, 5, 23, (*Lightness_RangeUP)}, //24---3级系统设置灯光亮度设置--减小亮度
{25, 26, 5, 25, (*Temperature_RangeDown)}, //25--3级系统设置温度设置--增大温度
{26, 26, 5, 25, (*Temperature_RangeUP)}, //26---3级系统设置温度设置--减小温度
{27, 28, 5, 27, (*NetMode_SerchDown)}, //27--3级系统设置网络模式选择
{28, 28, 5, 27, (*NetMode_SerchUP)}, //28---3级系统设置网络模式选择
};
菜单要显示的内容,我们定义为指针数组形式
const uint8_t *MenuItem[MainItemsCount] = {
"Psd Authen",
"Sys Settings",
"Com Settings",
"User Games",
"About Me",
"About Version",
};
const uint8_t *Com_SubItem[Sys_SubMenuItems] = {
"Slave Addr:",
"BoundRate:",
"reserved",
"reserved",
"reserved",
"reserved",
};
const uint8_t *Game_SubItem[Game_SubMenuItems] = {
"Greedy snake ",
"Super Marie ",
"Sokoban",
"Double Dragon",
"Metal Slug ",
"Contra Force",
};
...
按键部分:
#define KEYPINS GPIOC->IDR
KEY_DATA key_LOR[3];
Menu_Para Menu_Parameter;
unsigned int key_read(void)
{
unsigned char i;
Menu_Parameter.key_value = KEYPINS;
Menu_Parameter.key_value >>= 6;
Menu_Parameter.key_value &= 0x000000E;
for (i = 0; i < 3; i++)
{
if ((Menu_Parameter.key_value & (0x02 << i)) == 0) //有按键按下
{
key_LOR[i].T_press++;
if (key_LOR[i].T_press > 600) //长按键
{
}
}
else
{
if (key_LOR[i].T_press > 100)
{
key_LOR[i].press = 1; //short press
Menu_Parameter.key_new[i] = 1;
key_to_lcd();
}
key_LOR[i].T_press = 0;
key_LOR[i].L_press = 0;
key_LOR[i].press = 0;
}
}
return 1;
}
然后是查询按键按下对应的索引值处理:
void key_to_lcd(void)
{
Menu_Parameter.key_left = Menu_Parameter.key_new[0];
Menu_Parameter.key_ok = Menu_Parameter.key_new[1];
Menu_Parameter.key_right = Menu_Parameter.key_new[2];
memset(Menu_Parameter.key_new, 0, 3);
if (Menu_Parameter.key_left == 1)
{
Menu_Parameter.func_index = k_table[Menu_Parameter.func_index].left;
}
if (Menu_Parameter.key_right == 1)
{
Menu_Parameter.func_index = k_table[Menu_Parameter.func_index].right;
}
if (Menu_Parameter.key_ok == 1)
{
Menu_Parameter.func_index = k_table[Menu_Parameter.func_index].ok;
}
if (Menu_Parameter.func_index_last == 1 ||
Menu_Parameter.func_index_last == 2 ||
Menu_Parameter.func_index_last == 6 ||
Menu_Parameter.func_index_last == 7 ||
Menu_Parameter.func_index_last == 10 ||
Menu_Parameter.func_index_last == 11 ||
Menu_Parameter.func_index_last == 14 ||
Menu_Parameter.func_index_last == 15 ||
Menu_Parameter.func_index_last == 17 ||
Menu_Parameter.func_index_last == 18 ||
Menu_Parameter.func_index_last == 19 ||
Menu_Parameter.func_index_last == 20 ||
Menu_Parameter.func_index_last == 21 ||
Menu_Parameter.func_index_last == 22 ||
Menu_Parameter.func_index_last == 23 ||
Menu_Parameter.func_index_last == 24 ||
Menu_Parameter.func_index_last == 25 ||
Menu_Parameter.func_index_last == 26 ||
Menu_Parameter.func_index_last == 27 ||
Menu_Parameter.func_index_last == 28
)
{
current_operation_index = k_table[Menu_Parameter.func_index].current_operation;
(*current_operation_index)();
Menu_Parameter.func_index_last = Menu_Parameter.func_index;
}
else if (Menu_Parameter.func_index != Menu_Parameter.func_index_last)
{
current_operation_index = k_table[Menu_Parameter.func_index].current_operation;
(*current_operation_index)();
Menu_Parameter.func_index_last = Menu_Parameter.func_index;
}
//UI_printf("\r\n func_index_last is:%d", Menu_Parameter.func_index_last);
//UI_printf("\r\n Down_index is:%d", MenuSerchPara.Down_index);
}
有个需要注意的地方,我们一般使用的时候,在一个仅仅是显示信息,即不做其他操作,显示一些版本信息的时候,我们按除了返回键是不想让他响应的,否则,按一下就会刷新一次,体验很不好,所以需要做一下处理,简单来说就是,两次的索引值相同,就不再响应
但是一些情况下,我们是一直处于同一个界面,比如上翻、下翻,始终是处于同一个父界面,这时候需要响应以完成子项选择
if (Menu_Parameter.func_index_last == 1 ||
Menu_Parameter.func_index_last == 2 ||
Menu_Parameter.func_index_last == 6 ||
Menu_Parameter.func_index_last == 7 ||
Menu_Parameter.func_index_last == 10 ||
Menu_Parameter.func_index_last == 11 ||
Menu_Parameter.func_index_last == 14 ||
Menu_Parameter.func_index_last == 15 ||
Menu_Parameter.func_index_last == 17 ||
Menu_Parameter.func_index_last == 18 ||
Menu_Parameter.func_index_last == 19 ||
Menu_Parameter.func_index_last == 20 ||
Menu_Parameter.func_index_last == 21 ||
Menu_Parameter.func_index_last == 22 ||
Menu_Parameter.func_index_last == 23 ||
Menu_Parameter.func_index_last == 24 ||
Menu_Parameter.func_index_last == 25 ||
Menu_Parameter.func_index_last == 26 ||
Menu_Parameter.func_index_last == 27 ||
Menu_Parameter.func_index_last == 28
)
{
current_operation_index = k_table[Menu_Parameter.func_index].current_operation;
(*current_operation_index)();
Menu_Parameter.func_index_last = Menu_Parameter.func_index;
}
其实整个框架就这么多,很简单,编写程序主要是理清思绪,界面的切换、转换索引相连,就可以很轻松的编写多级菜单了
欢迎添加小飞哥好友,也可以进群一起交流,欢迎优秀的你结识更多优秀的同行者