声明:本处所说的菜单是用在128*64这种小屏幕的菜单,不是彩屏上的GUI。例如下面这种4 W/ H$ J' J0 X4 f$ [, w
% W3 k5 m" m/ a, a1 D, m4 B! N" z- n$ V1 H% m9 A3 M6 O: {
& L/ h9 b( @2 ?: d" u
; I4 `6 o3 q' a
. I: \ n# j, f1 f: f9 F2 d5 p: c
作为一个底层驱动工程师,驱动写完了,是要写硬件测试程序的。
& }6 d6 K0 U7 G- J- t这个测试程序,一般给测试部/硬件工程师用来测试硬件,8 f& K! {+ Q7 g
也会给工厂产线测试准成品。4 M, j1 F, V* v1 a$ L
4 V* o, U: p9 H/ t9 ~ G
开始的人偷懒,不想一秒就直接上,所有菜单都这样做,一层套一层) ~/ F U( R- k0 N l! [' @4 M% ]) I" z
- void test_main(void)
/ n/ G7 N3 {% k4 e8 Z' l9 t - {
# Z. i0 x4 Q4 v x - while(1), R2 U$ Q v8 d& C6 H6 U& \$ N' f$ M
- {; W1 z4 y" O" k* \/ D5 F4 c7 O
- get_key(&key);
( w& b6 R& I. @0 \9 F& a' j6 G
- M! e6 |9 v1 A7 D- switch(key)+ J& L1 P( P8 J- O9 A% W4 v
- {8 }4 Q1 c3 n/ K' E) ?8 r, y
- case 1:; i8 p7 N8 Z; v$ g) e
- test_key();; S5 |$ r. s g& V5 I$ m
- break;
1 Y* |- e; J& V! `/ }% z: r - , B( @! ?9 Q! n# p3 r
- case 2:- M+ e$ N8 _: z2 [# j6 c9 T" g1 L
- test_lcd();
^. I* T) L+ M, ~, A0 a - break;9 @: H" a% K$ z4 O' `0 `7 e
- 0 H8 P- o/ S- c# E8 J$ C
- ....9 n& ~" F; Z- v- M1 z
. x( q b8 b2 {5 Q-
" j0 E& }* b8 R9 m! @ - $ Z- b. V9 }, I1 x1 E/ t
- }
& e2 F: w+ Q4 ?7 |* T0 ^ -
- \% ^( X: ^- C9 C4 d% j - } d1 G3 Q4 V5 K |
- }
复制代码 当菜单越来越多,就开始纠结了,,,,* V4 }. v9 N% Z; A! m! Q8 A
这样写维护不便,看起来也不美,还浪费程序空间。
( F" h# L) m7 L! O
# r7 F. w; H! F0 B作为一个天天看《编程之美》的码农,决定改变现状。
1 ^5 L$ J/ [7 Z" ~0 ]3 M酷狗百度一番,找到了两个参考:0 E& f; b# o0 {/ q% [8 o8 m
《基于二叉树的多层的液晶菜单界面设计》3 ]7 J1 }0 q$ v& T0 E! C# u5 C
《--基于节点编号的通用树状菜单设计方法与实现.pdf》* P( b& ~! o* |7 ]% L% Y/ t
按照他们的设计方法,鼓捣了一个版本,能用,挺好,但是也纠结。4 u# b; [" U! f2 A; _
因为他们用了树这种数据结构。对于程序运行来说,非常好。
: K2 Q: l3 n! f/ q但是对于我来说,菜单代码是一次性的,但是菜单内容,却是会经常改的。
\' O0 |5 _/ M- C6 m. D让我用人脑去维护一个几十个上百个菜单的树,不容易。
, o& P- U; M L) b0 Q4 \( E- F$ z
( }& U M1 [4 ~' W想来想去,这些菜单到底有什么不好?对于我来说,为什么不好用?8 g% `" E% U* P5 }7 m
得出下面结论:
6 \ X: J- j& d! R# A0 q" ]1 管得太宽
0 C1 T1 U7 o- E" U1 I/ h. u8 V1 U菜单,你就管菜单切换就行了,到了最低一层,也就是实际的测试功能了,就不要管了。: ]! P, v# Y" |/ v: l2 D
菜单切换时类似的,实际测试都是不同的。比如在菜单中,1,是进入第一个菜单。但是在测试中,按键1,功能都不一样。
" h+ \2 |, N+ x: t* e& g7 m5 t" }如果菜单连这个也要管,太累。
( s6 N2 j" U/ H; _! Z2 出发点不一样/ f! {' R/ h' g& r: \
上面说到的菜单,出发点都是如何设计一个好的菜单数据结构,让程序快速,高效运行。
6 j2 S' c# V+ z( T9 v1 b4 O我想要的却是一个容易维护的菜单结构,至于菜单的代码有多乱多纠结,没关系,: E3 u& t: m9 R; q; h
而且,几百上千个菜单,就算用轮询的方法,也不过几百us吧,没关系。1 O* E1 I9 z0 h2 s
3 R0 s' P$ e' A* D# p' d
根据需求,设计了一个菜单结构体5 K, @& a: b) I8 x) z
- /**5 m u# K `2 K* Q; V. }8 L
- * @brief 菜单对象
; Y+ x9 K" F& t" J& @6 O - */5 S9 k; o$ E* i" m9 l3 Y! }
- typedef struct _strMenu( Q" Y* F1 A& F1 `
- {+ c6 ?$ l# I0 Q [/ ^0 Z5 \1 `
- MenuLel l; ///<菜单等级) _" u8 O) o8 B0 V, H. C6 d0 B
- char cha[MENU_LANG_BUF_SIZE]; ///中文
1 Y. w3 i) c3 N7 Z! W - char eng[MENU_LANG_BUF_SIZE]; ///英文# D8 |; @8 n8 f+ d3 e* D, u% y
- MenuType type; ///菜单类型9 @! l, P6 B2 a$ L. m. {! ^
- s32 (*fun)(void); ///测试函数# ~, p) b2 [4 V$ Z) X0 |4 ~- V
- P; d! S3 p G1 @" d5 Z# z" M
- } MENU;
复制代码 9 o K& ~+ N) S) s8 l3 P
是的,就这么简单,每一个菜单都是这个结构体
9 @( P5 X, N' g3 M5 S用这个结构体填充一个列表,就是我们的菜单了
3 G* o5 q3 V' w7 T
7 B1 \- T0 {6 u' Y- const MENU EMenuListTest[]=
u% N* B; E Z, q+ A - {
$ p2 o6 o x8 z0 _5 y - MENU_L_0,//菜单等级
6 u+ {+ g4 L- D+ @ - "测试程序",//中文
" E1 o3 k/ C' o% J4 r - "test", //英文# U; S- U/ \4 [: j
- MENU_TYPE_LIST,//菜单类型
! M! T4 l0 P! d* n: H7 a, l - NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行; Z% y4 k4 A$ J3 k/ C) P3 H
" T* b2 b- R6 _! J- MENU_L_1,//菜单等级9 d+ x, F* H" o# M
- "LCD",//中文
. {9 M6 x; T4 } - "LCD", //英文) C1 }- H5 a2 p4 P6 ?6 ]1 O* L
- MENU_TYPE_LIST,//菜单类型$ Y+ b% f8 j' Z# T3 @6 ^5 f+ @
- NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
$ q* P- V; }4 C - MENU_L_2,//菜单等级- E% i6 H3 o* H
- "VSPI OLED",//中文5 h$ c) e2 P- P1 p
- "VSPI OLED", //英文+ M: x, g2 e( i
- MENU_TYPE_FUN,//菜单类型' p+ E) x f$ { _5 R7 X2 z% m
- test_oled,//菜单函数,功能菜单才会执行,有子菜单的不会执行- v# @! {. L/ m$ m) q
. b6 I% \! a: F- C- MENU_L_2,//菜单等级) l! {* ~5 r6 M( ^* h5 W
- "I2C OLED",//中文5 K. p8 F2 u; n+ }; x
- "I2C OLED", //英文
8 p3 ~* R+ g. A+ \6 L; k, ` - MENU_TYPE_FUN,//菜单类型& t3 E( ~3 \+ W( ^- b6 k* i
- test_i2coled,//菜单函数,功能菜单才会执行,有子菜单的不会执行
* C C! l. c0 C2 }
1 B0 \( [* V- K3 A# B4 y7 D- / G! M3 B4 |+ L1 o5 j* l6 r T
- MENU_L_1,//菜单等级8 O! X2 }( G% I. C6 L% m9 _
- "声音",//中文
: u& Q' C! `* Y+ R( `6 ^5 r - "sound", //英文
% N9 j) S2 Z6 r* X$ _ - MENU_TYPE_LIST,//菜单类型
9 E6 r7 @- G$ ?+ u* L: s" q* g - NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
. y7 E* G. y) o4 F - MENU_L_2,//菜单等级
A) i! O% e$ I( i - "蜂鸣器",//中文
0 H% {( n3 i2 U! `9 ^ - "buzzer", //英文
! q/ }$ G- H4 k9 I0 s" s' c - MENU_TYPE_FUN,//菜单类型+ u( B# j. h$ c1 S6 n
- test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行+ Q5 @+ X/ F/ A J
5 I9 O* ~5 [+ q3 V- MENU_L_2,//菜单等级
! v a3 Q* b3 p9 g3 ?0 _5 C - "DAC音乐",//中文
: D. ^+ D3 c1 h* g0 v - "DAC music", //英文
, a# @* y- z" l6 V; E# @" l - MENU_TYPE_FUN,//菜单类型0 v2 V% \) X3 S* C5 f" _; R
- test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行1 j2 u2 c+ p. _
: n( b! X8 g& r0 Z3 h; Z+ t- MENU_L_2,//菜单等级
# {, P$ |- [" f* E% l1 N1 X - "收音",//中文/ p! a! w+ r- G; R! I, B; w
- "FM", //英文
& M( p* _7 ~0 u8 d, { - MENU_TYPE_FUN,//菜单类型2 ^; @5 x1 e0 O
- test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行
: ]7 R# P/ B7 i3 B - & Y) B' [8 J) V9 n
3 ]4 K: ^7 a) m2 y* V- MENU_L_1,//菜单等级4 x# C! h. l, j" P8 Y5 a' `% @
- "触摸屏",//中文
) H1 |" E5 p" P; M! z - "tp", //英文
2 z5 g4 E) p' K5 W0 S - MENU_TYPE_LIST,//菜单类型& C" h D5 |8 q
- NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行( }, h) Z+ f3 M; \8 d: l
-
+ Z$ L/ ?% \- s7 Y - MENU_L_2,//菜单等级
3 ?; b7 h6 M: H6 l" i' S1 X - "校准",//中文$ M- f: m% |) f/ ], _
- "calibrate", //英文 B& v/ B+ N0 j
- MENU_TYPE_FUN,//菜单类型
2 Q" L, C* M) G# a2 ~8 d - test_cal,//菜单函数,功能菜单才会执行,有子菜单的不会执行
* z% e/ V- k0 K( z
3 d& b4 ?- n- S5 k7 {& t- MENU_L_2,//菜单等级, Y2 W k9 {) c: y' w8 a3 x
- "测试",//中文
" n+ A; V) r. W9 Y/ {6 f1 _ - "test", //英文0 u7 R5 w" g: S) n, i
- MENU_TYPE_FUN,//菜单类型
# L1 Q8 |# {7 Q! T- z2 e/ {( P - test_tp,//菜单函数,功能菜单才会执行,有子菜单的不会执行) Z" p, p4 p# _& ]( c2 p$ b4 B
-
3 I6 `9 i, d- m& K9 k+ t - MENU_L_1,//菜单等级) |; A& j8 D8 s7 D
- "按键",//中文) d6 X( ?' H6 J" b9 y8 N* @' y
- "KEY", //英文
; Q; S" Y k! h* d% @ - MENU_TYPE_FUN,//菜单类型' [( @, K/ l; ?4 n! t$ T
- test_key,//菜单函数,功能菜单才会执行,有子菜单的不会执行
/ U2 G1 Q- w8 e; K5 o - ! Q. e C5 v" @1 k
- /*最后的菜单是结束菜单,无意义*/ 8 p: U2 Q' v% Q$ Z: s6 _
- MENU_L_0,//菜单等级: ~8 T7 Z( e) _* F1 ^7 K0 ~
- "END",//中文5 C+ r: k7 D) D+ S0 A
- "END", //英文
* G/ `' d4 c7 p$ h% h! z( l - MENU_TYPE_NULL,//菜单类型+ }/ y) x. ] g# ]% u) y0 ?* x" ?3 a
- NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行8 l! r6 h/ M+ i
- };
复制代码 这个菜单列表有什么特点和要求呢?
9 A2 b! i9 U% l- x7 {) q1 需要一个根节点和结束节点
& B2 A8 k/ ^( [" m" H! W N8 j2 子节点必须跟父节点,类似下面结构
' n3 p. b7 c) `+ _4 j4 _( ^-----------------------------------------------
3 h p1 ]$ a* o* d- b根节点0 k2 x7 A0 s' d3 r
第1个1级菜单( p' O2 O+ W, B7 c0 \# U
第1个子菜单) n2 F0 v9 n- b) p% a8 X3 l. b
第2个子菜单
0 W" {- @2 o1 e2 o5 N9 E9 L9 G+ I 第3个子菜单
2 @5 h$ a' {" ^5 h# z# u, w 第2个1级菜单
# T) y/ Z2 {! O: `: ?, N1 j2 w 第1个子菜单. h+ K: K5 i( O; ^$ U$ H% x
第1个孙菜单5 o! U9 {/ e- Y- z' S
第2个孙菜单5 x9 `$ J' A7 |
第2个子菜单4 ?* U6 X' b8 W. [8 a% @3 X b3 `
第3个子菜单
1 _, E0 v) a& _7 o 第3个1级菜单
+ Z6 `6 ]0 n0 j, D( ]) e: h+ u" W8 P 第4个1级菜单
5 Y$ x1 w( a7 Z y5 o 第5个1级菜单
4 B) o3 C8 f' i$ N结束节点
; T8 I( W1 g) ?# O------------------------------------------------
. K1 b7 {3 p1 K) L& `- {2 `第2个1级菜单有3个子菜单,子菜单是2级菜单,其中第1个子菜单下面又有2个孙菜单(3级菜单)。
+ E2 L5 R$ s' |" f8 M
' {. R2 N/ B6 w' w% V2 x, q维护菜单,就是维护这个列表,添加删除修改,非常容易。, {% i7 c/ Q1 l+ n: @% h
那菜单程序怎么样呢?管他呢。
& d7 S! ?/ e, H# I% g; S定义好菜单后,通过下面函数运行菜单,3 x6 V" g- y7 A6 b* `
- emenu_run(WJQTestLcd, (MENU *)&WJQTestList[0], sizeof(WJQTestList)/sizeof(MENU), FONT_SONGTI_1616, 2);
复制代码 -第1个参数是在哪个LCD上显示菜单,7 y; v3 {/ Q( a# {& u# H% B
-第2个是菜单列表," y+ E; \* G' p# j3 n- Y* X
-第3个是菜单长度,
" I+ }6 d7 |- N-第4个四字体,
0 N) f; g' [; v! w-第5则是行间距
. j; O# a0 s6 ]+ \# b( U& V- h4 m% B# n3 T' G0 a1 y
3 W. s% e; W, T8 y2 R8 G
注意:5 b Y1 |9 w+ R) c& j
运行这个菜单需要有rtos,因为菜单代码是while(1)的,陷进去就不出来了。8 ?! W! l+ A" d6 W7 _
需要有其他线程(TASK)维护系统,例如按键扫描。
, x h& o( W2 ?) M: T
, M/ M/ p* Y9 n6 I3 N9 ~' r代码托管在github:http://github.com/wujique/stm32f407/tree/sw_arch) L, e. A- G7 [* w U! Z
相关文件:emenu.c/emenu.h/emenu_test.c' ?- F0 V9 A0 `; i& V; ~% \, {
" `: t) d2 K0 o
当前代码: A, v) Z5 ?7 z7 t
1实现了双列菜单,用数字键选择进入下一层。每页最多显示8个菜单(4*4键盘用1-8键)
9 w+ J% a. ~# N' c8 t2 实现了单列菜单,通过上下翻查看菜单,确认键进入菜单。; j3 `7 p* f8 v% ~9 \
3 天顶菜单未实现,谁有兴趣可以加上。! i3 M$ u. J7 `+ x, S% K
3 基于LCD驱动架构,这个简易菜单自适应于多种LCD。( t1 s4 @( z5 a* @& K
+ w- J% S% x$ _( b/ E" r# y+ d
效果如下,有需要的尽管拿去,不用谢。9 j z T! Y3 ~9 Y3 K
0 U, o3 f5 P, E3 p
OLED效果4 q: b; F, Q3 H& t9 X/ i- L4 M
3 M! \! E+ D/ F7 e% ?; S; F+ O. b
7 q3 L4 d/ s1 L- ~" N) r) t4 Q' {# W1 c
1.44 128*128 tftlcd 效果
4 M8 E: w" v1 M
k% x7 m0 g9 ^0 I: f2 \& c+ |6 ~% U k7 p- V2 [
' t& h8 L4 ^% W7 \. g
# W' D9 ^1 B' Q# i5 R4 D7 s( O2.8 320*240 tftlcd 效果4 E- {6 h6 {( F$ W
3 T9 Z( c! c0 X# {" _3 N, @5 p
! X; a2 u# h1 A, f& V2 N
# l, v. a+ ^8 b: r) _( a2 I$ o) @
9 m" t2 @6 `+ j8 t
) v5 v+ c" O( Z5 ~路过点个赞呗!& _" }, l: Q0 d: P. i/ T0 z8 q& ]2 \
* N0 d0 x' `6 Y! ?) K9 C4 t4 l" w' S2 O& O" k8 X1 t
|
在github上有最新的
大把的人用 很实用 感谢楼主分享
版主,你是说返回上级菜单有问题吗?
有什么好设计提醒提醒我,我优化优化
潜在问题:返回上级菜单