C语言实现事件驱动型按键驱动模块MultiButton

文章目录

  • C语言实现事件驱动型按键驱动模块MultiButton
    • 简介
    • 使用方法
    • 特性
    • 按键事件
    • 使用方法举例
    • 核心代码分析
      • 头文件声明
      • 函数定义源代码

简介

在嵌入式系统尤其是单片机系统中经常用到按键检测和处理,这里提供一个标准的驱动函数模块MultiButton,能够提供按下、弹起、单击、双击、连击、长按等按键事件。

MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。

驱动模块代码的原作者是Zibin Zheng,发布于github上开源,代码写的非常精彩,可以拿来学习。

这里介绍其使用方法,并分析代码实现的原理,重要代码本文作者均额外加了中文注释,方便理解;

github上原作者代码有bug,本文已经给予修订。无bug版的完整源代码和使用demo请在这里下载,可以直接在项目中使用。

使用方法

1.先申请一个按键结构

struct Button button1;

2.初始化按键对象,绑定按键的GPIO电平读取接口read_button_pin() ,后一个参数设置有效触发电平

button_init(&button1, read_button_pin, 0);

3.注册按键事件

button_attach(&button1, SINGLE_CLICK, Callback_SINGLE_CLICK_Handler);
button_attach(&button1, DOUBLE_CLICK, Callback_DOUBLE_Click_Handler);
...

4.启动按键

button_start(&button1);

5.设置一个5ms间隔的定时器循环调用后台处理函数button_ticks(),

如果是在嵌入式操作系统使用,则可以搞一个每5ms调用一次的按键任务去执行button_ticks()即可

while(1) {...if(timer_ticks == 5) {timer_ticks = 0;button_ticks();}
}

特性

MultiButton 使用C语言实现,基于面向对象方式设计思路,每个按键对象单独用一份数据结构管理:

struct Button {uint16_t ticks;uint8_t  repeat: 4;uint8_t  event : 4;uint8_t  state : 3;uint8_t  debounce_cnt : 3;uint8_t  active_level : 1;uint8_t  button_level : 1;uint8_t  (*hal_button_Level)(void);BtnCallback  cb[number_of_event];struct Button* next;
};

这样每个按键使用单向链表相连,依次进入 button_handler(struct Button handle)* 状态机处理,所以每个按键的状态彼此独立。

按键事件

事件 说明
PRESS_DOWN 按键按下,每次按下都触发
PRESS_UP 按键弹起,每次松开都触发
PRESS_REPEAT 重复按下触发,变量repeat计数连击次数
SINGLE_CLICK 单击按键事件
DOUBLE_CLICK 双击按键事件
LONG_PRESS_START 达到长按时间阈值时触发一次
LONG_PRESS_HOLD 长按期间一直触发

使用方法举例

#include "multi_button.h"struct Button btn1;uint8_t read_button1_GPIO()
{return HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin);
}
void BTN1_PRESS_DOWN_Handler(void* btn)
{//do something...
}void BTN1_PRESS_UP_Handler(void* btn)
{//do something...
}...
int main()
{button_init(&btn1, read_button1_GPIO, 0);   button_attach(&btn1, PRESS_DOWN,      BTN1_PRESS_DOWN_Handler);button_attach(&btn1, PRESS_UP,         BTN1_PRESS_UP_Handler);button_attach(&btn1, PRESS_REPEAT,     BTN1_PRESS_REPEAT_Handler);button_attach(&btn1, SINGLE_CLICK,     BTN1_SINGLE_Click_Handler);button_attach(&btn1, DOUBLE_CLICK,     BTN1_DOUBLE_Click_Handler);button_attach(&btn1, LONG_PRESS_START, BTN1_LONG_PRESS_START_Handler);button_attach(&btn2, LONG_PRESS_HOLD,  BTN1_LONG_PRESS_HOLD_Handler);button_start(&btn1);//make the timer invoking the button_ticks() interval 5ms.//This function is implemented by yourself.__timer_start(button_ticks, 0, 5);while(1){}
}

核心代码分析

头文件声明

#include "stdint.h"
#include "string.h"//According to your need to modify the constants.
#define TICKS_INTERVAL 5 //ms,系统节拍5ms
#define DEBOUNCE_TICKS 3 //MAX 8,去抖的次数
#define SHORT_TICKS (300 / TICKS_INTERVAL)  //单击短按键的时间是300ms以上
#define LONG_TICKS (1000 / TICKS_INTERVAL)  //长按键的时间是1000ms以上
#define LONG_HOLD_CYC (500 / TICKS_INTERVAL)    //处于长按键保持触发状态时,每500ms调用一次回调函数typedef void (*BtnCallback)(void *);//声明一个按键事件的枚举类型,包括了所有的按键操作类型
typedef enum
{PRESS_DOWN = 0,   //按键按下,每次按下都触发 PRESS_UP,     //按键弹起,每次松开都触发PRESS_REPEAT,  //重复按下触发,变量repeat计数连击次数SINGLE_CLICK, //单击按键事件DOUBLE_CLICK,   //双击按键事件LONG_PRESS_START,   //达到长按时间阈值时触发一次LONG_PRESS_HOLD, //长按期间一直触发number_of_event,  //事件数量NONE_PRESS        //没有任何按键事件
} PressEvent;//声明一个按键结构体类型(链表),
typedef struct Button
{uint16_t ticks;            //系统节拍计数uint8_t repeat : 4;     //重复按键,双击、三击……uint8_t event : 4;     //当前按键事件uint8_t state : 3;      //当前按键状态uint8_t debounce_cnt : 3;   //去抖次数  uint8_t active_level : 1;   //按键按下的有效电平         uint8_t button_level : 1;   //按键电平状态,获取一次更新一次uint8_t (*hal_button_Level)(void);  //获取按键电平的函数指针BtnCallback cb[number_of_event];   //按键回调函数指针数组struct Button *next;    //指向链表的下一个
} Button;#ifdef __cplusplus
extern "C"
{#endif//按键结构体初始化,赋予按键获取电平的函数,并读出当前电平void button_init(struct Button *handle, uint8_t (*pin_level)(), uint8_t active_level);//按键注册(赋予特定的按键事件以回调函数)void button_attach(struct Button *handle, PressEvent event, BtnCallback cb);//获取按键事件返回到按键结构体PressEvent get_button_event(struct Button *handle);//启动一个按键,即新增一个按键结构体链表节点int button_start(struct Button *handle);//停止一个按键,从链表中删除节点void button_stop(struct Button *handle);//按键节拍,每一次节拍事件遍历按键列表中所有按键,调用驱动函数void button_ticks(void);#ifdef __cplusplus
}
#endif

函数定义源代码

模块的核心函数是void button_handler(struct Button *handle),这是一个状态机程序,该函数流程如下图所示:

代码实现如下:

#define EVENT_CB(ev)    \if (handle->cb[ev]) \handle->cb[ev]((Button *)handle)//button handle list head.
//按键结构体链表,初始化指针为NULL,空链表
static struct Button *head_handle = NULL;/*** @brief  Initializes the button struct handle.* @param  handle: the button handle strcut.* @param  pin_level: read the HAL GPIO of the connet button level.* @param  active_level: pressed GPIO level.* @retval None*///按键结构体初始化,赋予按键获取电平的函数指针,并读出当前电平
void button_init(struct Button *handle, uint8_t (*pin_level)(), uint8_t active_level)
{memset(handle, 0, sizeof(struct Button));         //结构体初始化为全0handle->event = (uint8_t)NONE_PRESS;             //初始化为没有按键事件handle->hal_button_Level = pin_level;               //读取按键电平状态函数指针handle->button_level = handle->hal_button_Level(); //读取一次当前按键电平handle->active_level = active_level;            //按下的有效电平是1还是0
}/*** @brief  Attach the button event callback function.* @param  handle: the button handle strcut.* @param  event: trigger event type.* @param  cb: callback function.* @retval None*///按键注册(赋予特定的按键事件以回调函数)
void button_attach(struct Button *handle, PressEvent event, BtnCallback cb)
{handle->cb[event] = cb; //赋予某特定按键事件的回调函数指针
}/*** @brief  Inquire the button event happen.* @param  handle: the button handle strcut.* @retval button event.*///获取按键事件返回到按键结构体
PressEvent get_button_event(struct Button *handle)
{return (PressEvent)(handle->event); //返回按键事件
}/*** @brief  Button driver core function, driver state machine.* @param  handle: the button handle strcut.* @retval None*/
//按键驱动核心函数,驱动状态机
void button_handler(struct Button *handle)
{//读取当前电平状态uint8_t read_gpio_level = handle->hal_button_Level();//ticks counter working..//只要按键状态不是0,就递增记录系统节拍次数if ((handle->state) > 0)handle->ticks++;/*------------button debounce handle---------------*///如果这一次获取的电平状态与上一次状态不符,则连读几次去抖才能赋值当前电平if (read_gpio_level != handle->button_level){ //not equal to prev one//continue read 3 times same new level changeif (++(handle->debounce_cnt) >= DEBOUNCE_TICKS){handle->button_level = read_gpio_level;handle->debounce_cnt = 0;}}//否则不用去抖检测处理else{ //leved not change ,counter reset.handle->debounce_cnt = 0;}/*-----------------State machine-------------------*/switch (handle->state){case 0: //如果前次按键状态是0,且当前按键电平等于有效电平,则认为是按键开始按下了if (handle->button_level == handle->active_level){                                         //start press downhandle->event = (uint8_t)PRESS_DOWN; //有按键按下事件发生EVENT_CB(PRESS_DOWN);                 //如果存在回调函数则执行handle->ticks = 0;                     //节拍计数清零handle->repeat = 1;                     //事件重复次数为1handle->state = 1;                    //按键状态更新为1(按下)}else //如果不是有效电平,则无按键事件{handle->event = (uint8_t)NONE_PRESS;}break;case 1: //如果前次按键状态是1(按下了),且当前电平不是有效电平,则认为按键松开了if (handle->button_level != handle->active_level){                                     //released press uphandle->event = (uint8_t)PRESS_UP; //有按键松开弹起事件发生EVENT_CB(PRESS_UP);                  //如果存在回调函数则执行handle->ticks = 0;                 //节拍计数清零handle->state = 2;                  //按键状态更新为2(松开)}//如果节拍计数超过了长按键阀值,则认为是一次长按键事件else if (handle->ticks > LONG_TICKS){handle->event = (uint8_t)LONG_PRESS_START; //有长按键开始事件发生EVENT_CB(LONG_PRESS_START);                   //如果存在回调函数则执行handle->state = 5;                         //按键状态更新为5(长按开始)}break;case 2: //如果前次按键状态是2(松开了),且当前电平是有效电平,则认为按键又按下了if (handle->button_level == handle->active_level){                                         //press down againhandle->event = (uint8_t)PRESS_DOWN; //有按键按下事件发生EVENT_CB(PRESS_DOWN);                 //如果存在回调函数则执行handle->repeat++;                     //又一次按下,重复次数加1EVENT_CB(PRESS_REPEAT);                // repeat hit,如果存在回调函数则执行handle->ticks = 0;                  //节拍计数清零handle->state = 3;                  //按键状态更新为3(重复按键)}//如果不是有效电平,说明松开了,那么检查摁下的时间是否超过了短按键的节拍计数阀值else if (handle->ticks > SHORT_TICKS){ //released timeout//超过阀值,且只按下了1次,则认为是一次单击事件if (handle->repeat == 1){handle->event = (uint8_t)SINGLE_CLICK; //有单击事件发生EVENT_CB(SINGLE_CLICK);                 //如果存在回调函数则执行}//如果按下了2次,则认为是一次双击事件else if (handle->repeat == 2){handle->event = (uint8_t)DOUBLE_CLICK; //有双击事件发生EVENT_CB(DOUBLE_CLICK);                 // repeat hit 如果存在回调函数则执行}handle->state = 0; //按键状态更新为0(未按下)}break;case 3: //如果前次按键状态是3(重复按下),且当前电平不是有效电平,则认为是按键弹起或短按键事件if (handle->button_level != handle->active_level){                                     //released press uphandle->event = (uint8_t)PRESS_UP; //有按键弹起事件发生EVENT_CB(PRESS_UP);                //如果存在回调函数则执行//如果记录的节拍时间小于300ms,if (handle->ticks < SHORT_TICKS){handle->ticks = 0; //节拍计数清零handle->state = 2; //repeat press按键状态更新为2(松开)}else{handle->state = 0; //按键状态更新为0(未按下)}}break;case 5: //如果前次按键状态是5(长按键开始),且当前电平是有效电平,则认为处于长按键保持出发状态if (handle->button_level == handle->active_level){//continue hold triggerhandle->event = (uint8_t)LONG_PRESS_HOLD; //有按键弹起事件发生if (handle->ticks % LONG_HOLD_CYC == 0)    //每500ms调用一次回调函数{EVENT_CB(LONG_PRESS_HOLD); //如果存在回调函数则执行}}else                                //如果不是有效电平,则认为是按键弹起事件{                                       //releasdhandle->event = (uint8_t)PRESS_UP; //有按键弹起事件发生EVENT_CB(PRESS_UP);                  //如果存在回调函数则执行handle->state = 0;                 //reset按键状态复位}break;}
}/*** @brief  Start the button work, add the handle into work list.* @param  handle: target handle strcut.* @retval 0: succeed. -1: already exist.*///启动一个按键,即新增一个按键结构体链表节点
int button_start(struct Button *handle)
{struct Button *target = head_handle;while (target){if (target == handle)return -1; //already exist.target = target->next;}handle->next = head_handle;head_handle = handle;return 0;
}/*** @brief  Stop the button work, remove the handle off work list.* @param  handle: target handle strcut.* @retval None*///停止一个按键,即从链表中删除该按键节点
void button_stop(struct Button *handle)
{struct Button **curr;for (curr = &head_handle; *curr;){struct Button *entry = *curr;if (entry == handle){*curr = entry->next;//            free(entry);}elsecurr = &entry->next;}
}/*** @brief  background ticks, timer repeat invoking interval 5ms.* @param  None.* @retval None*///按键节拍,每一次节拍事件遍历按键列表中所有按键,调用驱动函数
void button_ticks()
{struct Button *target;for (target = head_handle; target; target = target->next){button_handler(target);}
}

无bug版的完整源代码和使用demo请在这里下载

C语言实现事件驱动型按键驱动模块MultiButton相关推荐

  1. 【嵌入式开源库】MultiButton的使用,简单易用的事件驱动型按键驱动模块

    MultiButton 简介 下载 使用介绍 工程移植 代码分析 完整使用流程 实验效果 简介 MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步 ...

  2. 让开源按键组件MultiButton支持菜单操作(事件驱动型)

    看到之前一个老友写的MultiButton开源按键组件的剖析讲解,它的设计思想简洁且高效,以下是他的博客介绍的MultiButton这个教程: MultiButton | 一个小巧简单易用的事件驱动型 ...

  3. 一个好用的按键驱动模块

    发现一个比较好用的按键驱动模块MultiButton .以下是原文连接(尊重原创) https://github.com/0x1abin/MultiButton MultiButton 是一个小巧简单 ...

  4. 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线...

    重温.NET下Assembly的加载过程 最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后发现 ...

  5. ASP.NET Core Web API下事件驱动型架构的实现(四):CQRS架构中聚合与聚合根的实现

    在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅.通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现.接下来对于事件驱动型架构的讨论,就需 ...

  6. R语言将字符型(Character)变量转化为数值型(Numeric)

    R语言将字符型(Character)变量转化为数值型(Numeric) 目录 R语言将字符型(Character)变量转化为数值型(Numeric) #基本语法

  7. ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线

    在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容.下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了. 其 ...

  8. ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理

    在ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发.订阅和处理的流程.这种实现太简单了,百十行 ...

  9. ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现

    很长一段时间以来,我都在思考如何在ASP.NET Core的框架下,实现一套完整的事件驱动型架构.这个问题看上去有点大,其实主要目标是为了实现一个基于ASP.NET Core的微服务,它能够非常简单地 ...

最新文章

  1. ACE_Task::putq(转)
  2. Java Thread.join()详解
  3. 网页图表Highcharts实践教程之图表区
  4. redis的五种数据类型及常见操作
  5. (十三)洞悉linux下的Netfilteramp;iptables:为防火墙增添功能模块【实战】
  6. 基于javaweb的物资配送管理系统_智慧物流之RFID仓库管理系统,为传统的仓库管理带来了希望...
  7. 五个你绝不可忽视的HTML5特性
  8. MarshalHelper
  9. spring mvc 入门DispatcherServlet转发
  10. python中map函数返回值类型_Python函数精解:map函数
  11. 官宣了!百度36亿美元收购YY直播,一个用户“值”87.4美元
  12. Java进程与子进程交互
  13. 2012.4.16总结(二)
  14. Xamarin University-----Xamarin Mobile Certification Exam考试经历(还没写完)
  15. linux 历史命令列表,LinuxShell命令history
  16. 微信好友管理工具_助手_系统软件哪个最好?
  17. 获取电脑的唯一识别码_无锡电脑办公,office软件培训,学会为止
  18. Cesium资料大全
  19. PS教程:紫色光晕效果实现
  20. 什么是CDN,为什么用CDN,如何用CDN

热门文章

  1. 你真的知道Optional怎么使用吗?
  2. WORD 模板相关问题
  3. 如何从点云创建深度图像,看这篇你就懂了(附详细代码)
  4. 计算机基础之二进制、十进制、十六进制转换(上)
  5. Nginx服务器优点
  6. 108-Spring的底层原理(下篇)
  7. C++类static成员函数的调用
  8. 【Unity 25】 Unity的UI基础1 (基本UI介绍,Text, Button)
  9. 盘点最好用的linux发行版本!
  10. 解决cuda error:device-side assert triggered