你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

一种简易菜单的思考  

[复制链接]
wujique 发布时间:2018-6-24 11:52
声明:本处所说的菜单是用在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: {
1.jpg & 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
  1. void test_main(void)
    / n/ G7 N3 {% k4 e8 Z' l9 t
  2. {
    # Z. i0 x4 Q4 v  x
  3.         while(1), R2 U$ Q  v8 d& C6 H6 U& \$ N' f$ M
  4.         {; W1 z4 y" O" k* \/ D5 F4 c7 O
  5.                 get_key(&key);
    ( w& b6 R& I. @0 \9 F& a' j6 G

  6. - M! e6 |9 v1 A7 D
  7.                 switch(key)+ J& L1 P( P8 J- O9 A% W4 v
  8.                 {8 }4 Q1 c3 n/ K' E) ?8 r, y
  9.                         case 1:; i8 p7 N8 Z; v$ g) e
  10.                                 test_key();; S5 |$ r. s  g& V5 I$ m
  11.                                 break;
    1 Y* |- e; J& V! `/ }% z: r
  12. , B( @! ?9 Q! n# p3 r
  13.                         case 2:- M+ e$ N8 _: z2 [# j6 c9 T" g1 L
  14.                                 test_lcd();
      ^. I* T) L+ M, ~, A0 a
  15.                                 break;9 @: H" a% K$ z4 O' `0 `7 e
  16. 0 H8 P- o/ S- c# E8 J$ C
  17.                         ....9 n& ~" F; Z- v- M1 z

  18. . x( q  b8 b2 {5 Q
  19.                        
    " j0 E& }* b8 R9 m! @
  20.                         $ Z- b. V9 }, I1 x1 E/ t
  21.                 }
    & e2 F: w+ Q4 ?7 |* T0 ^
  22.        
    - \% ^( X: ^- C9 C4 d% j
  23.         }  d1 G3 Q4 V5 K  |
  24. }
复制代码
当菜单越来越多,就开始纠结了,,,,* 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
  1. /**5 m  u# K  `2 K* Q; V. }8 L
  2. * @brief  菜单对象
    ; Y+ x9 K" F& t" J& @6 O
  3. */5 S9 k; o$ E* i" m9 l3 Y! }
  4. typedef struct _strMenu( Q" Y* F1 A& F1 `
  5. {+ c6 ?$ l# I0 Q  [/ ^0 Z5 \1 `
  6.     MenuLel l;     ///<菜单等级) _" u8 O) o8 B0 V, H. C6 d0 B
  7.     char cha[MENU_LANG_BUF_SIZE];   ///中文
    1 Y. w3 i) c3 N7 Z! W
  8.     char eng[MENU_LANG_BUF_SIZE];   ///英文# D8 |; @8 n8 f+ d3 e* D, u% y
  9.     MenuType type;  ///菜单类型9 @! l, P6 B2 a$ L. m. {! ^
  10.     s32 (*fun)(void);  ///测试函数# ~, p) b2 [4 V$ Z) X0 |4 ~- V
  11.   P; d! S3 p  G1 @" d5 Z# z" M
  12. } MENU;
复制代码
9 o  K& ~+ N) S) s8 l3 P
是的,就这么简单,每一个菜单都是这个结构体
9 @( P5 X, N' g3 M5 S用这个结构体填充一个列表,就是我们的菜单了
3 G* o5 q3 V' w7 T

  1. 7 B1 \- T0 {6 u' Y
  2. const MENU EMenuListTest[]=
      u% N* B; E  Z, q+ A
  3. {
    $ p2 o6 o  x8 z0 _5 y
  4.         MENU_L_0,//菜单等级
    6 u+ {+ g4 L- D+ @
  5.         "测试程序",//中文
    " E1 o3 k/ C' o% J4 r
  6.         "test",        //英文# U; S- U/ \4 [: j
  7.         MENU_TYPE_LIST,//菜单类型
    ! M! T4 l0 P! d* n: H7 a, l
  8.         NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行; Z% y4 k4 A$ J3 k/ C) P3 H

  9. " T* b2 b- R6 _! J
  10.                 MENU_L_1,//菜单等级9 d+ x, F* H" o# M
  11.                 "LCD",//中文
    . {9 M6 x; T4 }
  12.                 "LCD",        //英文) C1 }- H5 a2 p4 P6 ?6 ]1 O* L
  13.                 MENU_TYPE_LIST,//菜单类型$ Y+ b% f8 j' Z# T3 @6 ^5 f+ @
  14.                 NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
    $ q* P- V; }4 C
  15.                         MENU_L_2,//菜单等级- E% i6 H3 o* H
  16.                         "VSPI OLED",//中文5 h$ c) e2 P- P1 p
  17.                         "VSPI OLED",        //英文+ M: x, g2 e( i
  18.                         MENU_TYPE_FUN,//菜单类型' p+ E) x  f$ {  _5 R7 X2 z% m
  19.                         test_oled,//菜单函数,功能菜单才会执行,有子菜单的不会执行- v# @! {. L/ m$ m) q

  20. . b6 I% \! a: F- C
  21.                         MENU_L_2,//菜单等级) l! {* ~5 r6 M( ^* h5 W
  22.                         "I2C OLED",//中文5 K. p8 F2 u; n+ }; x
  23.                         "I2C OLED",        //英文
    8 p3 ~* R+ g. A+ \6 L; k, `
  24.                         MENU_TYPE_FUN,//菜单类型& t3 E( ~3 \+ W( ^- b6 k* i
  25.                         test_i2coled,//菜单函数,功能菜单才会执行,有子菜单的不会执行
    * C  C! l. c0 C2 }

  26. 1 B0 \( [* V- K3 A# B4 y7 D
  27. / G! M3 B4 |+ L1 o5 j* l6 r  T
  28.                 MENU_L_1,//菜单等级8 O! X2 }( G% I. C6 L% m9 _
  29.                 "声音",//中文
    : u& Q' C! `* Y+ R( `6 ^5 r
  30.                 "sound",        //英文
    % N9 j) S2 Z6 r* X$ _
  31.                 MENU_TYPE_LIST,//菜单类型
    9 E6 r7 @- G$ ?+ u* L: s" q* g
  32.                 NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行
    . y7 E* G. y) o4 F
  33.                         MENU_L_2,//菜单等级
      A) i! O% e$ I( i
  34.                         "蜂鸣器",//中文
    0 H% {( n3 i2 U! `9 ^
  35.                         "buzzer",        //英文
    ! q/ }$ G- H4 k9 I0 s" s' c
  36.                         MENU_TYPE_FUN,//菜单类型+ u( B# j. h$ c1 S6 n
  37.                         test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行+ Q5 @+ X/ F/ A  J

  38. 5 I9 O* ~5 [+ q3 V
  39.                         MENU_L_2,//菜单等级
    ! v  a3 Q* b3 p9 g3 ?0 _5 C
  40.                         "DAC音乐",//中文
    : D. ^+ D3 c1 h* g0 v
  41.                         "DAC music",        //英文
    , a# @* y- z" l6 V; E# @" l
  42.                         MENU_TYPE_FUN,//菜单类型0 v2 V% \) X3 S* C5 f" _; R
  43.                         test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行1 j2 u2 c+ p. _

  44. : n( b! X8 g& r0 Z3 h; Z+ t
  45.                         MENU_L_2,//菜单等级
    # {, P$ |- [" f* E% l1 N1 X
  46.                         "收音",//中文/ p! a! w+ r- G; R! I, B; w
  47.                         "FM",        //英文
    & M( p* _7 ~0 u8 d, {
  48.                         MENU_TYPE_FUN,//菜单类型2 ^; @5 x1 e0 O
  49.                         test_test,//菜单函数,功能菜单才会执行,有子菜单的不会执行
    : ]7 R# P/ B7 i3 B
  50. & Y) B' [8 J) V9 n

  51. 3 ]4 K: ^7 a) m2 y* V
  52.                 MENU_L_1,//菜单等级4 x# C! h. l, j" P8 Y5 a' `% @
  53.                 "触摸屏",//中文
    ) H1 |" E5 p" P; M! z
  54.                 "tp",        //英文
    2 z5 g4 E) p' K5 W0 S
  55.                 MENU_TYPE_LIST,//菜单类型& C" h  D5 |8 q
  56.                 NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行( }, h) Z+ f3 M; \8 d: l
  57.                        
    + Z$ L/ ?% \- s7 Y
  58.                         MENU_L_2,//菜单等级
    3 ?; b7 h6 M: H6 l" i' S1 X
  59.                         "校准",//中文$ M- f: m% |) f/ ], _
  60.                         "calibrate",        //英文  B& v/ B+ N0 j
  61.                         MENU_TYPE_FUN,//菜单类型
    2 Q" L, C* M) G# a2 ~8 d
  62.                         test_cal,//菜单函数,功能菜单才会执行,有子菜单的不会执行
    * z% e/ V- k0 K( z

  63. 3 d& b4 ?- n- S5 k7 {& t
  64.                         MENU_L_2,//菜单等级, Y2 W  k9 {) c: y' w8 a3 x
  65.                         "测试",//中文
    " n+ A; V) r. W9 Y/ {6 f1 _
  66.                         "test",        //英文0 u7 R5 w" g: S) n, i
  67.                         MENU_TYPE_FUN,//菜单类型
    # L1 Q8 |# {7 Q! T- z2 e/ {( P
  68.                         test_tp,//菜单函数,功能菜单才会执行,有子菜单的不会执行) Z" p, p4 p# _& ]( c2 p$ b4 B
  69.                        
    3 I6 `9 i, d- m& K9 k+ t
  70.                 MENU_L_1,//菜单等级) |; A& j8 D8 s7 D
  71.                 "按键",//中文) d6 X( ?' H6 J" b9 y8 N* @' y
  72.                 "KEY",        //英文
    ; Q; S" Y  k! h* d% @
  73.                 MENU_TYPE_FUN,//菜单类型' [( @, K/ l; ?4 n! t$ T
  74.                 test_key,//菜单函数,功能菜单才会执行,有子菜单的不会执行
    / U2 G1 Q- w8 e; K5 o
  75. ! Q. e  C5 v" @1 k
  76.         /*最后的菜单是结束菜单,无意义*/                        8 p: U2 Q' v% Q$ Z: s6 _
  77.         MENU_L_0,//菜单等级: ~8 T7 Z( e) _* F1 ^7 K0 ~
  78.         "END",//中文5 C+ r: k7 D) D+ S0 A
  79.         "END",        //英文
    * G/ `' d4 c7 p$ h% h! z( l
  80.         MENU_TYPE_NULL,//菜单类型+ }/ y) x. ]  g# ]% u) y0 ?* x" ?3 a
  81.         NULL,//菜单函数,功能菜单才会执行,有子菜单的不会执行8 l! r6 h/ M+ i
  82. };
复制代码
这个菜单列表有什么特点和要求呢?
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* `
  1. 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.jpg
3 M! \! E+ D/ F7 e% ?; S; F+ O. b
4.jpg
7 q3 L4 d/ s1 L- ~" N) r) t4 Q' {# W1 c
1.44 128*128 tftlcd 效果
4 M8 E: w" v1 M 1.jpg
  k% x7 m0 g9 ^0 I: f2 \& c+ |6 ~% U  k7 p- V2 [
2.jpg ' 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 5.jpg
! X; a2 u# h1 A, f& V2 N
# l, v. a+ ^8 b: r) _( a2 I$ o) @ 6.jpg 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

评分

参与人数 3 ST金币 +15 收起 理由
五哥1 + 5 对于常用嵌套编程的人来说这个有新的思路.
toofree + 5 很给力!
子曰好人 + 5 很给力!

查看全部评分

赞 收藏 3 评论19 发布时间:2018-6-24 11:52

举报

19个回答
wujique 回答时间:2019-2-23 23:32:03
l297915958 发表于 2019-2-23 11:12
6 c! o# `" g1 w  Z' `, w楼主哥哥,新手32学习,您这个菜单的实验程序有吗,可以分享一下吗,我加你联系方式 ...
8 ^8 t5 u9 I! y4 h# p+ P
在github上有最新的
勿忘心安110 回答时间:2018-6-26 08:41:31
huangxuejia-292 发表于 2018-6-25 09:14: I4 o) ~% i/ @, f
都在玩GUI,这么简陋的东西,不知道还有没有人用。。。。。
# C+ \8 j3 L9 {2 W% M, C/ e
大把的人用 很实用 感谢楼主分享
wujique 回答时间:2018-6-25 13:22:55
wolfgang2015 发表于 2018-6-25 11:00* \9 e0 ?, c  D  W  S
麻烦的东东,继续优化;
" d$ }; F8 v/ ]# n3 d潜在问题:返回上级菜单

' L' O9 I6 I1 j3 q9 K. R% ~版主,你是说返回上级菜单有问题吗?
wujique 回答时间:2018-6-25 09:14:02
都在玩GUI,这么简陋的东西,不知道还有没有人用。。。。。* Z3 O8 X& I6 f' K9 `
子曰好人 回答时间:2018-6-25 09:38:40
虽然我目前用不上GUI,但是楼主给的思路有一定的启发作用,感谢分享
Bowen1028 回答时间:2018-6-25 10:01:06
我们产品上就有菜单,和楼主思路类似
wujique 回答时间:2018-6-25 10:03:32
strang 发表于 2018-6-25 10:011 T& `5 D* |* _" `
我们产品上就有菜单,和楼主思路类似
! T9 V- t, H" U% z: G
有什么好设计提醒提醒我,我优化优化
Bowen1028 回答时间:2018-6-25 10:07:15
用函数指针做界面,每个函数里都有while(1)
wolfgang 回答时间:2018-6-25 11:00:47
麻烦的东东,继续优化;
/ K& j3 o. a) l! `9 K潜在问题:返回上级菜单
程序员 回答时间:2018-6-25 11:26:30
很赞!
GKoSon 回答时间:2018-6-25 16:09:28
双向链表 基本都是菜单的路子
l297915958 回答时间:2019-2-23 11:12:59
楼主哥哥,新手32学习,您这个菜单的实验程序有吗,可以分享一下吗,我加你联系方式
西点钟灵毓秀 回答时间:2019-2-23 19:31:56
这个要点赞。
路痴痴路 回答时间:2019-5-30 19:46:03
谢谢楼主收藏
12下一页

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版