[蓝桥杯嵌入式]多按键长按单击双击监测!按键反应速度较快,可直接在比赛中使用!

本文讲述了作者在准备蓝桥杯嵌入式比赛时,如何优化一个优快云上的按键框架以解决按键反应迟缓问题,通过简化双击监测并利用状态机提高运行速度,还分享了一个高效按键驱动程序ButtonDrive及其GitHub地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在蓝桥杯嵌入式的比赛中,按键是每年必考的,题目要求的运行逻辑基本都需要按键的参与,而且按键监测的直接反应到系统运行的流畅程度上,所以按键非常重要。

        我在准备比赛的过程中,想实现同时监测四个按键的短按长按双击这功能,查阅了很多资料,使用了一个很多人在优快云上写的按键框架,但是按键的反应非常迟缓,一直被这个问题所困扰,后面我对这个框架进行了一些小小的修改,可以显著提高运行速度

        1、接下来我先写出我修改的思路

        2、最后再放一个我在GitHub上找到的按键程序,是一个大神写的,写的很厉害!

        3、之后再将按键部分的全部代码附上可以直接复制修改使用

1、修改思路:

        仅仅只给需要双击的按键提供双击功能的监测,其余不需要双击的按键的,直接跳过双击监测这个功能。这样的好处是可以提高按键的反应速度,因为监测一个按键的双击是非常耗时的,会造成很严重的漏检测现象,而监测按键的单击和长按,是不耗时的。

        根据代码流程走一遍就可以知道,首先每隔10ms获取一次每个按键对应GPIO的值,如果初次按下按键情况,则先进行消抖,消抖完成后继续监测同时开始计时,如果按键仍处于按下状态,则根据定义的长按时间判断是否为长按,如果在过程中松开了,则直接返回为单击状态,如果确实是达到了长按的时长,则返回为长按状态。可以看到在监测长按短按的流程中是没有额外耗时的。但是如果是监测双击,则在接收到按键为单击状态后,仍需等待一个判断为双击的最大时间,如果等待结束后确定没有第二次按下了,才返回为单击。如果用户的想法是单击,不是双击,则这个等待判断双击的时间,就是额外的耗时了,这就是用大家都用的按键框架的弊端。

        而且这套代码,是使用状态机的思想写的,很容易理解, 自己跟着流程写两遍,到时候基本上就可以写出来了。到考场上可修改的空间也很大很方便,很容易就可以把这套代码修改为监测四个按键的单击、修改为监测四个按键单击和一个按键长按的组合。若是同时检测四个按键单击双击长按,确实会反应迟缓,但真实的考题应该不会有这种要求,真有的话这套代码也能用。

2、GitHub上的ButtonDrive

        之前韦东山老师的MQTT智能家居的课,用的也是这个大神写的开源的MQTT库。

        作者对的Button_drive简介:Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在RT-Thread上测试过。 写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。

GitHub - jiejieTop/ButtonDrive: 纯C语言实现的一个按键驱动,可移植性强,支持单双击、连按、连按释放、长按;采用回调处理按键事件(自定义消抖时间),使用只需3步,1:创建按键,2:按键事件与回调处理函数链接映射。然后周期检查按键。

3、全部代码

/**
 * @file mycode.c
 * @brief 可以同时监测四个按键的单击、长按,监测第一个按键的单击、长按、双击。
 * 		  使用TIM2定时器,开启定时器中断,每10ms轮询一次。
 * @author wangkaiwei (1357689541@qq.com)
 * @version 1.0
 * @date 2024-03-11
 * @copyright Copyright (c) {2024}  CJLU
 * @par 修改日志:
 * 2024-03-11 1.0     wangkaiwei
 *    
 */

#include "main.h"
#include "tim.h"

#define KEY_B1 HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin)
#define KEY_B2 HAL_GPIO_ReadPin(B2_GPIO_Port, B2_Pin)
#define KEY_B3 HAL_GPIO_ReadPin(B3_GPIO_Port, B3_Pin)
#define KEY_B4 HAL_GPIO_ReadPin(B4_GPIO_Port, B4_Pin)

typedef enum 
{
	key_no     = 0,		/* 无按键按下 */
	key_click  = 1,		/* 单击 */
	key_long   = 2,		/* 长按 */
	key_double = 3      /* 双击 */
}KEY_EVENT;


typedef struct key{
	u8 key_input_val;				/* GPIO读取出来按键的真实值 */
	u8 key_state_buff1;				/* 按键执行状态1,用于记录状态 */
	u8 key_state_buff2;				/* 按键执行状态2,用于记录状态,只有按键1用 */
	KEY_EVENT key_real_result;		/* 最终判断结果 */
	unsigned char key_time_cnt1;	/* 定时器1 */
	unsigned char key_time_cnt2;	/* 定时器2,只有按键1用 */
}t_key;

t_key key[4];

/* 按住500ms以上判断为长按 */
#define KEY_LONG_PRESS_TIME 50
/* 两次按下间隔200ms以内判断为双击 */
#define KEY_DOUBLE_GAP_TIME 20

/**
 * @brief 初始化按键的状态和值,同时开启tim2的中断
 */
void key_init(void)
{
    for(int i=0;i<4;i++)
    {
        key[i].key_state_buff1 = 0;
		key[i].key_state_buff2 = 0;
        key[i].key_real_result = key_no;
        key[i].key_time_cnt1 = 0;
        key[i].key_time_cnt2 = 0;
    }
    HAL_TIM_Base_Start_IT(&htim2);
}


/**
 * @brief  监测4个按键的长按和短按,这个函数放在tim2的溢出中断中
 * @param  knum             按键编号
 * @return KEY_EVENT 
 */
static KEY_EVENT key_driver(int knum)
{
	KEY_EVENT key_result = key_no;
	switch(key[knum].key_state_buff1)
	{
		case 0:
		{
			if(key[knum].key_input_val == 0)
			{
				/* 如果按键按下,状态切换到按键消抖和确认状态 */
				key[knum].key_state_buff1 = 1;
			}
			break;
		}
		case 1:/* 消抖和确认状态 */
		{
			if(key[knum].key_input_val == 0)
			{
				/* 检测到按键仍处于按下状态 */
				/* 消抖完成,计时器key_time_cnt1开始计时 */
				/* 状态切换到计时状态 */
				key[knum].key_time_cnt1 = 0;
				key[knum].key_state_buff1 = 2;
			}
			else
			{
				/* 按键松开在抖动,返回state_0 */
				key[knum].key_state_buff1 = 0;
			}
			break;
		}
		case 2:/* 计时状态 */
		{
			if(key[knum].key_input_val == 1)
			{
				/* 按键松开了,产生一次click操作 */
				key_result = key_click;
				key[knum].key_state_buff1 = 0;
			}
			else if( ++key[knum].key_time_cnt1 >  KEY_LONG_PRESS_TIME)
			{
				/* 继续按下超过1000ms,返回长按操作 */
				key_result = key_long;
				key[knum].key_state_buff1 = 3;/* 状态切换到按键释放状态,等待按键松开 */
			}
			break;
		}
		case 3:
		{
			/* 等待按键松开,松开后回到初始状态 */
			if(key[knum].key_input_val == 1)
			{
				key[knum].key_state_buff1 = 0;
			}
			break;
		}
	}
	return key_result;
}

/**
 * @brief TIM溢出中断回调函数。记得一定要在CUBEMX中勾选TIM2的中断,设置其触发一次中断时间为10ms。
 * @param  htim             My Param doc
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	KEY_EVENT key_result;
	if(htim->Instance == TIM2)
	{
		/* 10ms读取一次 */
		key[0].key_input_val = KEY_B1;
		key[1].key_input_val = KEY_B2;
		key[2].key_input_val = KEY_B3;
		key[3].key_input_val = KEY_B4;
		
		for(int i=0;i<4;i++)
		{
			if(i != 0)
			{
				/* 按键2 3 4仅判断长按和单击 */
				key[i].key_real_result = key_driver(i);
			}
			else
			{
				/* 单独判断按键1的双击情况 */
				key_result = key_driver(i);
				switch(key[i].key_state_buff2)
				{
					case 0:
					{
						if(key_result == key_click)
						{
							/* 对于单击,判断是否还有还有下次一按下,进入到下一个状态判断是否为双击 */
							key[i].key_time_cnt2 = 0;
							key[i].key_state_buff2 = 1;
						}
						else
						{
							/* 如果返回结果不是双击,则是长按,长按的话直接返回结果即可 */
							key[i].key_real_result = key_result;
						}
						break;
					}
					case 1:
					{
						if(key_result == key_click)/* 又一次单击,时间间隔小于KEY_DOUBLE_GAP_TIME定义的时间 */
						{
							key[i].key_real_result = key_double; /* 返回双击事件 */
							key[i].key_state_buff2 = 0;
						}
						else if(++key[i].key_time_cnt2 > KEY_DOUBLE_GAP_TIME)
						{
							key[i].key_real_result = key_click;/* 时间超过KEY_DOUBLE_GAP_TIME定义的时间,返回为一次的单击 */
							key[i].key_state_buff2 = 0;
						}
						break;
					}
				}
			}
		}
	}
}


void key_proc(void)
{
/*B1*/
	if (key[0].key_real_result == key_click)
	{
		LCD_ClearLine(Line1);
		LCD_DisplayStringLine(Line1, (uint8_t *)"     key1_click    ");
		key[0].key_real_result = key_no;/* 清除按键事件,置为无键事件 */	
	}
	else if (key[0].key_real_result == key_long)
	{
		LCD_ClearLine(Line1);
		LCD_DisplayStringLine(Line1, (uint8_t *)"     key1_long     ");
		key[0].key_real_result = key_no;			
	}
	else if (key[0].key_real_result == key_double)
	{
		LCD_ClearLine(Line1);
		LCD_DisplayStringLine(Line1, (uint8_t *)"     key_double     ");
		key[0].key_real_result = key_no;			
	}
/*B2*/
	if (key[1].key_real_result == key_click)
	{
		LCD_ClearLine(Line2);
		LCD_DisplayStringLine(Line2, (uint8_t *)"     key2_click    ");
		key[1].key_real_result = key_no;			
	}
	else if (key[1].key_real_result == key_long)
	{
		LCD_ClearLine(Line2);
		LCD_DisplayStringLine(Line2, (uint8_t *)"     key2_long      ");
		key[1].key_real_result = key_no;	
	}
/*B3*/
	if (key[2].key_real_result == key_click)
	{
		LCD_ClearLine(Line3);
		LCD_DisplayStringLine(Line3, (uint8_t *)"     key3_click    ");
		key[2].key_real_result = key_no;			
	}
	else if (key[2].key_real_result == key_long)
	{
		LCD_ClearLine(Line3);
		LCD_DisplayStringLine(Line3, (uint8_t *)"     key3_long      ");
		key[2].key_real_result = key_no;	
	}
/*B4*/
	if (key[3].key_real_result == key_click)
	{
		LCD_ClearLine(Line4);
		LCD_DisplayStringLine(Line4, (uint8_t *)"     key4_click    ");
		key[3].key_real_result = key_no;			
	}
	else if (key[3].key_real_result == key_long)
	{
		LCD_ClearLine(Line4);
		LCD_DisplayStringLine(Line4, (uint8_t *)"     key4_long      ");
		key[3].key_real_result = key_no;	
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值