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

一、工程简介

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

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

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

  (1)可无限扩展按键;

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

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

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

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

  (2)状态机的思想;

  (3)单向链表语法。

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

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

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

分享一款嵌入式开源按键框架代码工程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 None  27   */  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 None  44   */  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 None  64   */  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 one  74     //continue read 3 times same new level change  75     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 down  87       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 up  99       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  //按键状态轮询周期,单位ms   2 #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_INTERVAL   5             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 Buttons  21   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!rn");  37 }  38    39 void BTN1_PRESS_UP_Handler(void* btn)  40 {  41   printf("BTN1_PRESS_UP_Handler!rn");  42 }  43    44 void BTN1_PRESS_REPEAT_Handler(void* btn)  45 {  46   printf("BTN1_PRESS_REPEAT_Handler, repeatcount = %d!rn",btn1.repeat);  47 }  48    49 void BTN1_SINGLE_Click_Handler(void* btn)  50 {  51   printf("BTN1_SINGLE_Click_Handler!rn");  52 }  53    54 void BTN1_DOUBLE_Click_Handler(void* btn)  55 {  56   printf("BTN1_DOUBLE_Click_Handler!rn");  57 }  58    59 void BTN1_LONG_PRESS_START_Handler(void* btn)  60 {  61   printf("BTN1_LONG_PRESS_START_Handler!rn");  62 }  63    64 void BTN1_LONG_PRESS_HOLD_Handler(void* btn)  65 {  66   printf("BTN1_LONG_PRESS_HOLD_Handler!rn");  67 }  68    69 void BTN2_SINGLE_Click_Handler(void* btn)  70 {  71   printf("BTN2_SINGLE_Click_Handler!rn");  72 }  73    74 void BTN2_DOUBLE_Click_Handler(void* btn)  75 {  76   printf("BTN2_DOUBLE_Click_Handler!rn");  77 }  78    79 void BTN3_LONG_PRESS_START_Handler(void* btn)  80 {  81   printf("BTN3_LONG_PRESS_START_Handler!rn");  82 }  83    84 void BTN3_LONG_PRESS_HOLD_Handler(void* btn)  85 {  86   printf("BTN3_LONG_PRESS_HOLD_Handler!rn");  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)现在按键事件较多的情况时,需要多个绑定的事件函数?

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


↓↓↓更多技术内容和书籍资料获取,入群技术交流敬请关注“明解嵌入式”↓↓↓ 

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

 

发表评论

相关文章

  • 0