在单片机开发中,屏幕有时用于显示内容。当需要逐步显示内容时,需要使用多级菜单。
1 多级菜单
多级菜单的实现一般分为两种设计理念:
- 通过双向链表实现
- 通过数组查表实现
总体思路是将菜单的界面连接起来,可以从上级菜单跳到下级菜单,也可以从下级菜单返回上级菜单。
数组查表的方式比较简单,容易理解。本文将使用数组查表发布STM在32上显示多级菜单。
2 代码实现
2.1 数组查表
:
typedef struct {
uchar current; uchar up;///向上翻索引号 uchar down;//向下翻索引号 uchar enter;///确认索引号 void (*current_operation)(); } key_table;
- current:的索引号
- up:按下“页面索引号将在按钮后跳转
- down:按下“页面索引号将在按钮后跳转
- enter:按下“页面索引号将在按钮后跳转
- current_operation:,这是函数指针
注:对于菜单显示的操作,如果单片机上有三个按钮:向下、向下和确认IO资源紧张,可以省去向上翻按钮,只有向下翻按钮才能实现循环访问,相应的结构也可以去除成员。
,定义如何在每个页面之间跳转
key_table table[30]= {
//第0层 {
0,0,0,1,(*fun_0)}, //第1层 {
1,4,2, 5,(*fun_a1)}, {
2,1,3, 9,(*fun_b1)}, {
3,2,4,13,(*fun_c1)}, {
4,3,1, 0,(*fun_d1)},
//第2层
{
5,8,6,17,(*fun_a21)},
{
6,5,7,18,(*fun_a22)},
{
7,6,8,19,(*fun_a23)},
{
8,7,5, 1,(*fun_a24)},
{
9,12,10,20,(*fun_b21)},
{
10, 9,11,21,(*fun_b22)},
{
11,10,12,22,(*fun_b23)},
{
12,11, 9, 2,(*fun_b24)},
{
13,16,14,23,(*fun_c21)},
{
14,13,15,24,(*fun_c22)},
{
15,14,16,25,(*fun_c23)},
{
16,15,13, 3,(*fun_c24)},
//第3层
{
17,17,17,5,(*fun_a31)},
{
18,18,18,6,(*fun_a32)},
{
19,19,19,7,(*fun_a33)},
{
20,20,20, 9,(*fun_b31)},
{
21,21,21,10,(*fun_b32)},
{
22,22,22,11,(*fun_b33)},
{
23,23,23,13,(*fun_c31)},
{
24,24,24,14,(*fun_c32)},
{
25,25,25,15,(*fun_c33)},
};
这里解释一下该表是如何工作的:
- 此表,表示了4级菜单的显示关系(注意第0层其实只是一个欢迎界面)
- 第一层菜单,只有4个选项,因此这里只列了4行(注意最后一个选项用作返回上一级,无实际内容含义)
- 第二层菜单,就是对第一层菜单中的3个实际的选项进行进一步的介绍,每种介绍又有4个子项(注意最后一个选项也是用作返回上一级,无实际内容含义),因此,这里的第二层菜单列了3x4=12行
- 第三层菜单,又是对第二层菜单中的子项进行进一步的介绍(3个分类,每类有3个子项),所以第三层菜单列了9行
- 注意数组中每一行的第1个数组,是索引号,先列举一个实际的例子进行分析:
上图就是一个实际的4级菜单要显示的内容,每个条目前,标记了索引号(0~25),即对应数组在定义的索引号。
比如数组关于第0层和第1层的定义:
//第0层
{
0,0,0,1,(*fun_0)},
//第1层
{
1,4,2, 5,(*fun_a1)},
{
2,1,3, 9,(*fun_b1)},
{
3,2,4,13,(*fun_c1)},
{
4,3,1, 0,(*fun_d1)},
- 先看第一行:,显示欢迎界面;表示此时按“上翻”和“下翻”无效,继续显示欢迎界面;表示按下“确认”按钮后,跳转到索引1处(即显示第1级目录,且指向第1级的第1个子项);是此索引要显示的具体内容,fun_0就是控制屏幕显示欢迎界面
- 再看第二行:,显示第1级目录,且指向第1级的第1个子项(天气);表示此时按“上翻”跳转到索引4,即显示第1级目录,且指向第1级的第4个子项(Return);表示此时按“下翻”跳转到索引2,即显示第1级目录,且指向第1级的第2个子项(音乐);表示按下“确认”按钮后,跳转到索引5处(即显示第2级目录,且指向第2级的第1个子项-杭州);是此索引要显示的具体内容,fun_a1就是控制屏幕显示第1级目录,且指向第1级的第1个子项(天气)
- 其它行的含义与之类似
通过分析,不难发现,这些数组在空间上的关系:
对于菜单的最底层,因为没有上翻和下翻的功能需求,因此每行的前3个数字都是当前的索引号:
//第3层
{
17,17,17,5,(*fun_a31)},
{
18,18,18,6,(*fun_a32)},
{
19,19,19,7,(*fun_a33)},
{
20,20,20, 9,(*fun_b31)},
{
21,21,21,10,(*fun_b32)},
{
22,22,22,11,(*fun_b33)},
{
23,23,23,13,(*fun_c31)},
{
24,24,24,14,(*fun_c32)},
{
25,25,25,15,(*fun_c33)},
2.2 具体的显示函数
对于函数要显示的具体内容,根据自己的实现需要显示即可。
这里我使用的是OLED屏幕,借助U8g2图形库进行内容显示,以下是部分显示示例:
/*********第1层***********/
void fun_a1()
{
u8g2_DrawStr(&u8g2,0,16,">");
u8g2_DrawStr(&u8g2,16,16,"[1]Weather");
u8g2_DrawStr(&u8g2,16,32,"[2]Music");
u8g2_DrawStr(&u8g2,16,48,"[3]Device Info");
u8g2_DrawStr(&u8g2,16,64,"<--");
}
void fun_b1()
{
u8g2_DrawStr(&u8g2,0,32,">");
u8g2_DrawStr(&u8g2,16,16,"[1]Weather");
u8g2_DrawStr(&u8g2,16,32,"[2]Music");
u8g2_DrawStr(&u8g2,16,48,"[3]Device Info");
u8g2_DrawStr(&u8g2,16,64,"<--");
}
void fun_c1()
{
u8g2_DrawStr(&u8g2,0,48,">");
u8g2_DrawStr(&u8g2,16,16,"[1]Weather");
u8g2_DrawStr(&u8g2,16,32,"[2]Music");
u8g2_DrawStr(&u8g2,16,48,"[3]Device Info");
u8g2_DrawStr(&u8g2,16,64,"<--");
}
void fun_d1()
{
u8g2_DrawStr(&u8g2,0,64,">");
u8g2_DrawStr(&u8g2,16,16,"[1]Weather");
u8g2_DrawStr(&u8g2,16,32,"[2]Music");
u8g2_DrawStr(&u8g2,16,48,"[3]Device Info");
u8g2_DrawStr(&u8g2,16,64,"<--");
}
/*********第2层***********/
void fun_a21()
{
u8g2_DrawStr(&u8g2,0,16,">");
u8g2_DrawStr(&u8g2,16,16,"* HangZhou");
u8g2_DrawStr(&u8g2,16,32,"* BeiJing");
u8g2_DrawStr(&u8g2,16,48,"* ShangHai");
u8g2_DrawStr(&u8g2,16,64,"<--");
}
//省略...
2.3 按键切换页面
页面的切换,这里里简单的按钮轮询为例,比如下,按下不同按键后,通过数组查表,确定要跳转到的索引号,然后根据索引号,通过函数指针执行索引号对应的显示函数,即实现了一次页面切换。
然后,就是,收到下一个按钮指令,再切换到下一个显示状态。
void (*current_operation_index)(); //定义一个函数指针
//...
while(1)
{
if((KEY1==0)||(KEY2==0)||(KEY3==0))
{
delay_ms(10);//消抖
if(KEY1==0)
{
func_index = table[func_index].up; //向上翻
while(!KEY1);//松手检测
}
if(KEY2==0)
{
func_index = table[func_index].down; //向下翻
while(!KEY2);
}
if(KEY3==0)
{
func_index = table[func_index].enter; //确认
while(!KEY3);
}
}
if (func_index != last_index)
{
current_operation_index = table[func_index].current_operation;
u8g2_ClearBuffer(&u8g2);
(*current_operation_index)();//执行当前操作函数
u8g2_SendBuffer(&u8g2);
last_index = func_index;
}
}
3 演示
测试效果如下: https://www.bilibili.com/video/BV1r5411R7eA?share_source=copy_web
4 总结
本篇介绍了一种简易的多级菜单的显示方法,本质是通过数组查表,实现各级菜单的各个页面(状态)的切换(跳转),并在STM32上编程实现,通过OLED屏幕,以及借助U8g2图形库,测试了多级菜单的显示功能。