1. Project description
A small and easy-to-use event-driven button driver module, which can expand buttons infinitely. The asynchronous processing method of callback of button events can simplify the program structure, remove redundant button processing hard codes, and make button business logic clearer.
The GitHub source code address is as follows:
Github source code address
2. Code porting
The development board used in this article is Punctual Atomic Explorer F407. First, use STM32CubMx to initialize peripheral information. Requirements: 1. Initialize any one of the key input pins PE2, PE3, and PE4, and 2. Serial port printing function.
If you are unfamiliar, you can refer to the previous blog; the
button
serial port
initialization configuration interface is as follows:
Transplant the code project and add the MultiButton code to the new project file directory: as shown in the
figure:
Write button callback function:
uint8_t read_button1_GPIO()
{
return HAL_GPIO_ReadPin(K1_GPIO_Port,K1_Pin);
}
void btn1_press_down_Handler(void *btn)
{
printf("K1 press down...\r\n");
HAL_GPIO_TogglePin(L1_GPIO_Port,L1_Pin);
}
void btn1_press_up_Handler(void *btn)
{
printf("K1 press up...\r\n");
HAL_GPIO_TogglePin(L0_GPIO_Port,L0_Pin);
}
Add button initialization and start button function before while(1):
printf("MultiButton Test...\r\n");
button_init(&button1,read_button1_GPIO,0);
button_attach(&button1,PRESS_DOWN,btn1_press_down_Handler);
button_attach(&button1,PRESS_UP,btn1_press_up_Handler);
button_start(&button1);
printf("MultiButton Test...\r\n");
Add the following code to while(1)
while (1)
{
button_ticks();
HAL_Delay(5);
}
Experimental phenomenon:
Press button K1, LED0 flips, lift K1 LED1 flips.
MultiButton source code analysis
First, the time type of the key is defined, including events such as key press, lift, single click, repeated key press, double-click, long-press single trigger, and long-press always trigger.
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;
Define the key list structure, where the bit field operation is used to solve the storage space of bytes.
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;
The initialization of the button object structure, the initialization members include the button handle, bind the GPIO level read function, and set the effective trigger level
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;
}
After the initialization button is completed, the button binding operation will be performed, and the button structure members will be bound, the button trigger event, and the button callback function.
void button_attach(struct Button* handle, PressEvent event, BtnCallback cb)
{
handle->cb[event] = cb;
}
Button start: that is, adding the button to the linked list and starting the button. The insertion method selected here is the head insertion method, which inserts the key node at the head of the linked list. High efficiency, time complexity is O(1).
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;
}
Press delete to delete the key from the current linked list. Deletes a key element using a secondary pointer. Same as the previous multi-timer deletion method.
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;
}
}
The button tick function triggers a button event every 5ms to drive the state machine to run.
void button_ticks()
{
struct Button* target;
//按键中断依次循环读取链表中所有的按键元素信息,并且驱动状态机工作
for(target=head_handle; target; target=target->next) {
button_handler(target);
}
}
Soul of multi-button design:
State machine processing idea: read the state of the current pin and get which state the button currently belongs to.
PressEvent get_button_event(struct Button* handle)
{
return (PressEvent)(handle->event);
}
The state machine, the key processing core function, drives the state machine.
/**
* @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++;
/*------------button debounce handle---------------*/
/*读取句柄引脚电平,连续读取3次引脚电平实现按键消抖功能*/
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;
}
}
State Machine Flowchart