首页>技术知识>电商资讯 解决方法:stm32单片机按键消抖、长按、多击终极解决方案
25QI导航
2024-12-03
双击功能其实就是在单击的基础上进行两次单击间隔的时间判断,如果小于最大时间间隔就返回双击信号,不然两次均返回单击;三击功能其实就是在判断双击的第二击与第三击的时间间隔;推广一下,N击就是在判断第N-1击的最后一击与第N击之间的时间间隔。

在计算机系统开发领域,按键检测看似简易,实则隐藏诸多易被忽略的细节。人们常以为按键检测仅是识别按键的按下与否,然而其中涉及的逻辑状态、计数等方面实则相当复杂。这正是今天我们要深入剖析的问题所在。

按键状态信息的构成

按键所需记录的信息涉及多个层面,共计5项。这一设定并非随意为之,而是基于我们的操作环境和目标精心确定的。以逻辑状态为例,它明确分为关闭、开启和长按三种,这一划分是通过多次实验和验证得出的。这样的分类在软件开发或系统构建中,为程序员提供了明确的判断标准。每种状态都代表着对按键操作的不同理解,有助于更准确地把握用户的操作意图。

记录这些状态时,程序信号的处理方式独特,无需额外存储。这是因为它能直接从逻辑和物理状态的关系中推导出结论,这样做既节省了资源,又简化了程序流程。这一点提醒开发者,在设计系统时,应注重探索降低存储需求的策略,以提升资源使用效率。

按键逻辑状态与程序信号的关系

程序最终获取的信号与按键的实际逻辑状态并不一致。逻辑状态只涵盖了少数几种情形,而程序接收到的信号与逻辑状态之间有着复杂的对应。例如,长按时,程序会接收到长按的信号。在特定的抖动阶段,两者有时会呈现相似的状态。当它们处于(1,1)状态时,这种情况会在按键关闭抖动时出现。这种关系的识别对于判断按键是否进入长按状态至关重要,因为此时需要累加开启计数,计数一旦达到特定值,就会切换到(2,1)状态。这一连串的状态变化背后,体现的是系统为了精确捕捉用户按键操作所进行的复杂处理。开发者必须深刻领会这种关系,以便实现精确的按键功能。

在开发功能按键时,这种关系的逻辑判断尤为重要。准确理解这种关系,有助于我们在构建软件或系统时,避免将长按或短按误判,进而提高用户的使用体验。

按键消抖的处理

按键操作时,抖动现象会极大地干扰操作的精确度。之所以要将计数开启和关闭的动作分开,主要是为了确保在按键的开启和关闭阶段都能进行有效的消抖处理。无论是按键开启还是关闭时出现的抖动,若不妥善解决,都可能导致程序对按键动作的错误判断。以实体按键的按下或释放为例,电压或信号在这一瞬间可能会出现波动,也就是所谓的抖动。

//按键状态结构体,存储四个变量
typedef struct
{
 	uint8_t KeyLogic;
	uint8_t KeyPhysic;
 	uint8_t KeyONCounts;
 	uint8_t KeyOFFCounts;
}KEY_TypeDef;

通过开启和关闭计数功能来消除抖动,可以有效地解决这一问题。在具体操作中,准确计算每次抖动的数值范围和时间间隔等数据至关重要,这样才能恰当地设定计数阈值。这需要我们既考虑硬件的灵敏度等特性,也要关注软件接收信号的特性。

//宏定义
#define    	KEY_OFF	   		0
#define    	KEY_ON	   	 	1
#define    	KEY_HOLD		2
#define		KEY_IDLE		3
#define		KEY_ERROR		10
#define		HOLD_COUNTS			50
#define 	SHAKES_COUNTS		5

key_scan函数的特性

//按键结构体数组,初始状态都是关闭
static KEY_TypeDef Key[2] =
	{{KEY_OFF, KEY_OFF, 0, 0},
	 {KEY_OFF, KEY_OFF, 0, 0}};

key_scan函数是按键检测的关键,必须在操作系统任务中不断循环,不能出现任何阻塞或延迟。这样的设计是为了保证操作系统整体运行的顺畅。比如,在需要快速响应的系统里,若key_scan函数出现阻塞,就会降低系统对其他任务的处理速度。

这个函数在应对不同逻辑状况时,细节相当丰富。比如在KEY_OFF逻辑里,对于连续多次点击,得进行特别处理。这里会用到多击计数来判定两次点击之间的间隔是否超过200毫秒。这样的计算是为了精确地区分是双击还是连续多次点击。在此过程中,正确运用宏定义可以便于后续功能的拓展。若要增加更高次数的连续点击功能,只需添加新的宏定义,无需改动原有代码。这显著增强了代码的可复用性和扩展性,体现了简洁高效的设计理念。

/*
 * 函数名:Key_Scan
 * 描述  :检测是否有按键按下
 * 输入  :GPIOx:gpio的port
 *		   GPIO_Pin:gpio的pin
 * 输出  :KEY_OFF、KEY_ON、KEY_HOLD、KEY_IDLE、KEY_ERROR
 */
 
uint8_t Key_Scan(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
	KEY_TypeDef *KeyTemp;
	//检查按下的是哪一个按钮
	switch ((uint32_t)GPIOx)
	{
	case ((uint32_t)KEY1_GPIO_PORT):
		switch (GPIO_Pin)
		{
		case KEY1_GPIO_PIN:
			KeyTemp = &Key[0];
			break;
		//port和pin不匹配
		default:
			printf("error: GPIO port pin not match\r\n");
			return KEY_IDLE;
		}
		break;
	case ((uint32_t)KEY2_GPIO_PORT):
		switch (GPIO_Pin)
		{
		case KEY2_GPIO_PIN:
			KeyTemp = &Key[1];
			break;
		//port和pin不匹配
		default:
			printf("error: GPIO port pin not match\r\n");

抖音双击和单击的区别_抖音双击能赚钱吗_抖音里什么叫双击

return KEY_IDLE; } break; default: printf("error: key do not exist\r\n"); return KEY_IDLE; } /* 检测按下、松开、长按 */ KeyTemp->KeyPhysic = GPIO_ReadInputDataBit(GPIOx, GPIO_Pin); switch (KeyTemp->KeyLogic) { case KEY_ON: switch (KeyTemp->KeyPhysic) { //(1,1)中将关闭计数清零,并对开启计数累加直到切换至逻辑长按状态 case KEY_ON: KeyTemp->KeyOFFCounts = 0; KeyTemp->KeyONCounts++; if (KeyTemp->KeyONCounts >= HOLD_COUNTS) { KeyTemp->KeyONCounts = 0; KeyTemp->KeyLogic = KEY_HOLD; return KEY_HOLD; } return KEY_IDLE; //(1,0)中对关闭计数累加直到切换至逻辑关闭状态 case KEY_OFF: KeyTemp->KeyOFFCounts++; if (KeyTemp->KeyOFFCounts >= SHAKES_COUNTS) { KeyTemp->KeyLogic = KEY_OFF; KeyTemp->KeyOFFCounts = 0; return KEY_OFF; } return KEY_IDLE; default: break; } case KEY_OFF: switch (KeyTemp->KeyPhysic) { //(0,1)中对开启计数累加直到切换至逻辑开启状态 case KEY_ON: (KeyTemp->KeyONCounts)++; if (KeyTemp->KeyONCounts >= SHAKES_COUNTS) { KeyTemp->KeyLogic = KEY_ON; KeyTemp->KeyONCounts = 0; return KEY_ON; } return KEY_IDLE; //(0,0)中将开启计数清零 case KEY_OFF: (KeyTemp->KeyONCounts) = 0; return KEY_IDLE; default: break; } case KEY_HOLD: switch (KeyTemp->KeyPhysic) { //(2,1)对关闭计数清零 case KEY_ON: KeyTemp->KeyOFFCounts = 0; return KEY_HOLD;

抖音双击能赚钱吗_抖音双击和单击的区别_抖音里什么叫双击

//(2,0)对关闭计数累加直到切换至逻辑关闭状态 case KEY_OFF: (KeyTemp->KeyOFFCounts)++; if (KeyTemp->KeyOFFCounts >= SHAKES_COUNTS) { KeyTemp->KeyLogic = KEY_OFF; KeyTemp->KeyOFFCounts = 0; return KEY_OFF; } return KEY_IDLE; default: break; } default: break; } //一般不会到这里 return KEY_ERROR; }

双击功能的实现

双击操作虽源自单击,却非易事。它包含一个预备双击的标志,即从按键第一次按下到第二次按下之间的等待时段。控制这一时段是实现双击功能的关键因素之一。

static void DataProcess_Task(void *parameter)
{
    while (1)
    {
        switch (Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN))
        {
        case KEY_ON:
            printf("Key1ON\n");
            break;
        
        case KEY_HOLD:
            printf("Key1HOLD\n");
            break;
        case KEY_OFF:
            printf("Key1OFF\n");
            break;
        case KEY_ERROR:
            printf("error\n");
            break;
        default:
            break;
        }
        switch (Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN))
        {
        case KEY_ON:
            printf("Key2ON\n");
            break;
        
        case KEY_HOLD:
            printf("Key2HOLD\n");
            break;
        
        case KEY_OFF:
            printf("Key2OFF\n");
            break;
        case KEY_ERROR:
            printf("error\n");
            break;
        default:
          
            break;
        }
        
        vTaskDelay(20);
    }
}

操作时,若用户连续点击两次的时间差小于预设的最大值,系统将识别为双击。否则,每次点击都会被单独记录。要确保用户操作习惯的准确性,对这一时间差的控制至关重要。时间太短或太长,用户尝试双击的功能都可能无法成功。开发者需对各种设备进行多次测试和调整,以适应不同性能对响应速度的影响。

总结与思考

因此,每个按键都需记录一系列状态,以确保准确地向程序传递信号。这一方法是在经过详尽分析后形成的,相当成熟。从基础逻辑到高级功能的实现,各个环节紧密相连。每个步骤的精准计算与处理,旨在确保按键在操作系统中的功能既准确又稳定。那么,在开发按键功能或遇到按键操作问题时,你是否有过更深入的思考或找到了独特的解决策略?一个优秀的按键检测机制对提升用户体验大有裨益,希望大家能点赞并分享这篇文章,让更多开发者从中受益。

显示全部内容...