单片机系统一个非常实用的按键处理框架MultiButton
前言
在嵌入式开发中,对于输入设备有这几种,屏幕、按键、编码器等。
按键是我们使用最常见的一种。但是处理按键的时候也有一些麻烦之处,比方说我们对于一个按键进行程序消抖、区分按键单击、双击、长按、短按,如果没有一个按键驱动框架是很难实现的 ,在这里推荐一个非常好用的按键处理框架MultiButton
。
一、MultiButton 是什么?
MultiButton
就是这样一个按键管理框架,它不仅占用内存小,而且实现了按键操作的诸多功能(具体功能如下),使用回调函数进行业务的处理,是一个非常实用的按键框架。
本篇文章对 MultiButton
进行简略的介绍,并且对业务处理代码进行了解读
GitHub仓库 https://github.com/0x1abin/MultiButton
2. 使用步骤和代码的问题
2.1 使用步骤
- 代码如下
// 1. 申请按键结构体
struct Button key2;
// 2. 初始化按键
// handle 按键结构体指针
// uint8_t(*pin_level)() 获取按键值回调函数
// active_level 触发电平
void button_init(struct Button* handle, uint8_t(*pin_level)(), uint8_t active_level)
button_init(&key2,Read_Key2_Pin , 1);
// 3. 注册回调函数
// handle 按键结构体指针
// event 触发条件,看下面表格
// BtnCallback 触发回调函数
void button_attach(struct Button* handle, PressEvent event, BtnCallback cb)
// 4. 启动 按键
button_start(&key2);
// 5. 在循环或者使用定时器,每5ms调用一次次函数
button_ticks();
2.2 按键库的问题
- 因为处理函数设计的问题,如果只有一个按键结构体,就会导致段错误
void button_ticks()
{
struct Button* target;
for(target=head_handle; target; target=target->next) {
button_handler(target);
}
}
2.3 按键库一些默认参数的配置
#define TICKS_INTERVAL 5 //ms // 心跳时钟
#define DEBOUNCE_TICKS 3 //MAX 8 // 防抖次数
#define SHORT_TICKS (300 /TICKS_INTERVAL) // 短按时间
#define LONG_TICKS (1000 /TICKS_INTERVAL) // 长按时间
3. 代码解读
-
按键的处理流程为下图,按照下图看代码会更加清晰
-
C文件
/* * Copyright (c) 2016 Zibin Zheng <znbin@qq.com> * All rights reserved */ #include "multi_button.h" #define EVENT_CB(ev) if(handle->cb[ev])handle->cb[ev]((Button*)handle) //button handle list head. 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)); handle->event = (uint8_t)NONE_PRESS; handle->hal_button_Level = pin_level; handle->button_level = handle->hal_button_Level(); handle->active_level = active_level; } /** * @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.. /* 判断定时器是否需要工作 */ if((handle->state) > 0) handle->ticks++; /*------------防抖处理---------------*/ if(read_gpio_level != handle->button_level) { //not equal to prev one //continue read 3 times same new level change // 默认防抖次数为三次,也就是此函数循环第三次才会进行按键判断 if(++(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: if(handle->button_level == handle->active_level) { // 按键按键 handle->event = (uint8_t)PRESS_DOWN; // 设置按键为按下 EVENT_CB(PRESS_DOWN); // 调用相应的回调函数 handle->ticks = 0; // 时钟重置为0 handle->repeat = 1; // 设置按键为第一次按下 handle->state = 1; // 状态机设置为1 } else { handle->event = (uint8_t)NONE_PRESS; // 状态设置为没有按下 } break; case 1: if(handle->button_level != handle->active_level) { // 释放按钮 handle->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: if(handle->button_level == handle->active_level) { // 再次按下 handle->event = (uint8_t)PRESS_DOWN; // 设置按钮为按下 EVENT_CB(PRESS_DOWN); // 调用相应回调函数 handle->repeat++; // 连按次数累加 EVENT_CB(PRESS_REPEAT); // repeat hit // 重复按下出发 handle->ticks = 0; // 清空时钟 handle->state = 3; // 状态机设置为3 } else if(handle->ticks > SHORT_TICKS) { // 按键时间超时 if(handle->repeat == 1) { // 如果按键只按了一次 handle->event = (uint8_t)SINGLE_CLICK; // 设置为单次点击 EVENT_CB(SINGLE_CLICK); // 调用单次点击回调函数 } else if(handle->repeat == 2) { // 如果按键按了两次 handle->event = (uint8_t)DOUBLE_CLICK; // 设置为双击状态 EVENT_CB(DOUBLE_CLICK); // repeat hit // 调用双击回调函数 } handle->state = 0; // 状态机设置为0 } break; case 3: if(handle->button_level != handle->active_level) { // 释放按钮 handle->event = (uint8_t)PRESS_UP; // 按键标记为释放状态 EVENT_CB(PRESS_UP); // 调用释放状态回调函数 if(handle->ticks < SHORT_TICKS) { // 时钟如果小于短按时间 handle->ticks = 0; // 时钟清零 handle->state = 2; //repeat press // 状态机设置为2 } else { handle->state = 0; } }else if(handle->ticks > SHORT_TICKS){ // 长按松开 handle->state = 0; } break; case 5: if(handle->button_level == handle->active_level) { //continue hold trigger handle->event = (uint8_t)LONG_PRESS_HOLD; // 长按状态 EVENT_CB(LONG_PRESS_HOLD); // 调用长按回调函数 } else { // 长按松开 handle->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); return;//glacier add 2021-8-18 } else curr = &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); } }
-
头文件
/* * Copyright (c) 2016 Zibin Zheng <znbin@qq.com> * All rights reserved */ #ifndef _MULTI_BUTTON_H_ #define _MULTI_BUTTON_H_ #include "stdint.h" #include "string.h" //According to your need to modify the constants. #define TICKS_INTERVAL 5 //ms // 心跳时钟 #define DEBOUNCE_TICKS 3 //MAX 8 // 防抖次数 #define SHORT_TICKS (300 /TICKS_INTERVAL) // 短按时间 #define LONG_TICKS (1000 /TICKS_INTERVAL) // 长按时间 typedef void (*BtnCallback)(void*); typedef enum { PRESS_DOWN = 0, PRESS_UP, PRESS_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; // GPIO触发电平 uint8_t button_level : 1; // 读取到的按键的状态 0 or 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 #endif