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

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

[复制链接]
xiaojie0513 发布时间:2019-1-23 10:06
button drive
杰杰自己写的一个按键驱动,支持单双击、连按、长按;采用回调处理按键事件(自定义消抖时间),使用只需3步,创建按键,按键事件与回调处理函数链接映射,周期检查按键。, {9 ~( f6 P9 T
源码地址:http://github.com/jiejieTop/ButtonDrive。作者:杰杰
前言
前几天写了个按键驱动,参考了MulitButton的数据结构的用法,逻辑实现并不一样。0 B5 _, T) B" s! {% W
在这里感谢所有的开源开发者,让我从中学到了很多,同时网络也是一个好平台,也希望所有的开发者能形成良性循环,从网络中学知识,回馈到网络中去。感谢MulitButton的作者0x1abin,感谢两位rtt的大佬:大法师、流光。
Button_drive简介
Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在RT-Thread上测试过。6 P5 u0 ?0 F, y
写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。
Button_drive使用效果
  • 单击与长按
    " B  O) Z" [: b# b
单击与长按
  • 双击- i5 ]! e, o- Z: I. s' u
双击
  • 连按
    - f' T: W7 W" l. M
连按
  • 连按释放
    6 X# q, L& b( O5 o  m
连按释放使用方法
  • 创建按键句柄
    ) L+ _7 N. ^$ o7 o, D. y
Button_t Button1;) h3 e0 V; u& G5 h& U. ?
Button_t Button2; " S9 I& C1 W% s. ~' E* _7 r
  • 创建按键,初始化按键信息,包括按键名字、按键电平检测函数接口、按键触发电平。- T  N9 ]: h7 z/ l* ?: y: h
  Button_Create("Button1",                //按键名字9 b% p' g( k! {
                &Button1,                 //按键句柄
: d+ @2 T/ e* x5 t$ J! R                Read_Button1_Level,     //按键电平检测函数接口# R7 \1 F% _4 L/ d  w3 d0 ~; X: M
                BTN_TRIGGER);            //触发电平" b+ d5 [3 W, q0 n

+ a$ v. O; H* j) W) l                ......
  k9 w' z2 S/ h
  • 按键触发事件与事件回调函数链接映射,当按键事件被触发的时候,自动跳转回调函数中处理业务逻辑。
    ( e' B1 Y# ]9 r# ?  v  k
  Button_Attach(&Button1,BUTTON_DOWM,Btn2_Dowm_CallBack);        //按键单击
4 ?7 H9 \. X9 l  Button_Attach(&Button1,BUTTON_DOUBLE,Btn2_Double_CallBack);    //双击
7 j) J- `- M& [1 c! Z  Button_Attach(&Button1,BUTTON_LONG,Btn2_Long_CallBack);        //长按
4 L( G" ]. X; n! P4 m6 [  c, V+ v, t( q5 K+ t3 b5 }; O
                .......: \4 l$ B9 @! J) @
  • 周期调用回调按键处理函数即可,建议调用周期20-50ms。/ ]+ ]/ O5 E* e4 }8 y
Button_Process();     //需要周期调用按键处理函数& p! w4 g! H& w. \  ]5 m% `

需要用户实现的 2 个函数:
  • 按键电平检测接口:; p- s# v3 O' R% l' a  j3 S
uint8_t Read_Button1_Level(void)& g: u; q# r, j& o! a
{2 ~+ ~4 F% V; m0 z
  return GPIO_ReadInputDataBit(BTN1_GPIO_PORT,BTN1_GPIO_PIN);$ j# ]- ]2 K3 A! O( h
}
2 u# e/ @7 ]0 E+ W  r
/ J+ K5 i, V2 r0 K! K9 P8 L# I% _1 ?* Huint8_t Read_Button2_Level(void)
, e7 x3 z0 S- A  l; @{* _4 J7 N9 l; e  @2 J, S8 ~- t
  return GPIO_ReadInputDataBit(BTN2_GPIO_PORT,BTN2_GPIO_PIN);
/ T- v# E/ T/ J8 T}
) q* ~9 y. V! I+ M
+ c! e) k6 _9 T  A) Z5 u// 这是我在stm32上简单测试的伪代码,以实际源码为准% }2 R1 g7 w, P! q$ n( y3 m  @$ ?
  • 按键逻辑处理
    ' Q1 o( {9 d% b& j
void Btn1_Dowm_CallBack(void *btn)- B& g1 z0 w  z( m/ _6 l
{
! r3 D* y( Y2 ^  PRINT_INFO("Button1 单击!");
3 m* W3 h9 m# z  I) x  l# e! Y: ~}
" s$ r; O6 }$ y% o1 q7 [
4 V2 U  n* \/ Tvoid Btn1_Double_CallBack(void *btn)
) {$ x0 q5 v. v' \& Y
{; s4 K7 W1 J8 g' |5 I2 u
  PRINT_INFO("Button1 双击!");8 q& |6 u- Z6 c$ \3 @! h
}2 h7 ^1 D% |3 v, C8 A
2 L1 i  Q' ~$ o1 M3 p, c
void Btn1_Long_CallBack(void *btn)
1 I' N, Q! v" O- f
{( }7 {2 y* y" {8 x+ [! \+ f, V
  PRINT_INFO("Button1 长按!");
3 U) Z, G' F. ]
. ]5 D3 f3 g$ \; M6 ~  Button_Delete(&Button2);
* d1 R" d! k1 x" p# ^  PRINT_INFO("删除Button1");
- T- r! q" D6 |  Search_Button();
' O! k: e" o. g$ K}4 Z5 K$ ^0 A! }! P# \
特点
Button_drive开放源码,按键控制块采用数据结构方式,按键事件采用枚举类型,确保不会重复,也便于添加用户需要逻辑,采用宏定义方式定义消抖时间、连按触发时间、双击时间间隔、长按时间等,便于修改。
8 O8 l4 k) b# W9 g同时所有被创建的按键采用单链表方式连击,用户只管创建,无需理会按键处理,只需调用Button_Process()即可,在函数中会自动遍历所有被创建的按键。9 M' [# H1 a  g' O3 r4 o* ~4 Z* c# A
支持按键删除操作,用户无需在代码中删除对应的按键创建于映射链接代码,也无需删除关于按键的任何回调事件处理函数,只需调用Button_Delete()函数即可,这样子,就不会处理关于被删除按键的任何状态。当然目前按键内存不会释放,如果使用os的话,建议释放按键内存。
按键控制块/*
/ z; [. O" p, h. g( ~) g    每个按键对应1个全局的结构体变量。
  Z# c8 m4 o' S6 Q$ ?    其成员变量是实现消抖和多种按键状态所必须的) A- W6 ^2 X5 o* `3 E9 J* ~
*/

& u* c; {9 n+ D& htypedef struct button8 Y! t! H) |/ ~1 t9 s
{

; C, D4 `; Q2 H+ f    /* 下面是一个函数指针,指向判断按键手否按下的函数 */3 d+ j, q& e# |( v% ]
    uint8_t (*Read_Button_Level)(void); /* 读取按键电平函数,需要用户实现 */5 u0 t+ U' `! ]

+ @; p) @5 s+ w3 S  char Name[BTN_NAME_MAX];
& u& j" K6 l( J# Z$ ]( P" h, ]2 v# V. E# h6 A% h5 o& _+ k
  uint8_t Button_State              :   4;      /* 按键当前状态(按下还是弹起) */
  {( h; @& _, m+ m5 T5 A  uint8_t Button_Last_State         :   4;      /* 上一次的按键状态,用于判断双击 */. h0 l/ W7 n- }; g; |( t
  uint8_t Button_Trigger_Level      :   2;    /* 按键触发电平 */
/ |! w) `; J0 Q5 T  uint8_t Button_Last_Level         :   2;    /* 按键当前电平 */
. X* T& m  h& W( D9 c
: g% ~6 U+ X7 j& y  uint8_t Button_Trigger_Event;     /* 按键触发事件,单击,双击,长按等 */1 A" k  z, O3 k8 M& y8 g

2 D6 J* C( }, z  Button_CallBack CallBack_Function[number_of_event];6 }3 L/ [$ p5 z4 j
  uint8_t Button_Cycle;               /* 连续按键周期 */5 Y  U- b1 W4 C: o

! v' y4 I: h' x  uint8_t Timer_Count;            /* 计时 */4 v$ B) ~5 `+ o+ u% h( }
  uint8_t Debounce_Time;        /* 消抖时间 */0 S2 C2 Q8 Z$ K9 e5 s; C) x3 M: w$ [
, z+ s# g+ r1 f2 l+ h- h
  uint8_t Long_Time;          /* 按键按下持续时间 */4 c4 H7 I$ j* A& B1 h% [$ z4 h& U
- S; J6 t8 i" B# a2 f3 q
  struct button *Next;9 x4 d! s. ]- h) k$ w$ W

( L( e( c' _+ n  g}Button_t;
" g2 }# C6 b0 L& {8 `触发事件typedef enum {! M: c; }3 T  E
  BUTTON_DOWM = 0,. n/ }9 Y2 w6 m/ S5 l
  BUTTON_UP,
% C& I# |9 [' ]; ^/ _/ G$ G  BUTTON_DOUBLE,. X* c6 W, n3 |. @& O6 I2 s8 z
  BUTTON_LONG,
+ d% X3 C5 E0 U1 L, D  BUTTON_CONTINUOS,
0 {/ s9 ?* j  R6 q1 P1 I5 l8 i  BUTTON_CONTINUOS_FREE,
" }) G. f. @: D7 R  BUTTON_ALL_RIGGER,
( x& G* i# [7 @  R7 M1 f* g; U  number_of_event, /* 触发回调的事件 */
5 L# [; `& ?' d' G' R  NONE_TRIGGER
% i+ W7 f* u/ `, A% P  S' m2 c}Button_Event;
( a9 I- R$ j9 H6 ?4 T# ?宏定义选择#define BTN_NAME_MAX  32     //名字最大为32字节
- _3 a9 N. N0 a3 X7 M! ^
: O2 w4 J( _" D% `3 m/* 按键消抖时间40ms, 建议调用周期为20ms' S* c; `4 G- O8 k& a! N
只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件  v- y) N* S* O$ P
*/

0 a2 h& M+ P; t, W- R( x0 U
2 V! |$ u# ^" ^3 K7 G0 y$ u% k' X#define CONTINUOS_TRIGGER             0  //是否支持连续触发,连发的话就不要检测单双击与长按了    1 h: y4 K- g9 Z9 ?4 I6 s4 m

( p: B$ F2 r2 b/* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应,
' x- e. Y& J" q" g: B! J   因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。% T- A; v" ~% D  l0 r& F6 }7 l
   而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击,$ \4 Q! F4 T) S$ f( r. m0 l
   因为双击必须是有一次按下并且释放之后才产生的 */
. T$ `* S) j2 U. x0 e4 b
#define SINGLE_AND_DOUBLE_TRIGGER     1
6 Z* ~( \2 R' ^+ a* v1 [( a2 }0 b3 Y3 r+ P
/* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按,
/ [5 K# c' w7 B- S& `, `; T   否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */

% `5 ]* X* O( U1 K' o' b#define LONG_FREE_TRIGGER             0
, ~; Y2 c- n. W% @* T; Q( u# R  ^. x" A0 _% f8 a. \0 U
#define BUTTON_DEBOUNCE_TIME       2   //消抖时间      (n-1)*调用周期
" P, i3 H7 `1 w: _0 K#define BUTTON_CONTINUOS_CYCLE  1      //连按触发周期时间  (n-1)*调用周期  
: n  [4 K' H# a7 h. F#define BUTTON_LONG_CYCLE       1      //长按触发周期时间  (n-1)*调用周期
  P" }# h+ g; g6 E#define BUTTON_DOUBLE_TIME      15     //双击间隔时间  (n-1)*调用周期  建议在200-600ms4 m' J0 M3 Y7 ]: K9 K
#define BUTTON_LONG_TIME           50        /* 持续n秒((n-1)*调用周期 ms),认为长按事件 */- Z! H0 n' w$ G5 w7 H" g
2 S& T+ e7 [! f, S
#define TRIGGER_CB(event)   \
1 o3 M# E' e0 r6 F  n0 J        if(btn->CallBack_Function[event]) \; ?% Y6 ]8 e, p* n! A. @9 q
          btn->CallBack_Function[event]((Button_t*)btn)
+ c# e8 {1 h3 b  M( \' `. R
例子  Button_Create("Button1",2 ?& z1 d# e! |6 C
              &Button1,
& l4 A4 V0 k. \+ J7 W* r) x5 n              Read_KEY1_Level,
9 l* d: e8 K- z/ O, K6 [              KEY_ON);
( ^% @5 S; m; \* c" L" V  Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack);                       //单击# H/ ]$ ~: a! W6 n
  Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack);                   //双击! C6 A, ]6 T- O& x/ S
  Button_Attach(&Button1,BUTTON_CONTINUOS,Btn1_Continuos_CallBack);             //连按  6 a" A  c1 g% F5 Q- ?- H! B
  Button_Attach(&Button1,BUTTON_CONTINUOS_FREE,Btn1_ContinuosFree_CallBack);    //连按释放  # _2 M* \+ l. V% X. L9 ^* {
  Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack);                       //长按
- \+ f+ Z$ ]6 X$ e5 N% I$ V: g4 m9 a% @& t5 t+ D6 v; \9 l

, @1 o  [6 h: B4 I9 n  Button_Create("Button2",0 t. L5 h& e2 C0 v8 u' u3 u
              &Button2,
/ j* D' ~7 O+ s. w! N$ g; b              Read_KEY2_Level,
: G; |& \: y9 |( H  W1 l- h              KEY_ON);
% i- H8 M9 T9 I& F" n  Button_Attach(&Button2,BUTTON_DOWM,Btn2_Dowm_CallBack);                     //单击
1 k+ B) ?& _! [# k+ K; ~+ T8 ]  Button_Attach(&Button2,BUTTON_DOUBLE,Btn2_Double_CallBack);                 //双击9 N0 A, D; E% v( j* N
  Button_Attach(&Button2,BUTTON_CONTINUOS,Btn2_Continuos_CallBack);           //连按
  Z- w1 R0 z' \* [" f# ~- R  C( J/ j' {  Button_Attach(&Button2,BUTTON_CONTINUOS_FREE,Btn2_ContinuosFree_CallBack);  //连按释放
) l$ D9 @3 v" o& p. ]  Button_Attach(&Button2,BUTTON_LONG,Btn2_Long_CallBack);                     //长按
) T% J* O5 T+ i% l6 m8 |
1 E3 ?- C5 h5 R( }* G) y1 w1 L6 `; L: T  Get_Button_Event(&Button1);
1 ~0 _; h$ W. w7 w1 G& k1 i  Get_Button_Event(&Button2);8 c9 k! O! Y' x  f9 `+ m
后续
流光大佬的要求,让我玩一玩RTT的rtkpgs,打算用Button_drive练一练手吧。
ButtonDrive在env使用
目前我已将按键驱动做成软件包(packages),如果使用RT-Thread操作系统的话,可以在env中直接配置使用!
步骤如下:
  • 选择在线软件包' y6 F$ o1 o" P- @! t% Y  P
  • 选择软件包属性为外设相关
    0 D* |0 M# j! l2 i
  • 选择button_drive
    % S2 v) {: S3 ?( C
  • 进入驱动的选项配置(自带默认属性): |1 ]* S3 j8 _. h- k7 i7 b
  • 如果不懂按键的配置是什么意思,按下“shift+?”,即可有解释. ?( y+ \! H0 A, ]/ o3 @4 q7 s" ~
  • 编译生成mdk/iar工程
    9 R$ y7 S# L4 u
关于rtkpgs简介 (English)
buildpkg 是用于生成 RT-Thread package 的快速构建工具。
一个优秀的 package 应该是这样的:
  • 代码优雅, 规范化。
  • examples 例程,提供通俗易懂的使用例程。
  • SConscript 文件,用于和 RT-Thread 环境一起进行编译。
  • README.md 文档,向用户提供必要的功能说明。
  • docs 文件夹, 放置除了 README 之外的其他细节文档。
  • license 许可文件,版权说明。
    $ u+ s4 S' w$ M7 ^
为了方便快速的生成 RT-Thread package 规范化模板 以及 减轻开源仓库迁移 RT-Thread 的前期准备工作的负担,基于此目的的 buildpkg 应运而生,为开发 Rt-Thread 的 package 的开发者提供辅助开发工具。
序号
支持功能
描述
$ O" B1 R  I$ Z8 Y1 q
1构建 package 模板创建指定名称 package , 自动添加 readme /版本号/ github ci脚本/demo/开源协议文件; ~1 s6 y/ |1 o# H; M
2迁移开源仓库从指定 git 仓库构建 package , 自动添加readme/版本号/ github ci脚本/demo/开源协议文件, 但是迁移的仓库需要用户自己按照实际情况修改
! w$ e; D) v% n% |* o- }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 ~, i+ k' d- A1 q4 a7 C
--version=v1.0.0-v v1.0.0设置 package 的版本' }6 P" \6 U. Q, j
--license=MIT-l MIT设置 package 所遵循的版权协议
; j* E  _& z8 t+ I--submodule-s删除 git 子模块Windows10 及 Linux 平台的演示动图buildpkg测试平台
序号
测试平台
测试结果
$ S5 t8 F5 w5 \+ l% r8 X
1win10exe测试通过, py测试通过0 A6 O/ c$ G4 R5 `) Z! C
2win7exe待测试, py待测试
# z  D3 z0 e0 e8 h: P2 y; h3macpy脚本不知道是否兼容, 没有测试条件, 后面维护下' w1 K. I1 a; u7 Y
4linuxpy脚本不知道是否兼容, 没有测试条件, 后面维护下联系人
, E3 v$ o, ]) {5 V+ N6 N2 _) m
赞 收藏 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, g: `0 r# S. j, J9 \3 C
谢谢分享  好东西

; S1 {, R, B3 \3 {  {
qiangtech 回答时间:2019-1-23 15:23:42
细节处见真功夫。
xiaojie0513 回答时间:2019-1-23 15:53:04
qiangtech 发表于 2019-1-23 15:236 s5 G1 a& c: ~+ Y4 a2 r
细节处见真功夫。

. x3 Y. O- g) m7 J1 e1 `
单片机爱好者 回答时间: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
确实复杂,但是,也不一定没用,
+ i; Y* c8 N$ }% G: k- T& U, A+ j做嵌入开发,其实就是一个权衡的过程。7 \. ~. I9 j8 c; }! N- T
如果本身对速度RAM消耗等不是非常敏感,而又希望提供简单易用的二次开发接口给别人使用,
- \- I+ _9 n/ [, `( r用这种面向对象的方法,也不是不可取。6 y' C1 I4 H& m; }
这样的方法,更多是往LINUX设计方法靠拢。
8 M4 q% H8 G2 p0 c; @. T  x5 \
雪山飞狼 回答时间: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 手机版