优雅的按键模块—–Multi-button
优雅的按键模块—–Multi-button
在我们日常开发和使用的过程中常常使用了一些按键,利用按键实现不同的功能,比如长按,短按,双击等等。但是每次都是采用标志等等来实现信息的读取,是否有一个优雅的方式来使用按键呢?答案是有的。
## Multi-button
作者的简介是:
MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。
首先来看看头Mul-Button的数据结构
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;
uint8_t button_level : 1;
uint8_t (*hal_button_Level)(void);
BtnCallback cb[number_of_event];
struct Button* next;
}Button;
第一个是枚举类型,像按键按下的类型的枚举
- PRESS_DOWN
按下
- PRESS_UP
按下后弹起
- PRESS_REPEAT
重复按
- SINGLE_CLICK
单击
- DOUBLE_CLICK
双击
- LONG_PRESS_START
长按到一定阈值触发
- LONG_PRESS_HOLD
长按期间一直触发
第二个是按键对下结构体的定义,解释其中重要的
button_level:有效电平
(*hal_button_Level)(void):按键读取函数
BtnCallback cb:按键对应事件的回调函数
大家有没有注意到
uint8_t repeat : 4;
uint8_t event : 4;
uint8_t state : 3;
uint8_t debounce_cnt : 3;
uint8_t active_level : 1;
uint8_t button_level : 1;
这个“:”是结构体中的位域,因为有些后面的数字代表着占据了多少的bit,这个有机会在以后来讲,总之是一个节省内存的方式
接下来看看几个开放出来比较重要的API
/*初始化Button的结构体*/
void button_init(struct Button* handle, uint8_t(*pin_level)(), uint8_t active_level);
/*结构体和对应事件以及其回调函数的绑定*/
void button_attach(struct Button* handle, PressEvent event, BtnCallback cb);
/*开启按键*/
int button_start(struct Button* handle);
/*按键的时钟*/
void button_ticks(void);
具体的用法如下
首先先创建Button对象
/*
申请三个按键对象
*/
struct Button Button_Up;
struct Button Button_OK;
struct Button Button_Down;
为Button添加时基
/*我这里选择STM32上的定时器11*/
void TIM1_TRG_COM_TIM11_IRQHandler(void)
{
/* USER CODE BEGIN TIM1_TRG_COM_TIM11_IRQn 0 */
/* USER CODE END TIM1_TRG_COM_TIM11_IRQn 0 */
HAL_TIM_IRQHandler(&htim11);
/* USER CODE BEGIN TIM1_TRG_COM_TIM11_IRQn 1 */
button_ticks();
HAL_TIM_Base_Start_IT(&htim11);
/* USER CODE END TIM1_TRG_COM_TIM11_IRQn 1 */
}
为Button创建读取电平的函数,设置其有效电平,并启动对象
/*设置读取电平的函数*/
uint8_t read_button_up()
{
return HAL_GPIO_ReadPin(Button_Up_GPIO_Port,Button_Up_Pin);
}
uint8_t read_button_ok()
{
return HAL_GPIO_ReadPin(Button_OK_GPIO_Port,Button_OK_Pin);
}
uint8_t read_button_down()
{
return HAL_GPIO_ReadPin(Button_Down_GPIO_Port,Button_Down_Pin);
}
/*初始化三个对象,并为其绑定读取函数*/
button_init(&Button_Up, read_button_up, 0);
button_init(&Button_OK, read_button_ok, 0);
button_init(&Button_Down, read_button_down, 0);
/*将对象添加到Button链表*/
button_start(&Button_OK);
button_start(&Button_Up);
button_start(&Button_Down);
为对象的对应事件及其对应时间的函数来绑定
/*为了方便,我这里只举例其中Button_OK*/
/*长按事件触发的函数*/
static void Button_ok_long_press_callback(void *btn)
{
/*将当前页的退出标志位置1*/
Page[Page_Tim_ID].Exit_flag = true;
}
/*绑定到Button_OK的长按事件*/
button_attach(&Button_OK, LONG_PRESS_START, Button_ok_long_press_callback);
然后就可以,效果也是非常的好
其中有几个重要参数在头文件里可以根据自己的情况来修改
#define TICKS_INTERVAL 1 //这个是时基的间隔,单位是ms#define SHORT_TICKS (50 /TICKS_INTERVAL)//这个是短按的阈值时间#define LONG_TICKS (500 /TICKS_INTERVAL)//这个是长按的阈值时间
至此multi-Button模块就到这里结束,接下重要的是他的设计思路
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;}
这个是开启Button的函数,显然是采用链表的形式,每次开启对象都将对象加入链表
void button_ticks(){ struct Button* target; for(target=head_handle; target; target=target->next) { button_handler(target); }}
这个是开启button_ticks()的时基函数,每次触发遍历这个Button的链表,然后去将每个对象传入button_handler()
void button_handler(struct Button* handle){ uint8_t read_gpio_level = handle->hal_button_Level(); //ticks counter working.. if((handle->state) > 0) handle->ticks++; /*------------button debounce handle---------------*/ 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) { //start press down handle->event = (uint8_t)PRESS_DOWN; EVENT_CB(PRESS_DOWN); handle->ticks = 0; handle->repeat = 1; handle->state = 1; } else { handle->event = (uint8_t)NONE_PRESS; } break; case 1: if(handle->button_level != handle->active_level) { //released press up handle->event = (uint8_t)PRESS_UP; EVENT_CB(PRESS_UP); handle->ticks = 0; handle->state = 2; } else if(handle->ticks > LONG_TICKS) { handle->event = (uint8_t)LONG_PRESS_START; EVENT_CB(LONG_PRESS_START); handle->state = 5; } break; case 2: if(handle->button_level == handle->active_level) { //press down again handle->event = (uint8_t)PRESS_DOWN; EVENT_CB(PRESS_DOWN); handle->repeat++; EVENT_CB(PRESS_REPEAT); // repeat hit handle->ticks = 0; handle->state = 3; } else if(handle->ticks > SHORT_TICKS) { //released timeout 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; } break; case 3: if(handle->button_level != handle->active_level) { //released press up handle->event = (uint8_t)PRESS_UP; EVENT_CB(PRESS_UP); if(handle->ticks < SHORT_TICKS) { handle->ticks = 0; handle->state = 2; //repeat press } else { handle->state = 0; } }else if(handle->ticks > SHORT_TICKS){ // long press up 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 { //releasd handle->event = (uint8_t)PRESS_UP; EVENT_CB(PRESS_UP); handle->state = 0; //reset } break; }}
这个则是对传入对象进行对应事件的判断,并且触发对应的事件回调函数,设计的整体式一个状态机的思想,有兴趣的可以自己去看看
OK,码字不易,多多点赞!
开源地址