[资料分享与下载] 两种键盘扫描程序的比较

[复制链接]
1242|8
 楼主| Luis德华 发表于 2015-10-22 08:41 | 显示全部楼层 |阅读模式
新到公司同事写了一个键盘处理程序,我们可以浏览一下这段代码
  1. /*********************************/
  2. //采集当前键盘端口数据,并且返回键盘端口的值
  3. /*********************************/
  4. unsigned char scanKey( )
  5. {
  6.      unsigned char value1,value2;
  7.      unsigned char  m,n;

  8.      P2 =0xff;
  9.      value1 = P2;

  10.     if(value1!=0xff)
  11.     {
  12.         for(m=0;m<100;m++)
  13.             for(n=0;n<100;n++);
  14.          value2 = P2;

  15.           if(value1==value2)
  16.           {           
  17.              while(P2!=0xff)
  18.             {   

  19.             }
  20.          return value1;
  21.      }
  22.    }
  23.    return 0xff;
  24. }
这段代码运行是没有问题的,通过
  1. for(m=0;m<100;m++)
  2.             for(n=0;n<100;n++);
  3.          value2 = P2;

  4.           if(value1==value2)
  5.           {           
  6.              while(P2!=0xff)
  7.             {   

  8.             }
  9.          return value1;


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

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

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

  8.   static unsigned int  KEY_VALUE =0;

  9. unsigned char readKey( )
  10. {
  11.     unsigned char keyValue = 0x00;

  12.      SET_KEY_PIN      =1;  
  13.      WATER_KEY_PIN    =1;
  14.      UP_KEY_PIN       =1;
  15.      DOWN_KEY_PIN     =1;
  16.      STOP_KEY_PIN     =1;
  17.      START_KEY_PIN    =1;
  18.   if( !SET_KEY_PIN )    keyValue  = SET_KEY_BIT;
  19.   if( !WATER_KEY_PIN )  keyValue |= WATER_KEY_BIT;
  20.   if( !UP_KEY_PIN )     keyValue |= UP_KEY_BIT;
  21.   if( !DOWN_KEY_PIN )   keyValue |= DOWN_KEY_BIT;
  22.   if( !STOP_KEY_PIN )   keyValue |= STOP_KEY_BIT;
  23.   if( !START_KEY_PIN )  keyValue |= RUN_KEY_BIT;
  24.   return keyValue;
  25. }
 楼主| Luis德华 发表于 2015-10-22 08:42 | 显示全部楼层
  1. void keyScan( )
  2. {
  3.     static unsigned char stateMachine = 0;
  4.     static unsigned char keyValue1 = 0;
  5.     static unsigned char keyValue2 = 0;

  6.     switch( stateMachine )
  7.     {
  8.       case 0:
  9.              keyValue1=readKey( );
  10.              if(!keyValue1) return;
  11.              KEY_DELAY_DEC_TIMER = KEY_DELAY_DEC_TIMER_MAX;
  12.              stateMachine = 1;        
  13.            break;
  14.       case 1:
  15.           if( KEY_DELAY_DEC_TIMER )
  16.           {
  17.                keyValue2=readKey( );

  18.               if( keyValue1 != keyValue2) stateMachine = 0;
  19.            }
  20.            else
  21.            {
  22.                if(( keyValue2== STOP_KEY_BIT )
  23.                    ||( keyValue2== SET_KEY_BIT ))
  24.                {
  25.                    stateMachine = 2;
  26.                    KEY_DELAY_INC_TIMER =0;
  27.                 }
  28.                else
  29.                {
  30.                     KEY_VALUE = keyValue2;
  31.                     stateMachine = 3;
  32.                }

  33.             }
  34.          break;
  35.        case 2: //等待延时3秒以上
  36.              keyValue2 = readKey( );
  37.              if( keyValue2 )
  38.              {
  39.                   if( KEY_DELAY_INC_TIMER > KEY_DELAY_INC_TIMER_MAX)  
  40.                   {                  
  41.                       if(keyValue2== STOP_KEY_BIT )
  42.                   {
  43.                       //清除错误
  44.                       KEY_VALUE = CLEAR_ER_KEY_CMD;
  45.                    }
  46.                    else
  47.                    {

  48.                        if( keyValue2== SET_KEY_BIT )
  49.                        {

  50.                            //设定键--进入用户设定菜单
  51.                            KEY_VALUE = ENTER_USER_MENU_CMD;
  52.                         }
  53.                        else
  54.                         {
  55.                              KEY_VALUE = 0;

  56.                          }
  57.                     }
  58.                       stateMachine = 3;
  59.                  }
  60.              }
  61.             else
  62.             {

  63.                      KEY_VALUE = keyValue1;
  64.                       stateMachine = 3;
  65.             }

  66.              break;
  67.      case 3:  //等待键盘释放
  68.                keyValue2 = readKey( );
  69.                if( !keyValue2 )
  70.                {
  71.                    stateMachine = 0;
  72.                }  
  73.                break;
  74.      default:
  75.               break;
  76.    }
  77. }
 楼主| Luis德华 发表于 2015-10-22 08:42 | 显示全部楼层
  1. unsigned int getKey( )
  2. {
  3.     unsigned int keyVlue ;

  4.      keyVlue = KEY_VALUE;

  5.      KEY_VALUE = 0x00;

  6.       return keyVlue;

  7. }

readKey( )是读取键盘IO脚电压。键盘弹起时IO脚电平为低,键盘按下时IO脚电平位高。读取并且返回。 keyScan( )是键盘扫描程序,调用 readKey( )更新本地全局变量KEY_VALUE 。KEY_DELAY_DEC_TIMER 是键盘扫描定时器,在定时器中递减。代码如下:
  1. INTERRUPT (TIMER4_ISR, INTERRUPT_TIMER4)
  2. {
  3.     if( KEY_DELAY_DEC_TIMER > 0 ) KEY_DELAY_DEC_TIMER--;
  4.     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 清零。程序架构比较好,分层也很清楚!

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

  4.    。。。。
  5.      keyScan( );
  6.      key = getKey( );
  7.      exeKeyCmd( key );
  8.    。。。。。


  9.    }

我推荐这位工程师的键盘处理方法,显得比较成熟、老练。扩展性好,代码复用率也很好!
小番茄 发表于 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,同样很好理解。
仙女山 发表于 2015-10-22 09:26 | 显示全部楼层
Luis德华 发表于 2015-10-22 08:42
readKey( )是读取键盘IO脚电压。键盘弹起时IO脚电平为低,键盘按下时IO脚电平位高。读取并且返回。 keySca ...

这个工程师写的程序确实严谨,学习了
芙蓉洞 发表于 2015-10-22 09:33 | 显示全部楼层

第二种按键程序写的确实棒,收下了
gyh974 发表于 2015-10-22 14:55 | 显示全部楼层
很详细的分析!谢谢楼主
您需要登录后才可以回帖 登录 | 注册

本版积分规则

40

主题

370

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部