以前用单片机做用户交互的菜单的时候,都比较痛苦,如何写一个复用性高,方便维护,可扩展性高的GUI框架呢?当然可以自己动手写一个,这个过程充满了艰辛和挑战,现在我推荐一个很棒的框架,直接拿来用就行,也可以借鉴和学习其中的思路,一定会收获颇丰。
知道有多少人折腾过液晶显示的菜单,我觉得很多人都应该搞过,我还记得以前大学参加电子设计竞赛获奖的作品,我就用到了一个12864,里面有菜单功能。
以前可能觉得菜单高大上,其实并不是想象中的复杂,本文为大家分享一个用单色屏做的菜单框架。
代码托管在github:
https://github.com/wujique/stm32f407/tree/sw_arch
公众号回复"菜单"也可获得源码.
1、概述
本处所说的菜单是用在128*64这种小屏幕的菜单,例如下面这种,不是彩屏上的GUI。

2、菜单框架设计
作为一个底层驱动工程师,驱动写完了,是要写硬件测试程序的。这个测试程序,一般给测试部/硬件工程师用来测试硬件, 也会给工厂产线测试准成品。
开始的人偷懒,不想一秒就直接上,所有菜单都这样做,一层套一层 1void test_main(void)
2{
3 while(1)
4 {
5 get_key(&key);
6 switch(key)
7 {
8 case 1:
9 test_key();
10 break;
11 case 2:
12 test_lcd();
13 break;
14 ....
15 }
16 }
17}
作为一个天天看《编程之美》的码农,决定改变现状。
搜索引擎找了很久,找到了两个参考:
《基于二叉树的多层的液晶菜单界面设计》
《基于节点编号的通用树状菜单设计方法与实现.pdf》
按照他们的设计方法,鼓捣了一个版本,能用,挺好,但是也纠结。因为他们用了树这种数据结构。对于程序运行来说,非常好,效率高。但是对于我来说,菜单代码是一次性的,但是菜单内容,却是会经常改的。让我用人脑去维护一个包含几十个上百个菜单的树,不容易。
想来想去,这些菜单到底有什么不好?对于我来说,为什么不好用?得出下面结论:
根据需求,我重新设计了一个菜单结构体
1/**
2 * @brief 菜单对象
3*/
4typedef struct _strMenu
5{
6 MenuLel l; ///<菜单等级
7 char cha[MENU_LANG_BUF_SIZE]; ///中文
8 char eng[MENU_LANG_BUF_SIZE]; ///英文
9 MenuType type; ///菜单类型
10 s32 (*fun)(void); ///测试函数
11
12} MENU;
是的,就这么简单,每一个菜单都是这个结构体 用这个结构体填充一个列表,就是我们的菜单了、
1const MENU EMenuListTest[]=
2{
3 MENU_L_0,//菜单等级
4 "测试程序",//中文
5 "test", //英文
6 MENU_TYPE_LIST,//菜单类型
7 NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
8
9 MENU_L_1,//菜单等级
10 "LCD",//中文
11 "LCD", //英文
12 MENU_TYPE_LIST,//菜单类型
13 NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
14 MENU_L_2,//菜单等级
15 "VSPI OLED",//中文
16 "VSPI OLED", //英文
17 MENU_TYPE_FUN,//菜单类型
18 test_oled,//菜单函数,功能菜单才会执行,有子菜单的不会执行
19
20 MENU_L_2,//菜单等级
21 "I2C OLED",//中文
22 "I2C OLED", //英文
23 MENU_TYPE_FUN,//菜单类型
24 test_i2coled,//菜单函数,功能菜单才会执行,有子菜单的不会执行
25
26
27 MENU_L_1,//菜单等级
28 "声音",//中文
29 "sound", //英文
30 MENU_TYPE_LIST,//菜单类型
31 NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
32 MENU_L_2,//菜单等级
33 "蜂鸣器",//中文
34 "buzzer", //英文
35 MENU_TYPE_FUN,//菜单类型
36 test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行
37
38 MENU_L_2,//菜单等级
39 "DAC音乐",//中文
40 "DAC music", //英文
41 MENU_TYPE_FUN,//菜单类型
42 test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行
43
44 MENU_L_2,//菜单等级
45 "收音",//中文
46 "FM", //英文
47 MENU_TYPE_FUN,//菜单类型
48 test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行
49
50
51 MENU_L_1,//菜单等级
52 "触摸屏",//中文
53 "tp", //英文
54 MENU_TYPE_LIST,//菜单类型
55 NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
56
57 MENU_L_2,//菜单等级
58 "校准",//中文
59 "calibrate", //英文
60 MENU_TYPE_FUN,//菜单类型
61 test_cal,//菜单函数,功能菜单才会执行,有子菜单的不会执行
62
63 MENU_L_2,//菜单等级
64 "测试",//中文
65 "test", //英文
66 MENU_TYPE_FUN,//菜单类型
67 test_tp,//菜单函数,功能菜单才会执行,有子菜单的不会执行
68
69 MENU_L_1,//菜单等级
70 "按键",//中文
71 "KEY", //英文
72 MENU_TYPE_FUN,//菜单类型
73 test_key,//菜单函数,功能菜单才会执行,有子菜单的不会执行
74
75 /*最后的菜单是结束菜单,无意义*/
76 MENU_L_0,//菜单等级
77 "END",//中文
78 "END", //英文
79 MENU_TYPE_NULL,//菜单类型
80 NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
81};
这个菜单列表有什么特点和要求呢?1 需要一个根节点和结束节点 2 子节点必须跟父节点,类似下面结构
1-----------------------------------------------
2根节点
3 第1个1级菜单
4 第1个子菜单
5 第2个子菜单
6 第3个子菜单
7 第2个1级菜单
8 第1个子菜单
9 第1个孙菜单
10 第2个孙菜单
11 第2个子菜单
12 第3个子菜单
13 第3个1级菜单
14 第4个1级菜单
15 第5个1级菜单
16结束节点
17------------------------------------------------
第2个1级菜单有3个子菜单,子菜单是2级菜单,其中第1个子菜单下面又有2个孙菜单(3级菜单)。
维护菜单,就是维护这个列表,添加删除修改,非常容易。那菜单程序怎么样呢?管他呢。定义好菜单后,通过下面函数运行菜单,
1 emenu_run(WJQTestLcd, (MENU *)&WJQTestList[0], sizeof(WJQTestList)/sizeof(MENU), FONT_SONGTI_1616, 2);
-第1个参数是在哪个LCD上显示菜单, -第2个是菜单列表, -第3个是菜单长度, -第4个四字体, -第5则是行间距
注意:运行这个菜单需要有rtos,因为菜单代码是while(1)的,陷进去就不出来了。需要有其他线程(TASK)维护系统,例如按键扫描。
4、菜单实现效果
相关文件:emenu.c、emenu.h、emenu_test.c
当前代码:
1实现了双列菜单,用数字键选择进入下一层。每页最多显示8个菜单(4*4键盘用1-8键)
2 实现了单列菜单,通过上下翻查看菜单,确认键进入菜单。3 天顶菜单未实现,谁有兴趣可以加上。
3 基于LCD驱动架构,这个简易菜单自适应于多种LCD。
效果如下,有需要的尽管拿去,不用谢。
显示效果
128*64 OLED
128*128 tft lcd

320*240 tft lcd
5、最后说明
以上菜单框架来源屋脊雀工作室,适合初学者练习。我看下这个菜单框架,其实还有很多改进地方。
我当初大学电子设计竞赛用到类似结构体方式,但我那菜单框架用到了二级指针,可以做到无限极扩展,而且可以指向(跳转)任意菜单,方便按键进入、返回等操作。
本文就分享到这里,感兴趣的读者可以自己写一个菜单框架。
素材源于:网络,作者:屋脊雀工作室

????点击关注,技术干货准时送达!????

