[讨论] 思路决定出路--键盘扫描详解

liuxq8079   2008-6-12 10:53 楼主
按键扫描是每个搞单片机的都会遇到的问题,也是一个开发人员必须具备的基本功。先从最基本的说起。在此声明,没有代码,也不要向我要代码,也不想穿裤子,如果你看了帖子还写不出代码,那么我只能说你太笨了。。。。。。。。还是那句话,搞开发重要的是思想,而不是代码,代码只是工具
通常的按键扫描程序是这样做的
键盘按下?YES---延时去抖----键盘按下?YES-----确实按下了,按键有效---退出
   NO                           NO
  退出                         退出
很多教材上都是这样写,但这个程序却是误人子第的
问题来了,这个程序不太好用,有时按一下,或按久一点,程序会认为你按了很多次!!为什么???
因为没有判断按键释放,当我们按下键盘时,程序可能已经跑了N个来回了。

好了,我们来个改进版的按键扫描程序
键盘按下?YES---延时去抖----键盘按下?YES-----确实按下了,按键有效---A按键释放?YES--退出
   NO                           NO                                       NO
  退出                         退出                                    返回A
这是大多数人的按键扫描程序,在一般的场合也能用。这个程序比第一个要好点,但也好不到哪里去。为什么?判断按键释放的方法太笨了,如果我们一直按下这个键盘会怎么样?程序在此死等,可能你说我还有中断,但中断处理完了你还得回来傻等!而且其他按键也将被屏蔽。

那么,让我们好好想想该怎么做。。。。。。。(未完待续,欢迎大家跟贴,谈一下自己的解决方法;看有没有同道中人)

继上:
谢谢大家的跟贴,看来同道中人还很多
在这里严重BS一下自以为是老鸟的菜鸟【2楼】 xingcn 星尘  你除了模仿别人的东西还会什么?如果你

有原创的东西就让大家看看。
;=========================================================================================
言归正传,其实楼下很多人都给出了较好的按键扫描程序,但没有人解释一下为什么要那么做。
现在我来谈谈自己的看法 。先假设按键平时是高电平,按下后是低电平。也就是按键低电平有效
那么一个完整的按键过程会发生什么?按键输入脚电平变化顺序是  高----》低----》高
                                                          未按键  按下键   键释放
真正的高手看到这里就恍然大悟,后面的基本不用看了。
好的按键扫描程序不是判断按键是否被按下,而是判断按键电平变化的变化顺序是否符合  高----》低

----》高 ,其他的按键电平变化顺序都是非法的 。这是按键扫描的基本原理。那么该怎么来判断呢?
进入按键扫描后,我们可能会扫到高电平,那么就需要判断到底是未按键导致的高电平还是按下后键释

放导致的高电平,这里用一个位变量(取名为键前状态,1=曾经按下,0=未按下)做为标志就可以了;

也可能会扫到低电平,那么需要判断这个低电平是由未按键到按下键得到的,还是扫描之前本来就是低

电平,这里也用一个位变量(取名为键有效1=有效,0=无效)做为标志。好了,下面给一个简单的流程
主程序初始化操作,键前状态=0,键有效=0
                                       按键扫描流程
      
键盘===高电平----》键有效=1--》A1键盘曾经按下后变成高电平,键盘已释放,复位键前状态=0---》退出
  !           !
  !           --》键有效=0--》未按下---》退出  
  !      
  !      
  !      
  !===低电平----》键前状态=1--》键盘曾经按下,现在仍然是低电平--》退出
               !
               --》键前状态=0--》未按下---》A2键盘由高电平变成低电平,置键有效=1,键前状态=1;---》退出  

为了突出重点,这个流程没有加去抖处理,想要稳定的效果,必须加延时去抖处理。按这个流程处理按键,响应速度快,
也不会误判。你就是按一天也,不会傻等。按键有效之后,每次只进来看一下就走。最大的好处就是可以实现真正的模块化
按键处理完全由这个程序控制,不必在其他地方判断按键释放没有。
另外再简单谈一下长按,短按的判断。要判断长按,在按键有效之后(A2处)启动定时器,然后在按键释放(A1处)后判断时
间的长短,大于一定时间算长按。至于组合键盘的处理,在上面的流程上稍加改动就可以实现,等有时间再详谈。

回复评论 (7)

回复 楼主 的帖子

我一般用第二种方法。
通常情况下,按键过程中除了中断(多数是显示中断这些直观的东西),你还指望程序干别的吗?
点赞  2008-6-12 10:55

回复 沙发 的帖子

稍微用代码表示一下更能说明问题
按键处理的事情的确很多,按下后只处理一次,等待一段时间后连续处理,处理的间隔时间随按住的时间增加而减少,按键的组合,长按和短按,单次和双击,密切关注LZ这些功能是不是都能讲到。
点赞  2008-6-12 10:56

回复 板凳 的帖子

我的按键是这样处理的
1.读健状态,与上次健状态比较,不同,更新健状态和当前时间,相同则时间不变(或者加一)
2.当前时间与最后一次更新按键时间比较,超出规定时间,则可以判断健已经稳定。
3.与以前比较,就可以进行健处理了。

缺点,用的RAM多,优点,不等待

#include
#include

void ReadKey(void)
{
        byte i,Temp0;
        bit KeyFlag;
        static byte Q0;         

        P1 = 0xff;
        Temp0 = ~P1;

        if(Temp0 != Qsw0)
        {
               Qsw0 = Temp0;
                KeyTime = Time20ms;
        }
        else
        {
                if(Time20ms - KeyTime > 3)
                        {KeyFlag = 1;}         
        }

        if(KeyFlag)
        {        KeyFlag = 0;

                i = Qsw0 &~Q0 & 0xbb; //健按下
                if(i)
                {  
                        if(i & 0x80)        {KeyValue = BryRightKey;}
                        if(i & 0x20)        {KeyValue = AddRightKey;}
                        if(i & 0x10)        {KeyValue = SubbRightKey;}
                        if(i & 0x08)        {KeyValue = ReadPhotoRightKey;}
                        if(i & 0x02)        {KeyValue = LgyRightKey;}
                        if(i & 0x01)        {KeyValue = LightRightKey;}
                }

               i = Q0 &~Qsw0  & 0xbb; //健松开
                if(i)
                {
                   //健松开,处理代码;
                }   
                Q0 = Qsw0;         
        }
}
点赞  2008-6-12 10:57

回复 4楼 的帖子

我也贴一段吧,这是我的一个项目中的键盘扫描函数,基于UCOSII的,功能有按下处理一次,连续按住一段时间能连续处理,函数返回键值,键盘扫描是单独一个任务,定时扫描的,在时间上没有大的等待,这样说可以帮助理解


INT8U KeyScan(void)
{
    static INT8U keyrepeat,keyprevalue,times,fastad;
    INT8U KeyOut=0xfb;
    INT8U keyvalue;
    INT8U keyfirst;
    for(;KeyOut!=0x1f;KeyOut=KeyOut>>1)
    {
      KeyOutPort &= 0xf8;
      KeyOutPort |= (KeyOut&0x07);
      __delay_cycles(5);
      keyvalue = KeyInputPort&0x38;
      if(keyvalue!=0x38)
      {
          keyvalue |= (KeyOut&0x07);
          if(keyprevalue==keyvalue)
          {
              if(keyrepeat!=0xff) keyrepeat=0xff;
              else if((keyvalue==KeyLeft)||(keyvalue==KeyRight)||(keyvalue==KeyUp)||(keyvalue==KeyDown))
              {
                  if(fastad==0xff)    //针对调整按键 进行快速按键处理
                  {
                      if(times>=(100/KeyScanDelay)) times=0;
                      else
                      {
                          times++;
                          keyvalue=0xff;
                      }
                  }
                  else if(times>=(1000/KeyScanDelay))
                  {
                      fastad=0xff;
                      times=0;
                  }
                  else  
                  {
                      times++;
                      keyvalue=0xff;
                  }
              }
              else keyvalue=0xff;
          }
          else  
          {
              keyprevalue = keyvalue;
              keyfirst = 0xff;
          }
          break;
      }
      else keyvalue = 0;
    }
    if(keyvalue==0)  
    {
        keyrepeat = 0;
        keyprevalue = 0;
        times = 0;
        fastad = 0;
    }
    else if((keyvalue==0xff)||(keyfirst==0xff)) keyvalue = 0;
    return keyvalue;
}
点赞  2008-6-12 10:58

LZ仍然在误人子弟

直接言归正传不行吗, 延时等待不一样很笨吗?
点赞  2008-6-14 22:42

Re: [讨论] 思路决定出路--键盘扫描详解

条理还好,但有代码说明更好
点赞  2008-12-5 02:22
楼主是不是原创阿, 这些介绍和代码网上很多  长得一模一样
期待原创
支持开源
点赞  2009-5-1 09:33
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复