很多人用按键处理时,只会单击(短按)和长按这两种,这是非常不正确的想法,也不能展示出个人的实力!!!
真正的按键高手可以使用按键处理算法来实现 单击,双击,三击,长按,2个按键组合新按键。这是非常有用的。
向你们平时使用的电脑按键,是不是有个组合按键,按下Fn + F1
进行电脑快捷按键的开闭,其实MCU也可以使用组合按键,主要是你们的思想已经封闭了,弄不出来!!!!
本文就是我个人自创的按键最齐全的算法,实现了按键的所有功能,包括单击,双击,三击,短按,长按,组合按键。你想要的都有!!!!
好了,进入正题!!!
1.打开原理图
可以看到,KE1和KE2分别对应PA0和PA1
2.核心代码如下
#include "ebtn_app.h"
/* -------------------------------- 初始化和处理函数 -------------------------------- */
/** ***************************************************************************
* @brief easy_button初始化,在主函数中调用
* @NOTE 如果使用组合键,则需要在ebtn_init之后调用ebtn_combo_btn_add_btn添加组合键
*/
void ebtn_APP_Key_INIT(void)
{
// 初始化easy_button库
ebtn_init(btn_array, btn_array_size,
btn_combo_array, btn_combo_array_size,
ebtn_Get_State, ebtn_Event_Handler);
/* ---------------------------- 此处向组合键结构体数组静态添加按键 --------------------------- */
// 结构体数组索引值与ebtn_custom_config.c中组合键结构体数组btn_combo_array中的索引值一致
// 示例:四个组合键
// 为组合键1添加按键KEY_1和KEY_2
ebtn_combo_btn_add_btn(&btn_combo_array[0], KEY_1);
ebtn_combo_btn_add_btn(&btn_combo_array[0], KEY_2);
// // 为组合键2添加按键KEY_3和KEY_4
// ebtn_combo_btn_add_btn(&btn_combo_array[1], KEY_3);
// ebtn_combo_btn_add_btn(&btn_combo_array[1], KEY_4);
// // 为组合键3添加按键KEY_1和KEY_3
// ebtn_combo_btn_add_btn(&btn_combo_array[2], KEY_1);
// ebtn_combo_btn_add_btn(&btn_combo_array[2], KEY_3);
// // 为组合键4添加按键KEY_2和KEY_4
// ebtn_combo_btn_add_btn(&btn_combo_array[3], KEY_2);
// ebtn_combo_btn_add_btn(&btn_combo_array[3], KEY_4);
}
/* --------------------------------- 自定义配置部分结束 -------------------------------- */
/** ***************************************************************************
* @brief 处理按键事件,需要定期调用,建议以20ms为周期执行一次
* @note Tick时基为1ms
*/
void ebtn_APP_Key_Process(void)
{
ebtn_process(ebtn_custom_hal.Get_Tick()); // 获取时间处理按键事件
}
/* -------------------------------- 辅助部分 ------------------------------- */
/** ***************************************************************************
* @brief 检查按键是否激活
* @param key_id: 按键ID
* @return uint8_t: 1-激活,0-未激活
*/
uint8_t ebtn_APP_Is_Key_Active(uint16_t key_id)
{
ebtn_btn_t *btn = ebtn_get_btn_by_key_id(key_id);
return ebtn_is_btn_active(btn);
}
/** ***************************************************************************
* @brief 检查是否有按键处于处理中
* @return uint8_t: 1-有按键处理中,0-无按键处理中
*/
uint8_t ebtn_APP_Is_Any_Key_In_Process(void)
{
return ebtn_is_in_process();
}
/** ***************************************************************************
* @brief 获取指定按键的按键状态
* @param key_id: 按键ID
* @return uint8_t: 1-按下,0-松开
*/
uint8_t ebtn_APP_Get_Key_State(uint16_t key_id)
{
ebtn_btn_t *btn = ebtn_get_btn_by_key_id(key_id);
if (btn == NULL)
{
return 0;
}
return ebtn_Get_State(btn);
}
#ifndef EBTN_APP_H
#define EBTN_APP_H
#include "ebtn.h"
#include "ebtn_app.h"
#include "ebtn_custom_callback.h"
#include "ebtn_custom_hal.h"
#include "ebtn_custom_config.h"
void ebtn_APP_Key_INIT(void);
void ebtn_APP_Key_Process(void);
uint8_t ebtn_APP_Is_Key_Active(uint16_t key_id);
uint8_t ebtn_APP_Is_Any_Key_In_Process(void);
uint8_t ebtn_APP_Get_Key_State(uint16_t key_id);
#endif /* EBTN_APP_H */
#include "ebtn_custom_callback.h"
volatile uint8_t key_count = 0;
/* ---------------------------- 此函数中可自定义按键状态检测方式 ---------------------------- */
/** ***************************************************************************
* @brief 获取按键状态回调函数
* 此函数默认采用了查表检测,免去需要手动为每个按键添加检测方式
* 也可为特殊按键自定义检测方法
* @note 查表检测需要配合ebtn_custom_config.c中的按键配置结构体数组使用
* @param btn: easy_button按键结构体指针
* @return 按键状态,0表示未按下,1表示按下
*/
uint8_t ebtn_Get_State(struct ebtn_btn *btn)
{
// 查表法检测
for (int i = 0; i < key_list_size; i++)
{
if (key_list.key_id == btn->key_id)
{
uint8_t pin_state = ebtn_custom_hal.Read_Pin(key_list.gpio_port, key_list.gpio_pin);
// 根据有效电平转换
if (key_list.active_level == pin_state)
{
pin_state = 1;
}
else
{
pin_state = 0;
}
return pin_state;
}
}
/* ----------------------------- 此处可自定义按键状态获取方式 ----------------------------- */
// 可自定义特殊按键的检测方式,如矩阵按键的检测
return 0; // 未找到按键ID,返回0
}
/* ------------------------------- 此函数可修改按键触发事件 ------------------------------- */
/** ***************************************************************************
* @brief 按键事件处理回调函数,在此定义按键触发事件
* 推荐将不同按键或事件的处理逻辑拆分为独立的函数,并参考原有的 switch(ebtn->key) 结构,
* 在对应按键编号的 case 分支中调用相应的处理函数,以提升代码的可读性和可维护性
* @param btn: easy_button按键结构体指针
* @param evt: 事件类型
*/
void ebtn_Event_Handler(struct ebtn_btn *btn, ebtn_evt_t evt)
{
switch (btn->key_id) // 按键ID
{
/* ---------------------------------- KEY1 ---------------------------------- */
case KEY_1:
/* ---------------------------------- 按下按键时 --------------------------------- */
if (evt == EBTN_EVT_ONPRESS)
{
}
/* ---------------------------------- 松开按键时 --------------------------------- */
else if (evt == EBTN_EVT_ONRELEASE)
{
}
/* ----------------------------- 短按按键时(可获取连击次数) ----------------------------- */
else if (evt == EBTN_EVT_ONCLICK)
{
/* ----------------------------------- 单击时 ---------------------------------- */
if (btn->click_cnt == 1)
{
printf("KEY_1 单击\r\n");
key_count++;
if(key_count>=7)key_count=0;
}
/* ----------------------------------- 双击时 ---------------------------------- */
else if (btn->click_cnt == 2)
{
printf("KEY_1 双击\r\n");
key_count += 2;
if(key_count>=7)key_count=0;
}
/* ----------------------------------- 三击时 ---------------------------------- */
else if (btn->click_cnt == 3)
{
printf("KEY_1 三击\r\n");
key_count += 3;
if(key_count>=7)key_count=0;
}
}
/* ------------------------- 长按达到最短时间(配置默认600ms),触发长按计数时 ------------------------ */
else if (evt == EBTN_EVT_KEEPALIVE)
{
/* ------------------------------- 长按计数到达指定值时 ------------------------------- */
if (btn->keepalive_cnt == 1)
{
key_count++;if(key_count>=7)key_count=0;
printf("KEY_1 长按\r\n");
}
}
break;
/* ----------------------------------- KEY2 ---------------------------------- */
case KEY_2:
/* ---------------------------------- 按下按键时 --------------------------------- */
if (evt == EBTN_EVT_ONPRESS)
{
}
/* ---------------------------------- 松开按键时 --------------------------------- */
else if (evt == EBTN_EVT_ONRELEASE)
{
}
/* ----------------------------- 短按按键时(可获取连击次数) ----------------------------- */
else if (evt == EBTN_EVT_ONCLICK)
{
/* ----------------------------------- 单击时 ---------------------------------- */
if (btn->click_cnt == 1)
{
printf("KEY_2 单击\r\n");
key_count--;
if(key_count==0)key_count=0;
}
/* ----------------------------------- 双击时 ---------------------------------- */
else if (btn->click_cnt == 2)
{
printf("KEY_2 双击\r\n");
key_count -= 2;
if(key_count==0)key_count=0;
}
/* ----------------------------------- 三击时 ---------------------------------- */
else if (btn->click_cnt == 3)
{
printf("KEY_2 三击\r\n");
key_count -= 3;
if(key_count==0)key_count=0;
}
}
/* ------------------------- 长按到达最最短时间(默认600ms),触发长按计数时 ------------------------ */
else if (evt == EBTN_EVT_KEEPALIVE)
{
/* ------------------------------- 长按计数到达指定值时 ------------------------------- */
if (btn->keepalive_cnt == 1)
{
printf("KEY_2 长按\r\n");
key_count--;
if(key_count==0)key_count=0;
}
}
break;
/* ----------------------------------- 组合键1 ---------------------------------- */
case COMBO_KEY_1:
/* ---------------------------------- 按下按键时 --------------------------------- */
if (evt == EBTN_EVT_ONPRESS)
{
printf("按组合按键KEY1 KEY2 \r\n");
}
/* ---------------------------------- 松开按键时 --------------------------------- */
else if (evt == EBTN_EVT_ONRELEASE)
{
//printf("松开KEY_1 和 KEY_2 组合按键\r\n");
}
break;
/* ---------------------------------- 组合键2 ---------------------------------- */
case COMBO_KEY_2:
/* ---------------------------------- 按下按键时 --------------------------------- */
if (evt == EBTN_EVT_ONPRESS)
{
}
/* ---------------------------------- 松开按键时 --------------------------------- */
else if (evt == EBTN_EVT_ONRELEASE)
{
}
break;
/* ---------------------------------- 组合键3 ---------------------------------- */
case COMBO_KEY_3:
/* ---------------------------------- 按下按键时 --------------------------------- */
if (evt == EBTN_EVT_ONPRESS)
{
}
/* ---------------------------------- 松开按键时 --------------------------------- */
else if (evt == EBTN_EVT_ONRELEASE)
{
}
break;
/* ---------------------------------- 组合键4 ---------------------------------- */
case COMBO_KEY_4:
/* ---------------------------------- 按下按键时 --------------------------------- */
if (evt == EBTN_EVT_ONPRESS)
{
}
/* ---------------------------------- 松开按键时 --------------------------------- */
else if (evt == EBTN_EVT_ONRELEASE)
{
}
break;
}
}
#ifndef EBTN_CUSTOM_CALLBACK_H
#define EBTN_CUSTOM_CALLBACK_H
#include "ebtn_app.h"
#include "ebtn_custom_hal.h"
#include "ebtn_custom_config.h"
/* -------------------------- 此处添加所需的按键触发事件的函数声明的头文件 ------------------------- */
extern volatile uint8_t key_count;
/* -------------------------------- 自定义配置部分结束 ------------------------------- */
uint8_t ebtn_Get_State(struct ebtn_btn *btn);
void ebtn_Event_Handler(struct ebtn_btn *btn, ebtn_evt_t evt);
#endif
#include "ebtn_custom_config.h"
/** ***************************************************************************
* @brief 定义按键参数结构体
* @ref ebtn_Custom_x.h
*/
ebtn_btn_param_t buttons_parameters = EBTN_PARAMS_INIT(
DEBOUNCE_TIME, // 按下防抖超时
RELEASE_DEBOUNCE_TIME, // 松开防抖超时
CLICK_AND_PRESS_MIN_TIME, // 按键最短时间
CLICK_AND_PRESS_MAX_TIME, // 按键最长时间
MULTI_CLICK_MAX_TIME, // 连续点击最大间隔(ms)
KEEPALIVE_TIME_PERIOD, // 长按报告事件间隔(ms)
MAX_CLICK_COUNT // 最大连续点击次数
);
/* --------------------------------- 此处修改按键 --------------------------------- */
// 所需的按键ID定义在ebtn_custom_config.h中增添
/** ***************************************************************************
* @brief 按键列表结构体数组,用于将按键ID与GPIO引脚以及触发电平进行绑定,
* 同时使用查表检测,免去需要为每个按键手动添加检测方式
* @note 此处填入所需的按键ID及其GPIO信息和触发时电平
*/
ebtn_custom_config_key_list_t key_list[] = {
// 示例:4个按键
{KEY_1, GPIOA, GPIO_PIN_0, 0}, // KEY_1按键,PA4,低电平有效
{KEY_2, GPIOA, GPIO_PIN_1, 0}, // KEY_2按键,PB13,低电平有效
//{KEY_3, GPIOA, GPIO_PIN_1, 0}, // KEY_3按键,PB1,低电平有效
//{KEY_4, GPIOA, GPIO_PIN_0, 0}, // KEY_4按键,PB12,低电平有效
};
/** ***************************************************************************
* @brief 按键结构体数组,用于将按键ID与按键参数进行绑定
* @note 此为静态注册方法,动态注册详见easy_button库Github仓库
*/
ebtn_btn_t btn_array[] = {
EBTN_BUTTON_INIT(KEY_1, &buttons_parameters),
EBTN_BUTTON_INIT(KEY_2, &buttons_parameters),
//EBTN_BUTTON_INIT(KEY_3, &buttons_parameters),
//EBTN_BUTTON_INIT(KEY_4, &buttons_parameters),
};
/** ***************************************************************************
* @brief 组合键结构体数组,用于将多个按键组合成一个按键进行事件判断
* @note 此为静态注册方法,动态注册详见easy_button库Github仓库
* @note 需要在ebtn_app.c的ebtn_APP_Key_INIT函数中使用ebtn_Combo_btn_add_btn向组合键结构体数组添加对应按键
* 其中结构体数组索引与此处结构体数组的索引顺序一致,详见ebtn_app.c
*/
ebtn_btn_combo_t btn_combo_array[] = {
EBTN_BUTTON_COMBO_INIT(COMBO_KEY_1, &buttons_parameters),
EBTN_BUTTON_COMBO_INIT(COMBO_KEY_2, &buttons_parameters),
//EBTN_BUTTON_COMBO_INIT(COMBO_KEY_3, &buttons_parameters),
//EBTN_BUTTON_COMBO_INIT(COMBO_KEY_4, &buttons_parameters),
};
/* -------------------------------- 自定义配置部分结束 ------------------------------- */
const uint8_t btn_array_size = EBTN_ARRAY_SIZE(btn_array);
const uint8_t btn_combo_array_size = EBTN_ARRAY_SIZE(btn_combo_array);
const uint8_t key_list_size = EBTN_ARRAY_SIZE(key_list);
#ifndef EBTN_CUSTOM_CONFIG_H
#define EBTN_CUSTOM_CONFIG_H
#include "ebtn.h"
#include "ebtn_custom_hal.h"
/* -------------------------------- 此处修改按键参数定义 -------------------------------- */
#define DEBOUNCE_TIME 20 // 防抖处理,按下防抖超时,配置为0,代表不启用
#define RELEASE_DEBOUNCE_TIME 20 // 防抖处理,松开防抖超时,配置为0,代表不启用
#define CLICK_AND_PRESS_MIN_TIME 20 // 按键超时处理,触发最短时间,小于该时间则不触发Click和Press,配置为0,代表不检查最小值。用于防误触
#define CLICK_AND_PRESS_MAX_TIME 200 // 按键超时处理,短按最长时间,配置为0xFFFF,代表不检查最大值。用于区分长按和短按事件,大于该时间则不触发Click而触发Press
#define MULTI_CLICK_MAX_TIME 200 // 多击处理超时结算,两个按键之间的时间是连击的超时时间(ms)
#define KEEPALIVE_TIME_PERIOD 600 // 长按处理,长按周期,每个周期增加keepalive_cnt计数(ms)
#define MAX_CLICK_COUNT 3 // 最大连续短击次数,配置为0,代表不进行连击检查
/* -------------------------------- 此处修改按键ID定义 -------------------------------- */
/** ***************************************************************************
* @brief 按键ID枚举
* @note 此处定义按键ID
*/
typedef enum
{
// 示例:4个按键和四个组合键,包含最大按键,用于提供按键数量
KEY_1,
KEY_2,
KEY_3,
KEY_4,
MAX_KEY, // 最大按键,用于提供按键数量
COMBO_KEY_1,
COMBO_KEY_2,
COMBO_KEY_3,
COMBO_KEY_4,
MAX_COMBO_KEY // 最大组合键,用于提供组合键数量
} ebtn_custom_config_key_enum_t;
/* ---------------------------------- 自定义配置部分结束 ---------------------------------- */
/** ***************************************************************************
* @brief 自定义按键配置结构体宏定义
*/
typedef struct
{
uint16_t key_id; // 按键按键ID
GPIO_T* gpio_port; // GPIO端口
uint16_t gpio_pin; // GPIO引脚
uint8_t active_level; // 有效电平(0=低电平有效,1=高电平有效)
} ebtn_custom_config_key_list_t;
extern ebtn_custom_config_key_list_t key_list[]; // 按键配置数组
extern ebtn_btn_t btn_array[]; // 按键结构体数组
extern ebtn_btn_combo_t btn_combo_array[]; // 组合键结构体数组
extern const uint8_t btn_array_size; // 按键结构体数组大小
extern const uint8_t btn_combo_array_size; // 组合键结构体数组大小
extern const uint8_t key_list_size; // 按键配置数组大小
#endif /* EBTN_CUSTOM_CONFIG_H */
#include "ebtn_custom_hal.h"
/* ------------------------------ 此处实现硬件抽象回调函数 ------------------------------ */
/** ***************************************************************************
* @brief 读取GPIO电平,在ebtn_app.c中被使用,用于读取按键引脚的电平
* @param GPIOx 指向GPIO端口的指针
* @param GPIO_Pin GPIO引脚号
* @return GPIO引脚的电平状态(0/1)
*/
uint8_t ebtn_HAL_Read_Pin(GPIO_T* p_ctrl, uint16_t pin)
{
}
/** ***************************************************************************
* @brief 获取系统滴答计数器值,用于为ebtn_process()函数提供时间基准
* @note SysTick时基为1ms
* @return 系统滴答计数器ms时间值
*/
uint32_t ebtn_HAL_Get_Tick(void)
{
return HAL_GetTick(); // 示例:STM32HAL库
}
/* -------------------------------- 自定义配置部分结束 ------------------------------- */
/** ***************************************************************************
* @brief ebtn_Custom回调函数结构体实例化
*/
ebtn_custom_hal_t ebtn_custom_hal = {
.Read_Pin = ebtn_HAL_Read_Pin,
.Get_Tick = ebtn_HAL_Get_Tick,
};
#ifndef EBTN_CUSTOM_HAL_H
#define EBTN_CUSTOM_HAL_H
/* ------------------------------ 此处包含单片机平台的头文件 ----------------------------- */
// 包含单片机平台的声明了GPIO和SysTick相关的头文件
#include "main.h"
/* -------------------------- 此处修改硬件回调函数自定义声明(如有需求) ------------------------- */
/** ***************************************************************************
* @brief 自定义回调函数结构体声明
*/
typedef struct
{
/** ***************************************************************************
* @brief 读取GPIO电平,在ebtn_app.c中被使用,用于读取按键引脚的电平
* @param GPIOx 指向GPIO端口的指针
* @param GPIO_Pin GPIO引脚号
* @return GPIO引脚的电平状态(0/1)
*/
uint8_t (*Read_Pin)(GPIO_T* p_ctrl, uint16_t pin);
/** ***************************************************************************
* @brief 获取系统滴答计数器值,用于为ebtn_process()函数提供时间基准
* @note SysTick时基为1ms
* @return 系统滴答计数器ms时间值
*/
uint32_t (*Get_Tick)(void);
} ebtn_custom_hal_t;
/* -------------------------------- 自定义配置部分结束 ------------------------------- */
extern ebtn_custom_hal_t ebtn_custom_hal; // ebtn适配层回调函数结构声明
#endif /* EBTN_CUSTOM_HAL_H */
自己定义一个20ms定时器,在里面调用按键钩子函数
3.编译代码,烧录到板子
4.打开串口助手
根据写好的串口函数,来判断按键的多功能
可以看到已经完美的实现了KEY1,LEY2按键的单击, 双击,三击,长按,短按,KEY1和KEY2的组合按键,非常的完美!!!!
f附件是按键hex文件,大家可以自行烧录测试!!!
|
|