概念区分
看到好多教程说了这些概念,有的说单击就是短按,连续按就是长按等等。
其实,仔细想想,这几个概念是有一些区别的。
假如一个按键,没按下时是高电平,按下时是低电平,以此来理解这些概念。
单击:按键按下后,第一次检测到低电平,则触发动作,之后即使按键还处于低电平状态,也不会触发动作。
双击:必须连续按下两次,才会触发相应的动作。
连续按:只要按键一直被按下,即一直处于低电平状态,则会根据调用间隔不停地触发动作。
短按:按键按下后不会立即触发动作,而是会经过一小段时间,才会触发动作。
长按:按键按下后不会立即触发动作,而是会经过一长段时间,才会触发动作。
注意区分以上五种功能,以下记录实现思路。
补充注意:在配置外部中断时,有个上升沿触发或者下降沿触发,这里指的是,下降沿发生时触发了中断,但是判断按键是否处于按下状态,依然是判断低电平。
连续按
连续按相对简单,所以先讲这个。
按下去之后,就会一直有效,具体有效的间隔时长,可以自行设定,比如快点的话,就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; } …… }
注意,编程时,不一定非要根据某种动作来执行另一种动作,可以先根据一种动作来设置一些相应的标志位,然后在其他地方根据标志位再进行一些动作,这样更加灵活。