├── README.md ├── SConscript ├── examples ├── event_async.c └── event_inquire.c ├── multi_button.c ├── multi_button.h └── states.png /README.md: -------------------------------------------------------------------------------- 1 | # MultiButton 2 | 3 | ## 简介 4 | MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。 5 | 6 | MultiButton的作者是0x1abin, github地址: https://github.com/0x1abin/MultiButton. 7 | 8 | ## 使用方法 9 | 1.先申请一个按键结构 10 | 11 | ```c 12 | struct Button button1; 13 | ``` 14 | 2.初始化按键对象,绑定按键的GPIO电平读取接口**read_button_pin()** ,后一个参数设置有效触发电平 15 | 16 | ```c 17 | button_init(&button1, read_button_pin, 0); 18 | ``` 19 | 3.注册按键事件 20 | 21 | ```c 22 | button_attach(&button1, SINGLE_CLICK, Callback_SINGLE_CLICK_Handler); 23 | button_attach(&button1, DOUBLE_CLICK, Callback_DOUBLE_Click_Handler); 24 | ... 25 | ``` 26 | 4.启动按键 27 | 28 | ```c 29 | button_start(&button1); 30 | ``` 31 | 5.设置一个5ms间隔的定时器循环调用后台处理函数 32 | 33 | ```c 34 | while(1) { 35 | ... 36 | if(timer_ticks == 5) { 37 | timer_ticks = 0; 38 | 39 | button_ticks(); 40 | } 41 | } 42 | ``` 43 | 44 | ## 特性 45 | 46 | MultiButton 使用C语言实现,基于面向对象方式设计思路,每个按键对象单独用一份数据结构管理: 47 | 48 | ```c 49 | struct Button { 50 | uint16_t ticks; 51 | uint8_t repeat: 4; 52 | uint8_t event : 4; 53 | uint8_t state : 3; 54 | uint8_t debounce_cnt : 3; 55 | uint8_t active_level : 1; 56 | uint8_t button_level : 1; 57 | uint8_t (*hal_button_Level)(void); 58 | BtnCallback cb[number_of_event]; 59 | struct Button* next; 60 | }; 61 | ``` 62 | 这样每个按键使用单向链表相连,依次进入 button_handler(struct Button* handle) 状态机处理,所以每个按键的状态彼此独立。 63 | 64 | 65 | ## 按键事件 66 | 67 | 事件 | 说明 68 | ---|--- 69 | PRESS_DOWN | 按键按下,每次按下都触发 70 | PRESS_UP | 按键弹起,每次松开都触发 71 | PRESS_REPEAT | 重复按下触发,变量repeat计数连击次数 72 | SINGLE_CLICK | 单击按键事件 73 | DOUBLE_CLICK | 双击按键事件 74 | LONG_PRESS_START | 达到长按时间阈值时触发一次 75 | LONG_PRESS_HOLD | 长按期间一直触发 76 | 77 | 78 | ## Examples 79 | 80 | ```c 81 | #include "button.h" 82 | 83 | struct Button btn1; 84 | 85 | uint8_t read_button1_GPIO() 86 | { 87 | return HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin); 88 | } 89 | 90 | void BTN1_PRESS_DOWN_Handler(void* btn) 91 | { 92 | //do something... 93 | } 94 | void BTN1_PRESS_UP_Handler(void* btn) 95 | { 96 | //do something... 97 | } 98 | 99 | int main() 100 | { 101 | button_init(&btn1, read_button1_GPIO, 0); 102 | button_attach(&btn1, PRESS_DOWN, BTN1_PRESS_DOWN_Handler); 103 | button_attach(&btn1, PRESS_UP, BTN1_PRESS_UP_Handler); 104 | button_attach(&btn1, PRESS_REPEAT, BTN1_PRESS_REPEAT_Handler); 105 | button_attach(&btn1, SINGLE_CLICK, BTN1_SINGLE_Click_Handler); 106 | button_attach(&btn1, DOUBLE_CLICK, BTN1_DOUBLE_Click_Handler); 107 | button_attach(&btn1, LONG_PRESS_START, BTN1_LONG_PRESS_START_Handler); 108 | button_attach(&btn1, LONG_PRESS_HOLD, BTN1_LONG_PRESS_HOLD_Handler); 109 | button_start(&btn1); 110 | 111 | //make the timer invoking the button_ticks() interval 5ms. 112 | //This function is implemented by yourself. 113 | __timer_start(button_ticks, 0, 5); 114 | 115 | while(1) 116 | {} 117 | } 118 | 119 | ``` 120 | 121 | ## 状态图 122 | 123 | ![states.png](states.png) 124 | -------------------------------------------------------------------------------- /SConscript: -------------------------------------------------------------------------------- 1 | from building import * 2 | 3 | # get current dir path 4 | cwd = GetCurrentDir() 5 | 6 | # init src and inc vars 7 | src = [] 8 | inc = [] 9 | 10 | # add MultiButton common include 11 | inc = inc + [cwd] 12 | 13 | # add MultiButton basic code 14 | src = src + ['./multi_button.c'] 15 | 16 | # add MultiButton Test code 17 | if GetDepend('MULTIBUTTON_USING_EXAMPLE_ASYNC'): 18 | src = src + ['./examples/event_async.c'] 19 | if GetDepend('MULTIBUTTON_USING_EXAMPLE_INQUIRE'): 20 | src = src + ['./examples/event_inquire.c'] 21 | 22 | # add group to IDE project 23 | group = DefineGroup('MultiButton', src, depend = ['PKG_USING_MULTIBUTTON'], CPPPATH = inc) 24 | 25 | Return('group') 26 | -------------------------------------------------------------------------------- /examples/event_async.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "multi_button.h" 4 | 5 | static struct button btn; 6 | 7 | #define BUTTON_PIN (10) 8 | 9 | static uint8_t button_read_pin(void) 10 | { 11 | return rt_pin_read(BUTTON_PIN); 12 | } 13 | 14 | void button_callback(void *btn) 15 | { 16 | uint32_t btn_event_val; 17 | 18 | btn_event_val = get_button_event((struct button *)btn); 19 | 20 | switch(btn_event_val) 21 | { 22 | case PRESS_DOWN: 23 | rt_kprintf("button press down\n"); 24 | break; 25 | 26 | case PRESS_UP: 27 | rt_kprintf("button press up\n"); 28 | break; 29 | 30 | case PRESS_REPEAT: 31 | rt_kprintf("button press repeat\n"); 32 | break; 33 | 34 | case SINGLE_CLICK: 35 | rt_kprintf("button single click\n"); 36 | break; 37 | 38 | case DOUBLE_CLICK: 39 | rt_kprintf("button double click\n"); 40 | break; 41 | 42 | case LONG_PRESS_START: 43 | rt_kprintf("button long press start\n"); 44 | break; 45 | 46 | case LONG_PRESS_HOLD: 47 | rt_kprintf("button long press hold\n"); 48 | break; 49 | } 50 | } 51 | 52 | void btn_thread_entry(void* p) 53 | { 54 | while(1) 55 | { 56 | /* 5ms */ 57 | rt_thread_delay(RT_TICK_PER_SECOND/200); 58 | button_ticks(); 59 | } 60 | } 61 | 62 | int multi_button_test(void) 63 | { 64 | rt_thread_t thread = RT_NULL; 65 | 66 | /* Create background ticks thread */ 67 | thread = rt_thread_create("btn", btn_thread_entry, RT_NULL, 1024, 10, 10); 68 | if(thread == RT_NULL) 69 | { 70 | return RT_ERROR; 71 | } 72 | rt_thread_startup(thread); 73 | 74 | /* low level drive */ 75 | rt_pin_mode (BUTTON_PIN, PIN_MODE_INPUT); 76 | button_init (&btn, button_read_pin, PIN_LOW); 77 | button_attach(&btn, PRESS_DOWN, button_callback); 78 | button_attach(&btn, PRESS_UP, button_callback); 79 | button_attach(&btn, PRESS_REPEAT, button_callback); 80 | button_attach(&btn, SINGLE_CLICK, button_callback); 81 | button_attach(&btn, DOUBLE_CLICK, button_callback); 82 | button_attach(&btn, LONG_PRESS_START, button_callback); 83 | button_attach(&btn, LONG_PRESS_HOLD, button_callback); 84 | button_start (&btn); 85 | 86 | return RT_EOK; 87 | } 88 | INIT_APP_EXPORT(multi_button_test); 89 | -------------------------------------------------------------------------------- /examples/event_inquire.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "multi_button.h" 4 | 5 | static struct button btn; 6 | 7 | #define BUTTON_PIN (10) 8 | 9 | static uint8_t button_read_pin(void) 10 | { 11 | return rt_pin_read(BUTTON_PIN); 12 | } 13 | 14 | void btn_test_thread_entry(void *p) 15 | { 16 | uint32_t btn_event_val; 17 | 18 | while(1) 19 | { 20 | if(btn_event_val != get_button_event(&btn)) 21 | { 22 | btn_event_val = get_button_event(&btn); 23 | 24 | switch(btn_event_val) 25 | { 26 | case PRESS_DOWN: 27 | rt_kprintf("button press down\n"); 28 | break; 29 | 30 | case PRESS_UP: 31 | rt_kprintf("button press up\n"); 32 | break; 33 | 34 | case PRESS_REPEAT: 35 | rt_kprintf("button press repeat\n"); 36 | break; 37 | 38 | case SINGLE_CLICK: 39 | rt_kprintf("button single click\n"); 40 | break; 41 | 42 | case DOUBLE_CLICK: 43 | rt_kprintf("button double click\n"); 44 | break; 45 | 46 | case LONG_PRESS_START: 47 | rt_kprintf("button long press start\n"); 48 | break; 49 | 50 | case LONG_PRESS_HOLD: 51 | rt_kprintf("button long press hold\n"); 52 | break; 53 | } 54 | } 55 | 56 | button_ticks(); 57 | rt_thread_delay(RT_TICK_PER_SECOND/200); 58 | } 59 | } 60 | 61 | int multi_button_test(void) 62 | { 63 | rt_thread_t thread = RT_NULL; 64 | 65 | thread = rt_thread_create("btn_test", btn_test_thread_entry, RT_NULL, 1024, 15, 10); 66 | if(thread == RT_NULL) 67 | { 68 | return RT_ERROR; 69 | } 70 | rt_thread_startup(thread); 71 | 72 | /* low level drive */ 73 | rt_pin_mode (BUTTON_PIN, PIN_MODE_INPUT); 74 | button_init (&btn, button_read_pin, PIN_LOW); 75 | button_start(&btn); 76 | 77 | return RT_EOK; 78 | } 79 | INIT_APP_EXPORT(multi_button_test); 80 | -------------------------------------------------------------------------------- /multi_button.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @File: multi_button.c 3 | * @Author: Zibin Zheng 4 | * @Date: 2018-01-23 20:36:01 5 | * 6 | * @LICENSE: Copyright (c) 2016 Zibin Zheng 7 | * All rights reserved. 8 | * 9 | * @NOTE: The original author of MultiButton is Zibin Zheng. 10 | * Please contact the original author for authorization 11 | * before use.I(liu2guang) only adapt the library to 12 | * rt-thread and fix some bugs. I(liu2guang) am not 13 | * responsible for the authorization of the library. 14 | * 15 | * Change Logs: 16 | * Date Author Notes 17 | * 2018-01-23 liu2guang Adapter rtthread and Fix the bug. 18 | */ 19 | #include "multi_button.h" 20 | 21 | #define EVENT_CB(ev) if(handle->cb[ev]) handle->cb[ev]((button*)handle) 22 | 23 | static struct button* head_handle = NULL; 24 | 25 | /** 26 | * @brief Initializes the button struct handle. 27 | * @param handle: the button handle struct. 28 | * @param pin_level: read the pin of the connected button level. 29 | * @param active_level: pin pressed level. 30 | * @retval None 31 | */ 32 | void button_init(struct button* handle, uint8_t(*pin_level)(void), uint8_t active_level) 33 | { 34 | memset(handle, 0, sizeof(struct button)); 35 | handle->event = (uint8_t)NONE_PRESS; 36 | handle->hal_button_Level = pin_level; 37 | handle->button_level = handle->hal_button_Level(); 38 | handle->active_level = active_level; 39 | } 40 | 41 | /** 42 | * @brief Attach the button event callback function. 43 | * @param handle: the button handle struct. 44 | * @param event: trigger event type. 45 | * @param cb: callback function. 46 | * @retval None 47 | */ 48 | void button_attach(struct button* handle, PressEvent event, BtnCallback cb) 49 | { 50 | handle->cb[event] = cb; 51 | } 52 | 53 | /** 54 | * @brief Inquire the button event happen. 55 | * @param handle: the button handle struct. 56 | * @retval button event. 57 | */ 58 | PressEvent get_button_event(struct button* handle) 59 | { 60 | return (PressEvent)(handle->event); 61 | } 62 | 63 | /** 64 | * @brief button driver core function, driver state machine. 65 | * @param handle: the button handle struct. 66 | * @retval None 67 | */ 68 | static void button_handler(struct button* handle) 69 | { 70 | uint8_t read_gpio_level = handle->hal_button_Level(); 71 | 72 | //ticks counter working.. 73 | if((handle->state) > 0) 74 | { 75 | handle->ticks++; 76 | } 77 | 78 | /*------------button debounce handle---------------*/ 79 | if(read_gpio_level != handle->button_level) 80 | { 81 | //not equal to prev one 82 | //continue read 3 times same new level change 83 | if(++(handle->debounce_cnt) >= DEBOUNCE_TICKS) 84 | { 85 | handle->button_level = read_gpio_level; 86 | handle->debounce_cnt = 0; 87 | } 88 | } 89 | else 90 | { 91 | // level not change ,counter reset. 92 | handle->debounce_cnt = 0; 93 | } 94 | 95 | /*-----------------State machine-------------------*/ 96 | switch (handle->state) 97 | { 98 | case 0: 99 | if(handle->button_level == handle->active_level) 100 | { 101 | handle->event = (uint8_t)PRESS_DOWN; 102 | EVENT_CB(PRESS_DOWN); 103 | handle->ticks = 0; 104 | handle->repeat = 1; 105 | handle->state = 1; 106 | } 107 | else 108 | { 109 | handle->event = (uint8_t)NONE_PRESS; 110 | } 111 | break; 112 | 113 | case 1: 114 | if(handle->button_level != handle->active_level) 115 | { 116 | handle->event = (uint8_t)PRESS_UP; 117 | EVENT_CB(PRESS_UP); 118 | handle->ticks = 0; 119 | handle->state = 2; 120 | 121 | } 122 | else if(handle->ticks > LONG_TICKS) 123 | { 124 | handle->event = (uint8_t)LONG_PRESS_START; 125 | EVENT_CB(LONG_PRESS_START); 126 | handle->state = 5; 127 | } 128 | break; 129 | 130 | case 2: 131 | if(handle->button_level == handle->active_level) 132 | { 133 | handle->event = (uint8_t)PRESS_DOWN; 134 | EVENT_CB(PRESS_DOWN); 135 | handle->repeat++; 136 | EVENT_CB(PRESS_REPEAT); 137 | handle->ticks = 0; 138 | handle->state = 3; 139 | } 140 | else if(handle->ticks > SHORT_TICKS) 141 | { 142 | if(handle->repeat == 1) 143 | { 144 | handle->event = (uint8_t)SINGLE_CLICK; 145 | EVENT_CB(SINGLE_CLICK); 146 | } 147 | else if(handle->repeat == 2) 148 | { 149 | handle->event = (uint8_t)DOUBLE_CLICK; 150 | EVENT_CB(DOUBLE_CLICK); 151 | } 152 | handle->state = 0; 153 | } 154 | break; 155 | 156 | case 3: 157 | if(handle->button_level != handle->active_level) 158 | { 159 | handle->event = (uint8_t)PRESS_UP; 160 | EVENT_CB(PRESS_UP); 161 | 162 | if(handle->ticks < SHORT_TICKS) 163 | { 164 | handle->ticks = 0; 165 | handle->state = 2; 166 | } 167 | else 168 | { 169 | handle->state = 0; 170 | } 171 | } 172 | else if(handle->ticks > SHORT_TICKS) // SHORT_TICKS < press down hold time < LONG_TICKS 173 | { 174 | handle->state = 1; 175 | } 176 | break; 177 | 178 | case 5: 179 | if(handle->button_level == handle->active_level) 180 | { 181 | handle->event = (uint8_t)LONG_PRESS_HOLD; 182 | if (handle->ticks % LONG_HOLD_CYC == 0) 183 | { 184 | EVENT_CB(LONG_PRESS_HOLD); 185 | } 186 | } 187 | else 188 | { 189 | handle->event = (uint8_t)PRESS_UP; 190 | EVENT_CB(PRESS_UP); 191 | 192 | handle->state = 0; 193 | } 194 | break; 195 | 196 | default: 197 | handle->state = 0; /* reset */ 198 | break; 199 | } 200 | } 201 | 202 | /** 203 | * @brief Start the button work, add the handle into work list. 204 | * @param handle: target handle struct. 205 | * @retval 0: succeed. -1: already exist. 206 | */ 207 | int button_start(struct button* handle) 208 | { 209 | struct button* target = head_handle; 210 | 211 | while(target) 212 | { 213 | if(target == handle) 214 | { 215 | return -1; //already exist. 216 | } 217 | 218 | target = target->next; 219 | } 220 | 221 | handle->next = head_handle; 222 | head_handle = handle; 223 | 224 | return 0; 225 | } 226 | 227 | /** 228 | * @brief Stop the button work, remove the handle off work list. 229 | * @param handle: target handle struct. 230 | * @retval None 231 | */ 232 | void button_stop(struct button* handle) 233 | { 234 | struct button** curr; 235 | 236 | for(curr = &head_handle; *curr;) 237 | { 238 | struct button* entry = *curr; 239 | 240 | if (entry == handle) 241 | { 242 | *curr = entry->next; 243 | return; 244 | } 245 | else 246 | { 247 | curr = &entry->next; 248 | } 249 | } 250 | } 251 | 252 | /** 253 | * @brief background ticks, timer repeat invoking interval 5ms. 254 | * @param None. 255 | * @retval None 256 | */ 257 | void button_ticks(void) 258 | { 259 | struct button* target; 260 | 261 | for(target = head_handle; target != NULL; target = target->next) 262 | { 263 | button_handler(target); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /multi_button.h: -------------------------------------------------------------------------------- 1 | #ifndef __MULTI_BUTTON_H_ 2 | #define __MULTI_BUTTON_H_ 3 | 4 | #include 5 | #include 6 | 7 | //According to your need to modify the constants. 8 | #define TICKS_INTERVAL 5 //ms 9 | #define DEBOUNCE_TICKS 3 //MAX 7 (0 ~ 7) 10 | #define SHORT_TICKS (300 / TICKS_INTERVAL) 11 | #define LONG_TICKS (1000 / TICKS_INTERVAL) 12 | #define LONG_HOLD_CYC (500 / TICKS_INTERVAL) 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 (*hal_button_Level)(void); 37 | BtnCallback cb[number_of_event]; 38 | struct button* next; 39 | }button; 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | 45 | void button_init(struct button* handle, uint8_t(*pin_level)(void), uint8_t active_level); 46 | void button_attach(struct button* handle, PressEvent event, BtnCallback cb); 47 | PressEvent get_button_event(struct button* handle); 48 | int button_start(struct button* handle); 49 | void button_stop(struct button* handle); 50 | void button_ticks(void); 51 | 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liu2guang/MultiButton/eeeade9b5440dd09489db311807fee0f0feb8f2e/states.png --------------------------------------------------------------------------------