高效灵活的stm32按键库实现单击、双击、长按、注册函数


前言

在嵌入式开发中,按键输入是常见的用户交互方式。但如何高效并灵活地处理按键输入事件,特别是如单击、双击、长按等复杂事件,对许多开发者来说是一个挑战。在本文中,我将分享一个灵活并高效的嵌入式按键库,它支持多种按键事件,包括单击、双击、长按等,并且可以灵活地定制按键处理逻辑。


一、主要函数

按键初始化

/*
这个函数初始化按键模块。它接收以下参数:
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代码

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是一个使用状态机实现单击双击按功能的程序: 首先,我们需要定义一些常量和变量: ```c #define DEBOUNCE_TIME 20 // 消抖时间,单位ms #define LONG_PRESS_TIME 1000 // 按时间,单位ms #define DOUBLE_CLICK_TIME 250 // 双击间隔时间,单位ms GPIO_TypeDef* KEY_PORT = GPIOB; // 按键所在的GPIO端口 uint16_t KEY_PIN = GPIO_PIN_0; // 按键所在的GPIO引脚 typedef enum { KEY_IDLE, // 按键空闲状态 KEY_PRESS, // 按键按下状态 KEY_RELEASE, // 按键释放状态 KEY_LONG_PRESS, // 按状态 KEY_DOUBLE_CLICK_WAIT, // 等待双击状态 KEY_DOUBLE_CLICK // 双击状态 } key_state_t; key_state_t key_state = KEY_IDLE; // 当前按键状态 uint32_t key_press_time = 0; // 按键按下时间 uint32_t key_release_time = 0; // 按键释放时间 uint32_t key_last_release_time = 0; // 上一次按键释放时间 uint8_t click_count = 0; // 点击次数 ``` 然后,我们可以编写一个状态机函数,用于检测按键状态和执行相应的操作: ```c void key_state_machine() { // 获取当前时间 uint32_t t = HAL_GetTick(); // 检测按键状态 switch (key_state) { case KEY_IDLE: if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) { // 按键按下 key_press_time = t; key_state = KEY_PRESS; } break; case KEY_PRESS: if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_SET) { // 按键释放 key_release_time = t; if (key_release_time - key_press_time > DEBOUNCE_TIME) { // 消抖 click_count++; if (click_count == 1) { // 第一次单击 key_last_release_time = key_release_time; key_state = KEY_IDLE; } else if (click_count == 2) { // 第二次单击 if (key_release_time - key_last_release_time < DOUBLE_CLICK_TIME) { // 双击 click_count = 0; key_state = KEY_DOUBLE_CLICK; } else { // 第一次单击后超时,重新开始计数 click_count = 1; key_last_release_time = key_release_time; key_state = KEY_IDLE; } } else { // 连续单击 key_last_release_time = key_release_time; key_state = KEY_IDLE; } } } else if (t - key_press_time > LONG_PRESS_TIME) { // 按 click_count = 0; key_state = KEY_LONG_PRESS; } break; case KEY_RELEASE: if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) { // 按键重新按下 key_press_time = t; key_state = KEY_PRESS; } else { // 按键完全释放 key_state = KEY_IDLE; } break; case KEY_LONG_PRESS: if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_SET) { // 按释放 key_state = KEY_RELEASE; } break; case KEY_DOUBLE_CLICK_WAIT: if (t - key_release_time > DOUBLE_CLICK_TIME) { // 双击超时 if (click_count == 1) { // 单击 key_state = KEY_RELEASE; } else { // 连续单击 key_last_release_time = key_release_time; key_state = KEY_IDLE; } } else if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) { // 双击按下 key_press_time = t; key_state = KEY_PRESS; } break; case KEY_DOUBLE_CLICK: // 双击完成 key_state = KEY_IDLE; break; } // 执行按键状态的相应操作 switch (key_state) { case KEY_LONG_PRESS: // 按操作 break; case KEY_DOUBLE_CLICK: // 双击操作 break; } } ``` 在主函数的`while`循环中,我们只需要调用状态机函数即可: ```c while (1) { key_state_machine(); } ``` 以上就是一个使用状态机实现单击双击按功能的程序,可以根据需要进行修改和优化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值