本帖最后由 murphyzhao 于 2018-12-27 13:50 编辑 9 O4 C# Y) q- F; p% i0 }- \ & \ O5 u. x/ Q1 C' _' W! F& p* R ## 前言/ K# t* h% r9 m$ |' s. n# q7 | 正好工作中用到按键处理,需要处理单击、长按等按键事件,然后就造了这么一个轮子,为了以后更方便地加入其它的项目中使用,遂将其开源到 GitHub 中。 6 K7 O: N# K# h- Q 后面发现 RT-Thread 软件包里也有一个开源的按键库 [MultiButton](http://github.com/liu2guang/MultiButton),看到这个按键库的时候,心想,完了,又重复造轮子了,好伤心 :joy:。想想,既然按键处理方式不同,而且时间已经花出去了,那就把轮子圆一圆,放到 GitHub 中,给有缘人用吧,然后就有了这个不太圆的轮子 [FlexibleButton](http://github.com/zhaojuntao/FlexibleButton)。; P- F5 E$ s7 M6 T% Q+ f. c j ## 概述 K3 _# e: S- N/ j! S0 E $ H) ?( y/ U/ C- G# B& G, N1 o FlexibleButton 是一个基于 C 语言的小巧灵活的按键处理库。该按键库解耦了具体的按键硬件结构,理论上支持轻触按键与自锁按键,并可以无限扩展按键数量。另外,FlexibleButton 使用扫描的方式一次性读取所有所有的按键状态,然后通过事件回调机制上报按键事件。! B/ n4 Y! j) e, q" v3 U+ @& G & N8 B1 z3 c3 q 该按键库使用 C 语言编写,驱动与应用程序解耦,便于灵活应用,比如用户可以方便地在应用层增加按键中断、处理按键功耗、定义按键事件处理方式,而无需修改 FlexibleButton 库中的代码。; Z/ A) R: }2 i. i8 l & H2 D2 g( ^! N0 H7 u' z! U 另外,使用 C 语言标准库 API 编写,也使得该按键库可以无缝兼容任意的处理器平台,并且支持任意 OS 和 non-OS(裸机程序)。5 ?. F3 h" b& Z* b + |7 P$ |7 W! h5 T% T5 H6 J/ |5 A 另外,该按键库核心的按键扫描代码仅有三行,没错,就是经典的**三行按键扫描算法**,出自哪位大神之手就无从得知了,也欢迎知道此高人的读者文后留言哈。算法介绍可以去搜索引擎里查找,这里就不作介绍了。9 l2 S0 J/ `2 |# m5 X, E# J ## 获取 ( {" c0 |) ?2 E( n- L ```SHELL# b; G/ G* m# |) z/ c git clone http://github.com/zhaojuntao/FlexibleButton.git: T/ a u8 ?8 W1 N& m; m# X ```. p$ K* J& H* {5 K5 v: t F 7 w$ Y$ L: j- }4 h" \' k8 o$ s ## 测试1 R" e# C% [ a7 I7 y# d / Z7 @1 j1 `4 k) I6 q9 ^: Y FlexibleButton 库中提供了一个 DEMO `flexible_button_demo.c`,这里基于 RT-Thread OS 进行测试,当然你可以选择使用其他的 OS,或者使用裸机测试,只需要移除 OS 相关的特性即可。 将 FlexibleButton 库放到 RT-Thread BSP 下即可使用 scons 进行编译构建。; _6 k1 s) o! }( |7 v$ U- r ## DEMO 程序说明' x- F" s( _" {0 @0 F$ e X ### 程序入口, p, A1 A) B. g6 Y1 J- X" h ```C/ ~. {; V0 z8 D; F int flex_button_main(void)" a+ c8 M. r- H' e4 U { rt_thread_t tid = RT_NULL; user_button_init();( m' L, ~; t4 ~4 p! s6 z* Y% n: C /* Create background ticks thread */ tid = rt_thread_create("flex_btn", button_scan, RT_NULL, 1024, 10, 10); if(tid != RT_NULL) {4 H0 R' \( D b8 p$ J rt_thread_startup(tid); } return 0;1 [) z) ]( T6 S7 l: u } INIT_APP_EXPORT(flex_button_main); ``` 3 c8 H2 Q. \- J$ f9 x0 F* ~ 如上所示,首先使用 `user_button_init();` 初始化用户按键硬件,并挂载到 FlexibleButton库。然后,使用了 RT-Thread 的 `INIT_APP_EXPORT` 接口导出为上电自动初始化,创建了一个 “flex_btn” 名字的按键扫描线程,线程里扫描检查按键事件。, {7 X+ z( p% E. \- z 3 M6 Q0 ~* V4 w ### 用户初始化代码 * R/ Z+ W- z& A+ `4 w9 C9 ]; \ `user_button_init();` 初始化代码如下所示:4 m' _- U. X+ D' P2 g 9 p2 D) ~: l& ~) g6 t5 H5 A ```C9 m4 Y* B h* j2 ^ static void user_button_init(void)* V0 l. V9 i& i& a) V5 c7 j" l {2 u% R3 f3 u% j. M; z1 t& n int i;! H4 O$ ~" m) i3 }' b# c8 e0 z, d rt_memset(&user_button[0], 0x0, sizeof(user_button));9 d) k" _+ R+ r2 A# c user_button[USER_BUTTON_0].usr_button_read = button_key0_read;$ Q% y, k* k2 M) X9 p4 {9 U( O2 w# j" y user_button[USER_BUTTON_0].cb = (flex_button_response_callback)btn_0_cb; user_button[USER_BUTTON_1].usr_button_read = button_key1_read; user_button[USER_BUTTON_1].cb = (flex_button_response_callback)btn_1_cb;1 n: F. X0 u% A2 C0 J x* {. @ user_button[USER_BUTTON_2].usr_button_read = button_key2_read; user_button[USER_BUTTON_3].usr_button_read = button_keywkup_read; rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT); /* set KEY pin mode to input */1 c- w9 H. s8 R" ?) L e rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT); /* set KEY pin mode to input */ rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT); /* set KEY pin mode to input */ rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT); /* set KEY pin mode to input */0 [5 _' Z- g K% ~9 m for (i = 0; i < USER_BUTTON_MAX; i ++)" K! h/ M0 M" ^) f- c7 @1 v { user_button.status = 0; user_button.pressed_logic_level = 0; user_button.click_start_tick = 20; user_button.short_press_start_tick = 100; user_button.long_press_start_tick = 200; user_button.long_hold_start_tick = 300; if (i == USER_BUTTON_3)+ q$ `, A2 W9 K# }8 W { user_button[USER_BUTTON_3].pressed_logic_level = 1; }: u+ E% q- u1 ?5 N9 v8 G- P flex_button_register(&user_button);" M/ [$ K/ r5 o ?. E% k; G3 M }5 a, ]# T' n# V1 G; J } ```9 a. \0 k% z9 J$ y& C: o `user_button_init();` 主要用于初始化按键硬件,设置按键长按和短按的 tick 数(RT-Thread上面默认一个 tick 为 1ms),然后注册按键到 FlexibleButton 库。 * X5 r& M; e. u# [ e0 [ ### 事件处理代码1 v! q$ W+ i# ~9 B/ k / _% Z0 R' s/ O" ` ```C/ E3 ]* Y4 a5 v" M' ~ static void btn_0_cb(flex_button_t *btn) {- H$ N0 d" z2 \' O* h h rt_kprintf("btn_0_cb\n"); switch (btn->event) {! O7 o7 u5 i5 b B6 W case FLEX_BTN_PRESS_DOWN: rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_DOWN]\n");6 i4 ~: d$ k* w0 o) Y2 w break; case FLEX_BTN_PRESS_CLICK: rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_CLICK]\n");% b2 X: n0 L! c* X4 P' h break;' h+ f2 d& u/ h( t8 I6 Q case FLEX_BTN_PRESS_DOUBLE_CLICK: l5 c0 {7 i" D2 v rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_DOUBLE_CLICK]\n");- }* {9 x! p3 U! A4 D6 k break;5 J) x2 F' |+ b case FLEX_BTN_PRESS_SHORT_START:' L( X, {- E5 B0 C0 v rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_SHORT_START]\n"); break;$ o" }9 @/ z! a1 c6 o9 x2 @ case FLEX_BTN_PRESS_SHORT_UP:+ P+ g# {" N4 @8 ^7 H2 p rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_SHORT_UP]\n"); v9 u, F3 w3 f- F( o break; case FLEX_BTN_PRESS_LONG_START:4 F9 G. c8 b0 I( [6 I rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_START]\n"); break;6 V+ K, j' i! j8 p% m case FLEX_BTN_PRESS_LONG_UP:0 T/ N+ ^/ w" \+ n0 g rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_UP]\n");: ~4 J. _0 K6 ^; r7 X4 o1 |7 O n7 [( X break;' R( ]4 X8 B7 r4 G0 o case FLEX_BTN_PRESS_LONG_HOLD:# \4 v# w3 t }% t k0 i7 L0 D6 E v rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_HOLD]\n"); break;0 J0 T8 B* S, o2 r case FLEX_BTN_PRESS_LONG_HOLD_UP: rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_HOLD_UP]\n");% Y' n [+ B. ]5 ?$ i: d break; } }% J, ~$ u8 d" @: ^6 k1 Y$ u ``` 7 W, X, t, O& i2 l& o ## FlexibleButton 代码说明; B" H1 g3 Y% Y" p% @! B( B2 F / F3 H4 b- G2 F9 }' d$ C) n& U& s ### 按键事件定义 $ ]5 a. t% { ~# R' B 按键事件的定义并没有使用 Windows 驱动上的定义,主要是方便嵌入式设备中的应用场景(也可能是我理解的偏差),按键事件定义如下: . V) u7 v9 D& X1 x# u ```C* {. T% Q7 B% t typedef enum {4 }3 S' i/ _5 T/ k2 E1 q FLEX_BTN_PRESS_DOWN = 0, // 按下事件3 w1 p1 k4 v+ z% \( ^ FLEX_BTN_PRESS_CLICK, // 单击事件 FLEX_BTN_PRESS_DOUBLE_CLICK, // 双击事件' G6 ?9 j6 S& p2 m8 ^ FLEX_BTN_PRESS_SHORT_START, // 短按开始事件 FLEX_BTN_PRESS_SHORT_UP, // 短按抬起事件 FLEX_BTN_PRESS_LONG_START, // 长按开始事件; P' x' t7 K$ i3 C1 ?, b6 O% p FLEX_BTN_PRESS_LONG_UP, // 长按抬起事件 FLEX_BTN_PRESS_LONG_HOLD, // 长按保持事件$ ^' `. u3 j, S+ b# @ FLEX_BTN_PRESS_LONG_HOLD_UP, // 长按保持的抬起事件- S+ `: @, a2 l6 Y" V# w) A! ] FLEX_BTN_PRESS_MAX, x, v) p, a: b. z FLEX_BTN_PRESS_NONE,) j p" d" T+ m- d4 Y" W } flex_button_event_t;! j( i) G, C( i0 b- E ``` ### 按键注册接口 * @% B( G* X1 Z; T2 ] 使用该接口注册一个用户按键,入参为一个 flex_button_t 结构体实例的地址。 ) _8 I* l; O' N$ q. w ```C+ D. Z6 q% ^8 C$ Y% h8 c int8_t flex_button_register(flex_button_t *button); ``` ### 按键事件读取接口0 j; A# k5 n9 Y# O6 W4 M 0 ]2 N1 Q9 c' v2 I t8 j h; h 使用该接口获取指定按键的事件。/ r5 x, A, d. i, q% C" v0 ? ```C flex_button_event_t flex_button_event_read(flex_button_t* button);# p! L* ~, V$ [- ]" L ````8 q; |8 \- D2 h) L7 E # k: a7 j8 V% U9 D, { ### 按键扫描接口 ! i9 a. @; Z2 l 按键扫描的核心函数,需要放到应用程序中定时扫描间隔 5-20ms 即可。$ Y2 c3 {5 K, u* q# L ```C& z7 q6 e: `# H2 Z8 Y+ H! R void flex_button_scan(void);& p( y% U. f4 p% |7 e9 E# X6 m ```& L# Z7 t) H; x 9 a; d' ^( n2 W* _" G ## 问题和建议 , x3 t8 ^* K, }. c2 e& A t* d F 如果您在应用的时候遇到了问题,或者有好的想法和建议,欢迎到这个 [issue](http://github.com/zhaojuntao/FlexibleButton/issues/1) 上讨论,谢谢。6 K6 s( |5 Q; e( @& X4 N |
编辑器不支持 markdown 格式 |