優雅的按鍵模塊-----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,碼字不易,多多點贊!
開源地址
