背景
只要使用单片机,按键检测基本上是一定要实现的功能。按键检测要好用,最重要的是实时和去抖。初学者往往会在主循环调用按键检测程序(实时)并利用延时去抖(准确)。这种在主循环内延时的做法对整个程序非常不友好,也非常不高效。因此,本篇就我自己实现的一个检测按键并可判断按键是否长短按的程序做个介绍和记录。
正文
在硬件连接上,按键一端连接在普通IO口上,另一端接地,IO配置为内部弱上拉。
在软件上,先配置一个5ms定时器并打开中断,每进入该定时中断则置位一次标志位“key_handle”。接着在主循环调用一个“scan_key()”函数,判断“key_handle”标志位是否在定时器内被置位,若被置位则将该位复位并读取连接按键的IO口值。
此时,“scan_key()”函数内分为按键按下和松开两个分支:
-
按键按下,则计数值“longkey”每隔5ms自加一次,因为这个分支每隔5ms才会进入执行一次;
-
按键放开,则先判断“longkey”的值,若“longkey”的值换算出来代表按键按下时间在10ms-1s内,(10ms是去抖值,1s是与短按与长按的分界点。)则判断按键为短按;若“longkey”的值大于1s,则判断按键为长按。并将按键状态赋值给按键状态变量“keybuf”。同时,由于此时按键已经放开,因此“longkey”的值要置位“0”等待用户下次按下按键并执行从“0”开始的自加操作。
若程序又一次进入按键检测代码段,说明所有功能块代码已经获知key状态,有对key感兴趣的代码段也肯定已经进行过相应处理,因此此时要及时将“keybuf”置为无按键按下状态以此来同步实际按键状态。
实际代码如下:
void scan_key(void)
{
unsigned char key_status;
if((longkey == 0) && (keybuf != _KEYNULL)){
// 在keybuf被标记为长按或短按后,若是按键已经松开,
// 则在主循环跑完一次后,及时将按键状态标记为无按键按下。
keybuf = _KEYNULL;
}
if(key_handle == _ON) { //5ms进入一次
key_handle = _OFF; // 复位5ms标志位
key_status = GPIO_ReadInputDataBit(KEYRESET_GPIO_PORT, KEYRESET_GPIO_PIN);
if(key_status == 0) { //按下按键
longkey++;
}
else { // 松开按键
if((longkey >= 3) && (longkey <= 100) ){ // 15ms - 1s
keybuf = _SHORTKEY;
} else if(longkey >= 200) { // 2s
keybuf = _LONGKEY;
} else {
keybuf = _KEYNULL;// 若为扰动,按键状态也该为无按键按下
}
longkey = 0;
}
}
}
补充:
和Tony~Liu讨论了下,发现其实还有效率更高,占用CPU更少的按键检测办法。
在硬件上,按键连接在具有外部中断的IO口上,按键连接在该IO口上的脚外部上拉,按键另一端接地。
在软件上,连接按键的IO口配置为双边沿中断,同时配置一个1ms定时器中断。当按键按下时,触发外部中断,在外部中断内判断IO口电平,以此确定此为上升沿还是下降沿。下降沿则代表按键按下,开始计时,上升沿则代表按键松开,停止计时。上升沿中断时,在中断内置位“key_handle”,主循环在判断到“key_handle”被置位后,则开始判断计时器时间,若是时间在10ms-1s内,(10ms是去抖值,1s是与短按与长按的分界点。)则判断按键为短按;若时间大于1s,则判断按键为长按。具体实施也很简单,在此就不再贴代码,只是说说这种思想。
至此,记录完毕
记录时间:2017-1-19
记录地点:深圳WZ