multi_button

涉及内容

  • C语言
  • 单链表
  • 状态机
  • 回调函数

快速上手

  • 准备

    • 源码:multi_button.hmulti_button.c
    • 用户编写:main.cbsp_key.hbsp_key.c

    bsp_key.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #ifndef __BSP_KEY_H
    #define __BSP_KEY_H

    #include "multi_button.h"
    #include <stdio.h>

    void GPIO_KEY_Init(void);//IO初始化
    void MultiButton_Init(void);

    void BTN1_PRESS_DOWN_Handler(void* btn);
    void BTN1_PRESS_UP_Handler(void* btn);
    //...

    #endif

    bsp_key.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #include "bsp_key.h"

    struct Button btn1;
    struct Button btn2;

    //IO初始化
    void GPIO_KEY_Init(void)
    {


    }

    uint8_t read_button1_GPIO()
    {
    return HAL_GPIO_ReadPin(xxx1_GPIO_Port, xxx1_Pin);
    }

    uint8_t read_button2_GPIO()
    {
    return HAL_GPIO_ReadPin(xxx2_GPIO_Port, xxx2_Pin);
    }

    void MultiButton_Init(void)
    {
    //按键初始化
    button_init(&btn1, read_button1_GPIO, 0);//0表示按键按下时,GPIO为低电平
    button_init(&btn2, read_button2_GPIO, 0);

    //按键注册 事件注册
    button_attach(&btn1, PRESS_DOWN, BTN1_PRESS_DOWN_Handler);
    button_attach(&btn1, PRESS_UP, BTN1_PRESS_UP_Handler);
    button_attach(&btn1, PRESS_REPEAT, BTN1_PRESS_REPEAT_Handler);
    button_attach(&btn1, SINGLE_CLICK, BTN1_SINGLE_Click_Handler);
    button_attach(&btn1, DOUBLE_CLICK, BTN1_DOUBLE_Click_Handler);
    button_attach(&btn1, LONG_PRESS_START, BTN1_LONG_PRESS_START_Handler);
    button_attach(&btn1, LONG_PRESS_HOLD, BTN1_LONG_PRESS_HOLD_Handler);

    button_attach(&btn2, PRESS_DOWN, BTN2_PRESS_DOWN_Handler);
    button_attach(&btn2, PRESS_UP, BTN2_PRESS_UP_Handler);
    button_attach(&btn2, PRESS_REPEAT, BTN2_PRESS_REPEAT_Handler);
    button_attach(&btn2, SINGLE_CLICK, BTN2_SINGLE_Click_Handler);
    button_attach(&btn2, DOUBLE_CLICK, BTN2_DOUBLE_Click_Handler);
    button_attach(&btn2, LONG_PRESS_START, BTN2_LONG_PRESS_START_Handler);
    button_attach(&btn2, LONG_PRESS_HOLD, BTN2_LONG_PRESS_HOLD_Handler);

    //按键开始
    button_start(&btn1);
    button_start(&btn2);
    }

    //在此回调函数内执行相应操作
    void BTN1_PRESS_DOWN_Handler(void* btn)
    {
    //do something...
    }

    void BTN1_PRESS_UP_Handler(void* btn)
    {
    //do something...
    }

    //...

    main.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include "bsp_key.h"

    int main()
    {
    //初始化
    GPIO_KEY_Init();
    MultiButton_Init();

    while(1)
    {
    /**** 每隔5ms 按键扫描 *****/
    //用定时器中断,做一个5ms的定时器,或者用systick
    if(g_b_5ms_flag)
    {
    g_b_5ms_flag=0; //清除
    //扫描函数
    button_ticks();
    }
    }

    }

代码思路

  • 为按键创建一个单链表,分别挂载:按键1、按键2……;
    • 每个按键的结构体中包含其属性、回调函数和指向下一个按键的指针
    • 按键属性采用位域的方式,减小内存占用
    • 按键中回调函数有检测输入电平的函数,也有要执行具体按下类型的回调函数,以数组的形式存储。
    • 通过包含指向下一个按键的指针,构建一个链表。对于按键的操作也即是对单链表的操作。
  • 每5ms对整个链表进行遍历,遍历到某一按键,例如按键2,对其进行处理,包括状态机(哪种类型的按下:单击、双击、释放、长按、短按等)
    • 当发现按键2被按下时,先消抖,然后状态机判断并执行相应的回调函数,最后各种状态值恢复,继续遍历。
  • 状态机
  • 回调函数具体实现-留给用户写

代码解释

1
2
3
4
5
6
7
//基本单位(时间):5ms为1单位 TICKS_INTERVAL
#define TICKS_INTERVAL 5 //ms
//消抖时长:3个单位,最大为8个单位(3*5ms=15ms,消抖15ms)
#define DEBOUNCE_TICKS 3 //MAX 8
//时长判断标准:小于300个单位的为短,大于1000个单位的为长
#define SHORT_TICKS (300 /TICKS_INTERVAL)
#define LONG_TICKS (1000 /TICKS_INTERVAL)
1
2
3
4
5
6
7
8
9
10
11
12
//按键按下的类型
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
//按键结构体
typedef struct Button {
uint16_t ticks;//时长计数
uint8_t repeat : 4;//判断单击or双击
uint8_t event : 4;//按键按下的类型
uint8_t state : 3;//状态机
uint8_t debounce_cnt : 3;//消抖次数
uint8_t active_level : 1;//按键按下时,是高电平or低电平
uint8_t button_level : 1;//检测GPIO电平,与active_level比对
uint8_t (*hal_button_Level)(void);//检测GPIO电平的回调函数,参数为void,可以设为uint8_t button_id
BtnCallback cb[number_of_event];//执行按键按下事件的回调函数,数组形式的回调函数,表示含有多个回调函数
struct Button* next;//指向下一个按键
}Button;

源码附录

multi_button.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
* 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;
uint8_t button_level : 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

multi_button.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
* 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++;

/*------------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;
}
}
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;
}
}

/**
* @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);
} 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);
}
}