我的按键学习史

  实体按键作为人机界面中必不可少的一部分,即使现在已经大部分使用触控,但绝大部分的设备仍然还是保留着一小部分的实体按键,所以对于学习单片机微机控制来说,进一步学习按键还是极其有必要的。

     对于实体按键来说,原理基本就是利用单片机的IO口的读取功能,来识别IO口上的电平变化,这样除了高电平 就是低电平了;硬件电路比较简单,最常用的有以下几种

 

图1是一种最简单按键硬件电路模型,需要使能单片机内部上拉电阻

 

图2这种电路相对来说多加了一个电容,电阻,硬件上已经实现了滤波,及上拉

 

 

图3  4x4矩阵式键盘,8个IO口扫描式工作

 

对于硬件电路来说并没有什么变化,而主要变化的是驱动软件的实现。

在刚学习单片机那会儿,主要还是利用在while(1)大循环里不断的进行if判断是否有按键变化,根据对应的按键变化执行对应的功能,代码如下:

 

int mian()

{

     …………

While(1)

{

          if(!(PINB&0x01))                  //110,k1按下,K2,K3没按下,数码管复位,显示0

         {

              _delay_ms(20);               //防抖

              if(!(PINB&0x01))  

              {

                   ……

                   while(!(PINB&0x01));   //按键释放才完成操作      

              }

         }

         if(!(PINB&0x02))                 //101,k2按下,K1,K3没按下,数码管数字加一

         {   

              _delay_ms(20);               //防抖

              if(!(PINB&0x02))

              {

                   ……

                   while(!(PINB&0x02)); //按键释放才完成操作     

              }

         }

}

}

这种方式在简单的应用场合中还是可以应用的,如果代码一多,那么这个while就会又长又臭,无论是20毫秒(防抖)的延时还是while(!(PINB&0x01));都会严重浪费有限的系统资源

在学习了模块化编程后,针对按键建立两个文件 key.h 和key.c来专门分装一个基于按键操作的函数。

按键将代码更改如下:

key.h文件部分内容

#ifndef _USE_KEY_H

#define _USE_KEY_H  1

 

#define DDR_K1_IO     DDRC

#define PORT_K1_IO    PORTC

#define PIN_K1_IO     PINC

 

#define KEY_1              (1<<2)       //PC3

#define KEY_2              (1<<3)       //PC2

 

#define     FUN1            10

#define     FUN2            11

 

extern void Init_Key(void);

extern uchar Key_Cont(void);

extern uchar Key_Cont_Wait(void);

 

#endif

key.c文件部分内容

……………………………

uchar Key_Cont(void)

{

     Init_Key();

     if(!(PIN_K1_IO & KEY_2))

     {

         delay_ms(20);

         if(!(PIN_K1_IO & KEY_2))

         {   

              g_key_mod  =  FUN1;            

              while(!(PIN_K2_IO & KEY_2));

         }   

     }

     if(!(PIN_K1_IO & KEY_3))

     {

         delay_ms(20);

         if(!(PIN_K1_IO & KEY_3))

         {

              g_key_mod  =  FUN2;

              while(!(PIN_K1_IO & KEY_3));

         }   

     }

     return g_key_mod;

}

int main()

{

     …………

while(1)

{

          switch(Key_Cont())

         {

              case FUN1:

              {

                    ………   相应的功能代码

              }break;

              case FUN2:

              {

                  ………  相应的功能代码

              }break;

              default:break;

         }

}

}

 

  这样在主函数中用一个switch就可以执行按键功能了,在任意的需要按键的函数中只需调用Key_Cont()函数就可以完成按键的功能的实现,大大增加了可读性,和易用性,但仍然没有解决效率和系统资源浪费的问题。

  在《AVR单片机嵌入式系统原理与应用实践》一书中看到了一种基于状态机加定时器扫描(或调度器)的方法,这种方法就解决了上面说的资源浪费问题

key.h文件部分内容

#ifndef _USE_KEY_H

#define _USE_KEY_H  1

 

#define   KEY_DDR            DDRB

#define   KEY_PORT           PORTB

#define   KEY_PIN            PINB

 

#define   READ_KEY_1             (KEY_PIN & (1<<PB0))       //PB0

#define   READ_KEY_2             (KEY_PIN & (1<<PB1))       //PB1

#define   READ_KEY_3             (KEY_PIN & (1<<PB2))       //PB2

 

#define     KEY_STATE_0         0

#define     KEY_STATE_1         1

#define     KEY_STATE_2         2

 

#define     KEY_A            10    //click

#define     KEY_B           11    //dblclick

#define  KEY_C             12    //long_press

 

#define   KEY_VALUE     !READ_KEY_1||!READ_KEY_2||!READ_KEY_3

 

extern void key_init(void);

uchar key_con_mul(void);

 

#endif

key.c文件部分内容

uchar g_key_state;

uchar g_key_value;

uchar key_con_mul(void)

{

     Init_Key();

     if (KEY_VALUE) g_key_value = 1;

     else g_key_value = 0;

     switch(g_key_state)

     {

         case KEY_STATE_0:

         {

              if(g_key_value)

              {

                   g_key_state = KEY_STATE_1;   //有按键按下跳转到状态1

              }

         }break;

         case KEY_STATE_1:

         {

              if(g_key_value)                       //判断按键是否是真的按下

              {

                   if (!READ_KEY_1){g_key_num=10;}  //找到是哪个按键按下

                   if (!READ_KEY_2){g_key_num=11;}

                   if (!READ_KEY_3){g_key_num=12;}   

                   g_key_state = KEY_STATE_2;       // 确认按键编号后 转到状态2

              }

              else

              {

                   g_key_state = KEY_STATE_0;  //按键已经抬起,回到初始态   

              }   

         }break;

         case KEY_STATE_2:

         {

              if(!g_key_value)

              {

                   g_key_state = KEY_STATE_0;//按键已经抬起,回到初始态          

}

         }break;

         default:break;

     }

     return g_key_num;

}

  上面代码在原来的基础上增加了多按键的支持,要实现上述代码的功能还需要一个定时器的支持,给定时器一个固定的进入定时器中断的时间,在定时器中断中执行key_con_mul()函数,每执行一次key_con_mul()函数都会获取一个当前按键的状态,通过获取的当前按键的状态,来决定下一个按键的状态,通过每两次运行的时间间隔可以有效的做到防抖等效果。上面的功在调度器中使用更能发挥优势。、

  下面介绍一种基于状态机按键的增强型,在这个原来的基础上增加了单按键的 单击 双击  长按 等功能,主要原理是增加了两个计数变量,通过计数变量的值及组合方式,来决定按键的功能,基于这个基础还可以扩展更多的功能

key.h文件部分内容

#ifndef _USE_KEY_H

#define _USE_KEY_H  1

#define   KEY_DDR            DDRB

#define   KEY_PORT           PORTB

#define   KEY_PIN            PINB

 

#define   READ_KEY_1             (KEY_PIN & (1<<PB0))       //PB0

#define   READ_KEY_2             (KEY_PIN & (1<<PB1))       //PB1

#define   READ_KEY_3             (KEY_PIN & (1<<PB2))       //PB2

 

#define     KEY_STATE_0         0

#define     KEY_STATE_1         1

#define     KEY_STATE_2         2

#define     KEY_STATE_3         3

#define     KEY_STATE_4         4

 

#define     KEY_CLICK        10    //click

#define     KEY_DBLCK        11    //dblclick

#define    KEY_LONPR        12    //long_press

 

#define   KEY_VALUE     !READ_KEY_1||!READ_KEY_2||!READ_KEY_3

typedef struct        

{

     uint16_t count1;

     uint16_t count2;

     uint8_t fun;

     uint8_t mode;

}KEY_M;

KEY_M KEY_STATUS[3];

 

extern void key_init(void);

uchar key_con_mul(void);

 

#endif

key.c文件部分内容

uchar g_key_num;

uchar g_key_state;

uchar g_key_value;

 

uchar key_mode(uint8_t key_num)  //匹配键值

{

  if ((KEY_STATUS[key_num].count1>0)&&(KEY_STATUS[key_num].count1<100)&&(KEY_STATUS[key_num].count2==0))

     {

         KEY_STATUS[key_num].fun = KEY_CLICK;    //单击

     }

     else if ((KEY_STATUS[key_num].count1 > 100)&&(KEY_STATUS[key_num].count2==0))

     {

         KEY_STATUS[key_num].fun = KEY_DBLCK;    //双击

     }

     else if(KEY_STATUS[key_num].count2)

     {

         KEY_STATUS[key_num].fun = KEY_LONPR;    //长按

     }

     KEY_STATUS[key_num].count1=0;

     KEY_STATUS[key_num].count2=0;

}

uchar key_con_mul(void)

{

     key_init();

     if (KEY_VALUE)

     {

         g_key_value = 1;   //有按键按下

     }

     else

     {

         g_key_value = 0;   //没有按键按下 或按键已经释放

     }

     switch(g_key_state)

     {

         case KEY_STATE_0:

         {

              if(g_key_value)

              {

                   g_key_state = KEY_STATE_1;   //有按键按下,切换到状态1

              }

         }break;

         case KEY_STATE_1:

         {

              if(g_key_value)                      //如果按键仍按下,识别出是哪个按键

              {

                   if (!READ_KEY_1){g_key_num=0;} 

                   if (!READ_KEY_2){g_key_num=1;}

                   if (!READ_KEY_3){g_key_num=2;}

                   g_key_state = KEY_STATE_2;  //确认哪个按键按下后,切换到状态2

              }

              else

              {

                   g_key_state = KEY_STATE_0;  // 如果按键已抬起,则识别为误操作,转换到按键初始态0

              }

         }break;

         case KEY_STATE_2:

         {

              if(g_key_value)                //按键处于连续按下状态,该按键对应的计数器1 加1

              {

                   KEY_STATUS[g_key_num].count1++;

                   g_key_state = KEY_STATE_2; //按键未释放,继续执行 状态2

              }

              else

              {

                   g_key_state = KEY_STATE_3; //按键已释放,切换到状态3

              }

         }break;

         case KEY_STATE_3:

         {

              if(!g_key_value)              //按键处于释放状态,该按键对应的计数器2 加1

              {

                   KEY_STATUS[g_key_num].count2++;

                   if (KEY_STATUS[g_key_num].count2 < 20)//按键对应的计数器2 累加值没有超过判定值,继续执行状态3

                   {

                       g_key_state = KEY_STATE_3;

                   }

                   else

                   {

                       g_key_state = KEY_STATE_0;  //按键对应的计数器2 累加值没有超过判定值 可以判断按键已释放,转换到按键初始态

                       KEY_STATUS[g_key_num].count2=0;

                       key_mode(g_key_num);

                   }       

              }

              else

              {

                   g_key_state = KEY_STATE_4;  //当有第二次按键按下时,转换到按键状态4

                   KEY_STATUS[g_key_num].count2=0;

              }

         }break;

         case KEY_STATE_4:

         {

              if(g_key_value)

              {

                   KEY_STATUS[g_key_num].count2++;

                   g_key_state = KEY_STATE_4;//按键没有释放,继续回到KEY_STATE_4

              }

              else

              {

                   g_key_state = KEY_STATE_0;

                   key_mode(g_key_num);

              }

         }break;

         default:break;

     }

     return g_key_num;

}

 

功能越多其要花费的资源就越多,这是在所难免的,下面再分享一个在网上看到的及省资源的按键实现方法:

多个按键使用同一个端口,按键算法

unsigned char Trg;

unsigned char Cont;

void KeyRead( void )

{

     unsigned char ReadData = PINB^0xff;      // 1

     Trg = ReadData & (ReadData ^ Cont);      // 2

     Cont = ReadData;                         // 3

}

在资源极度缺乏的设备上可以一用。

posted @ 2013-09-12 16:52  xzwj  阅读(470)  评论(0编辑  收藏  举报