C语言实现事件驱动型按键驱动模块MultiButton
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相关推荐
- 【嵌入式开源库】MultiButton的使用,简单易用的事件驱动型按键驱动模块
MultiButton 简介 下载 使用介绍 工程移植 代码分析 完整使用流程 实验效果 简介 MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步 ...
- 让开源按键组件MultiButton支持菜单操作(事件驱动型)
看到之前一个老友写的MultiButton开源按键组件的剖析讲解,它的设计思想简洁且高效,以下是他的博客介绍的MultiButton这个教程: MultiButton | 一个小巧简单易用的事件驱动型 ...
- 一个好用的按键驱动模块
发现一个比较好用的按键驱动模块MultiButton .以下是原文连接(尊重原创) https://github.com/0x1abin/MultiButton MultiButton 是一个小巧简单 ...
- 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线...
重温.NET下Assembly的加载过程 最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后发现 ...
- ASP.NET Core Web API下事件驱动型架构的实现(四):CQRS架构中聚合与聚合根的实现
在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅.通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现.接下来对于事件驱动型架构的讨论,就需 ...
- R语言将字符型(Character)变量转化为数值型(Numeric)
R语言将字符型(Character)变量转化为数值型(Numeric) 目录 R语言将字符型(Character)变量转化为数值型(Numeric) #基本语法
- ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线
在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容.下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了. 其 ...
- ASP.NET Core Web API下事件驱动型架构的实现(二):事件处理器中对象生命周期的管理
在ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发.订阅和处理的流程.这种实现太简单了,百十行 ...
- ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现
很长一段时间以来,我都在思考如何在ASP.NET Core的框架下,实现一套完整的事件驱动型架构.这个问题看上去有点大,其实主要目标是为了实现一个基于ASP.NET Core的微服务,它能够非常简单地 ...
最新文章
- ACE_Task::putq(转)
- Java Thread.join()详解
- 网页图表Highcharts实践教程之图表区
- redis的五种数据类型及常见操作
- (十三)洞悉linux下的Netfilteramp;iptables:为防火墙增添功能模块【实战】
- 基于javaweb的物资配送管理系统_智慧物流之RFID仓库管理系统,为传统的仓库管理带来了希望...
- 五个你绝不可忽视的HTML5特性
- MarshalHelper
- spring mvc 入门DispatcherServlet转发
- python中map函数返回值类型_Python函数精解:map函数
- 官宣了!百度36亿美元收购YY直播,一个用户“值”87.4美元
- Java进程与子进程交互
- 2012.4.16总结(二)
- Xamarin University-----Xamarin Mobile Certification Exam考试经历(还没写完)
- linux 历史命令列表,LinuxShell命令history
- 微信好友管理工具_助手_系统软件哪个最好?
- 获取电脑的唯一识别码_无锡电脑办公,office软件培训,学会为止
- Cesium资料大全
- PS教程:紫色光晕效果实现
- 什么是CDN,为什么用CDN,如何用CDN