赞助论坛
  • 5069阅读
  • 0回复

51单片机C语言编程基础及实例之六[键盘驱动] [复制链接]

楼层直达
rkvclu  
发帖
17
精华
0
金币
65
威望
3
贡献
0
好评
0
注册
2008-06-14
楼主    rkvclu 发表于: 2008-06-16 15:19:08 
键盘驱动
指提供一些函数给任务调用,获取按键信息,或读取按键值。
定义一个头文档 <KEY.H>,描述可用函数,如下:


代码
#ifndef _KEY_H_ //防止重复引用该文档,如果没有定义过符号 _KEY_H_,则编译下面语句  
#define _KEY_H_ //只要引用过一次,即 #include <key.h>,则定义符号 _KEY_H_  
unsigned char keyHit( void ); //如果按键,则返回非0,否则返回0  
unsigned char keyGet( void ); //读取按键值,如果没有按键则等待到按键为止  
void keyPut( unsigned char ucKeyVal ); //保存按键值ucKeyVal到按键缓冲队列末  
void keyBack( unsigned char ucKeyVal ); //退回键值ucKeyVal到按键缓冲队列首  
#endif  

定义函数体文档 KEY.C,如下:


代码
#include “key.h”  
#define KeyBufSize 16 //定义按键缓冲队列字节数  
unsigned char KeyBuf[ KeyBufSize ]; //定义一个无符号字符数组作为按键缓冲队列。该队列为先进  
    //先出,循环存取,下标从0到 KeyBufSize-1  
unsigned char KeyBufWp=0; //作为数组下标变量,记录存入位置  
unsigned char KeyBufRp=0; //作为数组下标变量,记录读出位置  
//如果存入位置与读出位置相同,则表明队列中无按键数据  
unsigned char keyHit( void )  
{ if( KeyBufWp == KeyBufRp ) return( 0 ); else return( 1 ); }  
 
unsigned char keyGet( void )  
{ unsigned char retVal; //暂存读出键值  
while( keyHit()==0 ); //等待按键,因为函数keyHit()的返回值为 0 表示无按键  
retVal = KeyBuf[ KeyBufRp ]; //从数组中读出键值  
if( ++KeyBufRp >= KeyBufSize ) KeyBufRp=0; //读位置加1,超出队列则循环回初始位置  
return( retVal );  
}  
 
void keyPut( unsigned char ucKeyVal )  
{ KeyBuf[ KeyBufWp ] = ucKeyVal; //键值存入数组  
if( ++KeyBufWp >= KeyBufSize ) KeyBufWp=0; //存入位置加1,超出队列则循环回初始位置  
}  
/*****************************************************************************************  
由于某种原因,读出的按键,没有用,但其它任务要用该按键,但传送又不方便。此时可以退回按键队列。就如取错了信件,有必要退回一样  
******************************************************************************************/  
void keyBack( unsigned char ucKeyVal )  
{  
/*  
如果KeyBufRp=0; 减1后则为FFH,大于KeyBufSize,即从数组头退回到数组尾。或者由于干扰使得KeyBufRp超出队列位置,也要调整回到正常位置,  
*/  
if( --KeyBufRp >= KeyBufSize ) KeyBufRp=KeyBufSize-1;  
KeyBuf[ KeyBufRp ] = ucKeyVal; //回存键值  
}  


下面渐进讲解键盘物理层的驱动。
电路共同点:P2端口接一共阴数码管,共阴极接GND,P2.0接a段、P2.1接b段、…、P2.7接h段。
软件共同点:code unsigned char Seg7Code[10] 是七段数码管共阴编码表。
Code unsigned char Seg7Code[16]=
// 0   1   2   3   4   5   6   7   8   9   A   b   C   d   E   F
{0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};

例一:P1.0接一按键到GND,键编号为‘6’,显示按键。


代码
#include <at89x52.h>  
#include “KEY.H”  
void main( void )  
{ P1_0 = 1; //作为输入引脚,必须先输出高电平  
while( 1 ) //永远为真,即死循环  
{ if( P1_0 == 0 ) //如果按键,则为低电平  
{ keyPut( 6 ); //保存按键编号值为按键队列  
while( P1_0 == 0 ); //如果一直按着键,则不停地执行该循环,实际是等待松键  
}  
if( keyHit() != 0 ) //如果队列中有按键  
P2=Seg7Code[ keyGet() ]; //从队列中取出按键值,并显示在数码管上  
}  
}  


例二:在例一中考虑按键20ms抖动问题。


代码
#include <at89x52.h>  
#include “KEY.H”  
void main( void )  
{ P1_0 = 1; //作为输入引脚,必须先输出高电平  
while( 1 ) //永远为真,即死循环  
{ if( P1_0 == 0 ) //如果按键,则为低电平  
{ delay20ms(); //延时20ms,跳过接下抖动  
keyPut( 6 ); //保存按键编号值为按键队列  
while( P1_0 == 0 ); //如果一直按着键,则不停地执行该循环,实际是等待松键  
delay20ms(); //延时20ms,跳过松开抖动  
}  
if( keyHit() != 0 ) //如果队列中有按键  
P2=Seg7Code[ keyGet() ]; //从队列中取出按键值,并显示在数码管上  
}  
}  


例三:在例二中考虑干扰问题。即小于20ms的负脉冲干扰。


代码
#include <at89x52.h>  
#include “KEY.H”  
void main( void )  
{ P1_0 = 1; //作为输入引脚,必须先输出高电平  
while( 1 ) //永远为真,即死循环  
{ if( P1_0 == 0 ) //如果按键,则为低电平  
{ delay20ms(); //延时20ms,跳过接下抖动  
if( P1_0 == 1 ) continue; //假按键  
keyPut( 6 ); //保存按键编号值为按键队列  
while( P1_0 == 0 ); //如果一直按着键,则不停地执行该循环,实际是等待松键  
delay20ms(); //延时20ms,跳过松开抖动  
}  
if( keyHit() != 0 ) //如果队列中有按键  
P2=Seg7Code[ keyGet() ]; //从队列中取出按键值,并显示在数码管上  
}  
}  


例四:状态图编程法。通过20ms周期中断,扫描按键。


代码
/****************************************************************************************  
采用晶体为12KHz时,指令周期为1ms(即主频为1KHz),这样T0工作在定时器方式2,8位自动重载。计数值为20,即可产生20ms的周期性中断,在中断服务程序中实现按键扫描  
*****************************************************************************************/  
#include <at89x52.h>  
#include “KEY.H”  
void main( void )  
{  
TMOD = (TMOD & 0xf0 ) | 0x02; //不改变T1的工作方式,T0为定时器方式2  
TH0 = -20;   //计数周期为20个主频脉,即20ms  
TL0=TH0;     //先软加载一次计数值  
TR0=1;     //允许T0开始计数  
ET0=1;     //允许T0计数溢出时产生中断请求  
EA=1;     //允许CPU响应中断请求  
while( 1 ) //永远为真,即死循环  
{  
if( keyHit() != 0 ) //如果队列中有按键  
P2=Seg7Code[ keyGet() ]; //从队列中取出按键值,并显示在数码管上  
}  
}  
void timer0int( void ) interrupt 1 //20ms;T0的中断号为1  
{ static unsigned char sts=0;  
P1_0 = 1; //作为输入引脚,必须先输出高电平  
switch( sts )  
{  
case 0: if( P1_0==0 ) sts=1; break; //按键则转入状态1  
case 1:  
if( P1_0==1 ) sts=0; //假按错,或干扰,回状态0  
else{ sts=2; keyPut( 6 ); } //确实按键,键值入队列,并转状态2  
break;  
case 2: if( P1_0==1 ) sts=3; break; //如果松键,则转状态3  
case 3:  
  if( P1_0==0 ) sts=2; //假松键,回状态2  
  else sts=0;   //真松键,回状态0,等待下一次按键过程  
}  
}  


例五:状态图编程法。


代码
/****************************************************************************************  
如果采用晶体为12MHz时,指令周期为1us(即主频为1MHz),要产生20ms左右的计时,则计数值达到20000,T0工作必须为定时器方式1,16位非自动重载,即可产生20ms的周期性中断,在中断服务程序中实现按键扫描  
*****************************************************************************************/  
#include <at89x52.h>  
#include “KEY.H”  
void main( void )  
{  
TMOD = (TMOD & 0xf0 ) | 0x01; //不改变T1的工作方式,T0为定时器方式1  
TL0 = -20000;   //计数周期为20000个主频脉,自动取低8位  
TH0 = (-20000)>>8;   //右移8位,实际上是取高8位  
TR0=1;     //允许T0开始计数  
ET0=1;     //允许T0计数溢出时产生中断请求  
EA=1;     //允许CPU响应中断请求  
while( 1 ) //永远为真,即死循环  
{  
if( keyHit() != 0 ) //如果队列中有按键  
P2=Seg7Code[ keyGet() ]; //从队列中取出按键值,并显示在数码管上  
}  
}  
void timer0int( void ) interrupt 1 //20ms;T0的中断号为1  
{ static unsigned char sts=0;  
TL0 = -20000;   //方式1为软件重载  
TH0 = (-20000)>>8;   //右移8位,实际上是取高8位  
P1_0 = 1; //作为输入引脚,必须先输出高电平  
switch( sts )  
{  
case 0: if( P1_0==0 ) sts=1; break; //按键则转入状态1  
case 1:  
if( P1_0==1 ) sts=0; //假按错,或干扰,回状态0  
else{ sts=2; keyPut( 6 ); } //确实按键,键值入队列,并转状态2  
break;  
case 2: if( P1_0==1 ) sts=3; break; //如果松键,则转状态3  
case 3:  
  if( P1_0==0 ) sts=2; //假松键,回状态2  
  else sts=0;   //真松键,回状态0,等待下一次按键过程  
}  
}