button drive 杰杰自己写的一个按键驱动,支持单双击、连按、长按;采用回调处理按键事件(自定义消抖时间),使用只需3步,创建按键,按键事件与回调处理函数链接映射,周期检查按键。& t5 s* O: `# {- h' m 前言源码地址:http://github.com/jiejieTop/ButtonDrive。作者:杰杰 前几天写了个按键驱动,参考了MulitButton的数据结构的用法,逻辑实现并不一样。# e% v4 Y4 u- Y3 j9 N3 S% } Button_drive简介在这里感谢所有的开源开发者,让我从中学到了很多,同时网络也是一个好平台,也希望所有的开发者能形成良性循环,从网络中学知识,回馈到网络中去。感谢MulitButton的作者0x1abin,感谢两位rtt的大佬:大法师、流光。 Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在RT-Thread上测试过。4 {0 u- f! M& C0 u+ H9 V& |1 r6 | Button_drive使用效果写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。
Button_t Button2; 5 |* ] x( K9 u. Y: ?9 T* z/ y
&Button1, //按键句柄6 L/ @, g: g( ^2 x; p- ]8 a/ @ Read_Button1_Level, //按键电平检测函数接口 BTN_TRIGGER); //触发电平 : |& B; U* H" {& ~8 v4 T ......
Button_Attach(&Button1,BUTTON_DOUBLE,Btn2_Double_CallBack); //双击 Button_Attach(&Button1,BUTTON_LONG,Btn2_Long_CallBack); //长按, M: N) `4 |4 b! \- G 4 i2 s* W7 l+ h3 I W .......( d. t( r3 z8 L' n. d: s+ {
需要用户实现的 2 个函数:
{ return GPIO_ReadInputDataBit(BTN1_GPIO_PORT,BTN1_GPIO_PIN); } uint8_t Read_Button2_Level(void) { return GPIO_ReadInputDataBit(BTN2_GPIO_PORT,BTN2_GPIO_PIN); } // 这是我在stm32上简单测试的伪代码,以实际源码为准
{1 b4 s, I4 j( J$ k" [ PRINT_INFO("Button1 单击!");% a: o Z+ \! X. d) b( T }/ ~7 o( a6 i# \& a6 P; e% I j0 V void Btn1_Double_CallBack(void *btn) {# j h$ V) @& p! U. L% R PRINT_INFO("Button1 双击!"); }6 x) ]9 w4 d/ J- S/ L void Btn1_Long_CallBack(void *btn)4 [. N' f: b! H% l { PRINT_INFO("Button1 长按!"); $ M$ w: z1 ?9 s- F: p Button_Delete(&Button2); PRINT_INFO("删除Button1"); Search_Button(); } T. b9 J4 d1 K3 P 特点 Button_drive开放源码,按键控制块采用数据结构方式,按键事件采用枚举类型,确保不会重复,也便于添加用户需要逻辑,采用宏定义方式定义消抖时间、连按触发时间、双击时间间隔、长按时间等,便于修改。; ^1 E9 z( K* ]2 C$ T( P/ n" R 按键控制块/* g# d7 w( d4 T- h; V0 K. @5 R同时所有被创建的按键采用单链表方式连击,用户只管创建,无需理会按键处理,只需调用Button_Process()即可,在函数中会自动遍历所有被创建的按键。2 O& t' Q3 T. m( B 支持按键删除操作,用户无需在代码中删除对应的按键创建于映射链接代码,也无需删除关于按键的任何回调事件处理函数,只需调用Button_Delete()函数即可,这样子,就不会处理关于被删除按键的任何状态。当然目前按键内存不会释放,如果使用os的话,建议释放按键内存。 每个按键对应1个全局的结构体变量。7 o7 f# Y m" a. N 其成员变量是实现消抖和多种按键状态所必须的 */% K, ]) m5 Z+ u% r" U typedef struct button" w4 G& L! c! ~( b1 R { /* 下面是一个函数指针,指向判断按键手否按下的函数 */ uint8_t (*Read_Button_Level)(void); /* 读取按键电平函数,需要用户实现 */# ]: n) k8 h) N2 V# r- Z1 w 6 D7 W' d4 z2 l char Name[BTN_NAME_MAX];& X7 _2 n6 E: n7 Z9 R$ j+ S2 Y uint8_t Button_State : 4; /* 按键当前状态(按下还是弹起) */& x/ o1 j. x( L c; U uint8_t Button_Last_State : 4; /* 上一次的按键状态,用于判断双击 */. _0 A& M8 W- F9 W( w% b. ] uint8_t Button_Trigger_Level : 2; /* 按键触发电平 */ uint8_t Button_Last_Level : 2; /* 按键当前电平 */3 o. d$ ~0 F( N- }( d. E* B uint8_t Button_Trigger_Event; /* 按键触发事件,单击,双击,长按等 */8 H8 B( q" |$ O* Z; I8 ?4 q Button_CallBack CallBack_Function[number_of_event]; uint8_t Button_Cycle; /* 连续按键周期 */ uint8_t Timer_Count; /* 计时 */' `! _8 h2 d/ `$ y9 ?2 W uint8_t Debounce_Time; /* 消抖时间 */ : V- p4 R ^* q5 Y+ c! ] uint8_t Long_Time; /* 按键按下持续时间 *// x9 Z! l) [2 T; x, K struct button *Next;5 j& w3 @6 ~& U6 O" B3 {: f 5 d# a8 { g- D. _8 ?3 p- z }Button_t; 触发事件typedef enum {% F! p/ |+ d9 G* m' A& X8 d) u! Z/ w BUTTON_DOWM = 0, BUTTON_UP,/ o% w+ v9 ^5 D* ~- k. w+ | BUTTON_DOUBLE, BUTTON_LONG,3 u: ^8 s, ~( X; x$ e% T BUTTON_CONTINUOS,0 ]+ x, t7 y9 t8 L3 @ BUTTON_CONTINUOS_FREE,! a3 ^/ S9 e2 G5 I6 x BUTTON_ALL_RIGGER,, q* W. d4 P& \4 U b2 u number_of_event, /* 触发回调的事件 */" B8 D" U# y3 F- o- [( F1 y' V. q NONE_TRIGGER }Button_Event;- M/ Y( X% s9 J J! N% v; l! {1 n: R 宏定义选择#define BTN_NAME_MAX 32 //名字最大为32字节 /* 按键消抖时间40ms, 建议调用周期为20ms 只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件 */+ m( _6 \3 P( L# K7 | * `: |! {; |0 R U8 m #define CONTINUOS_TRIGGER 0 //是否支持连续触发,连发的话就不要检测单双击与长按了 4 L5 I" S9 M9 C 7 S& }1 y- @0 E! @# p/ t- ~7 @ /* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应, 因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。 而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击, 因为双击必须是有一次按下并且释放之后才产生的 */0 J7 g. g: y) n0 N. W! F' G #define SINGLE_AND_DOUBLE_TRIGGER 1 ! W, K b2 b, `' z/ A 7 v2 |7 C4 e7 f /* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按, 否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */ w1 n" ^/ S# J #define LONG_FREE_TRIGGER 0 , r, q) z% G- k3 y& x& I+ k #define BUTTON_DEBOUNCE_TIME 2 //消抖时间 (n-1)*调用周期9 }' n- I" t2 ]- P: e- Y #define BUTTON_CONTINUOS_CYCLE 1 //连按触发周期时间 (n-1)*调用周期 #define BUTTON_LONG_CYCLE 1 //长按触发周期时间 (n-1)*调用周期 2 l$ ]. y3 s4 A! J1 i @ #define BUTTON_DOUBLE_TIME 15 //双击间隔时间 (n-1)*调用周期 建议在200-600ms4 I6 ]* o' a, i( x9 U7 I #define BUTTON_LONG_TIME 50 /* 持续n秒((n-1)*调用周期 ms),认为长按事件 */ : T8 o) l3 ]6 `1 F& U- k% ~0 Y5 J #define TRIGGER_CB(event) \ if(btn->CallBack_Function[event]) \. b2 J) o; K8 Z btn->CallBack_Function[event]((Button_t*)btn)1 C$ S, S* g+ J! }6 H( w: g 例子 Button_Create("Button1",* E% _1 O m5 h0 |% J: y" \/ L &Button1, Read_KEY1_Level, KEY_ON); Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack); //单击0 D/ I4 M! T$ f, f. S+ b) ~ Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack); //双击; e1 `1 |' l. I9 e1 I+ z Button_Attach(&Button1,BUTTON_CONTINUOS,Btn1_Continuos_CallBack); //连按 Button_Attach(&Button1,BUTTON_CONTINUOS_FREE,Btn1_ContinuosFree_CallBack); //连按释放 Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack); //长按/ u7 Y( ^) p0 X: v9 ]- l Button_Create("Button2", &Button2, Read_KEY2_Level, KEY_ON); Button_Attach(&Button2,BUTTON_DOWM,Btn2_Dowm_CallBack); //单击 Button_Attach(&Button2,BUTTON_DOUBLE,Btn2_Double_CallBack); //双击 Button_Attach(&Button2,BUTTON_CONTINUOS,Btn2_Continuos_CallBack); //连按 Button_Attach(&Button2,BUTTON_CONTINUOS_FREE,Btn2_ContinuosFree_CallBack); //连按释放, }9 ?( A" t. |: Q0 S" V4 X: ]: e Button_Attach(&Button2,BUTTON_LONG,Btn2_Long_CallBack); //长按3 E% a! P8 q4 F7 F. Q; F0 T Get_Button_Event(&Button1); Get_Button_Event(&Button2); 后续ButtonDrive在env使用 目前我已将按键驱动做成软件包(packages),如果使用RT-Thread操作系统的话,可以在env中直接配置使用! 步骤如下:
buildpkg 是用于生成 RT-Thread package 的快速构建工具。 一个优秀的 package 应该是这样的:
为了方便快速的生成 RT-Thread package 规范化模板 以及 减轻开源仓库迁移 RT-Thread 的前期准备工作的负担,基于此目的的 buildpkg 应运而生,为开发 Rt-Thread 的 package 的开发者提供辅助开发工具。 序号 支持功能 描述 1构建 package 模板创建指定名称 package , 自动添加 readme /版本号/ github ci脚本/demo/开源协议文件* ^+ E' a7 x& A N$ I( q/ O 2迁移开源仓库从指定 git 仓库构建 package , 自动添加readme/版本号/ github ci脚本/demo/开源协议文件, 但是迁移的仓库需要用户自己按照实际情况修改 3更新 package生成package后可以再次更新之前设定的版本号,开源协议或者scons脚本等使用说明1. 构建package 2. 迁移开源仓库 3. 更新package 4. 可选配置 长参数 短参数 描述 7 c; V# \6 N7 ]/ u--version=v1.0.0-v v1.0.0设置 package 的版本 --license=MIT-l MIT设置 package 所遵循的版权协议 --submodule-s删除 git 子模块Windows10 及 Linux 平台的演示动图buildpkg测试平台 序号 测试平台 测试结果 1win10exe测试通过, py测试通过) O# r- s- z( ]- G0 f 2win7exe待测试, py待测试 3macpy脚本不知道是否兼容, 没有测试条件, 后面维护下 4linuxpy脚本不知道是否兼容, 没有测试条件, 后面维护下联系人
|
点评
做嵌入开发,其实就是一个权衡的过程。0 B2 f {: f8 x$ W
如果本身对速度RAM消耗等不是非常敏感,而又希望提供简单易用的二次开发接口给别人使用," z- C# p, `: t2 ^
用这种面向对象的方法,也不是不可取。! ~$ h* o7 l! z' \" P6 T+ v) m
这样的方法,更多是往LINUX设计方法靠拢。