在《玩转OLED2》https://bbs.eeworld.com.cn/thread-285761-1-1.html中我得意的炫耀了我自创的一个全中断键盘,最后有一些连击问题我还怪在了Systick头上(因为原理没问题,我以前也用过),后来又是给Systick清零,又是换通用定时器折腾了一个晚上。最后突然灵光一现,发现了一个答案~~我错了~~
错在哪儿了呢?我们还是先从按键消抖原理来说一下吧,先看一下这个按键按下的示意图:
图中上面是理想的按键按下的电平变化图,下面是实际的变化图,为了消除抖动,一般要在按键按下后等待一段时间(这里按照普遍采用的值,10mS)再去读按键的值。好了,看看我的程序流程吧。
1、 按键按下,产生GPIO中断
2、 在中断中关闭GPIO中断,防止再次触发,开启Systick,让Systick来给我延时,然后退出
3、 Systick中断到来,延时已经够了,关掉Systick定时器,已经用不着它了,读取按键的值,由于中断标志没有被清除,所以里面就记录了我们按下的按键值,直接读取该值,然后依此来确定键盘操作
4、 已经识别出按键,中断标志已经没有利用价值了,干掉它,再开启GPIO中断,等待下一次按键。
好了,整个过程就是这样,看,两个中断配合的天衣无缝…………我又开始得意了~~
不过大家注意一下第三点,“读取按键的值,由于中断标志没有被清除,所以里面就记录了我们按下的键值”直接读取这个内存单元不就可以了。这似乎是一个投机取巧的招,有问题吗?
大家再想想,这个标志是什么时候被设置的?一种恍然大悟的感觉吧(当时的感觉真是舒畅极了)。其实这里的Systick延时只是自欺欺人罢了,当第一次下降沿到来时,并非是该低电平持续一定时间后才判断按键有效的,而是任何先到来的下降沿,在延时一段时间后都当做有效按键来处理了。
那么为什么会产生连击呢?而且还有时连击有时又不连击。
前面第二点已经说了,在GPIO中断到来后会关掉GPIO中断,防止再次触发GPIO中断,而在按键事件处理完毕后又会打开GPIO中断。击键事件肯定是由GPIO中断引起的,所以也只有在GPIO在一次击键过程中多次开启才能造成的。GPIO中断会关闭多长时间呢?一次消抖延时,也就是我们的Systick中断的周期(10mS)。这样结论就出来了。
如果按键过程中第一出现的抖动下降沿和最后一次下降沿时间差超过10mS,就会引发连击事件。如果小于10mS(按键要干脆利落,爆发力强,我的天哪,怎么跟拳击似地),也就是我们的Systick中断周期覆盖了所有的抖动时间,就不会产生连击。而在键盘消抖原理中这个延时长短是跟前面抖动波形中的单个低电平持续时间进行比较的,很显然,一次击键会产生多次脉冲抖动。用一次延时的时间来对付多次抖动联合起来的时间,理论上绰绰有余的时间在这里也会捉襟见肘的。
好了,关于原理就说这么多了,下面是两个中断的代码:
void IntGPIOe(void)
{
IntDisable(INT_GPIOE);
GPIOPinIntClear(GPIO_PORTE_BASE,0x0f);
SysTickEnable();
}
void IntSysTick(void)
{
long My_IntStatus;
SysTickDisable();
My_IntStatus=GPIOPinRead(GPIO_PORTE_BASE,0x0f);
My_IntStatus&=0x0000000f;
GPIOPinWrite(GPIO_PORTF_BASE,0x01,GPIOPinRead(GPIO_PORTF_BASE, 0x01) ^ 0x01);
switch(My_IntStatus)
{
case 0x0e: Menu_Index=Menu_Unit[Menu_Index].Befo_Menu;
if (Menu_Unit[Menu_Index].CU_Line==0) Menu_Unit[Menu_Index].CU_Line=Menu_Unit[Menu_Unit[Menu_Index].Next_Menu].CU_Line - 1;
if (P_Function==0) P_Function=Menu_Display; else P_Next_Function=Menu_Display;
break;
case 0x0d: Menu_Index=Menu_Unit[Menu_Index].Next_Menu;
if (Menu_Unit[Menu_Index].CU_Line==0) Menu_Unit[Menu_Index].CU_Line=Menu_Unit[Menu_Unit[Menu_Index].Next_Menu].CU_Line + 1;
if (P_Function==0) P_Function=Menu_Display; else P_Next_Function=Menu_Display;
break;
case 0x0b: if (Menu_Unit[Menu_Index].Prev_Menu !=0)
{Menu_Index=Menu_Unit[Menu_Index].Prev_Menu;
if (P_Function==0) P_Function=Menu_Display; else P_Next_Function=Menu_Display;}
break;
case 0x07: if (Menu_Unit[Menu_Index].Child_Menu !=0)
{Menu_Index=Menu_Unit[Menu_Index].Child_Menu;
if (P_Function==0) P_Function=Menu_Display; else P_Next_Function=Menu_Display;}
break;
default :break;
}
IntEnable(INT_GPIOE);
}
IntSysTick中断中有一个对PF0进行操作的语句,这是我在测试中进行观察的。PF0接到开发板上的一个LED上,每进入一次IntSysTick中断,灯就会翻转一次。实际按键过程中,该灯有时会在一次按键中多次闪烁,表示多次进入过这个中断,而菜单上只有一次动作。说明程序现在已经能够消除掉无效的抖动了。