不错哦|多级菜单实现起来就是这么简单!
演示视频
摘要
产品是面向用户的,用户需要的仅仅是功能以及有好的交互界面,多级菜单在其中扮演着重要的角色,有限的按键实现复杂的界面,层层相扣,标志法对于菜单的管理相当费劲,多了自己都会被自己绕晕...下面介绍一种还算不错的多级菜单是实现方式
这个框架本身不复杂,小伙伴们可以直接看源码,有什么问题,可以一起交流
硬件连接
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; }
其实整个框架就这么多,很简单,编写程序主要是理清思绪,界面的切换、转换索引相连,就可以很轻松的编写多级菜单了