单片机中按键的短按,长按,单击,多击的驱动程序实现

接触过的按键驱动中,大部分是使用延时函数消抖,或者是在扫描到按键状态之后,直接就在按键扫描程序内部对事件进行处理。一直在思考一个不使用延时函数同时兼容性和移植性较强的按键驱动程序。之前用了半天时间总算是写出来这个驱动程序,还不是很成熟,不足之处多多指正。

在讲解之前,先说明一下按键,短按,长按,单击,多击分别是如何区分的。这里非常感谢张俊老师编著的《匠人手机》这本书中关于按键的讲解,给了我很大的启发。

相关详细的说明,请参阅《匠人笔记》中180页开始的手记13—按键漫谈。

在本次的驱动程序,需要一个定时器,6个字节的静态变量的资源开销。其中静态变量为一个无符号16位变量:key_longtime(用于长按按键时计时),两个无符号8位变量:key_time(按键动作结束计时)、C (状态标志),还有一个按键标志结构体;按键扫描程序的流程如下:


图1 按键扫描程序流程

这个按键扫描程序我是放在定时为1ms的定时器中断服务函数中执行。代码如下:

key.h文件

#ifndef _KEY_H
#define _KEY_H
#include "stm8l15x.h"
#include "bsp.h"

typedef enum{
  NoClick         = (u8)0x01,           //无按键的按键号
  SingleClick     = (u8)0x02,           //单击
  DoubleClick     = (u8)0x04,           //双击
  ThreeClick      = (u8)0x08,           //三击
  SingleLongClick = (u8)0x03            //长按
}KEY_NUM_FLAG;

typedef struct{
  bool state;             //按键状态
  u8 num;                   //按键号 
}KEY_NUM_STATE;

extern volatile KEY_NUM_STATE KEY_Flag;

#define KEY_Init() GPIO_Init(GPIOE,GPIO_Pin_7,GPIO_Mode_In_PU_No_IT)//按键I/O初始化
#define KEY GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_7)		    //按键状态获取

void KEYFlag_Init(void);
void KEY_Scan(void);

#endif

key.c文件

#include "key.h"

#define LONGMULTIHIT 0          //0,不使用长按多击功能              1,使用(长按需要松手才会有反应)
#define MAXKEYNUM ThreeClick	//按键号的范围

//这个数据是将按键扫描放在定时为1ms的中断中的数据,中断时间不同需要修改
#define LONGCLICKTIME 500		//长按按键计时
#define STOPCLICKTIME 200		//结束按键动作计时

/*
 * KEY_Flag.num (按键号) 
 * 0000 0010 短单击
 * 0000 0100 短双击
 * 0000 1000 短三击
 * 0001 0000 短四击 
 *     .       .
 *     .       .
 *     .       .
 *
 *
 * 0000 0011 长单击
 * 0000 0111 长双击
 *     .       .
 *     .       .
 *     .       .
 *
 *
 * KEY_Flag.state 为TRUE 确认状态,区分单击与多击时使用
 *
 * 使用按键号结束一定要清按键号(设置为NoClick)
 *
 */

volatile KEY_NUM_STATE KEY_Flag;

/* 按键初始化,程序开始前执行,也可以用来清除按键号 */
void KEYFlag_Init(void)
{
	KEY_Flag.num = NoClick;
	KEY_Flag.state = FALSE;
}

/************************************************
函数名称 : KEY_Scan
功    能 : 按键扫描,在定时为1ms的中断中
参    数 : 无
返 回 值 : 无
作    者 : zkx
*************************************************/
void KEY_Scan(void)
{
  static u16 key_longtime = 0;          //长按计时
  static u8 key_time = 0;               //结束动作计时
  static u8 C = 255;                    //按键状态标志
  if(C == 255)                          //松手检测
  {
    if(KEY == 0)                        //按键按下
    {
      key_longtime ++;                  //长按计时
      if(key_longtime>LONGCLICKTIME)    //确认按键长按
      {
        C = 1; 
      }
      key_time = 0;
    }
    else if(key_longtime>50)             //按键按下又松开,短按,大于50ms消抖
    {
      C = 0;                            //按键短按
      key_time = 0;
    }
	
#if LONGMULTIHIT
   else if(KEY_Flag.num != NoClick)     //按键动作结束后,无任何操作
   {
     key_time++;
   }
  }
#else
  }
   if(KEY_Flag.num != NoClick)     //按键动作结束后,无任何操作
   {
     key_time++;
   }
#endif

  if(C!=255)
  {
    if((C!=254)&&(KEY_Flag.num<=ThreeClick))             //未对按键值操作,按键使用范围
    {
      KEY_Flag.num = (KEY_Flag.num<<1)+C;
      key_longtime = 0;
      C = 254;
    }
    if(KEY)
    {
      C = 255;
    }
  }
  
  if(key_time>STOPCLICKTIME)    //无动作时间,确认当前状态
  {
    KEY_Flag.state = TRUE;
  }
}

说明下使用方法,将按键IO初始化(KEY_Init())和按键号初始化(KEYFlag_Init())之后,将按键扫描程序(KEY_Scan())放到定时为1ms的定时器中断服务函数中。接下来就不用管按键扫描程序了。

在需要使用按键的时候,直接读取按键号,这里分为两种情况。

1、不需要区分多击,只区分单击和长按:在需要使用按键的地方直接判断KEY_Flag.num的值,从而判断按键的状态。使用结束需要将KEY_Flag.num置为NoClick

2、需要区分多击:在需要使用按键的地方首先判断KEY_Flag.state是否为TRUE,为TRUE再判断KEY_Flag.num的值,从而判断按键的状态。使用结束需执行按键号初始化(KEYFlag_Init())

为了防止误触导致按键号KEY_Flag.num错乱,可以在大循环(while(1))最后,先判断KEY_Flag.state是否为TRUE,为TRUE再执行按键号初始化(KEYFlag_Init())。这里可以看出,这样操作就不需要在每次使用完按键号都进行初始化操作,确实不必这么做,所以在大循环清除一次就好。(此方法只适用于前后台系统)

猜你喜欢

转载自blog.csdn.net/cheerk/article/details/79911189