这也许是第一个接触的单片机输入设备了,而且这玩意能玩很久,基本上有啥没啥都能加一个上去,执行某些你想达到的功能。
这里的独立按键也叫“轻触式按键”,这是我们平日里面见到的大部分按键,(这里的轻触式机械层面上的接触,而不是感光或者电容屏),所以按键的特性其实是一种机械性质
这里的初始就是有没有按下去。但是,查阅资料发现,绿色导线是永久接通的?那我还真没试过,所以用按键的时候建议用万用表测这个按键哪两个引脚是嫩能够起到开关作用,某个自锁开关也是如此。
对于按键,还有一个问题要解决,那就是消除抖动。
抖动是啥意思?理想状态下按键按下去电平时这样的:
可实际上是:
这是由于通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。 只有一小段是平整的,其余抖动的部分都会给单片机捕获判断带来影响。
所以消除抖动,哦,应该是规避抖动范围(只不过我们都习惯叫消除抖动而已)其实就是对单片机捕获低电平判断(或者也有反向捕获高电平判断)。
首先来认识一下按键延时消抖,按键由于是机械结构,按下的时候难免产生抖动,一般抖动会在按下的时候与松开的时候产生,抖动时间大概是10ms,通常我们手动按键然后释放,这个动作中稳定闭合的时间超过了20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作,有专用的去抖动电路,也有专门的去抖动芯片,但通常我们采用延时的方法就可以解决抖动问题。
于是针对按键抖动就有了下面几个章节的解决方法。
在使用51单片机读取按键转态时,将按键连接的单片机接口赋值1(这种方式适合51单片机,但不一定适合其他单片机),如果按键按下,则端口被拉低。因此,通过读取单片机接口的电平状态就可以判断按键是否按下,如果输入时高电平,则按键没有按下;如果输入是低电平,则按键按下。
由上面的讲解我们已知,按键的按下会出现信号的抖动,这会造成单片机的误判断。可能造成按下一下按键却判断成按下了多次按键。为了得到正确的结果,要对按键进行去抖。去抖分为硬件去抖和软件去抖两种。
硬件去抖就是在按键的两端加上一个电容,软件去抖则不需要增加硬件成本。只需要软件处理。软件去抖的具体方法是:当判断有按键按下时,程序延时一段时间,跳过这个抖动区域,之后再检测按键状态。如果再次检测时输入时高电平,说明是抖动或干扰造成的。如果输入是低电平,说明确实有按键按下。
下面的代码是对按键按下的典型判断语句,先判断KEY的值是否为0,如果为0则延时10ms,然后再次读取KEY的值,依然为0则判断为按键按下,进行按键按下的处理代码,最后等待按键松开后退出。
按键软件延时函数消抖一般分为4步:
1、判断按键是否按下
2、消抖
3、再次判断按键是否按下
4、等待按键松开
if(KEY==0) //按键KEY按下
{
delay1ms(10); //延时10ms去抖
if(KEY==0) //再次判断按键KEY按下
{
//加入处理代码
}
while(KEY==0); //等待按键松开
}
延时函数按键消抖
检测出键闭合后执行一个延时程序,5ms~10ms(取决于机械特性)的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。当检测到按键释放后,也要给5ms~10ms的延时,待后沿抖动消失后才能转入该键的处理程序。
优点:简单方便
缺点:程序在空跑浪费CPU资源、不够精准
接下来我们就通过软件延时函数消抖的方式来让独立按键去操控led灯的亮灭。
示例程序如下:
#include
#define uint unsigned int
#define uchar unsigned char
//定义按键端口
sbit key = P3^4;
//定义LED灯端口
sbit led = P1^0;
//延时函数声明
void delay(uint xms);
//程序入口
void main()
{
led=0;
while(1)
{
if(key==0)//第一次判断
{
delay(1000); //延时10ms 消除抖动
while(key==0); //第二次判断
delay(1000);
led=~led; //led取反控制亮灭
}
}
}
void delay(unsigned int i)
{
while(i--);
}
接下来我们引入了外部中断来检测按键,用定时器来进行延时,51单片机的外部中断0引脚接一只按键,该按键通过上拉电阻接到电源,即没有按键发生时单片机检测到的是高电平,当按键按下时单片机检测到的是低电平。这样的好处就是可以不占用cpu资源,提高cpu的利用率。
外部中断消抖原理:
1、判断按键是否按下,
2、若检测到有按键按下,则开启定时器,开启定时中断,定时时间为10ms 左右,使得按键按下10ms后进入定时中断,进入中断的时候按键抖动时间已过
3、在定时器中断中再次判断按键是否按下
4、关闭定时器,等待按键松开
如图所示:
图中 t1到t3 这一段时间就是按键抖动,是需要消除的。设置按键为下降沿触发(由按键的电路决定),因此会在 t1、t2 和 t3 这三个时刻会触发按键中断,每次进入中断处理函数都会重新开器定时器中断,所以会在 t1、t2 和 t3 这三个时刻开器定时器中断。
但是 t1 ~ t2 和 t2 ~ t3 这两个时间段是小于我们设置的定时器中断周期(也就是消抖时间,比如 10ms),所以虽然 t1 开启了定时器,但是定时器定时时间还没到 t2 时刻就重置了定时器,最终只有 t3 时刻开启的定时器能完整的完成整个定时周期并触发中断,我们就可以在定时器中断处理函数里面做按键处理了。
优点:节约CPU资源
缺点:消耗一个定时器
示例程序如下:
#include
sbit key=P3^2; //定义key为P3.2
sbit led=P1^0; //定义LED为P1.0
void main(void)
{
TMOD|=0X01;//选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=(65536-10000)/256; //给定时器赋初值,定时10ms
TL0=(65536-10000)%256;
ET0=1;//打开定时器0中断允许
TR0=0;//关闭定时器
IT0=1;//跳变沿出发方式(下降沿)
EX0=1;//打开INT0的中断允许。
EA=1;//打开总中断
while(1)
{
}
}
/*定时器中断*/
void Timer0() interrupt 1
{
TH0=(65536-10000)/256; //给定时器赋初值,定时10ms
TL0=(65536-10000)%256;
TR0=0;//关闭定时器
if(key==0) //再次判断按键是否按下
{
led=!led; //控制led状态取反
}
}
/*外部中断0*/
void Int0() interrupt 0 //外部中断0的中断函数
{
TR0=1;//打开定时器
}
注:
1.消抖的定时时间由按键的机械特性决定,多调试。
2.中断处理函数处理完要清除相应中断标志。
3.进行按键处理后要清零和失能定时器,否则无按键按下时也在定时。
4.由于实验室的板子外部中断端口接了矩阵按键和独立按键,所以以上代码要实现需要同时按下S1和KEY9。
延时法简单实用,编程也容易,使用非常普遍。但是这个办法有些缺点,1是加上延时后,在延时期间单片机什么也没干,就在那里兜圈子耗时间,如果这时有其他事情需要处理也只好放一放,降低了运行效率。2是对一些需要较复杂按键功能的情况例如区别长按键和短按键难于实现。
所以我们也可以使用外部中断法,不按键就不查询,直到按键触发外部中断。但是单片机通常外部中断口很少而按键较多,给应用造成不便。
下面就来介绍一个新的按键查询方法:状态机法。
原理如下:
1,给按键设定3种状态:状态0:无按键,状态1:已经按下,状态2:已经释放。按键可以一直处于状态0,也可以由状态0转为状态1,也可以由状态1转为 状态2,然后恢复到状态0。如此顺序循环。
2,通过定时(例如定时器中断)每隔一段时间(例如10毫秒)检查一下按键 状态,根据上次检查的状态和当前的状态比较,来确定应该做什么。在上述时间间隔内,单片机就可以执行其他任务。一旦确认按键成立,就可以立即找出键码并进 行随后的键码处理程序而无需等待按键释放,加上适当的处置也可以对比较复杂的按键进行处理。
下面以一个具体的程序,详细解释怎么实现:读键函数key_scanf()。无参数,返回键码。
实例程序:
#include
#define uint unsigned int //对数据类型进行声明定义
#define uchar unsigned char
//读取按键
uchar key_scanf()
{
static int Key_on_off = 0 ;//按键自锁变量
uchar num , temp ;
num = P2 ; //将P2的值存在变量num中
num &= 0xf0 ; //将低四位清0
if(num != 0xf0)
{
if(Key_on_off == 0)
{
Key_on_off = 1 ;
switch(num)
{
//返回按键的编码
case 0xe0 : temp = 1 ; break ;
case 0xd0 : temp = 2 ; break ;
case 0xb0 : temp = 3 ; break ;
case 0x70 : temp = 4 ; break ;
}
}
}
else
Key_on_off = 0 ;
return temp ;
参考程序如下:
#include
#define uint unsigned int //对数据类型进行声明定义
#define uchar unsigned char
sbit LSA=P2^4;
sbit LSB=P2^5;
sbit LSC=P2^6;
sbit LSD=P2^7;
uchar DT_s = 0; //秒计时
uchar DT_min = 0; //分计时
/************************************************
函数名 :delay1ms
函数功能 :t=1,大约延时1ms
************************************************/
void delay(unsigned int i)
{
while(i--);
}
void smg(unsigned char x,y)
{
unsigned char a[17]={~0x3f,~0x06,~0x5b,~0x4f,~0x66,~0x6d,~0x7d,~0x07,~0x7f,~0x6f,~0x77,~0x7c,~0x39,~0x5e,~0x79,~0x71};//段选
switch(x)
{
case 1:LSA=0;LSB=1;LSC=1;LSD=1;break;//位选
case 2:LSA=1;LSB=0;LSC=1;LSD=1;break;
case 3:LSA=1;LSB=1;LSC=0;LSD=1;break;
case 4:LSA=1;LSB=1;LSC=1;LSD=0;break;
}
P0=a[y];
delay(100);
}
void smg1(unsigned char x,y)//末尾加上小数点
{
unsigned char a[17]={~0x3f&0x7f,~0x06&0x7f,~0x5b&0x7f,~0x4f&0x7f,~0x66&0x7f,~0x6d&0x7f,~0x7d&0x7f,~0x07&0x7f,~0x7f&0x7f,~0x6f&0x7f,~0x77&0x7f,~0x7c&0x7f,~0x39&0x7f,~0x5e&0x7f,~0x79&0x7f,~0x71&0x7f};//段选且加上小数点
switch(x)
{
case 1:LSA=0;LSB=1;LSC=1;LSD=1;break;//位选
case 2:LSA=1;LSB=0;LSC=1;LSD=1;break;
case 3:LSA=1;LSB=1;LSC=0;LSD=1;break;
case 4:LSA=1;LSB=1;LSC=1;LSD=0;break;
}
P0=a[y];
delay(100);
}
/************************************************
函数名 :Timer0Init
函数功能 :定时/计数器0中断初始化
*************************************************/
void Timer0Init()
{
TMOD = 0x01; //选择T0定时/计数器,工作在方式1,16位计数器
TH0 = 0xFC; //计数初始值,计数从64536开始,计1000个数,完成一次计数,时间为1ms
TL0 = 0x18;
ET0 = 1; //定时/计数器0中断允许位
EA = 1; //总中断
}
/************************************************
函数名 :S3
函数功能 :按下按钮S3时,秒表归零
*************************************************/
void S3()
{
DT_s = 0; //秒归零
DT_min = 0; //分归零
TR0 = 0; //运行控制位清0,关闭定时器
}
//读取按键
uchar key_scanf()
{
static int Key_on_off = 0 ;//按键自锁变量
uchar num , temp ;
num = P3 ; ///将P2的值存在变量num中
num &= 0xf0 ; //将低四位清0
if(num != 0xf0)
{
if(Key_on_off == 0)
{
Key_on_off = 1 ;
switch(num)
{
//返回按键的编码
case 0xe0 : temp = 1 ; break ;
case 0xd0 : temp = 2 ; break ;
case 0xb0 : temp = 3 ; break ;
case 0x70 : temp = 4 ; break ;
}
}
}
else
Key_on_off = 0 ;
return temp ;
}
void DisplayKey()
{
int key;
key=key_scanf();
if(key) //确认按键被按下
{
switch(key)
{
case 1: //S1(P3^2)被按下
TR0 = 1; //定时器0运行控制位为1,启动定时器0
break;
case 2: //S2(P3^1)被按下
TR0 = 0; //定时器0运行控制位为0,关闭定时器0
break;
case 3: //S3(P3^3)被按下
S3();
break;
}
}
}
/*******************************************************************
函数名 :DigDisplay
函数功能 :数码管动态扫描函数,循环扫描8个数码管显示
********************************************************************/
void DigDisplay(uchar s,uchar min)
{
smg(4,s%10);
smg(3,s/10);
smg1(2,min%10);
smg(1,min/10);
}
/**********************************************************
函数名 :主函数
函数功能 :无
**********************************************************/
void main(void)
{
P0 = 0x00; //读端口前写1
P3 = 0xFF; //读端口前写1
Timer0Init(); //定时器中断初始化函数
while(1)
{
DisplayKey();
DigDisplay(DT_s,DT_min); //数码管显示函数
}
}
/**********************************************************
函数名 :Timer0
函数功能 :定时器计数
**********************************************************/
void Timer0() interrupt 1
{
static uint count_s;
static uint count_min;
TH0 = 0xFC; //计数值初始化,从64536开始计数,计满时为65536,溢出时即为 1ms
TL0 = 0x18;
count_s++; //秒计数
count_min++; //分计数
if(count_s==1000) //计数到1s时,秒计数器开始工作
{
count_s = 0; //秒计数清零
DT_s++; //显示秒计数值自增
if(DT_s>59) //秒数最大为59
{
DT_s = 0;
}
}
if(count_min==60000) //计数到60000ms时,秒计数器开始工作
{
count_min = 0; //分计数清零
DT_min++; //显示分计数值自增
if(DT_min>59) //分数最大为59
{
DT_min = 0;
}
}