单片机中关于按键的优化
前言
实习了一个星期,就按键这个细节简单谈谈,好的按键反馈会让操作者心情愉悦,不好的按键体验会让操作者暴跳如雷,恨不得把板子吃了。平常用到的有三种按键方案:中断捕获按键,定时器扫描按键和while死等按键抬起。while死等就不用多说了,按下按键整个系统都停止工作了,体验极其不好,定时器扫描的体验会好很多,体验最好的莫过于中断捕获按键,但不巧的是在一些低端的芯片中,中断是宝贵的资源,功能简单的用中断捕获最好不过了,但是功能稍微复杂一点的中断就会被用于其他更重要的地方了,那么剩下的就只能用定时器扫描了。至于while死等按键抬起方案,在学校学学就可以了。
一、按键的工作原理?
将按键和地(负极)接在一起,单片机通过检测对应的引脚是否为低来判断按键是否按下,如果用while死等方案还要进行20ms的延时消抖,会占用cpu资源。定时器扫描不用消抖,因为定时器没20ms扫描一次按键,这个过程就已经消抖了。这个文章主要介绍我的定时器扫描按键。中断不做讲解。
二、定时器扫描实现过程:
开启一个1ms的定时器中断,定时器中断服务函数中定义一个静态变量Time_Cnt。用来检测进入了多少次中断。当Time_Cnt等于20时,将Key_Scan_Flag标志位置1(注:如果定时器比较空闲或者要处理的操作简单,比如给五六个变量赋值之类的简单操作,这个时候就可以不用Key_Scan_Flag标志位,直接在中断服务函数中进行赋值,本案例就没有用这个标志位,如果定时器要处理很多数据,就可以用这个标志位快进快出)。主循环中一直判断这个标志位,为1后调用Key_Scan函数。Key_Scan函数的功能是将两个全局变量before_state,now_state赋值,如果before_state = 1,now_state = 0,就表示按键1按下的一瞬间,before_state = 0,now_state = 1,就表示按键1弹起的一瞬间,before_state = 0,now_state = 0,就表示按键1持续按下,before_state = 1,now_state = 1,就表示按键1空闲状态。
代码实现:
1、key.c
unsigned char before_state=0,now_state=0;
unsigned char key_keyNumber;
unsigned char Key_GetState()
{
if(S1==0){ return 1; }
if(S2==0){ return 2; }
if(S3==0){ return 3; }
if(S4==0){ return 4; }
return 0;
}
void key_loop()
{
S1_flag = 1;
S2_flag = 1;
S3_flag = 1;
S4_flag = 1;
before_state = now_state;
now_state = Key_GetState(); //获取按键标志位
//上一个状态有按键按下,现在状态没有按键按下,说明处于按下后松手阶段
}
2、Key.h
#ifndef _KEY_H__
#define _KEY_H__
#include <STC8H.H>//单片机包含头文件
#define Key_FREE (before_state == 0 && now_state == 0) //没有按键按下
#define S1_PRESS (before_state ==1 && now_state == 0) //S1弹起的一瞬间
#define S1_UPSRRING (before_state == 0 && now_state == 1) //S1按下的一瞬间
#define S1_KEEP (before_state == 1 && now_state == 1) //S1保持(按下不松开)
#define S2_PRESS (before_state == 2 && now_state == 0)
#define S2_UPSRRING (before_state == 0 && now_state == 2)
#define S2_KEEP (before_state == 2 && now_state == 2)
#define S3_PRESS (before_state == 3 && now_state == 0)
#define S3_UPSRRING (before_state == 0 && now_state == 3)
#define S3_KEEP (before_state == 3 && now_state == 3)
#define S4_PRESS (before_state == 4 && now_state == 0)
#define S4_UPSRRING (before_state == 0 && now_state == 4)
#define S4_KEEP (before_state == 4 && now_state == 4)
/*按键对应的接口*/
sbit S1 = P1^4;
sbit S2 = P1^5;
sbit S3 = P1^6;
sbit S4 = P1^7;
extern bit S1_flag;
extern bit S2_flag;
extern bit S3_flag;
extern bit S4_flag;
void key_loop();
#endif
3、定时器初始化Timer.c
extern void key_loop();
void Timer0Init(void){ //1毫秒@11.0592MHz
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //16位自动重载
TL0 = 0xCD; //设置定时初值
TH0 = 0xD4; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
}
void TM0_Rountine(void) interrupt 1
{
static unsigned int key_scan=0;
key_scan++;
if(key_scan==20)//20ms按键检测
{
key_scan=0;
key_loop();
}
}
4、main函数
int main()
{
unsigned char keep_temp=0,key_val = 0 ,num = 0;
while(1)
{
if( S2_KEEP && S2_flag)//长按操作:长按每100ms加1,短按的话只要没有超过100ms就只加1,相当于短按一下按键。
{
keep_temp++;
if(keep_temp==5)//长按了100ms
{
keep_temp=0;
if( num < 255 )//长按每100ms对num进行++操作
{
num++;
}
}
S2_flag = 0;
}
if( S2_PRESS && S2_flag )//S2_flag防止多次触发按键动作
{
keep_temp=0;
key_val = before_state;//s2按下的一瞬间before_state=2,now_state = 0;
S2_flag=0;
}
if( S3_UPSRRING && S3_flag )//S3抬起的一瞬间
{
keep_temp=0;
key_val = now_state ;//s3抬起的一瞬间before_state=0,now_state = 3;
S3_flag=0;
}
display(1,key_val);//数码管显示函数,1表示第一个数码管,key_val表示要显示的数值
}
}
4、如果要求s1长按1秒:
main.c中加入:
extern bit Key_LongPress = 0 ;//Timer.c中的全局变量
static bit Key_LongPressFlag = 0 ;
if( S1_KEEP )//如果一直按下,给定时器一个信号,开始计时
{
Key_LongPressFlag = 1;//告诉定时器开始计时
}
else
{
Key_LongPressFlag = 0;//如果中途抬起按键,告诉定时器停止计时并清除状态
}
if( Key_LongPress )//如果定时器发出了已经计时1S的信号。
{
/*长按1S后的操作*/
SEG_FlashFLAG=1;//给定时器信号
Key_LongPress = 0;S1_flag=0;
}
定时器中断函数中加入:
static unsigned int Key_LongPressCnt=0;
if( Key_LongPressFlag )//如果main函数给了定时信号就开始计时
{
Key_LongPressCnt++;
if(Key_LongPressCnt > 999)//1s
{
Key_LongPress = 1;//告诉main函数,计时到了1秒,可以做出相应的操作
Key_LongPressCnt=0;
}
}
else//如果main中断了长按信号,定时器就清除计时并告诉main计时不足1S
{
Key_LongPressCnt = 0;
Key_LongPress = 0; //告诉main计时不足1S
}
总结
以上就是我对定时器扫描按键的优化建议及代码实现,如果有更好的优化方案,欢迎留言。
注:本文章是我根据目前的经验写的,只是一种实现的方案,如果完全Ctrl + c的话,可能达不到预期的要求。