打印
[资料分享与下载]

两种键盘扫描程序的比较

[复制链接]
951|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Luis德华|  楼主 | 2015-10-22 08:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
新到公司同事写了一个键盘处理程序,我们可以浏览一下这段代码
/*********************************/
//采集当前键盘端口数据,并且返回键盘端口的值
/*********************************/
unsigned char scanKey( )
{
     unsigned char value1,value2;
     unsigned char  m,n;

     P2 =0xff;
     value1 = P2;

    if(value1!=0xff)
    {
        for(m=0;m<100;m++)
            for(n=0;n<100;n++);
         value2 = P2;

          if(value1==value2)
          {           
             while(P2!=0xff)
            {   

            }
         return value1;
     }
   }
   return 0xff;
}
这段代码运行是没有问题的,通过
 for(m=0;m<100;m++)
            for(n=0;n<100;n++);
         value2 = P2;

          if(value1==value2)
          {           
             while(P2!=0xff)
            {   

            }
         return value1;


相关帖子

沙发
Luis德华|  楼主 | 2015-10-22 08:41 | 只看该作者
实现了键盘去抖。关于键盘一般书上也是这么介绍的。但是这段代码确实有不足之处。首先 for(m=0;m<100;m++)
    for(n=0;n<100;n++); 是延时代码,程序一直会停留在这里,浪费了系统的效率,是系统响应其他任务的变得迟钝。而且容易造成看门狗复位。在EMC实验中容易造成系统不稳定。如果有长按下键盘不太容易实现。

    我找到另外一位工程师的一段键盘代码,如下:
sbit SET_KEY_PIN      = P2^1;  // 设定键盘 S1
  sbit WATER_KEY_PIN    = P1^7;  // 排水键盘 S3
  sbit UP_KEY_PIN       = P2^2;  // 加键  S2
  sbit DOWN_KEY_PIN     = P2^5;  // 减键  S4
  sbit STOP_KEY_PIN     = P2^4;   // 停机键 S5
  sbit START_KEY_PIN    = P2^0;   // 启动键 S6

  sbit SET_CF_PIN       = P3^0;   //设定也许键盘

  static unsigned int  KEY_VALUE =0;

unsigned char readKey( )
{
    unsigned char keyValue = 0x00;

     SET_KEY_PIN      =1;  
     WATER_KEY_PIN    =1;
     UP_KEY_PIN       =1;
     DOWN_KEY_PIN     =1;
     STOP_KEY_PIN     =1;
     START_KEY_PIN    =1;
  if( !SET_KEY_PIN )    keyValue  = SET_KEY_BIT;
  if( !WATER_KEY_PIN )  keyValue |= WATER_KEY_BIT;
  if( !UP_KEY_PIN )     keyValue |= UP_KEY_BIT;
  if( !DOWN_KEY_PIN )   keyValue |= DOWN_KEY_BIT;
  if( !STOP_KEY_PIN )   keyValue |= STOP_KEY_BIT;
  if( !START_KEY_PIN )  keyValue |= RUN_KEY_BIT;
  return keyValue;
}

使用特权

评论回复
板凳
Luis德华|  楼主 | 2015-10-22 08:42 | 只看该作者
void keyScan( )
{
    static unsigned char stateMachine = 0;
    static unsigned char keyValue1 = 0;
    static unsigned char keyValue2 = 0;

    switch( stateMachine )
    {
      case 0:
             keyValue1=readKey( );
             if(!keyValue1) return;
             KEY_DELAY_DEC_TIMER = KEY_DELAY_DEC_TIMER_MAX;
             stateMachine = 1;        
           break;
      case 1:
          if( KEY_DELAY_DEC_TIMER )
          {
               keyValue2=readKey( );

              if( keyValue1 != keyValue2) stateMachine = 0;
           }
           else
           {
               if(( keyValue2== STOP_KEY_BIT )
                   ||( keyValue2== SET_KEY_BIT ))
               {
                   stateMachine = 2;
                   KEY_DELAY_INC_TIMER =0;
                }
               else
               {
                    KEY_VALUE = keyValue2;
                    stateMachine = 3;
               }

            }
         break;
       case 2: //等待延时3秒以上
             keyValue2 = readKey( );
             if( keyValue2 )
             {
                  if( KEY_DELAY_INC_TIMER > KEY_DELAY_INC_TIMER_MAX)  
                  {                  
                      if(keyValue2== STOP_KEY_BIT )
                  {
                      //清除错误
                      KEY_VALUE = CLEAR_ER_KEY_CMD;
                   }
                   else
                   {

                       if( keyValue2== SET_KEY_BIT )
                       {

                           //设定键--进入用户设定菜单
                           KEY_VALUE = ENTER_USER_MENU_CMD;
                        }
                       else
                        {
                             KEY_VALUE = 0;

                         }
                    }
                      stateMachine = 3;
                 }
             }
            else
            {

                     KEY_VALUE = keyValue1;
                      stateMachine = 3;
            }

             break;
     case 3:  //等待键盘释放
               keyValue2 = readKey( );
               if( !keyValue2 )
               {
                   stateMachine = 0;
               }  
               break;
     default:
              break;
   }
}

使用特权

评论回复
地板
Luis德华|  楼主 | 2015-10-22 08:42 | 只看该作者
unsigned int getKey( )
{
    unsigned int keyVlue ;

     keyVlue = KEY_VALUE;

     KEY_VALUE = 0x00;

      return keyVlue;

}

readKey( )是读取键盘IO脚电压。键盘弹起时IO脚电平为低,键盘按下时IO脚电平位高。读取并且返回。 keyScan( )是键盘扫描程序,调用 readKey( )更新本地全局变量KEY_VALUE 。KEY_DELAY_DEC_TIMER 是键盘扫描定时器,在定时器中递减。代码如下:
INTERRUPT (TIMER4_ISR, INTERRUPT_TIMER4)
{
    if( KEY_DELAY_DEC_TIMER > 0 ) KEY_DELAY_DEC_TIMER--;
    TMR4CN &= ~0xC0;                    // Clear Timer 4 overflow/underflow flag


}

使用特权

评论回复
5
Luis德华|  楼主 | 2015-10-22 08:43 | 只看该作者
在KEY_DELAY_DEC_TIMER_MAX定时器中断次数时间范围完成一次键盘扫描。采用了类似状态机的处理方法。主程序每执行一次keyScan( ),读取一次IO脚,执行效率比较高,键盘处理任务需要的时间比较短。在KEY_DELAY_DEC_TIMER时间范围内更新一次KEY_VALUE 。 getKey( )是提供给应用程序,获取当前键盘的稳定状态的值。并且将KEY_VALUE 清零。程序架构比较好,分层也很清楚!

我们看看在主程序调用:
while(1)
  {
      Watchdog_operation(FEED);

   。。。。
     keyScan( );
     key = getKey( );
     exeKeyCmd( key );
   。。。。。


   }

我推荐这位工程师的键盘处理方法,显得比较成熟、老练。扩展性好,代码复用率也很好!

使用特权

评论回复
6
小番茄| | 2015-10-22 09:17 | 只看该作者
说到按键扫描,最让我佩服的还是下面这种:

核心算法:
unsigned char Trg;
unsigned char Cont;
void KeyRead( void )
{
   unsigned char ReadData = PINB^0xff;     // 1
   Trg  = ReadData & (ReadData ^ Cont);    // 2
   Cont = ReadData;                        // 3
}

完了。有没有一种不可思议的感觉?当然,没有想懂之前会那样,想懂之后就会惊叹于这算法的精妙!!
下面是程序解释:
Trg(triger)代表的是触发,Cont(continue)代表的是连续按下。
1:读PORTB的端口数据,取反,然后送到ReadData 临时变量里面保存起来。
2:算法1,用来计算触发变量的。一个位与操作,一个异或操作,我想学过C语言都应该懂吧?Trg为全局变量,其它程序可以直接引用。
3:算法2,用来计算连续变量。

看到这里,有种“知其然,不知其所以然”的感觉吧?代码很简单,但是它到底是怎么样实现我们的目的的呢?好,下面就让我们绕开云雾看青天吧。
我们最常用的按键接法如下:AVR是有内部上拉功能的,但是为了说明问题,我是特意用外部上拉电阻。那么,按键没有按下的时候,读端口数据为1,如果按键按下,那么端口读到0。下面就看看具体几种情况之下,这算法是怎么一回事。

(1)没有按键的时候
端口为0xff,ReadData读端口并且取反,很显然,就是 0x00 了。
Trg  = ReadData & (ReadData ^ Cont); (初始状态下,Cont也是为0的)很简单的数学计算,因为ReadData为0,则它和任何数“相与”,结果也是为0的。
Cont = ReadData; 保存Cont 其实就是等于ReadData,为0;
结果就是:
ReadData = 0;
Trg  = 0;
Cont = 0;

(2)第一次PB0按下的情况
端口数据为0xfe,ReadData读端口并且取反,很显然,就是 0x01 了。
Trg  = ReadData & (ReadData ^ Cont);  因为这是第一次按下,所以Cont是上次的值,应为为0。那么这个式子的值也不难算,也就是 Trg  =  0x01 & (0x01^0x00) = 0x01
Cont = ReadData = 0x01;
结果就是:
ReadData = 0x01;
Trg  = 0x01;Trg只会在这个时候对应位的值为1,其它时候都为0
Cont = 0x01;

(3)PB0按着不松(长按键)的情况
端口数据为0xfe,ReadData读端口并且取反是 0x01 了。
Trg  = ReadData & (ReadData ^ Cont);  因为这是连续按下,所以Cont是上次的值,应为为0x01。那么这个式子就变成了 Trg  =  0x01 & (0x01^0x01) = 0x00
Cont = ReadData = 0x01;
结果就是:
ReadData = 0x01;
Trg  = 0x00;
Cont = 0x01;
因为现在按键是长按着,所以MCU会每个一定时间(20ms左右)不断的执行这个函数,那么下次执行的时候情况会是怎么样的呢?
ReadData = 0x01;这个不会变,因为按键没有松开
Trg  = ReadData & (ReadData ^ Cont) = 0x01 & (0x01 ^ 0x01)  =  0 ,只要按键没有松开,这个Trg值永远为 0 !!!
Cont = 0x01;只要按键没有松开,这个值永远是0x01!!

(4)按键松开的情况
端口数据为0xff,ReadData读端口并且取反是 0x00 了。
Trg  = ReadData & (ReadData ^ Cont)  =  0x00 & (0x00^0x01) = 0x00
Cont = ReadData = 0x00;
结果就是:
ReadData = 0x00;
Trg  = 0x00;
Cont = 0x00;

很显然,这个回到了初始状态,也就是没有按键按下的状态。
总结一下,不知道想懂了没有?其实很简单,答案如下:
Trg 表示的就是触发的意思,也就是跳变,只要有按键按下(电平从1到0的跳变),那么Trg在对应按键的位上面会置一,我们用了PB0则Trg的值为0x01,类似,如果我们PB7按下的话,Trg 的值就应该为 0x80 ,这个很好理解,还有,最关键的地方,Trg 的值每次按下只会出现一次,然后立刻被清除,完全不需要人工去干预。所以按键功能处理程序不会重复执行,省下了一大堆的条件判断,这个可是精粹哦!!Cont代表的是长按键,如果PB0按着不放,那么Cont的值就为 0x01,相对应,PB7按着不放,那么Cont的值应该为0x80,同样很好理解。

使用特权

评论回复
7
仙女山| | 2015-10-22 09:26 | 只看该作者
Luis德华 发表于 2015-10-22 08:42
readKey( )是读取键盘IO脚电压。键盘弹起时IO脚电平为低,键盘按下时IO脚电平位高。读取并且返回。 keySca ...

这个工程师写的程序确实严谨,学习了

使用特权

评论回复
8
芙蓉洞| | 2015-10-22 09:33 | 只看该作者

第二种按键程序写的确实棒,收下了

使用特权

评论回复
9
gyh974| | 2015-10-22 14:55 | 只看该作者
很详细的分析!谢谢楼主

使用特权

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

本版积分规则

40

主题

370

帖子

4

粉丝