本文用到的实验平台:
- 野火MINI-stm32开发板
- STM32CUBE-IDE开发工具
平时写一些程序的时候如果涉及到一些选择的话,经常需要用到按键什么的,不同的方法写的按键又不一样,有时候需要实现长按短按什么的也不太方便,因此我最近就从github上找了些例程,用起来还是很不错的,下面我对按键的使用进行一些总结,这里要特别感谢开源贡献者。
1、配置io口实现按键功能
野火MINIstm32开发板上的按键如下所示,可以看到这个按键做的还是很好的,加上了电容,电阻也有,可以很好的减缓按键按下时候的毛刺。
同时我们注意到这里按键是被硬件下拉了的,所以我们在stm32的配置中就无需设置上下拉输入了,直接设置为既不上拉也不下拉即可,不过一旦初始化之后就会被外部硬件拉低,所以我们直接读取肯定就是低电平,这个时候按下按键就会变为高电平了!
除此之外比较常用的还有中断的方式,中断其实分的挺细的,有事件中断,外部中断两种,也都是上升沿,下降沿,然后上升沿和下降沿,这样组合就是六种了,我们根据实际需求配置即可!
下面是我配置的框图,下降沿中断,引脚上拉
这里注意如果开启了中断,一定要去NVIC这里去把中断勾上,其他大部分外设都可以直接选,GPIO的比较隐藏,需要自己去勾选,不要忘了,不然中断不起来
中断函数都在it.c的文件中,可以查阅中文参考手册,因为我这个几个引脚都是10-15的,所以公用一根中断线
一般我们写中断后的事情也就是在中断回调函数里面来写了
2、第一种按键状态机及其实现
按键状态机使得我们可以傻瓜式的使用按键,对按键绑定事件,触发情况等,方便按键触发,下面的这种支持单击,连续按下,短按,长按等功能,具体使用如下:
绑定按键对象,并将按键处理函数放入循环中
编写回调函数,他是所有的按键都给了回调函数,所以就是在回调函数里面来选按键
回调函数代码如下
Button btn_k1,btn_k2;
#define BUTTON_KEY1_ID 1
#define BUTTON_KEY2_ID 2
//void button_pressing_callback(uint8_t button_id)
//{
// switch(button_id)
// {
// case BUTTON_KEY1_ID:
// printf("KEY1被按下\r\n");
// break;
// case BUTTON_KEY2_ID:
// printf("KEY2被按下\r\n");
// break;
// }
//}
//void button_release_callback(uint8_t button_id)
//{
// switch(button_id)
// {
// case BUTTON_KEY1_ID:
// printf("KEY1被松开\r\n");
// break;
// case BUTTON_KEY2_ID:
// printf("KEY2被松开\r\n");
// break;
// }
//}
void button_press_short_callback(uint8_t button_id)
{
// switch(button_id)
// {
// case BUTTON_KEY1_ID:
// printf("KEY1短按\r\n");
// break;
// case BUTTON_KEY2_ID:
// printf("KEY2短按\r\n");
// break;
// }
}
void button_pressing_timeout_callback(uint8_t button_id)
{
// switch(button_id)
// {
// case BUTTON_KEY1_ID:
// printf("KEY1长按\r\n");
// break;
// case BUTTON_KEY2_ID:
// printf("KEY2长按\r\n");
// break;
// }
}
void button_pressing_accel_callback(uint8_t button_id) //就感觉这个也有点像长按,基本功能是一样的
{
switch(button_id)
{
case BUTTON_KEY1_ID:
printf("KEY1快速\r\n");
break;
case BUTTON_KEY2_ID:
printf("KEY2快速\r\n");
break;
}
}
代码全文如下
button.c
#include "button.h"
__weak void button_pressing_callback(uint8_t button_id)
{
}
__weak void button_release_callback(uint8_t button_id)
{
}
__weak void button_press_short_callback(uint8_t button_id)
{
}
__weak void button_pressing_timeout_callback(uint8_t button_id)
{
}
__weak void button_pressing_accel_callback(uint8_t button_id)
{
}
void handle_button(Button *btn)
{
btn->current_status = HAL_GPIO_ReadPin(btn->gpio_port,btn->gpio_pin);
if(btn->button_active == BUTTON_ACTIVE_HIGH)
{
btn->current_status = !btn->current_status;
}
switch(btn->button_state)
{
case BUTTON_READ:
{
if((btn->current_status == 0 && btn->last_status == 1) )
{
btn->time_debounce = HAL_GetTick();
btn->button_state = BUTTON_WAIT_DEBOUND;
}
}
break;
case BUTTON_WAIT_DEBOUND:
{
if(HAL_GetTick() - btn->time_debounce>= TIME_DEBOUND_BUTTON)
{
if(btn->current_status ==0 && btn->last_status ==1)//nhan xuong
{
button_pressing_callback(btn->button_id);
btn->t_long_press = HAL_GetTick();
btn->last_status = 0;
btn->t_accel_press = HAL_GetTick();
btn->t_accel_call = TIME_ACCEL_MAX;
btn->button_state = BUTTON_WAIT_RELEASE_AND_CHECK_LONG_PRESS;
}
else if(btn->current_status ==1 && btn->last_status ==0)//nha ra
{
btn->t_long_press = HAL_GetTick() - btn->t_long_press;
if(btn->t_long_press <= TIME_SHORT_PRESS)
{
button_press_short_callback(btn->button_id);
}
button_release_callback(btn->button_id);
btn->last_status = 1;
btn->button_state = BUTTON_READ;
}
else //khong dung
{
btn->last_status = 1;
btn->button_state = BUTTON_READ;
}
}
}
break;
case BUTTON_WAIT_RELEASE_AND_CHECK_LONG_PRESS:
{
if(btn->current_status == 1 && btn->last_status == 0)
{
btn->button_state = BUTTON_WAIT_DEBOUND;
btn->time_debounce = HAL_GetTick();
}
else if(HAL_GetTick() - btn->t_long_press >= TIME_LONG_PRESS)
{
button_pressing_timeout_callback(btn->button_id);
btn->button_state = BUTTON_WAIT_RELEASE;
}
else if(HAL_GetTick() - btn->t_accel_press >= btn->t_accel_call)
{
btn->t_accel_call -=TIME_ACCEL_DELTA;
if(btn->t_accel_call <= TIME_ACCEL_MIN)
{
btn->t_accel_call = TIME_ACCEL_MIN;
}
button_pressing_accel_callback(btn->button_id);
btn->t_accel_press = HAL_GetTick();
}
}
break;
case BUTTON_WAIT_RELEASE:
{
if(btn->current_status == 1 && btn->last_status == 0)
{
btn->button_state = BUTTON_WAIT_DEBOUND;
btn->time_debounce = HAL_GetTick();
}
else if(HAL_GetTick() - btn->t_accel_press >= btn->t_accel_call)
{
btn->t_accel_call -=TIME_ACCEL_DELTA;
if(btn->t_accel_call <= TIME_ACCEL_MIN)
{
btn->t_accel_call = TIME_ACCEL_MIN;
}
button_pressing_accel_callback(btn->button_id);
btn->t_accel_press = HAL_GetTick();
}
}
break;
default:
break;
}
}
//button_active: BUTTON_ACTIVE_LOW, BUTTON_ACTIVE_HIGH
void Button_init(Button *btn,GPIO_TypeDef *port,uint16_t pin,uint8_t button_active,uint8_t button_id)
{
btn->gpio_port = port;
btn->gpio_pin = pin;
btn->button_active = button_active;
btn->button_state = BUTTON_READ;
btn->button_id = button_id;
btn->current_status = 1;
btn->last_status = 1;
}
button.h文件
#include "main.h"
#define TIME_DEBOUND_BUTTON 20
#define TIME_SHORT_PRESS 1000
#define TIME_LONG_PRESS 2000
#define TIME_ACCEL_MIN 100
#define TIME_ACCEL_MAX 1000
#define TIME_ACCEL_DELTA 200
#define BUTTON_ACTIVE_LOW 0
#define BUTTON_ACTIVE_HIGH 1
typedef enum
{
BUTTON_READ,
BUTTON_WAIT_DEBOUND,
BUTTON_WAIT_RELEASE_AND_CHECK_LONG_PRESS,
BUTTON_WAIT_RELEASE
}Button_State;
typedef struct
{
GPIO_TypeDef *gpio_port;
uint16_t gpio_pin;
uint8_t current_status;
uint8_t last_status;
uint32_t time_debounce;
uint32_t t_long_press;
Button_State button_state;
uint8_t button_active;
uint8_t button_id;
int16_t t_accel_call;
uint32_t t_accel_press;
}Button;
void handle_button(Button *btn);
void Button_init(Button *btn,GPIO_TypeDef *port,uint16_t pin,uint8_t button_active,uint8_t button_id);
3、第二种按键状态机及其实现
第二种方式我觉得就更加强大了,不仅支持短按长按,还有双击,连续按下的次数等,用起来也很方便
首先注册按键并设置按键模式,他这个就不是对所有按键都统一了,要看你设置的模式,有没有加入相关功能,之后就是编写回调函数了
在初始化部分我们初始化按键对象,之后这个按键需要提供一个5ms的心跳,使用while循环或者定时器都是可以的,这里偷懒直接使用while循环
把结果通过串口打印,可以看到还是很正常的!!!
第二种按键状态机的代码如下
button02.c
#include "Button02.h"
#define EVENT_CB(ev) \
if (handle->cb[ev]) \
handle->cb[ev]((Button_t)handle,(PressEvent)handle->event,(uint8_t)handle->repeat)
typedef void (*BtnCallback)(void*, PressEvent, uint8_t);
typedef struct {
uint16_t ticks;
uint8_t repeat :4; // max 15
uint8_t event :4;
uint8_t state :3;
uint8_t debounce_cnt :3;
uint8_t active_level :1;
uint8_t button_level :1;
GPIO_TypeDef *GPIOx;
uint16_t GPIO_PIN_x;
BtnCallback cb[EVENT_NUM];
struct Button *next;
} Button;
// button handle list head.
static Button *head_handle = NULL;
/**
初始化按键结构体
*/
Button_t button_init(const Btn_init_attr *attr) {
// allocate memory.
Button *thisHandle = (Button*) malloc(sizeof(Button));
if (!thisHandle) {
return NULL; // allocate memory failed.
}
memset(thisHandle, 0, sizeof(Button));
thisHandle->event = (uint8_t) NONE_PRESS;
thisHandle->GPIOx = attr->GPIOx;
thisHandle->GPIO_PIN_x = attr->GPIO_PIN_x;
thisHandle->active_level = (uint8_t) attr->active_level;
thisHandle->button_level = !thisHandle->active_level;
// Attach the button event callback function
for (uint8_t i = 0; i < attr->event_num; i++) {
thisHandle->cb[attr->event[i]] =
(void (*)(void*, PressEvent, uint8_t)) button_callback;
}
// start button
thisHandle->next = head_handle;
head_handle = thisHandle;
return (Button_t) thisHandle; // success
}
/**
按键驱动状态机
*/
static void button_handler(Button *handle) {
uint8_t read_gpio_level = (uint8_t) HAL_GPIO_ReadPin(handle->GPIOx,
handle->GPIO_PIN_x);
// 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++;
handle->event = (uint8_t) PRESS_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) {
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 { // released
handle->event = (uint8_t) PRESS_UP;
EVENT_CB(PRESS_UP);
handle->state = 0; // reset
}
break;
}
}
/**
移除按键实例化对象
*/
void button_deInit(Button_t handle) {
Button **curr;
for (curr = &head_handle; *curr;) {
Button *entry = *curr;
if (entry == (Button*) handle) {
*curr = entry->next;
free(entry);
} else
curr = &entry->next;
}
}
/**
定时器重复5ms进行
*/
void button_ticks() {
Button *target;
for (target = head_handle; target; target = target->next) {
button_handler(target);
}
}
/**
回调函数,输入实例化按键,事件,次数
*/
void __attribute__((weak)) button_callback(Button_t btn, PressEvent event,
uint8_t repeat) {
// nothing to do,please rewrite.
}
button02.h
#include "main.h"
#define TIME_DEBOUND_BUTTON 20
#define TIME_SHORT_PRESS 1000
#define TIME_LONG_PRESS 2000
#define TIME_ACCEL_MIN 100
#define TIME_ACCEL_MAX 1000
#define TIME_ACCEL_DELTA 200
#define BUTTON_ACTIVE_LOW 0
#define BUTTON_ACTIVE_HIGH 1
typedef enum
{
BUTTON_READ,
BUTTON_WAIT_DEBOUND,
BUTTON_WAIT_RELEASE_AND_CHECK_LONG_PRESS,
BUTTON_WAIT_RELEASE
}Button_State;
typedef struct
{
GPIO_TypeDef *gpio_port;
uint16_t gpio_pin;
uint8_t current_status;
uint8_t last_status;
uint32_t time_debounce;
uint32_t t_long_press;
Button_State button_state;
uint8_t button_active;
uint8_t button_id;
int16_t t_accel_call;
uint32_t t_accel_press;
}Button;
void handle_button(Button *btn);
void Button_init(Button *btn,GPIO_TypeDef *port,uint16_t pin,uint8_t button_active,uint8_t button_id);