• 分享一款嵌入式开源按键框架代码工程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 
     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!\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)现在按键事件较多的情况时,需要多个绑定的事件函数?

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


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

     

  • 相关阅读:
    Docker网络管理
    JAVA集合框架
    逼的测试/开发程序员是不是都这样?心无旁骛......
    Dapp智能合约开发搭建
    关于RocketMQ那些你可能不知道的性能优化!
    大数据Flink(九十六):DML:Deduplication
    CTFHUB-web-RCE
    数学建模Matlab之优化类方法
    论文解读《Adversarial training methods for semi-supervised text classification》
    TypeScript手写+项目实际
  • 原文地址:https://www.cnblogs.com/Sharemaker/p/18137037