前言
在嵌入式开发中,按键输入是常见的用户交互方式。但如何高效并灵活地处理按键输入事件,特别是如单击、双击、长按等复杂事件,对许多开发者来说是一个挑战。在本文中,我将分享一个灵活并高效的嵌入式按键库,它支持多种按键事件,包括单击、双击、长按等,并且可以灵活地定制按键处理逻辑。
一、主要函数
按键初始化
/*
这个函数初始化按键模块。它接收以下参数:
handle: 按键句柄,用于标识特定的按键。
pin_level: 一个函数指针,这个函数返回按键的当前状态(即电平)。
active_level: 表示按键活跃状态的电平,例如,对于低电平有效的按键,此参数应为0。
RepeatSpeed: 多击事件的时间阈值。
LongTime: 长按事件的时间阈值。
*/
void mybtn_init(KEY_T* handle, uint8_t(*pin_level)(void),uint8_t active_level,uint16_t RepeatSpeed,uint16_t LongTime);
关联事件以及回调函数
/*
此函数将特定事件的回调函数关联到特定的按键。当定义了KEY_CallBackFunc_ENABLE时,此函数才有效。
handle: 按键句柄,用于标识特定的按键。
event: 需要关联回调函数的按键事件。
cb: 需要关联的回调函数。
*/
#if KEY_CallBackFunc_ENABLE
void mybtn_attach(KEY_T* handle, PressEvent event, BtnCallback cb);
按键处理
/*
这个函数处理所有已启动的按键事件。当定义了KEY_ExplainPlan为0时,此函数才有效,它应在主程序的主循环或定时器中断中定期调用。
*/
void mybtn_ticks(void);
二、代码
1.引入库
下面是.c
文件
/* Includes ------------------------------------------------------------------*/
#include "separate_button.h"
/* Private macros ------------------------------------------------------------*/
/* Private types -------------------------------------------------------------*/
/* Private constants ---------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
#if ( KEY_ExplainPlan == 0 )
static struct KEY_T* head_handle = NULL;
#endif
/* Private functions ---------------------------------------------------------*/
/* Exported macros -----------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
/* Exported functions --------------------------------------------------------*/
void mybtn_handler(KEY_T* handle)
{
uint8_t read_gpio_level;
#if KEY_CallBackFunc_ENABLE
uint8_t i,mask;
#endif
/*
读取GPIO等级:通过调用separate_button_Level函数获取当前按键的状态(按下或未按下)。这个函数应当由用户提供。
*/
read_gpio_level = handle->separate_button_Level();
/*------------button debounce handle---------------*/
/*
按钮去抖:在读取的GPIO等级与上次的读取值不同的情况下,防抖动计数器debounce_cnt会增加。
如果连续多次(具体次数由DEBOUNCE_TICKS决定)读取的等级都与上次的不同,则认为按钮的状态发生了改变。如果读取的等级与上次的相同,则防抖动计数器被重置。
*/
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状态机-------------------*/
/*
状态机处理:根据防抖动处理后的结果,设定按钮的状态和触发标志位。然后,根据按钮的状态产生相应的信号,例如:按下信号、长按信号、松开信号等。
*/
handle->ReadData = (handle->button_level == handle->active_level);
handle->Trg = handle->ReadData & (handle->ReadData ^ handle->Cont);
handle->Cont = handle->ReadData;
handle->signal = 0;
if(handle->ReadData)
{
if(handle->Trg)
{
/************按下事件*******************/
handle->signal |= SIGNAL_PRESS_DOWN;
#if SIGNAL_LONG_PRESS_HOLD_ENABLE
/************长按事件*******************/
handle->LongCount = 0;
#endif
#if SINGLE_CLICK_ENABLE | DOUBLE_CLICK_ENABLE | MUCH_CLICK_ENABLE
/************多按事件*******************/
handle->RepeatNum += 1;
handle->RepeatCount = 0;
#endif
handle->state_old = 3;
}
else
{
#if SIGNAL_LONG_PRESS_HOLD_ENABLE
/************长按事件*******************/
handle->LongCount++;
if(handle->LongTime != 0)
{
if(handle->LongCount >= handle->LongTime)
{
handle->signal |= SIGNAL_LONG_PRESS_HOLD;
handle->LongCount = 0;
handle->long_press_flag = 1;//
}
}
#endif
handle->state_old = 2;
}
}
else
{
/************松开事件*******************/
if(handle->state_old > 0)
{
handle->signal |= SIGNAL_PRESS_UP;
}
#if SINGLE_CLICK_ENABLE | DOUBLE_CLICK_ENABLE | MUCH_CLICK_ENABLE
/************多按事件*******************/
if(handle->RepeatCount < handle->RepeatSpeed)
{
handle->RepeatCount++;
}
else if(handle->RepeatNum != 0)
{
#if PRESS_UP_DELAY_ENABLE
handle->signal |= SIGNAL_PRESS_UP_DELAY;
#endif
#if SINGLE_CLICK_ENABLE
if(1 == handle->RepeatNum)
{
handle->signal |= SIGNAL_SINGLE_CLICK;
}
else
#endif
#if DOUBLE_CLICK_ENABLE
if(2 == handle->RepeatNum)
{
handle->signal |= SIGNAL_DOUBLE_CLICK;
}
else
#endif
{
#if MUCH_CLICK_ENABLE
handle->RepeatNumOld = handle->RepeatNum;
handle->signal |= SIGNAL_MUCH_CLICK;
#endif
}
handle->RepeatNum = 0;
}
#endif
handle->state_old = 0;
}
#if KEY_CallBackFunc_ENABLE
if( handle->signal )
{
mask = 1;
for (i=0; i<number_of_event; i++)
{
if( handle->signal & mask )
{
if( handle->cb[i] != 0 )
{
handle->event_flg = mask;
handle->cb[i]( handle );
}
handle->signal &= ~mask;
}
mask <<= 1;
}
}
#endif
}
/*
这个函数接受五个参数:
KEY_T* handle:这是一个 KEY_T 类型的指针,表示一个按键句柄。
uint8_t(*pin_level)(void):这是一个函数指针,指向一个函数,这个函数用来获取当前按键的状态(按下或未按下)。这个函数应当由用户提供。
uint8_t active_level:这是一个无符号8位整数,表示按键的激活级别。通常情况下,高电平表示按键被按下,低电平表示按键被释放。但是,某些硬件设计可能会有所不同,
因此,这个激活级别需要由用户提供。
uint16_t RepeatSpeed:这是一个无符号16位整数,表示多击事件的重复速度。具体的数值表示两次点击之间的最大间隔,单位为毫秒。这个参数仅在启用多击事件时有效。
uint16_t LongTime:这是一个无符号16位整数,表示长按事件的长按时间。具体的数值表示需要持续按下按钮多长时间才算作一次长按,单位为毫秒。这个参数仅在启用长按事件时有效。
*/
void mybtn_init(KEY_T* handle, uint8_t(*pin_level)(void),uint8_t active_level,uint16_t RepeatSpeed,uint16_t LongTime)
{
memset(handle, 0, sizeof(KEY_T));
handle->separate_button_Level = pin_level;
handle->button_level = handle->separate_button_Level();
handle->active_level = active_level;
#if SINGLE_CLICK_ENABLE | DOUBLE_CLICK_ENABLE | MUCH_CLICK_ENABLE
handle->RepeatSpeed = RepeatSpeed/KEYTICKS_INTERVAL;
#else
RepeatSpeed = RepeatSpeed;
#endif
#if SIGNAL_LONG_PRESS_HOLD_ENABLE
handle->LongTime = LongTime/KEYTICKS_INTERVAL;
#else
LongTime = LongTime;
#endif
}
#if KEY_CallBackFunc_ENABLE
void mybtn_attach(KEY_T* handle, PressEvent event, BtnCallback cb)
{
handle->cb[event] = cb;
}
#endif
#if MYBTN_TIME_ENABLE
void mybtn_time(KEY_T* handle,uint16_t RepeatSpeed,uint16_t LongTime)
{
handle->separate_button_Level();
#if SINGLE_CLICK_ENABLE | DOUBLE_CLICK_ENABLE | MUCH_CLICK_ENABLE
handle->RepeatSpeed = RepeatSpeed/KEYTICKS_INTERVAL;
#else
RepeatSpeed = RepeatSpeed;
#endif
#if SIGNAL_LONG_PRESS_HOLD_ENABLE
handle->LongTime = LongTime/KEYTICKS_INTERVAL;
#else
LongTime = LongTime;
#endif
}
#endif
#if ( KEY_ExplainPlan == 0 )
int mybtn_start(KEY_T* handle)
{
KEY_T* target = head_handle;
while(target)
{
if(target == handle)
return -1; //already exist.
target = target->next;
}
handle->next = head_handle;
head_handle = handle;
return 0;
}
void mybtn_stop(KEY_T* handle)
{
KEY_T** curr;
KEY_T* entry;
curr = &head_handle;
while(*curr)
{
entry = *curr;
if(handle == entry)
{
*curr = entry->next;
break;
}
else
{
curr = &entry->next;
}
}
}
void mybtn_ticks(void)
{
KEY_T* target;
for(target=head_handle; target; target=target->next)
{
mybtn_handler(target);
}
}
#endif
下面是.h
文件
#ifndef _Separate_BUTTON_H_
#define _Separate_BUTTON_H_
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <string.h>
/* Private macros ------------------------------------------------------------*/
/* Private types -------------------------------------------------------------*/
/* Private constants ---------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/* Exported macros -----------------------------------------------------------*/
#define KEY_ExplainPlan 0
#define KEY_CallBackFunc_ENABLE 1 //启用回调函数功能
#define MYBTN_TIME_ENABLE 1 //是按键事件时间处理的开关
//根据你的需要修改常量。
#define KEYTICKS_INTERVAL 10 //ms //按键扫描的间隔时间
#define DEBOUNCE_TICKS 3 //MAX 7 //抖动计数器的阈值
#define SINGLE_CLICK_ENABLE 1
#define DOUBLE_CLICK_ENABLE 1
#define MUCH_CLICK_ENABLE 0
#define SIGNAL_LONG_PRESS_HOLD_ENABLE 1
#define PRESS_UP_DELAY_ENABLE 1
//宏用于定义各种按键事件的标志位。这里采用位移操作(1<<PRESS_DOWN 等)为每种事件生成一个唯一的标志位,以便在一个整数变量中同时表示多种事件状态。
#define SIGNAL_PRESS_DOWN (1<<PRESS_DOWN)
#define SIGNAL_PRESS_UP (1<<PRESS_UP)
#if SINGLE_CLICK_ENABLE
#define SIGNAL_SINGLE_CLICK (1<<SINGLE_CLICK)
#endif
#if DOUBLE_CLICK_ENABLE
#define SIGNAL_DOUBLE_CLICK (1<<DOUBLE_CLICK)
#endif
#if MUCH_CLICK_ENABLE
#define SIGNAL_MUCH_CLICK (1<<MUCH_CLICK)
#endif
#if SIGNAL_LONG_PRESS_HOLD_ENABLE
#define SIGNAL_LONG_PRESS_HOLD (1<<LONG_PRESS_HOLD)
#endif
#if PRESS_UP_DELAY_ENABLE
#define SIGNAL_PRESS_UP_DELAY (1<<PRESS_UP_DELAY)
#endif
/* Exported types ------------------------------------------------------------*/
#if KEY_CallBackFunc_ENABLE
typedef void (*BtnCallback)(void*);
#endif
typedef enum
{
PRESS_DOWN = 0,//按下
PRESS_UP, //松开
#if SINGLE_CLICK_ENABLE
SINGLE_CLICK, //单击
#endif
#if DOUBLE_CLICK_ENABLE
DOUBLE_CLICK, //双击
#endif
#if MUCH_CLICK_ENABLE
MUCH_CLICK, //多击
#endif
#if SIGNAL_LONG_PRESS_HOLD_ENABLE
LONG_PRESS_HOLD,//长按保持
#endif
#if PRESS_UP_DELAY_ENABLE
PRESS_UP_DELAY, //松开延时
#endif
number_of_event,
NONE_PRESS
}PressEvent;
typedef struct KEY_T
{
uint8_t debounce_cnt :3;//防反跳计数
uint8_t button_level :1;//上一次按钮状态
uint8_t active_level :1;//按下的状态
uint8_t ReadData :1;
uint8_t Trg :1;
uint8_t Cont :1;
uint8_t signal;
#if KEY_CallBackFunc_ENABLE
uint8_t event_flg;
#endif
uint8_t state_old;
#if SINGLE_CLICK_ENABLE | DOUBLE_CLICK_ENABLE | MUCH_CLICK_ENABLE
uint8_t RepeatNum;
uint8_t RepeatNumOld;
uint16_t RepeatCount; /* 连续按键计数器 */
uint16_t RepeatSpeed; /* 连续按键周期 */
#endif
#if SIGNAL_LONG_PRESS_HOLD_ENABLE
uint8_t long_press_flag : 1;//
uint16_t LongCount; /* 长按计数器 */
uint16_t LongTime; /* 按键按下持续时间, 0表示不检测长按 */
#endif
uint8_t (*separate_button_Level)(void); /* 按键按下的判断函数*/
#if KEY_CallBackFunc_ENABLE
BtnCallback cb[number_of_event]; //事件函数
#endif
#if ( KEY_ExplainPlan == 0 )
struct KEY_T* next;
#endif
}KEY_T;
/* Exported constants --------------------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
/* Exported functions --------------------------------------------------------*/
/*
这个函数初始化按键模块。它接收以下参数:
handle: 按键句柄,用于标识特定的按键。
pin_level: 一个函数指针,这个函数返回按键的当前状态(即电平)。
active_level: 表示按键活跃状态的电平,例如,对于低电平有效的按键,此参数应为0。
RepeatSpeed: 多击事件的时间阈值。
LongTime: 长按事件的时间阈值。
*/
void mybtn_init(KEY_T* handle, uint8_t(*pin_level)(void),uint8_t active_level,uint16_t RepeatSpeed,uint16_t LongTime);
/*
此函数将特定事件的回调函数关联到特定的按键。当定义了KEY_CallBackFunc_ENABLE时,此函数才有效。
handle: 按键句柄,用于标识特定的按键。
event: 需要关联回调函数的按键事件。
cb: 需要关联的回调函数。
*/
#if KEY_CallBackFunc_ENABLE
void mybtn_attach(KEY_T* handle, PressEvent event, BtnCallback cb);
#endif
/*
此函数用于设置多击和长按事件的时间阈值。参数和mybtn_init函数中的对应参数类似。
*/
void mybtn_time(KEY_T* handle,uint16_t RepeatSpeed,uint16_t LongTime);
/*
这个函数处理特定按键的事件。它应在主程序的主循环或定时器中断中定期调用。
*/
void mybtn_handler(KEY_T* handle);
#if ( KEY_ExplainPlan == 0 )
/*
此函数启动特定的按键事件处理。当定义了KEY_ExplainPlan为0时,此函数才有效。
*/
int mybtn_start(KEY_T* handle);
/*
函数停止特定的按键事件处理。当定义了KEY_ExplainPlan为0时,此函数才有效。
*/
void mybtn_stop(KEY_T* handle);
/*
这个函数处理所有已启动的按键事件。当定义了KEY_ExplainPlan为0时,此函数才有效,它应在主程序的主循环或定时器中断中定期调用。
*/
void mybtn_ticks(void);
#endif
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
2.函数的调用
由于这个按键库可以适用于大多数c编程的嵌入是产品所以这里以stm32为例。
采用注册函数的方法所以首先要有两个函数,一个是按键的检测函数,一个是按键处理的回调函数
uint8_t KEY_1(void)
{
if ((KEY1_GPIO_Port->IDR & KEY1_Pin) != (uint32_t)GPIO_PIN_RESET)
{
return 1;
}
else
{
return 0;
}
}
void XferExternalKey1_Handler( void* btn )
{
switch(((KEY_T*)btn)->event_flg)
{
case SIGNAL_SINGLE_CLICK:{
if(((KEY_T*)btn)->long_press_flag)
{
// 清除长按标志位
((KEY_T*)btn)->long_press_flag = 0;
}
else
{
i++;
}
}break;
case SIGNAL_LONG_PRESS_HOLD:{
i--;
}break;
case SIGNAL_DOUBLE_CLICK:{
dou_++;
}
}
}
随后进行按键的初始化
mybtn_init( &key1_struct, KEY_1, 0, 300, 1200 );
mybtn_attach( &key1_struct, SINGLE_CLICK, XferExternalKey1_Handler);
mybtn_attach( &key1_struct, DOUBLE_CLICK, XferExternalKey1_Handler);
mybtn_attach( &key1_struct, LONG_PRESS_HOLD, XferExternalKey1_Handler);
mybtn_start( &key1_struct );
最后在主函数或者是定时器里调用按键处理函数
mybtn_ticks();
注意:
KEY1_GPIO_Port->IDR & KEY1_Pin
是读取该端口的输入数据寄存器(IDR)的特定位,这特定位是和你的按键连接的。GPIO_PIN_RESET
是一个常量,通常代表逻辑低(0)。你的按键可能是拉高或拉低类型。
如果按键是拉高类型(按键未按下时电平为高,按下时为低),那么在你按下按键时,函数应返回 0,因为输入的 GPIO 位会被拉低。
如果按键是拉低类型(按键未按下时电平为低,按下时为高),那么在你按下按键时,函数应返回 1,因为输入的 GPIO 位会被拉高。
你应根据你的硬件设计来确定这一点。
mybtn_init(&key1_struct, KEY_1, 0, 300, 1200);
调用中,第三个参数(这里是 0)表示按键的活动电平,也就是当按键被按下时输入 GPIO 的电平。
因为你设定了这个值为 0,这表示你的按键是拉高类型:即当按键未被按下时,KEY_1() 函数返回 1(GPIO电平为高),而当按键被按下时,KEY_1() 函数返回 0(GPIO电平为低)。
所以,根据你的这个设定,当你按下按键时,KEY_1() 函数应返回 0。
总结
在这篇文章中,我尽可能地使这个按键库易于理解和使用。无论你是新手还是有经验的开发者,我相信你都能从这个按键库中找到你需要的功能,并快速地将其集成到你的项目中。
最后,如果你觉得这篇文章或者这个按键库对你有帮助,欢迎分享和点赞,你的支持是我持续改进和分享的动力。
stm32f103r8t6代码