打印

一个C语言写的键盘扫描小程序

[复制链接]
19420|62
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
xiangrui21|  楼主 | 2010-1-7 09:01 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
先说明下:新人!!
根据郭天翔学习版改写的,还好用吧,为了显示按键,加了数码显示。
请大侠们拍砖!!

#include <reg52.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
sbit dula=P2^6;
sbit wela=P2^7;
uchar code table[]={0xc0,0xf9,0xa4,0xb0,//数码显示依次为:0、1、2、3
     0x99,0x92,0x82,0xf8,              //              4、5、6、7
     0x80,0x90,0x88,0x83,             //      8、9、a、b
     0xc6,0xa1,0x86,0x8e,0xff       //              c、d、e、f、全暗
     };
uchar num,temp;
void delay(uint z)   //延时子程序
{
uint x,y;
for(x=z;x>0;x--)
  for(y=110;y>0;y--);
}
uchar keyscan()    //键盘扫描程序,返回uchar型参数
{
uchar i;
for(i=0;i<4;i++)
{
  P3=_crol_(0xfe,i);
  temp=P3;
  temp=temp&0xf0;
  while(temp!=0xf0)
  {
   delay(5);   //延时消抖
   temp=P3;
   temp=temp&0xf0;
   while(temp!=0xf0)
   {
    temp=P3;
    switch(temp)
    {
     case 0xee:num=1;
      break;
     case 0xde:num=2;
      break;
     case 0xbe:num=3;
      break;
     case 0x7e:num=4;
      break;
     case 0xed:num=5;
      break;
     case 0xdd:num=6;
      break;
     case 0xbd:num=7;
      break;
     case 0x7d:num=8;
      break;
     case 0xeb:num=9;
      break;
     case 0xdb:num=10;
      break;
     case 0xbb:num=11;
      break;
     case 0x7b:num=12;
      break;
     case 0xe7:num=13;
      break;
     case 0xd7:num=14;
      break;
     case 0xb7:num=15;
      break;
     case 0x77:num=16;
      break;
    }
    while(temp!=0xf0)  //等待按键释放
    {
     temp=P3;
     temp=temp&0xf0;
    }
   }
  }
}
return num;
}
void display(uchar aa)
{
dula=1;
P0=table[aa-1];
dula=0;
}
main()
{
num=17;
dula=1;
P0=0;
dula=0;
wela=1;
P0=0x20;
wela=0;
  
while(1)
{
  display(keyscan());
}
}

相关帖子

来自 2楼
红金龙吸味| | 2010-1-7 12:09 | 只看该作者
本帖最后由 红金龙吸味 于 2010-1-7 12:11 编辑

这个流程是好多教科书上的做法。可惜,误导了好多人。为什么呢。因为它根本就没有考虑实际情况。

unsigned char v_ReadKey_f( void )


{


unsigned char KeyPress ;


if
P17 == 0

{


Delay(20) ;
//
延时20MS

If( P17 == 0)


{



KeyPress = 1 ;


While( !P17) ;
//
等待释放

}


else


KeyPress = 0 ;


}


}



这样一个程序,相信对很多初学者而言都不陌生。因为好多书上基本都是这样的一个流程和写法。
当有一天,我们想做一个数码管加按键调整的时钟,发现当我们按键按下去的时候,数码管就不亮了。为什么呢。原因就在这个键盘扫描函数。平常没有按键按下还好。一旦有键按下,它先是浪费了CPU的大部分时间(就是那个什么事情都没做的延时20MS函数)然后,又霸占CPU( 就是哪个死死等在那里的whileP17);语句)直到按键释放。对于这种情况我们是忍无可忍的,那么就让我们彻底的抛弃它吧。那么到底按键扫描函数改如何写呢……..所谓众里寻她千百度,蓦然回首,那人却在灯火阑珊处。如果我们把CPU延时的那20MS拿出来去做其它事情,那么不就充分利用CPU的时间了吗。而一般情况下我们只要前沿去抖动就可以了。也就是说了,我们只需在按键按下后去抖就可以了,对于按键的释放抖动可以不必要过于关注。当然这主要和应用的场合有关。一个能有效识别按键按下并支持连发功能的按键已经能够应用到大多数的场合了。
下面以四个独立按键的处理程序为例来讲解(支持单击和连发)
        #include"regx52.h"
      sbit KeyOne = P1^0 ;
        sbit KeyTwo = P1^1 ;
        sbit KeyThree = P1^2 ;
        sbit KeyFour = P1^3 ;
        #define uint16 unsigned int
        #define uint8 unsigned char
        #define NOKEY  0xff
        #define KEY_WOBBLE_TIME 500              //去抖动时间(待定)
        #define KEY_OVER_TIME 15000          //等待进入连击时间(待定),该常数要比正常 //按键时间要长,防止非目的性进入连击模式
                #define KEY_QUICK_TIME 1000   //等待按键抬起的连击时间(待定)
       void v_KeyInit_f( void )
        {
                        KeyOne = 1 ;                //按键初始化(相应端口写1)
                        KeyTwo = 1 ;
                        KeyThree = 1 ;
                        KeyFour = 1 ;
        }
        uint8 u8_ReadKey_f(void)
        {
                        static uint8 LastKey = NOKEY ;                //保存上一次的键值
                        static uint16 KeyCount = 0 ;                //按键延时计数器
                        static uint16 KeyOverTime = KEY_OVER_TIME ; //按键抬起时间
                            uint8 KeyTemp = NOKEY ;                        //临时保存读到的键值
                        KeyTemp = P1 & 0x0f ;                                //读键值
                        if( KeyTemp == 0x0f )
                        {
                                        KeyCount = 0 ;
                                        KeyOverTime = KEY_OVER_TIME ;
                                        return NOKEY ;                        //无键按下返回NOKEY
                        }
                        else
                        {
                                if( KeyTemp == LastKey )        //是否第一次按下
                                {
        if( ++KeyCount == KEY_WOBBLE_TIME )        //不是第一次按下,则判断//抖动是否结束
                                        {
                                                return KeyTemp ;                                //去抖动结束,返回键值
                                        }
                                        else
                                        {
                                                if( KeyCount > KeyOverTime )
                                                {
                                                        KeyCount = 0 ;
                                                        KeyOverTime = KEY_QUICK_TIME ;
                                                }
                                                return NOKEY ;
                                }
                        }
                        else        //是第一次按下则保存键值,以便下次执行此函数时与读到的键值作比较
                        {
                                LastKey = KeyTemp ;                        //保存第一次读到的键值
                                KeyCount = 0 ;                                //延时计数器清零
                                KeyOverTime = KEY_OVER_TIME ;
                                return NOKEY ;
                        }
        }
        }
   
   下面是我测试用的主程序(相关头文件未列出,仅仅作测试演示用)
   void main(void)
{
        uint8 KeyValue ;
        int16 Count ;
        v_LcdInit_f() ;
        v_KeyInit_f() ;
        CLS
        LOCATE(3, 1)
        PRINT("Key Test")
        LOCATE(6, 2)
        SHOW_ICON
        while(1)
        {        
                KeyValue = u8_ReadKey_f() ;
                if( KeyValue != NOKEY )
                {        LOCATE(1, 2)
                        if( KeyValue == 0x0e )Count++ ;
                        if( KeyValue == 0x0d )Count-- ;
                        if( KeyValue == 0x0b )Count = 0 ;
                        if( KeyValue == 0x07 )Count = 0 ;
                        HIDE_ICON
                        PRINTD(Count, 5)
                    LOCATE(6, 2)
                }
                else
                {
                   //SHOW_ICON
                 }               
        }                 
}

每次执行读键盘函数时,只是对一些标志进行判断,然后退出。因此能够充分的利用CPU的资源。同时可以处理连发按键。此按键扫描按键函数可以直接放在主函数中。如果感觉按键太过灵敏或者迟钝则改一下相关消抖动的宏定义即可。此函数也可以通过中断标志位进行定时的扫描。此时,需要添加一个定时标志位,并将相关消抖动的和连击时间的宏定义改小即可。然后在主程序类似下面这样写即可
                if( KeyTime )       //定时扫描时间到
                {
                KeyValue = u8_ReadKey_f() ;
                }
具体的工作就交给您去完成啦。

使用特权

评论回复
评分
参与人数 6威望 +12 收起 理由
gan_elec + 1 赞一个!
古道热肠 + 2
xiangrui21 + 1
贺信 + 1
oksmn + 1

查看全部评分

来自 3楼
itelectron| | 2010-1-7 22:10 | 只看该作者
偶也 贴一个  

#define  TK                                8                        //主程序执行时间8ms  //8
#define  timer20ms          (30/TK)                //延时时间20ms       //(30/TK)
#define  timer3S                  (800/TK)        //延时时间3S         //(1200/TK)
#define  timer100ms          (500/TK)        //延时时间100ms      //(1000/TK)
KEY                KeyDat;                   //定义数据结构


/***************************
【函数】:HC166_read(void)
【功能】:HC166驱动与硬件相关
【参数】:无
***************************/
unsigned char HC166_read(void)//HC166_read
{
  unsigned char i,set,k;
  
  Bits1_PutBit(2, FALSE);//L cp底电平
  Bits1_PutBit(1, FALSE);//L PCLK时钟低电平
  Bits1_PutBit(1, TRUE); //H PCLK//时钟高电平,上升沿有效
  Bits1_PutBit(2, TRUE); //H cp高电平

  for(i = 0, set = 0; i < 8;i ++)//依次读取锁存的8位数据
  {
    set <<= 1;
          if (!Bit1_GetVal())set ++;
    Bits1_PutBit(1, FALSE);//L PCLK时钟低电平
    Bits1_PutBit(1, TRUE);//H PCLK//时钟高电平,上升沿有效
  }
    switch(set) //读取扫描值
  {
    case 0x00:      k=0x00;  break; //
    case K_Run:     k=0x01;  break; //GetRun
    case K_Stop:    k=0x02;  break; //GetStop
    case K_Down:    k=0x03;  break; //GetDown
    case K_Jog:     k=0x04;  break; //GetJog
    case K_Shif:    k=0x05;  break; //GetShif
    case K_Up:      k=0x06;  break; //GetUp
    case K_Enter:   k=0x07;  break; //GetEnter
    case K_Mode:    k=0x08;  break; //GetMode
    case K_Enter+K_Up:      k=0x09;  break; //组合键K_Enter+K_Up
    case K_Enter+K_Down:    k=0x10;  break; //组合键K_Enter+K_Down
    default:        k=0x00;  break; //点亮LED2
  }
  return k;
}


/***************************
【函数】:GetKey(void)
【功能】:判断是否有按键
【参数】:无
***************************/
void GetKey(void)
{

  KeyDat.KeyTemp=HC166_read();
  if(KeyDat.KeyTemp!=0)       //有按键
  {
    KeyDat.KeyDog=timer20ms;
    KeyDat.KeyPower++;       
  }
}

/***************************
【函数】:KeyDog(void)
【功能】:按键延时
【参数】:无
***************************/
void Key_Dog(void)
{
  if(0==--KeyDat.KeyDog)        //按键松开
  {
    KeyDat.KeyData=HC166_read();//读键盘
    if(KeyDat.KeyData!=0)
    {
      KeyDat.KeyPower++;       //进入KeyOffShort(void)
      KeyDat.KeyDog=timer3S;   //计数器装载3S
    }
    else
    {
      KeyDat.KeyPower=0;
      KeyDat.KeyData=0;         //返回
    }       
  }
}

/***************************
【函数】:KeyOffShort(void)
【功能】:判断按键是否松开
【参数】:无
***************************/
void KeyOffShort(void)
{
  if(HC166_read()==0)       //判断按键松开
  {
    KeyDat.KeyPower=0;
    KeyDat.KeyData|=HaveKey;//定义短击标记
  }
  else
  {
          if(0==--KeyDat.KeyDog)  //3秒延时到否
          {
      KeyDat.KeyDog=timer100ms;//计数器装载100ms
      KeyDat.KeyPower++;       //进入KeyOffLong(void)
          }
  }
}


/***************************
【函数】:KeyOffLong(void)
【功能】:判断按键长击
【参数】:无
***************************/
void KeyOffLong(void)
{
  if(HC166_read()!=0)//判断长击按键弹起
  {
    if(0==--KeyDat.KeyDog)//3秒延时到
          {
            if(KeyDat.KeyTemp==HC166_read())//校验数据(判断组合键用)
            {
                    KeyDat.KeyData|=DubClick;//DubClick|HaveKey;//长击标记
              KeyDat.KeyDog=timer20ms;
            }
            else     
      {
        KeyDat.KeyData=0;  //数据归0
        KeyDat.KeyPower=0; //长击按键弹起后返回
      }
          }
  }
  else     //长击按键弹起
  {
    KeyDat.KeyData=0;  //数据归0
    KeyDat.KeyPower=0; //长按键弹起后返回
  }

}
/***************************
【函数】:void(*SubKey[])()
【功能】:函数指针定义
【参数】:无
***************************/
void(*SubKey[4])()=
{
  GetKey,Key_Dog,KeyOffShort,KeyOffLong       
};

/***************************
【函数】:void KeyBoard(void)
【功能】:主循环或者定时中断调用
【参数】:无
***************************/
void KeyBoard(void)//扫描键盘
{
  (*SubKey[KeyDat.KeyPower])();       
}

/***************************
【函数】:unsigned char JB_KeyData(void)()
【功能】:用户功能函数调用
【参数】:无
***************************/
unsigned char JB_KeyData(void)
{
  unsigned char i=0;
  if(KeyDat.KeyData>DubClick)//DubClick=0x40
  {
    i=KeyDat.KeyData;
    KeyDat.KeyData=0;       
  }
  return i;
}

使用特权

评论回复
评分
参与人数 1威望 +4 收起 理由
古道热肠 + 4
地板
红金龙吸味| | 2010-1-7 09:45 | 只看该作者
延时消抖,不实用。

使用特权

评论回复
5
xiangrui21|  楼主 | 2010-1-7 10:27 | 只看该作者
那么有别的合适的方法消抖,能说下不?请教了哦~!:P

使用特权

评论回复
6
Thunder_f| | 2010-1-7 10:36 | 只看该作者
while(temp!=0xf0)
   {
    temp=P3;
    switch(temp)
这个while改成if似乎更便于理解
temp=P3;
  temp=temp&0xf0;  这个用C的写法写成temp&=0xf0;比较省事
这个可以合并写成temp=P3&0xf0;
个人认为,等待按键释放之后才执行命令的写法不是太好
另外,消抖我一般也不这样写

使用特权

评论回复
7
xiangrui21|  楼主 | 2010-1-7 10:45 | 只看该作者
谢谢楼上的,O(∩_∩)O~
能不能给个消抖的写法?

使用特权

评论回复
8
Thunder_f| | 2010-1-7 11:04 | 只看该作者
说实话,还不敢写出来,怕人拍砖...
不过拍拍砖进步得快
P3=_crol_(0xfe,i);
for(j=0;j<100;j++)
{
  temp=P3&0xf0;
  if(temp==0xf0) break;
}
if(temp==0xf0) continue;
switch(temp)
....

使用特权

评论回复
9
红金龙吸味| | 2010-1-7 11:08 | 只看该作者
for(j=0;j<100;j++)
{
  temp=P3&0xf0;
  if(temp==0xf0) break;
}
键按下了,扫100遍,累不累?

使用特权

评论回复
10
xiangrui21|  楼主 | 2010-1-7 11:25 | 只看该作者
呵呵,探讨使人进步呀。
6楼的for循环也不错的呀,比起延时更节省时间。
我先去用用,感觉下,谢了~!

使用特权

评论回复
11
Thunder_f| | 2010-1-7 11:26 | 只看该作者
扫100遍和delay(5)是一样的(具体是100还是其它数字需要算的),都累啊
这种写法最大的好处是在遇到干扰或抖动期间可以继续去做其它事情,对于要求效率的程序是有好处的.
当然,如果时效要求更高的话还有其它的写法

使用特权

评论回复
12
反质子| | 2010-1-7 11:41 | 只看该作者
按键如果是短按还是在释放阶段执行命令的

使用特权

评论回复
13
xiangrui21|  楼主 | 2010-1-7 11:51 | 只看该作者
恩,Thunder_f 说的很不错,赞一个。:victory:
又学到了不少东西, 不知道  红金龙吸味 大虾有没有时效性更好的呢?
教教小弟,共同进步撒~!;P

抛砖引玉,还得谢谢各位呀~!:P

使用特权

评论回复
14
xiangrui21|  楼主 | 2010-1-7 12:32 | 只看该作者
嘿嘿。谢谢  红金龙吸味  大虾:victory:,我这就去做做看。

小弟在此抛块砖,能引来如此多的美玉,实在是多谢各位的不吝赐教。:P

大家探讨下,我进步很大哦~~:D

使用特权

评论回复
15
badbird1234| | 2010-1-7 13:49 | 只看该作者
怎么把多建按下的情况全部给排除掉了呀

使用特权

评论回复
16
反质子| | 2010-1-7 14:40 | 只看该作者
恩 他这里不需要双击、多击、同击等的状态
哈哈 楼主可以去看看  匠人写的  按键漫谈

使用特权

评论回复
17
ningling_21| | 2010-1-7 15:47 | 只看该作者
12楼很强,学习了...

使用特权

评论回复
18
jaylondon| | 2010-1-7 16:02 | 只看该作者
很多单片机不是有具有按键中断了么~

使用特权

评论回复
19
贺信| | 2010-1-7 16:44 | 只看该作者
留印
赞12楼

使用特权

评论回复
20
xhp2527| | 2010-1-7 21:51 | 只看该作者
其实不一定要用程序去抖啊,那样果键盘比较大的话,同样占CPU的空间,而且容易地出错,可以很好的用硬件去做吗?

使用特权

评论回复
21
原野之狼| | 2010-1-7 22:08 | 只看该作者
动态扫描就行啊 记录状态 可以翻一下我早期的一篇帖子

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

主题

22

帖子

0

粉丝