分享一款嵌入式开源按键框架代码工程MultiButton

一、工程简介

  MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块。

  Github地址:https://github.com/0x1abin/MultiButton

  这个项目非常精简,只有两个文件:

  (1)可无限扩展按键;

  (2)按键事件的回调异步处理方式可以简化程序结构,去除冗余的按键处理硬编码,让按键业务逻辑更清晰。

  通过此工程可以学习到以下知识点:

  (1)按键各种类型事件;

  (2)状态机的思想;

  (3)单向链表语法。

  工程支持如下的按键事件:

   MultiButton 的按键状态及软件流程图:

 二、工程代码分析

  注:在使用源码工程时稍微修改了两个点,后续贴出修改后的完整代码,有需要查看修改的同学可以将源码工程下载后进行对比,修改的点如下:

  (1)将按键时间的相关参数通过接口定义进行初始化;

  (2)修改按键长按期间一直触发事件为真正的长按按键定时触发事件。

  在头文件multi_button.h中包括:

  (1)定义了按键时间相关参数;

  (2)定义了按键的事件类型;

  (3)定义按键链表结构体,这里使用了位域操作,解决字节的存储空间问题。

复制代码

 1 #ifndef _MULTI_BUTTON_H_2 #define _MULTI_BUTTON_H_3  4 #include <stdint.h>5 #include <string.h>6  7 typedef struct ButtonPara {8   uint8_t ticks_interval;9   uint8_t debounce_ticks;
10   uint16_t short_ticks;
11   uint16_t long_ticks;
12 }ButtonPara;
13  
14 typedef void (*BtnCallback)(void*);
15  
16 typedef enum {
17   PRESS_DOWN = 0,
18   PRESS_UP,
19   PRESS_REPEAT,
20   SINGLE_CLICK,
21   DOUBLE_CLICK,
22   LONG_PRESS_START,
23   LONG_PRESS_HOLD,
24   number_of_event,
25   NONE_PRESS
26 }PressEvent;
27  
28 typedef struct Button {
29   uint16_t ticks;
30   uint8_t  repeat : 4;
31   uint8_t  event : 4;
32   uint8_t  state : 3;
33   uint8_t  debounce_cnt : 3;
34   uint8_t  active_level : 1;
35   uint8_t  button_level : 1;
36   uint8_t  button_id;
37   uint8_t  (*hal_button_Level)(uint8_t button_id_);
38   BtnCallback  cb[number_of_event];
39   struct Button* next;
40 }Button;
41  
42 #ifdef __cplusplus
43 extern "C" {
44 #endif
45 void button_para_init(struct ButtonPara para);
46 void button_init(struct Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id);
47 void button_attach(struct Button* handle, PressEvent event, BtnCallback cb);
48 PressEvent get_button_event(struct Button* handle);
49 int  button_start(struct Button* handle);
50 void button_stop(struct Button* handle);
51 void button_ticks(void);
52  
53 #ifdef __cplusplus
54 }
55 #endif
56  
57 #endif

复制代码

  在源码文件multi_button.c中包括:

  (1)对按键时间参数进行初始化;

  (2)对按键对象结构体进行初始化,初始化成员包括按键句柄,绑定GPIO电平读取函数,设置有效触发电平;

  (3)初始化按键完成之后,进行按键绑定操作,将绑定按键结构体成员,按键触发事件,按键回调函数;

  (4)按键启动:也就是将按键加入链表当中,启动按键。这里选择的插入方式是头部插入法,在链表的头部插入按键节点,效率高,时间复杂度为O(1);

  (5)按键删除:将按键从当前链表中删除。使用到了二级指针删除一个按键元素。与链表中成员删除方法相同;

  (6)按键滴答函数:每间隔Nms触发一次按键事件,驱动状态机运行;

  (7)读取当前引脚的状态,获取按键当前属于哪种状态;

  (8)按键处理核心函数,驱动状态机。

复制代码

  1 #include "multi_button.h"2  3 #define EVENT_CB(ev)   if(handle->cb[ev])handle->cb[ev]((void*)handle)4 #define PRESS_REPEAT_MAX_NUM  15 /*!< The maximum value of the repeat counter */5  6 static struct ButtonPara buttonpara;7 //button handle list head.8 static struct Button* head_handle = NULL;9  10 static void button_handler(struct Button* handle);11  12  13 void button_para_init(struct ButtonPara para)14 {15   buttonpara.ticks_interval = para.ticks_interval;16   buttonpara.debounce_ticks = para.debounce_ticks;17   buttonpara.short_ticks = para.short_ticks;18   buttonpara.long_ticks = para.long_ticks;19 }20 /**21   * @brief  Initializes the button struct handle.22   * @param  handle: the button handle struct.23   * @param  pin_level: read the HAL GPIO of the connected button level.24   * @param  active_level: pressed GPIO level.25   * @param  button_id: the button id.26   * @retval None27   */28 void button_init(struct Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id)29 {30   memset(handle, 0, sizeof(struct Button));31   handle->event = (uint8_t)NONE_PRESS;32   handle->hal_button_Level = pin_level;33   handle->button_level = handle->hal_button_Level(button_id);34   handle->active_level = active_level;35   handle->button_id = button_id;36 }37  38 /**39   * @brief  Attach the button event callback function.40   * @param  handle: the button handle struct.41   * @param  event: trigger event type.42   * @param  cb: callback function.43   * @retval None44   */45 void button_attach(struct Button* handle, PressEvent event, BtnCallback cb)46 {47   handle->cb[event] = cb;48 }49  50 /**51   * @brief  Inquire the button event happen.52   * @param  handle: the button handle struct.53   * @retval button event.54   */55 PressEvent get_button_event(struct Button* handle)56 {57   return (PressEvent)(handle->event);58 }59  60 /**61   * @brief  Button driver core function, driver state machine.62   * @param  handle: the button handle struct.63   * @retval None64   */65 static void button_handler(struct Button* handle)66 {67   uint8_t read_gpio_level = handle->hal_button_Level(handle->button_id);68  69   //ticks counter working..70   if((handle->state) > 0) handle->ticks++;71  72   /*------------button debounce handle---------------*/73   if(read_gpio_level != handle->button_level) { //not equal to prev one74     //continue read 3 times same new level change75     if(++(handle->debounce_cnt) >= buttonpara.debounce_ticks) {76       handle->button_level = read_gpio_level;77       handle->debounce_cnt = 0;78     }79   } else { //level not change ,counter reset.80     handle->debounce_cnt = 0;81   }82  83   /*-----------------State machine-------------------*/84   switch (handle->state) {85   case 0:86     if(handle->button_level == handle->active_level) {  //start press down87       handle->event = (uint8_t)PRESS_DOWN;88       EVENT_CB(PRESS_DOWN);89       handle->ticks = 0;90       handle->repeat = 1;91       handle->state = 1;92     } else {93       handle->event = (uint8_t)NONE_PRESS;94     }95     break;96  97   case 1:98     if(handle->button_level != handle->active_level) { //released press up99       handle->event = (uint8_t)PRESS_UP;
100       EVENT_CB(PRESS_UP);
101       handle->ticks = 0;
102       handle->state = 2;
103     } else if(handle->ticks > buttonpara.long_ticks) {
104       handle->event = (uint8_t)LONG_PRESS_START;
105       EVENT_CB(LONG_PRESS_START);
106       handle->state = 5;
107     }
108     break;
109  
110   case 2:
111     if(handle->button_level == handle->active_level) { //press down again
112       handle->event = (uint8_t)PRESS_DOWN;
113       EVENT_CB(PRESS_DOWN);
114       if(handle->repeat != PRESS_REPEAT_MAX_NUM) {
115         handle->repeat++;
116       }
117       EVENT_CB(PRESS_REPEAT); // repeat hit
118       handle->ticks = 0;
119       handle->state = 3;
120     } else if(handle->ticks > buttonpara.short_ticks) { //released timeout
121       if(handle->repeat == 1) {
122         handle->event = (uint8_t)SINGLE_CLICK;
123         EVENT_CB(SINGLE_CLICK);
124       } else if(handle->repeat == 2) {
125         handle->event = (uint8_t)DOUBLE_CLICK;
126         EVENT_CB(DOUBLE_CLICK); // repeat hit
127       }
128       handle->state = 0;
129     }
130     break;
131  
132   case 3:
133     if(handle->button_level != handle->active_level) { //released press up
134       handle->event = (uint8_t)PRESS_UP;
135       EVENT_CB(PRESS_UP);
136       if(handle->ticks < buttonpara.short_ticks) {
137         handle->ticks = 0;
138         handle->state = 2; //repeat press
139       } else {
140         handle->state = 0;
141       }
142     } else if(handle->ticks > buttonpara.short_ticks) { // SHORT_TICKS < press down hold time < LONG_TICKS
143       handle->state = 1;
144     }
145     break;
146  
147   case 5:
148     if(handle->button_level == handle->active_level) {
149       //continue hold trigger
150       if(handle->ticks > buttonpara.long_ticks) {
151       handle->event = (uint8_t)LONG_PRESS_HOLD;
152       EVENT_CB(LONG_PRESS_HOLD);
153       handle->ticks = 0;
154       }
155     } else { //released
156       handle->event = (uint8_t)PRESS_UP;
157       EVENT_CB(PRESS_UP);
158       handle->state = 0; //reset
159     }
160     break;
161   default:
162     handle->state = 0; //reset
163     break;
164   }
165 }
166  
167 /**
168   * @brief  Start the button work, add the handle into work list.
169   * @param  handle: target handle struct.
170   * @retval 0: succeed. -1: already exist.
171   */
172 int button_start(struct Button* handle)
173 {
174   struct Button* target = head_handle;
175   while(target) {
176     if(target == handle) return -1;  //already exist.
177     target = target->next;
178   }
179   handle->next = head_handle;
180   head_handle = handle;
181   return 0;
182 }
183  
184 /**
185   * @brief  Stop the button work, remove the handle off work list.
186   * @param  handle: target handle struct.
187   * @retval None
188   */
189 void button_stop(struct Button* handle)
190 {
191   struct Button** curr;
192   for(curr = &head_handle; *curr; ) {
193     struct Button* entry = *curr;
194     if(entry == handle) {
195       *curr = entry->next;
196 //      free(entry);
197       return;//glacier add 2021-8-18
198     } else {
199       curr = &entry->next;
200     }
201   }
202 }
203  
204 /**
205   * @brief  background ticks, timer repeat invoking interval 5ms.
206   * @param  None.
207   * @retval None
208   */
209 void button_ticks(void)
210 {
211   struct Button* target;
212   for(target=head_handle; target; target=target->next) {
213     button_handler(target);
214   }
215 }

复制代码

三、工程代码应用

  以在freertos中应用为例,包括:

  (1)按键对象的定义及时间参数定义;

  (2)按键回调函数包括读取按键电平函数和各按键事件处理函数的编写;

  (3)按键初始化操作及启动按键功能;

  (4)在while(1)中添加按键滴答函数。

复制代码

  1 #define TICKS_INTERVAL    5  //按键状态轮询周期,单位ms2 #define DEBOUNCE_TICKS    3  //MAX 7 (0 ~ 7) 去抖时间次数,此为15ms/TICKS_INTERVAL=3次3 #define SHORT_TICKS       (300 /TICKS_INTERVAL) //短按时间次数,300ms/TICKS_INTERVAL 4 #define LONG_TICKS        (2000 /TICKS_INTERVAL) //长按时间次数,2000ms/TICKS_INTERVAL5          6 enum Button_IDs {7   btn1_id,8   btn2_id,9   btn3_id,10 };11 struct ButtonPara btnpara = {TICKS_INTERVAL, DEBOUNCE_TICKS, SHORT_TICKS, LONG_TICKS};12 struct Button btn1;13 struct Button btn2;14 struct Button btn3;15  16 //According to your need to modify the constants.17  18 uint8_t read_button_GPIO(uint8_t button_id)19 {20   // you can share the GPIO read function with multiple Buttons21   switch(button_id)22   {23     case btn1_id:24       return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4);25     case btn2_id:26       return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3);27     case btn3_id:28       return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2);29     default:30       return 0;31   }32 }33  34 void BTN1_PRESS_DOWN_Handler(void* btn)35 {36   printf("BTN1_PRESS_DOWN_Handler!\r\n");37 }38  39 void BTN1_PRESS_UP_Handler(void* btn)40 {41   printf("BTN1_PRESS_UP_Handler!\r\n");42 }43  44 void BTN1_PRESS_REPEAT_Handler(void* btn)45 {46   printf("BTN1_PRESS_REPEAT_Handler, repeatcount = %d!\r\n",btn1.repeat);47 }48  49 void BTN1_SINGLE_Click_Handler(void* btn)50 {51   printf("BTN1_SINGLE_Click_Handler!\r\n");52 }53  54 void BTN1_DOUBLE_Click_Handler(void* btn)55 {56   printf("BTN1_DOUBLE_Click_Handler!\r\n");57 }58  59 void BTN1_LONG_PRESS_START_Handler(void* btn)60 {61   printf("BTN1_LONG_PRESS_START_Handler!\r\n");62 }63  64 void BTN1_LONG_PRESS_HOLD_Handler(void* btn)65 {66   printf("BTN1_LONG_PRESS_HOLD_Handler!\r\n");67 }68  69 void BTN2_SINGLE_Click_Handler(void* btn)70 {71   printf("BTN2_SINGLE_Click_Handler!\r\n");72 }73  74 void BTN2_DOUBLE_Click_Handler(void* btn)75 {76   printf("BTN2_DOUBLE_Click_Handler!\r\n");77 }78  79 void BTN3_LONG_PRESS_START_Handler(void* btn)80 {81   printf("BTN3_LONG_PRESS_START_Handler!\r\n");82 }83  84 void BTN3_LONG_PRESS_HOLD_Handler(void* btn)85 {86   printf("BTN3_LONG_PRESS_HOLD_Handler!\r\n");87 }88  89 int main(void)90 { 91   button_para_init(btnpara);92   button_init(&btn1, read_button_GPIO, 0, btn1_id);93   button_init(&btn2, read_button_GPIO, 0, btn2_id);94   button_init(&btn3, read_button_GPIO, 0, btn3_id);95  96   button_attach(&btn1, PRESS_DOWN,       BTN1_PRESS_DOWN_Handler);97   button_attach(&btn1, PRESS_UP,         BTN1_PRESS_UP_Handler);98   button_attach(&btn1, PRESS_REPEAT,     BTN1_PRESS_REPEAT_Handler);99   button_attach(&btn1, SINGLE_CLICK,     BTN1_SINGLE_Click_Handler);
100   button_attach(&btn1, DOUBLE_CLICK,     BTN1_DOUBLE_Click_Handler);
101   button_attach(&btn1, LONG_PRESS_START, BTN1_LONG_PRESS_START_Handler);
102   button_attach(&btn1, LONG_PRESS_HOLD,  BTN1_LONG_PRESS_HOLD_Handler);
103  
104   button_attach(&btn2, PRESS_REPEAT,     BTN2_PRESS_REPEAT_Handler);
105   button_attach(&btn2, SINGLE_CLICK,     BTN2_SINGLE_Click_Handler);
106   button_attach(&btn2, DOUBLE_CLICK,     BTN2_DOUBLE_Click_Handler);
107   
108   button_attach(&btn3, LONG_PRESS_START, BTN3_LONG_PRESS_START_Handler);
109   button_attach(&btn3, LONG_PRESS_HOLD,  BTN3_LONG_PRESS_HOLD_Handler);
110  
111   button_start(&btn1);
112   button_start(&btn2);
113   button_start(&btn3);
114   
115  
116   xTaskCreate((TaskFunction_t )key_task,             
117                 (const char*    )"key_task",           
118                 (uint16_t       )KEY_STK_SIZE,        
119                 (void*          )NULL,                  
120                 (UBaseType_t    )KEY_TASK_PRIO,        
121                 (TaskHandle_t*  )&KeyTask_Handler);              
122     vTaskStartScheduler();          
123 }
124  
125  
126 void key_task(void *pvParameters)
127 {
128   while(1)
129   {
130     button_ticks();
131     vTaskDelay(5);      //5ms周期轮询
132   }
133 }

复制代码

四、思考

  使用中有如下问题值得思考:

  (1)组合键和矩阵按键如何实现?

  在函数uint8_t read_button_GPIO(uint8_t button_id)中进行组合键和矩阵按键返回值的自定义。

  (2)多个按键时,按键参数进行区分?

  去抖时间,短按时间,长按时间可以放在一个数组中区分,各个按键定义各自的参数。

  (3)现在按键事件较多的情况时,需要多个绑定的事件函数?

  可以将按键事件函数统一放在一个数组中进行初始化注册。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/636791.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sublime text的json快捷键

系统 macos 配置 sublime Text->Settings->Key Bindings 效果 可以看到&#xff0c;按&#xff1a;shiftcommandp&#xff0c;会出现快捷键窗口&#xff0c;打pretty&#xff0c;会出现Format JSON&#xff0c;最右侧显示⌘J&#xff0c;说明只需要macos的⌘和J同时按…

垃圾回收知识整理

1.为什么要有垃圾回收 提高开发效率:程序员无需显式地分配和释放内存&#xff0c;这是由Java虚拟机&#xff08;JVM&#xff09;自动处理的。这种自动内存管理大大简化了程序员的工作。 减少程序错误: 手动管理内存致各种内存管理错误&#xff08;如内存泄漏、野指针等&#xf…

linux 设备树-of_address_to_resource

实例分析-reg 属性解析(基于ranges属性) /{#address-cells <0x01>;#size-cells <0x01>;soc {compatible "simple-bus";#address-cells <0x01>;#size-cells <0x01>;ranges <0x7e000000 0x3f000000 0x1000000 0x40000000 0x40000000…

arkts子组件调用父组件的方法api10+

最近都在开发鸿蒙next的应用&#xff0c;记录下来关于子组件调用父组件的方法 以Tabs为例 首先父组件是Index&#xff0c;里面有一个子组件mainView&#xff0c;实现mainView调用了父组件changeTab的方法。 Entry Component struct Index {private tabsController: TabsContr…

idea项目启动异常:Command line is too long.

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; idea中启动项目报错&#xff1a; 解决方案 在idea 的运行配置中&#xff0c;修改enviroment下的shorten command line 为jar manifest 注&#xff1a; 有时shorten command line 可能不是默认存在的…

BAPI_BATCH_CHANGE:修改批次的特征值

文章目录 BAPI_BATCH_CHANGE&#xff1a;修改批次的特征值实现步骤定义变量获取对象/类等 获取已维护特性值新特性值更新 注意事项最终效果字段介绍 BAPI_BATCH_CHANGE&#xff1a;修改批次的特征值 现在有一个需求是要修改批次里面的某一个特征值&#xff0c;所以需要使用到B…

海绵结构:Hash as RO

参考文献&#xff1a; [BDPA07] Bertoni G, Daemen J, Peeters M, et al. Sponge functions[C]//ECRYPT hash workshop. 2007, 2007(9).[GPP11] Guo J, Peyrin T, Poschmann A. The PHOTON family of lightweight hash functions[C]//Advances in Cryptology–CRYPTO 2011: 31…

PTA 编程题(C语言)-- 统计字符

题目标题&#xff1a;统计字符 题目作者&#xff1a;颜晖 浙大城市学院 本题要求编写程序&#xff0c;输入10个字符&#xff0c;统计其中英文字母、空格或回车、数字字符和其他字符的个数。 输入格式: 输入为…

[linux]进程控制——进程等待

一、概念 进程等待&#xff0c;就是通过wait/waitpid的方式&#xff0c;让父进程&#xff08;一般&#xff09;对子进程进行资源回收的等待过程。 二、原因 &#xff08;1&#xff09; 当一个进程在退出的时候&#xff0c;如果不回收&#xff0c;就会变成僵尸状态&#xff0…

SpringBoot的配置文件application.yml的一些常用语法

目录 一、自定义配置数据 &#xff08;1&#xff09;配置简单数据 &#xff08;2&#xff09;配置对象数据 &#xff08;3&#xff09;配置集合数据 二、Value读取配置文件 三、ConfigurationProperties读取配置文件 配置文件的后缀可以是yaml或者yml&#xff0c;写法类似…

超分中使用的损失函数和经典文章

损失函数 https://towardsdatascience.com/super-resolution-a-basic-study-e01af1449e13 在GAN出现之前&#xff0c;使用的更多是MSE&#xff0c;PSNR,SSIM来衡量图像相似度&#xff0c;同时也使用他们作为损失函数。 MSE 表面上MSE直接决定了PSNR&#xff0c;MSE&#xff…

Golang数据类型

文章目录 数据类型的基本介绍基本数据类型整数类型字符类型浮点数类型复数类型布尔类型string类型 常量类型转换基本数据类型相互转换基本数据类型与string的转换 指针类型值类型和引用类型 数据类型的基本介绍 数据类型的基本介绍 Go中的每一种数据都定义了明确的数据类型&…