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

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

[复制链接]
xiaojie0513 发布时间:2019-1-23 10:06
button drive
杰杰自己写的一个按键驱动,支持单双击、连按、长按;采用回调处理按键事件(自定义消抖时间),使用只需3步,创建按键,按键事件与回调处理函数链接映射,周期检查按键。
* f6 b7 |; N& C. V: ?' b源码地址:http://github.com/jiejieTop/ButtonDrive。作者:杰杰
前言
前几天写了个按键驱动,参考了MulitButton的数据结构的用法,逻辑实现并不一样。
* X; B  n3 }3 z5 ?/ I2 L' Z在这里感谢所有的开源开发者,让我从中学到了很多,同时网络也是一个好平台,也希望所有的开发者能形成良性循环,从网络中学知识,回馈到网络中去。感谢MulitButton的作者0x1abin,感谢两位rtt的大佬:大法师、流光。
Button_drive简介
Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在RT-Thread上测试过。
; N+ U- N9 o* a! I写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。
Button_drive使用效果
  • 单击与长按7 ~0 v2 B! }% W
单击与长按
  • 双击, X; C+ ?1 X3 [. @& j/ I8 Y, j
双击
  • 连按( U& f! c- a; |7 r- i
连按
  • 连按释放( L* f* M' C- S  Y7 P+ U9 a( |9 Q# Y3 A
连按释放使用方法
  • 创建按键句柄2 ?- F* _2 C* J- W" P# p; b
Button_t Button1;
- B: n+ ~" J! ?6 N- R1 J$ n, eButton_t Button2;
9 X6 d% u: ?' B# d/ g
  • 创建按键,初始化按键信息,包括按键名字、按键电平检测函数接口、按键触发电平。. S) Y8 Z  X' D
  Button_Create("Button1",                //按键名字
! I% x$ p' ~( P4 l2 r' e1 m7 b! r9 T                &Button1,                 //按键句柄
: h. U, z5 c5 B! Z( t1 j                Read_Button1_Level,     //按键电平检测函数接口
( p) F* n3 w3 h4 b  H                BTN_TRIGGER);            //触发电平! x  X, P8 ~+ J+ d! W2 _# I5 |
  i& u) ?, X; k* x2 {0 w
                ......% W5 b% t" E; f  a  s3 Z. m
  • 按键触发事件与事件回调函数链接映射,当按键事件被触发的时候,自动跳转回调函数中处理业务逻辑。9 h) ^# n$ ?- W  {6 b
  Button_Attach(&Button1,BUTTON_DOWM,Btn2_Dowm_CallBack);        //按键单击
" ~: H% T7 `1 N& X5 U  Button_Attach(&Button1,BUTTON_DOUBLE,Btn2_Double_CallBack);    //双击
7 k  d$ b- Z0 N, t  Button_Attach(&Button1,BUTTON_LONG,Btn2_Long_CallBack);        //长按
1 G& C; X$ N. ~7 @! ?
% W( `7 f1 [  r% O                .......
, Q9 U4 |  _' G4 T! q, b
  • 周期调用回调按键处理函数即可,建议调用周期20-50ms。
    ; m7 Q" m7 o- P$ F/ ^; U* n* {) Z
Button_Process();     //需要周期调用按键处理函数
' _* r6 P: Y% f& o$ F
需要用户实现的 2 个函数:
  • 按键电平检测接口:
    * K) l0 i+ d. ~! ?( z
uint8_t Read_Button1_Level(void). F6 u$ I$ D9 E+ L/ o( B
{
8 h  t( Q2 `% J# O  return GPIO_ReadInputDataBit(BTN1_GPIO_PORT,BTN1_GPIO_PIN);1 _5 e+ d" ]) b/ @! x. V4 U
}9 M- S" y: q! c& B
- Y, G5 A* V- X. G
uint8_t Read_Button2_Level(void)/ U6 H$ k8 z/ ~1 E7 W
{
. i2 b0 A9 p" H* c9 u  return GPIO_ReadInputDataBit(BTN2_GPIO_PORT,BTN2_GPIO_PIN);: K" E7 D0 R; F& W1 c7 @5 R
}
; \' y- u  }# M( M3 V' T% m
/ c6 x  h0 _5 w3 n" Y# a- X// 这是我在stm32上简单测试的伪代码,以实际源码为准7 B/ ^! E3 C5 ]8 b1 C; D
  • 按键逻辑处理. H# N& T. V) A( O3 a& o+ j9 Z
void Btn1_Dowm_CallBack(void *btn)1 G4 g! d0 r6 L' e
{
2 w, }0 j/ F3 f6 W- U  PRINT_INFO("Button1 单击!");4 x; U8 x8 ~. o# p
}
# u4 t0 h8 t% G9 y0 |) D
5 _7 R) U" ?3 y! p7 _7 Q9 s+ kvoid Btn1_Double_CallBack(void *btn)6 e: J9 }! l$ ^* m
{
* B( g. Y7 m6 T1 M  PRINT_INFO("Button1 双击!");: l" r5 f- p4 [( n
}- ?0 @( @2 y. _& S/ ~8 o
* c# |7 R' _$ C& V* \
void Btn1_Long_CallBack(void *btn)
% D+ Z  V% B, Q# {
{# B1 e9 B# o2 K8 t6 n& Y% g4 f
  PRINT_INFO("Button1 长按!");
! l* S+ W: {& |) e+ K) t
2 M" y0 D$ g( u: m  Button_Delete(&Button2);
+ D4 Q+ A5 e# ?9 k5 H  PRINT_INFO("删除Button1");% k9 o. h) o: W! F8 P2 \* V
  Search_Button();
2 c' ]/ K1 \) b1 S4 o( F}( l6 b; I9 O; z1 _0 Z
特点
Button_drive开放源码,按键控制块采用数据结构方式,按键事件采用枚举类型,确保不会重复,也便于添加用户需要逻辑,采用宏定义方式定义消抖时间、连按触发时间、双击时间间隔、长按时间等,便于修改。
, E0 w2 R6 H0 s同时所有被创建的按键采用单链表方式连击,用户只管创建,无需理会按键处理,只需调用Button_Process()即可,在函数中会自动遍历所有被创建的按键。0 l; |: R5 J0 @" H  T
支持按键删除操作,用户无需在代码中删除对应的按键创建于映射链接代码,也无需删除关于按键的任何回调事件处理函数,只需调用Button_Delete()函数即可,这样子,就不会处理关于被删除按键的任何状态。当然目前按键内存不会释放,如果使用os的话,建议释放按键内存。
按键控制块/*: G3 C6 k: q* w8 r5 V
    每个按键对应1个全局的结构体变量。& c# l% I# O, ^; @; L7 j$ u7 @6 y
    其成员变量是实现消抖和多种按键状态所必须的5 S+ t* O+ M  ]/ f, |8 m. b3 @5 S
*/
5 B6 [& t1 ~- L6 u& k4 _2 X
typedef struct button
$ N& a/ |$ m, J7 |  I& k{

  }% w$ D1 v7 E" f9 G3 u    /* 下面是一个函数指针,指向判断按键手否按下的函数 */
6 j3 ~' ], c# r5 {2 U: g  `* C    uint8_t (*Read_Button_Level)(void); /* 读取按键电平函数,需要用户实现 */# Z/ R) a+ W5 l8 s% Y( O, j

# A9 S- }/ H. @7 {2 Y0 M  char Name[BTN_NAME_MAX];+ V0 J4 d( x/ B' T" L" z+ h+ j9 e
0 C$ \. G) F+ [) F/ i( D) g
  uint8_t Button_State              :   4;      /* 按键当前状态(按下还是弹起) */- [* |# J( f3 K$ ^4 n$ @( k
  uint8_t Button_Last_State         :   4;      /* 上一次的按键状态,用于判断双击 */
. p& G& W& P' b  uint8_t Button_Trigger_Level      :   2;    /* 按键触发电平 */
( ]2 p1 I7 i' H1 h! z: j& f  uint8_t Button_Last_Level         :   2;    /* 按键当前电平 */
5 T1 o' N$ A! ]9 O, h# T. z  O
8 }) V& f1 c! W- I  uint8_t Button_Trigger_Event;     /* 按键触发事件,单击,双击,长按等 */: s3 ?/ @8 H: t6 f: m

( D* N. {8 B1 A6 K, I% p  C  Button_CallBack CallBack_Function[number_of_event];
. K/ f+ M2 Z6 k' L# l% {* P  uint8_t Button_Cycle;               /* 连续按键周期 */* Y' @" k. x- p2 i! h0 ^

9 ?, Y  O: r, C1 b- i; e; f5 N  uint8_t Timer_Count;            /* 计时 */( S* B) o' }& U( R! z: P
  uint8_t Debounce_Time;        /* 消抖时间 */4 E9 C, G: A: M6 j
! o2 l, j# r( M& @
  uint8_t Long_Time;          /* 按键按下持续时间 */. ?4 A) }3 P8 W2 d8 h

+ p- ?  p, k1 `  struct button *Next;
' W3 u+ d* V: y7 m1 J. [
# o4 D$ i( U, I; ^}Button_t;( I! a. O9 X2 g! x) k) [. H2 z
触发事件typedef enum {
0 a$ v8 E: ~$ {- h4 ^: g  BUTTON_DOWM = 0,
- h! M: t+ f) k. w) L) |  BUTTON_UP,
6 i  p* G$ s! T: w  BUTTON_DOUBLE,7 D, {1 ]# u; g
  BUTTON_LONG,( f% b8 Z% m( D! v3 f' B9 u
  BUTTON_CONTINUOS,6 t) M) L/ d' }$ U
  BUTTON_CONTINUOS_FREE,  b7 {$ A3 h& w! A2 G- l, N9 E
  BUTTON_ALL_RIGGER,
5 P" f" R# {: x2 u  number_of_event, /* 触发回调的事件 */
7 a1 S9 X0 A( n2 \( Y  NONE_TRIGGER4 a4 \  _. k5 x* a
}Button_Event;
/ B2 y( a  @3 s1 c" Q宏定义选择#define BTN_NAME_MAX  32     //名字最大为32字节
, P5 o! H# P, k7 P0 B
0 O$ _3 F( L' C- q- P$ I/* 按键消抖时间40ms, 建议调用周期为20ms
  Q5 ^8 V" _2 s: J: B( V7 b 只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件
  @  U) U3 S7 A9 r; ^% {) t& A- R*/
7 F+ _: y" G1 z3 E# N. z
, x# U6 i! A. y2 }! k# z
#define CONTINUOS_TRIGGER             0  //是否支持连续触发,连发的话就不要检测单双击与长按了    + F/ a' f7 X$ n. w" @$ Z9 G" `# k1 x# g
3 o- N8 P9 n9 e) q2 R! l/ t! v
/* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应,: l9 j$ T% g  b* N- U" o
   因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。6 L* \3 F( J! G5 R3 o
   而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击,  F( F/ j$ J/ J/ i/ B
   因为双击必须是有一次按下并且释放之后才产生的 */

3 h0 V* ]/ o, n$ z- d! `#define SINGLE_AND_DOUBLE_TRIGGER     1 0 ]7 @8 j4 G8 c8 E* |, }

7 ]( [/ m$ F" J* T6 a9 M* g/* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按,- W$ {0 b- m2 |/ U/ `0 i
   否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */
' D$ p, h; q5 T) b. w" e
#define LONG_FREE_TRIGGER             0 9 c' H' j$ v3 s* t1 ?
* H! q' s- z, Z, h2 u/ n
#define BUTTON_DEBOUNCE_TIME       2   //消抖时间      (n-1)*调用周期' _: p6 y. Z3 U3 E& H
#define BUTTON_CONTINUOS_CYCLE  1      //连按触发周期时间  (n-1)*调用周期  - I: J' q+ O. d8 @  G
#define BUTTON_LONG_CYCLE       1      //长按触发周期时间  (n-1)*调用周期
! V/ ?' D1 v, B! t* e2 W/ e% x! O- o; G#define BUTTON_DOUBLE_TIME      15     //双击间隔时间  (n-1)*调用周期  建议在200-600ms; j" }/ j  ]/ f8 B# k9 K! u# _
#define BUTTON_LONG_TIME           50        /* 持续n秒((n-1)*调用周期 ms),认为长按事件 */
$ w$ l4 X5 f  [6 ~. A+ i, W7 Z' Q
, G* Z9 D! x  Q2 G2 U! ~$ r#define TRIGGER_CB(event)   \
! L: O9 a6 Z3 U! c4 q) L/ o        if(btn->CallBack_Function[event]) \
6 W6 C, I5 R$ ]3 P; \* i          btn->CallBack_Function[event]((Button_t*)btn)
. D4 n3 i8 l" B2 a4 }# z
例子  Button_Create("Button1",' A5 o3 U: C$ P6 @4 a8 A. b
              &Button1,
* c7 s+ m1 t" f1 Z. u8 B              Read_KEY1_Level, 6 [5 W5 u9 M7 }  z- i3 k6 N1 l) F
              KEY_ON);0 n- Z' N+ x( [+ Q/ l
  Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack);                       //单击
% K. \' r1 ~1 H7 H/ N6 I4 ?+ s) B  Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack);                   //双击7 J! P* T1 r  R% Y
  Button_Attach(&Button1,BUTTON_CONTINUOS,Btn1_Continuos_CallBack);             //连按  : k5 a& c6 u* q8 O$ D
  Button_Attach(&Button1,BUTTON_CONTINUOS_FREE,Btn1_ContinuosFree_CallBack);    //连按释放  
7 q3 l/ j2 Q( W# o8 `  Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack);                       //长按
- W- E. w0 B5 Z! r; J0 L5 I1 G; Z
. O8 H. u7 g! @
  Button_Create("Button2",
: z( H4 K1 L- }  A  U1 y2 g  Y: z. i. L              &Button2, 9 b+ t' s. L3 p
              Read_KEY2_Level, . y/ N2 R4 S' y! n: Q" T. r
              KEY_ON);$ J& N; z* E  ~) A$ g  m/ ^6 T
  Button_Attach(&Button2,BUTTON_DOWM,Btn2_Dowm_CallBack);                     //单击
9 k- P3 M% t8 X3 q; k/ e  Button_Attach(&Button2,BUTTON_DOUBLE,Btn2_Double_CallBack);                 //双击9 b: A5 v( [, D
  Button_Attach(&Button2,BUTTON_CONTINUOS,Btn2_Continuos_CallBack);           //连按
! O! Y5 _8 H/ q  \' A  Button_Attach(&Button2,BUTTON_CONTINUOS_FREE,Btn2_ContinuosFree_CallBack);  //连按释放9 _3 M" c9 ^# F! C; Z" [: M
  Button_Attach(&Button2,BUTTON_LONG,Btn2_Long_CallBack);                     //长按
  R6 n/ ]  V( C0 d$ X$ b# {- o6 L' p& r3 R& t; q, N  H# c
  Get_Button_Event(&Button1);& x: D% T2 {0 E
  Get_Button_Event(&Button2);
1 A' s" W# E- M' N3 S) P  ^后续
流光大佬的要求,让我玩一玩RTT的rtkpgs,打算用Button_drive练一练手吧。
ButtonDrive在env使用
目前我已将按键驱动做成软件包(packages),如果使用RT-Thread操作系统的话,可以在env中直接配置使用!
步骤如下:
  • 选择在线软件包
    $ @4 M% R$ S9 W/ }
  • 选择软件包属性为外设相关
    * k8 P  z7 Z4 J. W: C
  • 选择button_drive& ]9 q* a7 s8 c' O
  • 进入驱动的选项配置(自带默认属性)
    0 ?: ~" z, i; ~6 Q. h. w
  • 如果不懂按键的配置是什么意思,按下“shift+?”,即可有解释) E+ N/ K7 |1 ~. b' M9 ^
  • 编译生成mdk/iar工程8 x7 O. O* v# S# X7 x3 |% X8 O
关于rtkpgs简介 (English)
buildpkg 是用于生成 RT-Thread package 的快速构建工具。
一个优秀的 package 应该是这样的:
  • 代码优雅, 规范化。
  • examples 例程,提供通俗易懂的使用例程。
  • SConscript 文件,用于和 RT-Thread 环境一起进行编译。
  • README.md 文档,向用户提供必要的功能说明。
  • docs 文件夹, 放置除了 README 之外的其他细节文档。
  • license 许可文件,版权说明。+ B- F% t  c' T  j8 V
为了方便快速的生成 RT-Thread package 规范化模板 以及 减轻开源仓库迁移 RT-Thread 的前期准备工作的负担,基于此目的的 buildpkg 应运而生,为开发 Rt-Thread 的 package 的开发者提供辅助开发工具。
序号
支持功能
描述

: W: w& }- a+ @' d0 b# y1构建 package 模板创建指定名称 package , 自动添加 readme /版本号/ github ci脚本/demo/开源协议文件, l) [  i7 E6 G5 ]# |3 e5 H4 \8 H1 Z* e
2迁移开源仓库从指定 git 仓库构建 package , 自动添加readme/版本号/ github ci脚本/demo/开源协议文件, 但是迁移的仓库需要用户自己按照实际情况修改% s* R4 B9 {4 Z) {+ h/ G) f' G
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. 可选配置
长参数
短参数
描述
" d- W1 O: A) L0 x+ o- S7 P2 C
--version=v1.0.0-v v1.0.0设置 package 的版本
8 H$ T9 [+ T8 U1 q) x% _0 i/ d--license=MIT-l MIT设置 package 所遵循的版权协议
% [, g4 H2 a8 E) a* [4 v0 e--submodule-s删除 git 子模块Windows10 及 Linux 平台的演示动图buildpkg测试平台
序号
测试平台
测试结果
$ F9 N+ u/ n* E0 d1 J. y& y) H: Y1 f
1win10exe测试通过, py测试通过! ]/ C3 X9 }; V
2win7exe待测试, py待测试
5 w; E  }# Y/ c! _. Z. f3macpy脚本不知道是否兼容, 没有测试条件, 后面维护下
( B( t' z4 r( L) D7 }  [& D- @4linuxpy脚本不知道是否兼容, 没有测试条件, 后面维护下联系人
! a+ e7 x: G- w% {9 P8 G+ E
赞 收藏 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
# Z* e2 ?) p2 ]7 V谢谢分享  好东西

# t$ J7 p9 D  [  e8 d; C
qiangtech 回答时间:2019-1-23 15:23:42
细节处见真功夫。
xiaojie0513 回答时间:2019-1-23 15:53:04
qiangtech 发表于 2019-1-23 15:23: L" d( i8 F+ q; L1 ]3 R
细节处见真功夫。

( ]: @" [/ e9 B8 p% w+ ?5 P2 i2 h
单片机爱好者 回答时间: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 R1 i  l+ N% s. Q1 I% n做嵌入开发,其实就是一个权衡的过程。( a: T4 W" ~2 o( b" \+ g' D8 e( |) m
如果本身对速度RAM消耗等不是非常敏感,而又希望提供简单易用的二次开发接口给别人使用,
8 h( Z0 ~& x$ d2 O用这种面向对象的方法,也不是不可取。
7 F9 S. a6 O9 ?) s5 w0 A) p这样的方法,更多是往LINUX设计方法靠拢。" V4 D4 T! E( v, }
雪山飞狼 回答时间:2019-2-21 13:09:42
主要学习一下楼主的编程思想,按键这个事情,想简单可以简单实现,想做个驱动方便后续二次开发,就像楼主这样做个完善的驱动,没有必要在这里质疑别人,哈哈,反正我倒是挺支持楼主,把自己的编程思想和自己的作品开源,众人拾柴火焰高,很多大的项目,就是这样一点点积累起来的。
好心情123 回答时间:2019-2-21 17:18:25
看着很不错
lspring 回答时间:2019-2-21 22:52:42
感谢
12下一页
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版