在单片机中如果需要一个可以便于维护的菜单程序,那么设计一个便于封装的菜单数据结构就是必不可少的了。
最近观看B站UP主有手也不会发布的视频后,发现其写的菜单数据结构尤为好用,这里用于记录,有误之处还望大家指正!
按键采用Multibutton开源框架有兴趣可去GitHub上搜索,也可私信我,我发源码。
struct MenuItem
{
unsigned char menu_cnt; //当前菜单项目总数
unsigned char * display_str; //当前菜单要显示的字符
void (*subs)(); //选择某一菜单后要显示的功能函数
struct MenuItem *children_menus; //当前项目的子菜单
struct MenuItem *parent_menus; //当前项目的父菜单
};
从头到尾依次是:本级菜单所拥有的项目总数,当前项目所需要显示的菜单信息,所需要执行对应的菜单项目的回调函数,当前菜单的子菜单也就是下一级菜单,当前菜单的父菜单也就是上一级菜单。
在这本例移植中我是用了OLED12864进行显示
菜单数据,在此例子中共有两级菜单,每级菜单有三个项目
struct MenuItem parent_menu[] = {
{3, (unsigned char *) "meat", NULL, NULL, NULL},
{3, (unsigned char *) "vegetable", NULL, NULL, NULL},
{3, (unsigned char *) "drinks", NULL, NULL, NULL},
};
struct MenuItem childeren_menu1[] = {
{3, (unsigned char *) "pork", fun1, NULL, parent_menu},
{3, (unsigned char *) "beef", fun2, NULL, parent_menu},
{3, (unsigned char *) "mutton", fun3, NULL, parent_menu},
};
struct MenuItem childeren_menu2[] = {
{3, (unsigned char *) "cabbage", fun4, NULL, parent_menu},
{3, (unsigned char *) "tomato", fun5, NULL, parent_menu},
{3, (unsigned char *) "potato", fun6, NULL, parent_menu},
};
struct MenuItem childeren_menu3[] = {
{3, (unsigned char *) "milk", fun7, NULL, parent_menu},
{3, (unsigned char *) "cola", fun8, NULL, parent_menu},
{3, (unsigned char *) "orange juice", fun9, NULL, parent_menu},
};
菜单显示函数
select_menu //此变量是菜单结构体指针
select_item_num //此变量是记录当前要显示本级菜单的第几个项目
void menu_display(void)
{
unsigned char i;
OLED_SetScreen(0);//清除OLED的缓存,即清屏
for (i = 0;i < select_menu->menu_cnt;i++) {
OLED_ShowString(32, i*12, (select_menu+i)->display_str, FONT_SIZE);//OLED显示对应的菜单项目名字信息
if (i == select_item_num) OLED_ShowString(16, i*12, "->", FONT_SIZE);//如果是对应的项目则显示
}
OLED_RefreshGram();//将数据写入OLED中并显示
}
//主循环中放入如下
//allow_switch表示是否允许切换
while(1)
{
if (allow_switch) {
allow_switch = 0;
menu_display();
}
}
按键1单击表示本级菜单向下移动,双击表示返回上一级菜单
按键2单击表示本级菜单向上移动,双击表示进入下一级菜单
按键1处理回调函数
//recode变量可忽略,用于我自己的调试
void button1_callback_fun(void *btn, uint8_t event)
{
unsigned char i;
if ((event & SINGLE_CLICK) == SINGLE_CLICK) {//单击
LED1_TOGGLE;//LED1翻转,用于调试时便于观察
allow_switch = 1;//允许切换
select_item_num++;//显示项目往下移
if (select_item_num >= select_menu->menu_cnt) select_item_num = 0;//如果大于最大的显示项目回到第一个显示项目
} else if ((event & DOUBLE_CLICK) == DOUBLE_CLICK) {//双击
LED2_TOGGLE;//LED2翻转,用于调试时便于观察
for (i = 0;i < select_menu->menu_cnt;i++) {//遍历是本级菜单哪一个项目要进入上一级菜单
if (i == select_item_num) {//找到要进入上一级菜单的项目
allow_switch = 1;//允许切换
if ((select_menu->parent_menus != NULL) && (recode != 1)) {//上一级菜单是否为空
select_menu = (select_menu+select_item_num)->parent_menus;//将菜单指针定位到上一级菜单
select_item_num = 0;//回到上一级菜单从第一个项目开始
}
else if (recode == 1) {//可忽略
recode = 0;
}
}
}
delay_ms(500);
}
}
按键2处理回调函数
void button2_callback_fun(void *btn, uint8_t event)
{
unsigned char i;
if ((event & SINGLE_CLICK) == SINGLE_CLICK) {//单击
LED2_TOGGLE;//LED2翻转,用于调试时便于观察
allow_switch = 1;//允许切换
select_item_num--;//菜单往上移
if (select_item_num < 0) select_item_num = select_menu->menu_cnt-1;//如果回到第一个项目仍然需要往上移,则回到最后一个项目
} else if ((event & DOUBLE_CLICK) == DOUBLE_CLICK) {//双击
LED3_TOGGLE;//LED3翻转,用于调试时便于观察
for (i = 0;i < select_menu->menu_cnt;i++) {//遍历是本级菜单哪一个项目要进入下一级菜单
if (i == select_item_num) {//找到要进入下一级菜单的项目
allow_switch = 1;//允许切换
if (select_menu->children_menus != NULL) select_menu = (select_menu+select_item_num)->children_menus;//下一级菜单是否为空
else if (select_menu->subs != NULL) (select_menu+select_item_num)->subs();//如果回调函数不为空则执行对应的回调函数
select_item_num = 0;//显示第一个项目
}
}
delay_ms(500);
}
}