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

纯C语言写的按键驱动,将按键逻辑与按键处理事件分离~

[复制链接]
xiaojie0513 发布时间:2019-1-23 10:06
button drive
杰杰自己写的一个按键驱动,支持单双击、连按、长按;采用回调处理按键事件(自定义消抖时间),使用只需3步,创建按键,按键事件与回调处理函数链接映射,周期检查按键。& t5 s* O: `# {- h' m
源码地址:http://github.com/jiejieTop/ButtonDrive。作者:杰杰
前言
前几天写了个按键驱动,参考了MulitButton的数据结构的用法,逻辑实现并不一样。# e% v4 Y4 u- Y3 j9 N3 S% }
在这里感谢所有的开源开发者,让我从中学到了很多,同时网络也是一个好平台,也希望所有的开发者能形成良性循环,从网络中学知识,回馈到网络中去。感谢MulitButton的作者0x1abin,感谢两位rtt的大佬:大法师流光
Button_drive简介
Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在RT-Thread上测试过。4 {0 u- f! M& C0 u+ H9 V& |1 r6 |
写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。
Button_drive使用效果
  • 单击与长按* s- i6 R' k* b9 B% M
单击与长按
  • 双击: K/ G8 [1 c5 f
双击
  • 连按
    % Y! L" R2 B1 {$ G4 x9 ~3 w
连按
  • 连按释放
    4 x3 B: T6 i+ _. Q0 w: [& p( u
连按释放使用方法
  • 创建按键句柄
    3 e8 ?$ r$ U6 T$ [% J
Button_t Button1;0 F' j8 F5 P8 x5 E; I7 H% p
Button_t Button2; 5 |* ]  x( K9 u. Y: ?9 T* z/ y
  • 创建按键,初始化按键信息,包括按键名字、按键电平检测函数接口、按键触发电平。" R, T6 y) k: v; J( H0 P: t- j
  Button_Create("Button1",                //按键名字
& }8 e& ?1 {4 r4 N                &Button1,                 //按键句柄6 L/ @, g: g( ^2 x; p- ]8 a/ @
                Read_Button1_Level,     //按键电平检测函数接口
0 G; D- v- \! A4 Q  p                BTN_TRIGGER);            //触发电平
% j  c: g" b4 I) ~0 g% v9 E1 b: |& B; U* H" {& ~8 v4 T
                ......
# [& L3 G0 S8 b& O1 A
  • 按键触发事件与事件回调函数链接映射,当按键事件被触发的时候,自动跳转回调函数中处理业务逻辑。
      v4 M9 `* M( U+ k% g
  Button_Attach(&Button1,BUTTON_DOWM,Btn2_Dowm_CallBack);        //按键单击
% ^' {0 d1 }  @4 n& Y; D  Button_Attach(&Button1,BUTTON_DOUBLE,Btn2_Double_CallBack);    //双击
8 p( q+ \$ O2 |, Y. D- w, H  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+ {
  • 周期调用回调按键处理函数即可,建议调用周期20-50ms。2 i9 c; S0 G4 K0 z
Button_Process();     //需要周期调用按键处理函数* Y9 C" x2 T. |7 R9 ~

需要用户实现的 2 个函数:
  • 按键电平检测接口:
    % I* {( Q/ }8 l2 \8 \) _
uint8_t Read_Button1_Level(void)
4 w8 e( g( k) T* V6 o3 j{
8 l' I8 f+ c; ?. S9 w; {  return GPIO_ReadInputDataBit(BTN1_GPIO_PORT,BTN1_GPIO_PIN);
8 y1 F8 s: Q# a! p' e/ t$ A}
+ k  t" ?: u( W0 n' q
# o# ]5 i& @% ]! Puint8_t Read_Button2_Level(void)
. G. o+ L& K* C  }$ C$ k$ P6 Z! L{
, H0 K  f" Z9 b  return GPIO_ReadInputDataBit(BTN2_GPIO_PORT,BTN2_GPIO_PIN);
3 r- h) Z% J: j, Z& B; A}
+ j; t. t" N! M: d& E5 s% Y' s
" X3 h! p* ?% z$ e+ L5 q// 这是我在stm32上简单测试的伪代码,以实际源码为准
( D9 g8 B5 t( z4 V7 S3 v
  • 按键逻辑处理
    ; l, w9 z* i5 w, m0 p+ Q
void Btn1_Dowm_CallBack(void *btn)) `- r7 y6 w. P( |
{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

7 W9 ^" m6 {  h+ k: V( e" ~void Btn1_Double_CallBack(void *btn)
/ `# n8 p5 @9 m1 [0 `
{# j  h$ V) @& p! U. L% R
  PRINT_INFO("Button1 双击!");
- m) g* s+ W# R+ A# }8 _  y) j}6 x) ]9 w4 d/ J- S/ L

$ i7 m3 i. p4 K7 gvoid Btn1_Long_CallBack(void *btn)4 [. N' f: b! H% l
{
- T# g  Q; H3 G0 B  PRINT_INFO("Button1 长按!");
% f3 b5 d, [1 ~$ M$ w: z1 ?9 s- F: p
  Button_Delete(&Button2);
* H. B3 f* x" ?& g( n# U& h  PRINT_INFO("删除Button1");
! P( F$ j" z1 ^# I, V5 G  Search_Button();
* P  O) V4 `6 q  x6 d) u- f) t8 M& i4 [}  T. b9 J4 d1 K3 P
特点
Button_drive开放源码,按键控制块采用数据结构方式,按键事件采用枚举类型,确保不会重复,也便于添加用户需要逻辑,采用宏定义方式定义消抖时间、连按触发时间、双击时间间隔、长按时间等,便于修改。; ^1 E9 z( K* ]2 C$ T( P/ n" R
同时所有被创建的按键采用单链表方式连击,用户只管创建,无需理会按键处理,只需调用Button_Process()即可,在函数中会自动遍历所有被创建的按键。2 O& t' Q3 T. m( B
支持按键删除操作,用户无需在代码中删除对应的按键创建于映射链接代码,也无需删除关于按键的任何回调事件处理函数,只需调用Button_Delete()函数即可,这样子,就不会处理关于被删除按键的任何状态。当然目前按键内存不会释放,如果使用os的话,建议释放按键内存。
按键控制块/*  g# d7 w( d4 T- h; V0 K. @5 R
    每个按键对应1个全局的结构体变量。7 o7 f# Y  m" a. N
    其成员变量是实现消抖和多种按键状态所必须的
. O+ W* K# y4 I*/
% K, ]) m5 Z+ u% r" U
typedef struct button" w4 G& L! c! ~( b1 R
{

' C4 T1 J3 i, B: X; ^( E! g    /* 下面是一个函数指针,指向判断按键手否按下的函数 */
& E2 S7 n, z/ N+ T    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

0 g% N$ n5 {* F- `. v  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;    /* 按键触发电平 */
3 |( i2 U# B- b  uint8_t Button_Last_Level         :   2;    /* 按键当前电平 */3 o. d$ ~0 F( N- }( d. E* B

$ ~; o; v3 U8 Z" _, z- ~: U- ]  uint8_t Button_Trigger_Event;     /* 按键触发事件,单击,双击,长按等 */8 H8 B( q" |$ O* Z; I8 ?4 q

* R3 Q+ ]9 y+ O4 M2 u/ l. g  Button_CallBack CallBack_Function[number_of_event];
9 S1 c7 v. i1 d+ o  uint8_t Button_Cycle;               /* 连续按键周期 */
+ H* B: g! F$ |
2 k: _) E. L0 [1 l  uint8_t Timer_Count;            /* 计时 */' `! _8 h2 d/ `$ y9 ?2 W
  uint8_t Debounce_Time;        /* 消抖时间 */
4 B5 G  I. B1 j2 [2 s: V- p4 R  ^* q5 Y+ c! ]
  uint8_t Long_Time;          /* 按键按下持续时间 *// x9 Z! l) [2 T; x, K

8 G% B, ?& J9 H6 E  struct button *Next;5 j& w3 @6 ~& U6 O" B3 {: f
5 d# a8 {  g- D. _8 ?3 p- z
}Button_t;
4 t$ K2 i) r1 l7 D/ @, v! V触发事件typedef enum {% F! p/ |+ d9 G* m' A& X8 d) u! Z/ w
  BUTTON_DOWM = 0,
. x' O# y- d; Q  BUTTON_UP,/ o% w+ v9 ^5 D* ~- k. w+ |
  BUTTON_DOUBLE,
* Y9 n+ m5 u# S' V+ ~4 j& J5 E  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
9 t6 x* M" l8 D" ~, c! s}Button_Event;- M/ Y( X% s9 J  J! N% v; l! {1 n: R
宏定义选择#define BTN_NAME_MAX  32     //名字最大为32字节
; T' \# k$ A3 ~5 b* H  C) P
' e, Q3 g- b6 h. Z/* 按键消抖时间40ms, 建议调用周期为20ms
1 i! r4 N! l# I- ^5 ` 只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件
- s& |) e) d* z% z- i/ k1 x*/
+ 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 @
/* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应,
! s+ m- f2 U: \   因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。
) l# z. D9 Q! ?3 ^* ~0 i- I5 |# t/ T   而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击,
7 A' b6 G; ~4 X& p' ]  Q. @   因为双击必须是有一次按下并且释放之后才产生的 */
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
/* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按,
4 d% s4 f$ \9 g1 F/ V- o# ~( L! D  ]   否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */
  w1 n" ^/ S# J
#define LONG_FREE_TRIGGER             0 , r, q) z% G- k3 y& x& I+ k

- [3 U1 I6 s) S- S5 {+ k; y  t#define BUTTON_DEBOUNCE_TIME       2   //消抖时间      (n-1)*调用周期9 }' n- I" t2 ]- P: e- Y
#define BUTTON_CONTINUOS_CYCLE  1      //连按触发周期时间  (n-1)*调用周期  
- E4 s) l( r2 h+ Q" U/ ?0 I#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),认为长按事件 */
# S: E6 v- ~. u# ^1 }. W: T8 o) l3 ]6 `1 F& U- k% ~0 Y5 J
#define TRIGGER_CB(event)   \
0 }% P" ]( t3 \7 i        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,
6 J2 a. t8 D6 o% k2 N9 A              Read_KEY1_Level,
( m. B% ]$ ?* X. x+ Q              KEY_ON);
# i" J5 A( n- U8 x2 h  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);             //连按  
  }7 V/ Q* F( @0 d6 G  Button_Attach(&Button1,BUTTON_CONTINUOS_FREE,Btn1_ContinuosFree_CallBack);    //连按释放  
. S# b! }! U, t  Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack);                       //长按/ u7 Y( ^) p0 X: v9 ]- l

, x& ^3 M. E6 a( D- J. q
, M2 u! w3 |: U  Button_Create("Button2",
' f+ u- G, J: F' _! @$ J9 C' g# ^# p              &Button2,
: \# N7 B* T7 P5 {              Read_KEY2_Level,
8 Z. `, Y  w3 q0 h3 Z              KEY_ON);
. i7 q* Z; C, v3 C$ G& j  Button_Attach(&Button2,BUTTON_DOWM,Btn2_Dowm_CallBack);                     //单击
9 k7 o% \" W1 _0 x! Y% P  Button_Attach(&Button2,BUTTON_DOUBLE,Btn2_Double_CallBack);                 //双击
# {5 d: v  [( {  Button_Attach(&Button2,BUTTON_CONTINUOS,Btn2_Continuos_CallBack);           //连按
* {1 J8 s2 u+ F* S6 d7 ]4 g  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

" {9 R2 a( f; v2 h8 H7 E  Get_Button_Event(&Button1);
+ ?! r# y: k* H) f  Get_Button_Event(&Button2);
) j/ \% i. [7 k! T7 O8 ]6 ^0 k2 n后续
流光大佬的要求,让我玩一玩RTT的rtkpgs,打算用Button_drive练一练手吧。
ButtonDrive在env使用
目前我已将按键驱动做成软件包(packages),如果使用RT-Thread操作系统的话,可以在env中直接配置使用!
步骤如下:
  • 选择在线软件包0 O3 ~1 ?; r8 i: ?
  • 选择软件包属性为外设相关
      R2 ^1 s/ C7 O
  • 选择button_drive
    4 N  ~" r: T6 `& s8 |' g
  • 进入驱动的选项配置(自带默认属性)
    1 M# _- a# Z5 r
  • 如果不懂按键的配置是什么意思,按下“shift+?”,即可有解释
    ) d1 Q- j/ A: D  d0 p3 [2 V
  • 编译生成mdk/iar工程: \8 T6 O# @  R& j2 L' ^8 u
关于rtkpgs简介 (English)
buildpkg 是用于生成 RT-Thread package 的快速构建工具。
一个优秀的 package 应该是这样的:
  • 代码优雅, 规范化。
  • examples 例程,提供通俗易懂的使用例程。
  • SConscript 文件,用于和 RT-Thread 环境一起进行编译。
  • README.md 文档,向用户提供必要的功能说明。
  • docs 文件夹, 放置除了 README 之外的其他细节文档。
  • license 许可文件,版权说明。1 X9 `' @+ U. B# \7 ~
为了方便快速的生成 RT-Thread package 规范化模板 以及 减轻开源仓库迁移 RT-Thread 的前期准备工作的负担,基于此目的的 buildpkg 应运而生,为开发 Rt-Thread 的 package 的开发者提供辅助开发工具。
序号
支持功能
描述

' K% z% [# ^7 s1构建 package 模板创建指定名称 package , 自动添加 readme /版本号/ github ci脚本/demo/开源协议文件* ^+ E' a7 x& A  N$ I( q/ O
2迁移开源仓库从指定 git 仓库构建 package , 自动添加readme/版本号/ github ci脚本/demo/开源协议文件, 但是迁移的仓库需要用户自己按照实际情况修改
2 L9 ]! x( ]2 t& q8 @7 N8 P! {3更新 package生成package后可以再次更新之前设定的版本号,开源协议或者scons脚本等使用说明1. 构建package
  
buildpkg.exe make pkgdemo
2. 迁移开源仓库
  
buildpkg.exe make cstring http://github.com/liu2guang/cstring.git
3. 更新package
  
buildpkg.exe update pkgname
4. 可选配置
长参数
短参数
描述
7 c; V# \6 N7 ]/ u
--version=v1.0.0-v v1.0.0设置 package 的版本
* G# \) k" c% P  B--license=MIT-l MIT设置 package 所遵循的版权协议
: A  N8 C6 e9 S2 P--submodule-s删除 git 子模块Windows10 及 Linux 平台的演示动图buildpkg测试平台
序号
测试平台
测试结果

* r8 }9 z6 u6 V. q2 o9 D! \; D" \1win10exe测试通过, py测试通过) O# r- s- z( ]- G0 f
2win7exe待测试, py待测试
4 a$ i& n4 d9 }: u) o: h7 _3macpy脚本不知道是否兼容, 没有测试条件, 后面维护下
) C0 c, J# X- P2 G% M# y8 I0 A4linuxpy脚本不知道是否兼容, 没有测试条件, 后面维护下联系人- u3 j, h7 \5 L2 Y
收藏 5 评论16 发布时间:2019-1-23 10:06

举报

16个回答
天臆弄人 回答时间:2019-1-23 15:17:17
本来单片机按键代码就那么几行,被你搞得这么复杂,

点评

点赞  发表于 2019-1-23 15:52
mzy2364 回答时间:2019-1-23 11:40:08
谢谢分享  好东西
xiaojie0513 回答时间:2019-1-23 13:35:14
mzy2364 发表于 2019-1-23 11:40
" _) p3 r: O8 j2 y9 N4 S% G" I1 }( i谢谢分享  好东西
5 h& d( t9 W4 Z  ?9 _
qiangtech 回答时间:2019-1-23 15:23:42
细节处见真功夫。
xiaojie0513 回答时间:2019-1-23 15:53:04
qiangtech 发表于 2019-1-23 15:23
) O& ~9 C- ^, w细节处见真功夫。

# v1 F7 T% A. y: ^/ T
单片机爱好者 回答时间:2019-1-23 16:56:11
呵呵,已经实现了,,,,
roguebear2012 回答时间:2019-1-24 18:46:30
对于行列式键盘 或者iic 类的键盘。。?
回答时间:2019-2-21 11:04:09
学习一下~~~~~~~~
带我装逼带我飞 回答时间:2019-2-21 11:09:25
想楼主学习!
老牛洋车 回答时间:2019-2-21 11:14:12
单片机应用时通常就几个按键,简单的几行代码就可以搞定,有必要弄得这么复杂吗?
wujique 回答时间:2019-2-21 11:43:42
确实复杂,但是,也不一定没用,
$ @  _* [4 \: I3 a% o% q. I做嵌入开发,其实就是一个权衡的过程。0 B2 f  {: f8 x$ W
如果本身对速度RAM消耗等不是非常敏感,而又希望提供简单易用的二次开发接口给别人使用," z- C# p, `: t2 ^
用这种面向对象的方法,也不是不可取。! ~$ h* o7 l! z' \" P6 T+ v) m
这样的方法,更多是往LINUX设计方法靠拢。
! l; N0 X8 H, a/ p
雪山飞狼 回答时间:2019-2-21 13:09:42
主要学习一下楼主的编程思想,按键这个事情,想简单可以简单实现,想做个驱动方便后续二次开发,就像楼主这样做个完善的驱动,没有必要在这里质疑别人,哈哈,反正我倒是挺支持楼主,把自己的编程思想和自己的作品开源,众人拾柴火焰高,很多大的项目,就是这样一点点积累起来的。
好心情123 回答时间:2019-2-21 17:18:25
看着很不错
lspring 回答时间:2019-2-21 22:52:42
感谢
12下一页

所属标签

相似分享

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版