按键的单击、双击、连续按、短按和长按实现思路

概念区分

看到好多教程说了这些概念,有的说单击就是短按,连续按就是长按等等。

其实,仔细想想,这几个概念是有一些区别的。

假如一个按键,没按下时是高电平,按下时是低电平,以此来理解这些概念。

单击:按键按下后,第一次检测到低电平,则触发动作,之后即使按键还处于低电平状态,也不会触发动作。

双击:必须连续按下两次,才会触发相应的动作。

连续按:只要按键一直被按下,即一直处于低电平状态,则会根据调用间隔不停地触发动作。

短按:按键按下后不会立即触发动作,而是会经过一小段时间,才会触发动作。

长按:按键按下后不会立即触发动作,而是会经过一长段时间,才会触发动作。

注意区分以上五种功能,以下记录实现思路。

补充注意:在配置外部中断时,有个上升沿触发或者下降沿触发,这里指的是,下降沿发生时触发了中断,但是判断按键是否处于按下状态,依然是判断低电平。

连续按

连续按相对简单,所以先讲这个。

按下去之后,就会一直有效,具体有效的间隔时长,可以自行设定,比如快点的话,就1秒钟检测60次,慢点的话,就1秒钟检测十几二十次差不多,再慢点,就一秒钟检测一两次也行,具体根据需要来。但是间隔一定不能超过人能反应过来的时间。

只要固定间隔比如100ms检测一次电平,如果是有效电平,就触发动作即可。

对应的按键扫描思路如下:

这个代码可以写在硬件层,然后业务层调用后判断即可。

比如,业务层每隔100ms调用一次该函数,每次都返回的是1,说明一直处于按下状态,然后就执行相应的动作即可。

想想键盘按键被卡住。

单击

单击则是在连续按的基础上,做一些限制,如果之前已经按下过,那么之后即使还处于按下状态,也不会触发动作。

因此,需要加一个变量,来记录按键是否已经被按下过。

不支持连续按的实现,有一个关键点,就是这一次的判断和上一次的按键状态有关,怎么理解呢?如果上一次是高电平,这一次检测是低电平,那么就会因为是第一次触发,所以会被视为有效。但是如果这次检测到的是低电平,但是其上一次检测的也是低电平,那么,就表示是发生了连续按,此时,需要阻止其触发动作。

所以,扫描按键在判断时,不能只判断这一次是否处于按下状态,还要结合上一次的按键的状态。

对应的按键扫描思路如下:

这段代码的最后应该还有个return 0;

注意,这里的是否已经被按下标志key_up需要定义为static变量,是为了让其只在第一次调用时被初始化。

这里的代码理解就是,只有之前没被按下并且这次被按下才能生效,如果没有被按下,或者之前已经被按下这次还是按下,都不会生效。

上面的方式,轮询和外部中断中都适用。

如果是外部中断,还有另外的实现思路

就是下降沿触发进入中断后,给一个全局的状态标志,只要触发进入了中断,则状态标志置位,触发功能之后,状态标志再复位。

再简单点,直接将操作逻辑放在中断里,这样,按下一次就只会触发一次,后续即使还是按下的状态,也不会重复执行。

有时,为了不在中断里放过多的业务逻辑,保证中断的快进快出,就可以只在中断里判断标志位,然后在其他地方(比如按键的专用c文件中)根据标志位执行相应的动作。

其实,就算在中断里,也还是可以根据低电平来判断是不是处于按下状态,从而实现单击或者连续按。

这里算是提供基于边沿触发的外部中断下的一种思路吧。

双击

短按和长按

短按和长按其实都是同一种思路,区别就是延时触发的延迟时间长短不同。

所以,这里的关键就是,如何合理地检测低电平持续的时间

短按,其实可以理解为单击,就是按下之后要很快松开,因为时间长了会被判定为长按。

一开始,我的想法是,检测是否有低电平发生,如果有,就再延时一段时间比如5s,之后再次判断是否还是低电平,如果是,则表示发生了长按。可是,一细想,又发现这个思路有BUG,如果我先按下按键,然后松开,等到快5秒的时候再按下,这样,也会两次都检测到低电平,但实际并不是长按。

我又想,那么在5秒内,一直判断是不是低电平不就行了,所以,需要造一个5秒的for循环,在里面一直判断是不是低电平,只要有一个高电平发生,就不能判定为长按,而是被判定为短按。

static void KEY_Detect() 
{
	uint8_t i = 0;
	
	if(KEY1.KEY_Flag == TRUE)//先判断有按键按下
	{
		KEY1.Click = FALSE;
		KEY1.Press = TRUE;
		//触摸按键长按检测
		for(i=0;i<500;i++)
		{
			HAL_Delay(10);
			//如果5s内,按键状态出现高电平,此时按键为短按,跳出循环
			if(检测到高电平)
			{
				KEY1.Click = TRUE;
				KEY1.Press = FALSE;
				break; //跳出for循环
			}
		}
		
		if(是短按)
		{
			//短按对应的动作
		}
		
		if(是长按)
		{
			//长按对应的动作
		}
		
		//清除按键状态
		KEY1.KEY_Flag = FALSE;
		KEY1.Click = FALSE;
		KEY1.Press = FALSE;
	}
}

这种思路是可行的,但是根据for循环来实现按键时长,并不精准。

如果想要更精准的时间判断,怎么办呢?

最好是结合定时器。

定时器的思路其实和上述for循环是一样的。

也是每隔10ms判断一次按键状态,只是定时器方式是将这个判断放到定时器中了。

可以让其在10ms定时器里进入判断500次,只要没有高电平出现,就置位长按标志,否则置位短按标志。

之后再根据这个标志去执行相应动作即可。

又或者在定时器里面判断低电平来计数,只要计数大于等于500,就可以执行相应动作。

比如:

static int cnt=0;
if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
{
    cnt++;
    if(cnt>500)
    {
        PRINT("long press\n");
        ……
        cnt =0;
    }
……
}

注意,编程时,不一定非要根据某种动作来执行另一种动作,可以先根据一种动作来设置一些相应的标志位,然后在其他地方根据标志位再进行一些动作,这样更加灵活。

STM32按键单击双击按时检测通常需要结合中断定时器功能。这里简述一下基本步骤,并给出一个大致的伪代码框架,实际编码需配合HAL库API: 1. 首先,在初始化阶段,配置按键中断: ```c void EXTI_Config(uint8_t keyPin) { GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; // 配置GPIO输入模式 GPIO_InitStruct.Pin = keyPin; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 配置EXTI线 EXTI_InitStruct.Pin = keyPin; EXTI_InitStruct Line = EXTI_Line0; // 替换为你的按键对应的EXTI线 EXTI_InitStruct.Mode = EXTI_Mode Falling; EXTI_InitStruct.ActiveState = EXTI_ActiveHigh; EXTI_InitStruct.Interrupt = ENABLE; HAL_EXTI_Init(&EXTI_InitStruct); } ``` 2. 然后,设置定时器来检测按时限: ```c Timer_HandleTypeDef timerHandle; void TimerConfig(uint32_t timeout) { TIM_TimeBaseInitTypeDef.TIM_Prescaler = ...; // 根据系统频率计算预分频值 TIM_TimeBaseInitTypeDef.TIM_CounterMode = TIM_COUNTERMODE_UP; TIM_TimeBaseInitTypeDef.TIM_Period = timeout - 1; if (HAL_TIM_Base_Init(&timerHandle) != HAL_OK) { // 错误处理 } HAL_TIM_Base_Start_IT(&timerHandle); // 启动定时器 } ``` 3. 接下来是按键事件处理函数,它会检查是否是单击双击还是按时限: ```c static uint32_t lastPressTime = 0; // 上一次按键按下时间 static bool isLongPress = false; void Key_IRQHandler(void) { if (HAL_GPIO_ReadPin(GPIOA, keyPin)) { // 按键释放 uint32_t currentTime = HAL_GetTick(); if (currentTime - lastPressTime < 500) { // 双击检查 // 双击逻辑... isLongPress = false; } else { if (!isLongPress) { // 单击逻辑... } else { if (timeoutExpired) { // 按时达到9秒逻辑... } else { // 按时小于9秒逻辑... } } lastPressTime = currentTime; } } } ``` 这里的`500`是一个简化示例,实际双击间隔应根据应用需求调整。对于按时限,你需要在`Key_IRQHandler`中检查定时器是否超时。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值