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

button drive

杰杰自己写的一个按键驱动,支持单双击、连按、长按;采用回调处理按键事件(自定义消抖时间),使用只需3步,创建按键,按键事件与回调处理函数链接映射,周期检查按键。
源码地址:https://github.com/jiejieTop/ButtonDrive。作者:杰杰

前言

前几天写了个按键驱动,参考了MulitButton的数据结构的用法,逻辑实现并不一样。
在这里感谢所有的开源开发者,让我从中学到了很多,同时网络也是一个好平台,也希望所有的开发者能形成良性循环,从网络中学知识,回馈到网络中去。感谢MulitButton的作者0x1abin,感谢两位rtt的大佬:大法师流光

Button_drive简介

Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在RT-Thread上测试过。
写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。

Button_drive使用效果

  1. 单击与长按

[图片上传失败...(image-a99772-1541216874707)]

  1. 双击

[图片上传失败...(image-35e8e4-1541216874707)]

  1. 连按

[图片上传失败...(image-5c05ed-1541216874707)]

  1. 连按释放

[图片上传失败...(image-3026c2-1541216874707)]

使用方法

  1. 创建按键句柄
Button_t Button1;
Button_t Button2; 
  1. 创建按键,初始化按键信息,包括按键名字、按键电平检测函数接口、按键触发电平。
  Button_Create("Button1",              //按键名字
                &Button1,               //按键句柄
                Read_Button1_Level,     //按键电平检测函数接口
                BTN_TRIGGER);           //触发电平
                
                ......
  1. 按键触发事件与事件回调函数链接映射,当按键事件被触发的时候,自动跳转回调函数中处理业务逻辑。
  Button_Attach(&Button1,BUTTON_DOWM,Btn2_Dowm_CallBack);       //按键单击
  Button_Attach(&Button1,BUTTON_DOUBLE,Btn2_Double_CallBack);   //双击
  Button_Attach(&Button1,BUTTON_LONG,Btn2_Long_CallBack);       //长按
                
                .......
  1. 周期调用回调按键处理函数即可,建议调用周期20-50ms。
Button_Process();     //需要周期调用按键处理函数

需要用户实现的 2 个函数:

  • 按键电平检测接口:
uint8_t Read_Button1_Level(void)
{
  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上简单测试的伪代码,以实际源码为准

  • 按键逻辑处理
void Btn1_Dowm_CallBack(void *btn)
{
  PRINT_INFO("Button1 单击!");
}

void Btn1_Double_CallBack(void *btn)
{
  PRINT_INFO("Button1 双击!");
}

void Btn1_Long_CallBack(void *btn)
{
  PRINT_INFO("Button1 长按!");
  
  Button_Delete(&Button2);
  PRINT_INFO("删除Button1");
  Search_Button();
}

特点

Button_drive开放源码,按键控制块采用数据结构方式,按键事件采用枚举类型,确保不会重复,也便于添加用户需要逻辑,采用宏定义方式定义消抖时间、连按触发时间、双击时间间隔、长按时间等,便于修改。
同时所有被创建的按键采用单链表方式连击,用户只管创建,无需理会按键处理,只需调用Button_Process()即可,在函数中会自动遍历所有被创建的按键。
支持按键删除操作,用户无需在代码中删除对应的按键创建于映射链接代码,也无需删除关于按键的任何回调事件处理函数,只需调用Button_Delete()函数即可,这样子,就不会处理关于被删除按键的任何状态。当然目前按键内存不会释放,如果使用os的话,建议释放按键内存。

按键控制块
/*
    每个按键对应1个全局的结构体变量。
    其成员变量是实现消抖和多种按键状态所必须的
*/
typedef struct button
{
    /* 下面是一个函数指针,指向判断按键手否按下的函数 */
    uint8_t (*Read_Button_Level)(void); /* 读取按键电平函数,需要用户实现 */
  
  char Name[BTN_NAME_MAX];
    
  uint8_t Button_State              :   4;    /* 按键当前状态(按下还是弹起) */
  uint8_t Button_Last_State         :   4;    /* 上一次的按键状态,用于判断双击 */
  uint8_t Button_Trigger_Level      :   2;    /* 按键触发电平 */
  uint8_t Button_Last_Level         :   2;    /* 按键当前电平 */
  
  uint8_t Button_Trigger_Event;     /* 按键触发事件,单击,双击,长按等 */
  
  Button_CallBack CallBack_Function[number_of_event];
  uint8_t Button_Cycle;            /* 连续按键周期 */
  
  uint8_t Timer_Count;          /* 计时 */
  uint8_t Debounce_Time;        /* 消抖时间 */
  
  uint8_t Long_Time;          /* 按键按下持续时间 */
  
  struct button *Next;
  
}Button_t;

触发事件
typedef enum {
  BUTTON_DOWM = 0,
  BUTTON_UP,
  BUTTON_DOUBLE,
  BUTTON_LONG,
  BUTTON_CONTINUOS,
  BUTTON_CONTINUOS_FREE,
  BUTTON_ALL_RIGGER,
  number_of_event, /* 触发回调的事件 */
  NONE_TRIGGER
}Button_Event;

宏定义选择
#define BTN_NAME_MAX  32     //名字最大为32字节

/* 按键消抖时间40ms, 建议调用周期为20ms
 只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件
*/

#define CONTINUOS_TRIGGER             0  //是否支持连续触发,连发的话就不要检测单双击与长按了    

/* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应,
   因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。
   而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击,
   因为双击必须是有一次按下并且释放之后才产生的 */
#define SINGLE_AND_DOUBLE_TRIGGER     1 

/* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按,
   否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */
#define LONG_FREE_TRIGGER             0 

#define BUTTON_DEBOUNCE_TIME      2   //消抖时间      (n-1)*调用周期
#define BUTTON_CONTINUOS_CYCLE  1     //连按触发周期时间  (n-1)*调用周期  
#define BUTTON_LONG_CYCLE       1     //长按触发周期时间  (n-1)*调用周期 
#define BUTTON_DOUBLE_TIME      15  //双击间隔时间  (n-1)*调用周期  建议在200-600ms
#define BUTTON_LONG_TIME          50        /* 持续n秒((n-1)*调用周期 ms),认为长按事件 */

#define TRIGGER_CB(event)   \
        if(btn->CallBack_Function[event]) \
          btn->CallBack_Function[event]((Button_t*)btn)
例子
  Button_Create("Button1",
              &Button1, 
              Read_KEY1_Level, 
              KEY_ON);
  Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack);                       //单击
  Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack);                   //双击
  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);                       //长按


  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);  //连按释放
  Button_Attach(&Button2,BUTTON_LONG,Btn2_Long_CallBack);                     //长按

  Get_Button_Event(&Button1);
  Get_Button_Event(&Button2);

后续

流光大佬的要求,让我玩一玩RTT的rtkpgs,打算用Button_drive练一练手吧。

ButtonDrive在env使用

目前我已将按键驱动做成软件包(packages),如果使用RT-Thread操作系统的话,可以在env中直接配置使用!

步骤如下:

  1. 选择在线软件包

[图片上传失败...(image-dbd81-1541216874707)]

  1. 选择软件包属性为外设相关

[图片上传失败...(image-e18cb3-1541216874707)]

  1. 选择button_drive

[图片上传失败...(image-4a4909-1541216874707)]

  1. 进入驱动的选项配置(自带默认属性)

[图片上传失败...(image-c90a6c-1541216874707)]

  1. 如果不懂按键的配置是什么意思,按下“shift+?”,即可有解释

[图片上传失败...(image-8478b4-1541216874707)]

  1. 编译生成mdk/iar工程

[图片上传失败...(image-9b290a-1541216874707)]

[图片上传失败...(image-3eeacb-1541216874707)]

关于rtkpgs

简介 (English)

buildpkg 是用于生成 RT-Thread package 的快速构建工具。

一个优秀的 package 应该是这样的:

  1. 代码优雅, 规范化。
  2. examples 例程,提供通俗易懂的使用例程。
  3. SConscript 文件,用于和 RT-Thread 环境一起进行编译。
  4. README.md 文档,向用户提供必要的功能说明。
  5. docs 文件夹, 放置除了 README 之外的其他细节文档。
  6. license 许可文件,版权说明。

为了方便快速的生成 RT-Thread package 规范化模板 以及 减轻开源仓库迁移 RT-Thread 的前期准备工作的负担,基于此目的的 buildpkg 应运而生,为开发 Rt-Thread 的 package 的开发者提供辅助开发工具。

序号 支持功能 描述
1 构建 package 模板 创建指定名称 package , 自动添加 readme /版本号/ github ci脚本/demo/开源协议文件
2 迁移开源仓库 从指定 git 仓库构建 package , 自动添加readme/版本号/ github ci脚本/demo/开源协议文件, 但是迁移的仓库需要用户自己按照实际情况修改
3 更新 package 生成package后可以再次更新之前设定的版本号,开源协议或者scons脚本等

使用说明

1. 构建package

buildpkg.exe make pkgdemo

2. 迁移开源仓库

buildpkg.exe make cstring https://github.com/liu2guang/cstring.git

3. 更新package

buildpkg.exe update pkgname

4. 可选配置

长参数 短参数 描述
--version=v1.0.0 -v v1.0.0 设置 package 的版本
--license=MIT -l MIT 设置 package 所遵循的版权协议
--submodule -s 删除 git 子模块

Windows10 及 Linux 平台的演示动图

[图片上传失败...(image-65760f-1541216874707)]

测试平台

序号 测试平台 测试结果
1 win10 exe测试通过, py测试通过
2 win7 exe待测试, py待测试
3 mac py脚本不知道是否兼容, 没有测试条件, 后面维护下
4 linux py脚本不知道是否兼容, 没有测试条件, 后面维护下

联系人

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 3,477评论 1 11
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,969评论 3 119
  • 10/8/2016 To 亲爱的自己: Hey,最近突然有些郁闷,好像丢失了什么,心中空落落的,苦苦寻思了很久...
    喵暖阅读 207评论 0 0
  • 如何让footer固定在底部? 多次在项目中遇到需要将footer固定在底部即使是中间内容并没有充满一屏的高度,如...
    Alander阅读 420评论 4 0
  • 第三章 再次相遇 不知不觉地暑假来了,学生们也开始准备回家了。冉婷儿忙完期末考试后也终于闲下来了,她时常会...
    如果钢琴没有了心阅读 330评论 0 0