本次使用stm32作示范,并且出于某些原因,我身边没有实物stm32,使用的是Proteus 8 Professional仿真,可以会有些许误差或者问题。
一.按键消抖
按键消抖使用的是常见的定时器消抖,可以说是许多前辈的代码,以下是借鉴的代码出处。
int Key_GetNum(void)
{
int KeyNum=0;
if(Key1==0) KeyNum=1;
else if(Key2==0)KeyNum=2;
else if(Key3==0)KeyNum=3;
else if(Key4==0)KeyNum=4;
return KeyNum;
}
void Key_Run(void)
{
uint8_t Key_New,Key_Down,Key_Up;
if(Key_Time<10) return;//10毫秒后进入按键判断,实现消抖
Key_Time=0;
Key_New=Key_GetNum();
Key_Down=Key_New&(Key_New^Key_Old);
Key_Up=~Key_New&(Key_New^Key_Old);
Key_Old=Key_New;
switch(Key_Down)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
}
}
单次按下按键,Key_Down会进行一次识别,即会有一次有值,但一直按着的话只有Key_Old有值,Key_Down为0。
将key_run();放在主函数,Key_Time放在1ms定时器里自增。
int main(void)
{
Timer2_Init(72,1000);//72-1的分频,1000-1的计数,1ms
Key_Init();//按键初始化引脚
while(1)
{
Key_Run();
}
}
void TIM2_IRQHandler()
{
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update)==SET)
{
Key_Time++;//自增
TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);
}
}
注意:使用该代码时,主函数不宜使用长时间的delay延时。中断服务函数中代码不多时 Key_Run();可放在中断里。
以上代码可以实现简单的按键单击消抖,下面我进行一些扩展。
二.单击+松手+长按
松手很简单,就是对Key_Up进行判断就行。
长按就是一直按住不放,当按住时进行一个变量的自增,当到一定值时,就可以判断为长按,此次我再定义一个32位的变量Key_long_Time自增,便实现一个简单的长按函数了。
void Key_Run(void)
{
uint16_t Key_New,Key_Down,Key_Up;
if(Key_Time<10) return;
Key_Time=0;
Key_New=Key_GetNum();
Key_Down=Key_New&(Key_New^Key_Old);
Key_Up=~Key_New&(Key_New^Key_Old);
Key_Old=Key_New;
if(Key_Down)//一直按着时,不会进入这里
{
Key_long_Time=0;//此处清空,防止计数到长按规定的值
switch(Key_Down)
{
case 1:
break;
case 2:
break;
}
}
if(Key_long_Time>1000&&Key_Old)//长按
{
switch(Key_Old)
{
case 1:
break;
case 2:
break;
}
}
if(Key_Up)//松手
{
Key_long_Time=0;//松手清空长按数据
switch(Key_Up)
{
case 1:
break;
case 2:
break;
}
}
}
值得注意的是:长按时,短按也会执行一次,并且一直按时,会不断进入长按执行代码。
例如:定义一个num,短按一次num加1,长按num会快速增加。
如果你是希望一个按键短按自增1次,长按快速自增,这个函数应该还能用,但是我感觉还有提升空间,希望有能力的网友能完善。
void TIM2_IRQHandler()//1ms的中断服务函数
{
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update)==SET)
{
Key_Time++;
Key_long_Time++;
TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);
}
}
三.单击+多击+长按
此时看似多加了一个功能,但代码要变更许多。
if(Key_Down)//单击加上多击
{
Key_long_Time=0;
switch(Key_Down)
{
case 1:
if(Key_Down_Time==0)//判断没有按过一次
{
Key_Down_Count++;//加一次按下次数
Key_Down_Flag = 1;//开始计时标志位,在定时器中使Key_Down_Time自增
}
else if(Key_Down_Time>0&&Key_Down_Time<600)//600ms内判断按键按下次数,如果手速更快可以适当减少判断时间
{
Key_Down_Count++;//在规定的600ms时间内按下,按下次数自增。
}//此为识别按键次数
break;
case 2:
break;
}
if(Key_Down_Time>=600&&!Key_Old)//超过按下的规定时间,并且没有一直按住按键,开始执行按键代码
{
Key_Down_Flag = 0;//清空计数标志位
Key_Down_Time=0;//清空计数
switch(Key_Down_Count)//开始判断
{
//按下一次,即单击
case 1:
led[2]^=1;
Key_Down_Count=0;//执行完后,清空按下次数,以下亦如此
break;
//按下两次,即双击
case 2:
led[3]^=1;
Key_Down_Count=0;
break;
//按下三次,即三击
case 3:
led[1]^=1;
Key_Down_Count=0;
break;
//按下n次,即多击
//多击要适当延长判断时间
case n:
Key_Down_Count=0;
break;
}
}
else if(Key_Down_Time>=1000&&Key_Old)//超出长按时间,并且按键一直按着,执行长按代码
{
Key_Down_Flag = 0;//清空计数标志位
Key_Down_Time=0;//清空计数
led[0]^=1;
Key_Down_Count=0;//执行完后,清空按下次数,以下亦如此
}
如果你只使用单击双击和长按,且由于各按键的不同,你可以将判断的600ms时间缩短到你觉得适合的时间
void TIM2_IRQHandler()//1毫秒的中断服务函数
{
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update)==SET)
{
Key_Time++;
if(Key_Down_Flag==1)
{
Key_Down_Time++;
}
TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);
}
}
此代码仍有瑕疵,无论是单击,双击,三击亦或者多击,都要等到判断时间结束才能执行按键代码,并且使用多按键时,各变量可能会相互影响(目前我还没研究完,看看情况),建议在使用多按键时,不要同时按其他按键。长按时不会影响单击,并且只能执行一次,一直按下去不会进行多次执行。好处也很明显,各功能不会相互影响,把握好就能让一个按键实现多种功能
最后,本人新人,如果有什么不足之处,希望大佬们能指点一二。