基本的按键程序结构分析:
1 void key_scan(void) 2 { 3 if (!key) //检测按键是否按下 4 { 5 delay_ms(20); //延时去抖,一般20ms 6 if(!key) 7 { 8 ...... 9 } 10 while (!key); //等待按键释放 11 } 12 }
注意:以上基本按键程序中,在按键执行之后必须要加上等待按键释放,否则程序会出现一些奇怪的问题,比如说按键累加时按键一次,却累加了多次。
可识别长击和短击按键程序(有限状态机):
主函数文件:
main.c
1 #include "key.h" 2 3 sbit LED = P2^0; 4 5 u8 timer0_flag; //定时器10ms计时标记 6 7 8 void timer0_init(void) 9 { 10 TMOD |= 0x01; //定时器0的方式1(16位定时器) 11 TH0 = 0xDC; //定时10ms初值 12 TL0 = 0x00; 13 14 ET0 = 1; 15 TR0 = 1; 16 EA = 1; 17 } 18 19 void main(void) 20 { 21 key_init(); //按键初始化,51单片机在读取某个端口的值时,先拉高 22 timer0_init(); //定时器初始化 23 24 for (;;) 25 { 26 if (timer0_flag) 27 { 28 timer0_flag = 0; 29 30 switch (key_driver()) 31 { 32 case KEY_SHORT: LED = 0; break; //短击点亮LED灯 33 case KEY_LONG : LED = 1; break; //长击熄灭LED灯 34 } 35 } 36 } 37 } 38 39 void timer0_int(void) interrupt 1 //中断处理函数 40 { 41 TH0 = 0xDC; 42 TL0 = 0x00; 43 timer0_flag = 1; 44 }
主文件里非常重要的有两处:
1、时间粒度控制:本程序以10ms做时间单位,类似于时间片轮询的方式,每隔10ms对按键状态扫描一次,对应代码为:
void timer0_int(void) interrupt 1 //中断处理函数
{
TH0 = 0xDC;
TL0 = 0x00;
timer0_flag = 1; //10ms时间
}
2、对按键驱动函数返回值的判断,根据按键返回值,识别按键操作是短击还是长击后,执行相应的动作,对应代码为:
switch (key_driver())
{ case KEY_SHORT: LED = 0; break; //短击点亮LED灯
case KEY_LONG : LED = 1; break; //长击熄灭LED灯
}
按键驱动文件:
key.c
1 #include "key.h" 2 3 sbit KEY_INPUT = P3^3; //独立按键 4 5 key custom_key; //按键的数据结构体 6 7 void key_init(void) //按键初始化 8 { 9 KEY_INPUT = 1; 10 } 11 12 u8 key_driver(void) //按键驱动函数 13 { 14 custom_key.press = KEY_INPUT; //读取按键接口值 15 custom_key.value = KEY_NONE; //返回值初始化为无值 16 17 switch(custom_key.state) //按键状态判断 18 { 19 case KEY_STATE_JUDGE: //判断有无按键按下状态 20 if (!custom_key.press) //有按键按下 21 { 22 custom_key.state = KEY_STATE_DEBOUNCE; //转入消抖状态 23 custom_key.count = 0; //计数器清零 24 } 25 break; 26 27 case KEY_STATE_DEBOUNCE: //消抖状态 28 if (!custom_key.press) 29 { 30 custom_key.count ++; 31 if (custom_key.count >= SINGLE_KEY_TIME) 32 { 33 custom_key.state = KEY_STATE_SPAN; //消抖确认是有效按键,转入短击和长击判断状态 34 } 35 } 36 else 37 custom_key.state = KEY_STATE_JUDGE; //按键误动作,返回判断有无按键按下状态 38 break; 39 40 case KEY_STATE_SPAN: //短击和长击判断状态 41 if (custom_key.press) //在长击临界值之前释放按键,判断为短击 42 { 43 custom_key.value = KEY_SHORT; 44 custom_key.state = KEY_STATE_JUDGE; //返回判断有无按键按下状态 45 } 46 else //计数器值超过长击临界值,判断为长击 47 { 48 custom_key.count ++; 49 if (custom_key.count >= LONG_KEY_TIME) 50 { 51 custom_key.value = KEY_LONG; 52 custom_key.state = KEY_STATE_RELEASE; //进入按键释放状态 53 } 54 } 55 break; 56 57 case KEY_STATE_RELEASE: //按键释放状态 58 if (custom_key.press) 59 { 60 custom_key.state = KEY_STATE_JUDGE; //返回判断有无按键按下状态 61 } 62 break; 63 64 default: //默认返回判断有无按键按下状态 65 custom_key.state = KEY_STATE_JUDGE; 66 break; 67 } 68 return custom_key.value; //返回按键值 69 }
代码中做了详细的注释,需要说明的是按键的四种状态:
KEY_STATE_JUDGE:用来检测是否有按键按下, 当有按键按下后,转移到消抖状态,否则每次时间片扫描时都处于此状态 KEY_STATE_DEBOUNCE:消抖状态,用来检测按键有效还是误触发,假如只是误触发,则返回到按键等待状态 KEY_STATE_SPAN:判断按键是长击还是短击,如果在延时消抖后,按键在长按的临界值之前释放,则判断为短击,否则判断为长击,此处的临界值为2s KEY_STATE_RELEASE:按键释放状态,摆脱用while循环等待按键释放,当判断为长击以后,程序将进入此状态,在此之后只需在每次时间片到了以后判断是否释放即可
此处需要理解的是“并行”的思想:主程序一直在for(;;)循环中运行,同时定时器也在不断累加计数,当达到定时器中断触发条件后,定时器中断当前的循环,进入定时器服务程序。因此,
这四种状态在每次定时器中断触发后就会检测判断一次,相隔时间为10ms。
头文件:
key.h
1 #ifndef _KEY_H_ 2 #define _KEY_H_ 3 4 #include "reg52.h" 5 6 typedef unsigned char u8; 7 typedef unsigned int u16; 8 9 #define KEY_STATE_JUDGE 0 //判断有无按键按下状态 10 #define KEY_STATE_DEBOUNCE 1 //消抖状态 11 #define KEY_STATE_SPAN 2 //判断是短按还是长按 12 #define KEY_STATE_RELEASE 3 //按键释放 13 14 #define LONG_KEY_TIME 200 //按键持续超过2s,判断为长击 15 #define SINGLE_KEY_TIME 2 //消抖 16 17 #define KEY_NONE 0 //没有按下按键 18 #define KEY_SHORT 1 //短击 19 #define KEY_LONG 2 //长击 20 21 typedef struct 22 { 23 u8 press; //读取按键接口 24 u8 state; //按键状态 25 u8 value; //按键返回值 26 u16 count; //按键时间计数器 27 }key; 28 29 //extern key custom_key; 30 31 void key_init(void); //按键初始化函数 32 u8 key_driver(void); //按键驱动函数 33 34 #endif
文件中定义了需要的变量、数据结构,以及函数声明。