按键驱动-实现短按、长按、双击、长按抬起事件

简介: 按键驱动-实现短按、长按、双击、长按抬起事件

   按键驱动这是我早就想做的事情,想尽可能完善下各种不同的事件,以达到更少的按键实现更多的功能的目的。刚好最近有项目需要用到四个独立按键+LCD屏幕做各种界面的显示、切换、上下选择等操作,因此就捋了一下按键的驱动,后面如果有类似的需求,直接移植过去就好了。废话不多说,先看原理图。

       这四个GPIO采用MCU内部的上拉输入,没有按键按下的时候全是1,按键按下变成0。


       这个项目是使用32去做的,因此是基于32去开发的,移植到C51上也是一样适用的,逻辑都是一样的。

       

       先来看下引脚的定义:

/* 按键1 GPIO配置 */
#define  KEY1_GPIO_PORT          GPIOA
#define  KEY1_GPIO_PIN           GPIO_PIN_0
#define  KEY1_GPIO_MODE          GPIO_Mode_IPU
#define  KEY1_READ               GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN)
/* 按键2 GPIO配置 */
#define  KEY2_GPIO_PORT          GPIOA
#define  KEY2_GPIO_PIN           GPIO_PIN_1
#define  KEY2_GPIO_MODE          GPIO_Mode_IPU
#define  KEY2_READ               GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN)
/* 按键3 GPIO配置 */
#define  KEY3_GPIO_PORT          GPIOA
#define  KEY3_GPIO_PIN           GPIO_PIN_2
#define  KEY3_GPIO_MODE          GPIO_Mode_IPU
#define  KEY3_READ               GPIO_ReadInputDataBit(KEY3_GPIO_PORT, KEY3_GPIO_PIN)
/* 按键4 GPIO配置 */
#define  KEY4_GPIO_PORT          GPIOA
#define  KEY4_GPIO_PIN           GPIO_PIN_3
#define  KEY4_GPIO_MODE          GPIO_Mode_IPU
#define  KEY4_READ               GPIO_ReadInputDataBit(KEY4_GPIO_PORT, KEY4_GPIO_PIN)

   按键GPIO的初始化

/*
* 函数名称:Key_GPIOConfig
* 输入参数:None
* 返 回 值:None
* 作    者:Barry
* 功能描述:初始化按键GPIO为输入上拉模式
* 修改记录:None
*/
void Key_GPIOConfig(void)
{
    GPIO_InitType  Key_GPIOType;
    /* Key1引脚初始化 */
    Key_GPIOType.Pin        = KEY1_GPIO_PIN;
    Key_GPIOType.GPIO_Mode  = KEY1_GPIO_MODE;
    GPIO_InitPeripheral(KEY1_GPIO_PORT, &Key_GPIOType);
    /* Key2引脚初始化 */
    Key_GPIOType.Pin        = KEY2_GPIO_PIN;
    Key_GPIOType.GPIO_Mode  = KEY2_GPIO_MODE;
    GPIO_InitPeripheral(KEY2_GPIO_PORT, &Key_GPIOType);
    /* Key3引脚初始化 */
    Key_GPIOType.Pin        = KEY3_GPIO_PIN;
    Key_GPIOType.GPIO_Mode  = KEY3_GPIO_MODE;
    GPIO_InitPeripheral(KEY3_GPIO_PORT, &Key_GPIOType);
    /* Key4引脚初始化 */
    Key_GPIOType.Pin        = KEY4_GPIO_PIN;
    Key_GPIOType.GPIO_Mode  = KEY4_GPIO_MODE;
    GPIO_InitPeripheral(KEY4_GPIO_PORT, &Key_GPIOType);
    log_i("Key gpio config complete.\r\n");
}

 

       这些基本上使用过STM32的都很熟悉,就不详细赘述了。


       四个按键为了好区分分别是哪个按键被按下或者同时被按下,定义一个KeyVal保存键值做如下拼装:

 KeyVal = (KEY4_READ << 3) | (KEY3_READ << 2) | (KEY2_READ << 1) | KEY1_READ;

  这样一来KeyVal的低四位每一位可以表示一个按键的状态,即便四个同时按下也能区分出来。也能更加简单的实现组合按键。


核心代码:划重点

   keyscan.h

typedef enum
{
    KEY_NONE = 0,
    KEY1     = 1,
    KEY2     = 2,
    KEY3     = 3,
    KEY4     = 4,
}eKeyList;
typedef enum
{
    KEY_TYPE_NULL = 0,         /*! 没有按键*/ 
    KEY_TYPE_DOUBLE_CLICK = 1, /*! 双击按键 */
    KEY_TYPE_DOWN = 2,         /*! 按键按下*/
    KEY_TYPE_LONG = 3,         /*! 按键长按*/
    KEY_TYPE_HOLD = 4,         /*! 按键保持*/
    KEY_TYPE_SHORT_UP = 5,     /*! 按键短按弹起*/
    KEY_TYPE_LONG_UP = 6,      /*! 按键长按弹起*/
    KEY_TYPE_HOLD_UP = 7,      /*! 按键保持弹起*/
    KEY_TYPE_ALL = 255,        /*! 任意按键类型,不包含双击按键 */
}eKeyType;
typedef enum
{
    KEY_EVENT_NULL = 0,        /*! 没有按键事件*/
    SELECT_UP_EVENT = 1,       /*!向上选择事件*/
    SELECT_DOWN_EVENT = 2,     /*!向下选择时间*/
    ENTER_MENU_EVENT = 3,      /*! 进入菜单事件*/
    RETURN_MENU_EVENT = 4,     /*!返回上一级菜单事件*/
}eKeyEvent;
typedef struct 
{
    eKeyList Key;
    eKeyType KeyType;
}stKeyFunc;
#define  DEBOUNCE_TIME           50         /*! 消抖时间50ms*/
#define  CLICK_MAX_TIME          1200       /*! 单击最长时间(ms), 大于此时间被认为长按*/
#define  DOUBLE_CLICK_TIME       220        /*! 双击间隔时间*/

   keyscan.c

/*
* 函数名称:KeyScan
* 输入参数:None
* 返 回 值:None
* 作    者:Barry
* 功能描述:按键扫描,检测单击、双击、长按、短按抬起事件
* 修改记录:None
*/
void KeyScan(void)
{
    /* 按键按下的起始时间 */
    static uint32_t KeyStartTime = 0;
    /* 按键的触发标志 */
    static uint8_t  TrigFlag = 0;
    /* 上一次按下的键值 */
    static uint8_t  PreKeyVal = 0;
    /* 等待按键释放 */
    static uint8_t  WaitKeyUp = 0;
    /* 单击起始时间 */
    static uint32_t ClickStartTime = 0;
    /* 上次单击时间 */
    static uint32_t LastClickTime = 0;
    /* 双击标志位 */
    static uint8_t DoubleClickFlag = 0;
    /* 没有按键按下每个按键读取出来为1(配置为输入上拉),按下后为0 */
    /* 没有按键按下时默认KeyVal = 0x0F */
    KeyVal = (KEY4_READ << 3) | (KEY3_READ << 2) | (KEY2_READ << 1) | KEY1_READ;
    /* 按键在按下的状态--标记长按事件 */
    if(WaitKeyUp)
    {
        if(KeyVal == PreKeyVal) 
        {
            return;
        }
        WaitKeyUp = 0;
        KeyFunc.KeyType = KEY_TYPE_LONG_UP;
        //log_w("KEY_TYPE_LONG_UP.\r\n");
    }
    /* 有按键被按下 */
    if(KeyVal != 0x0F)
    {
        if(TrigFlag == 0)
        {
            TrigFlag = 1;   
            /* 保存键值 */
            PreKeyVal = KeyVal;
            /* 获取当前系统时钟计数值 */
            KeyStartTime = GetSysTickVal();
        }
        /* 之前已经按下该按键,并保持这个键按下 */
        else if((TrigFlag == 1) && (PreKeyVal == KeyVal))
        {
            uint32_t KeyDownTime = GetSysTickVal() - KeyStartTime;
            /* 按键按下时间小于消抖时间--消抖处理 */
            if(KeyDownTime <= DEBOUNCE_TIME) 
                return; 
            /* 大于单击的最长时间--认定为长按走这个分支 */
            else if(KeyDownTime > CLICK_MAX_TIME)
            {
                /* 清空标志位 */
                TrigFlag = 0;
                /* 置位等待释放标志位 */
                WaitKeyUp = 1;
                /* 标记按键按下状态 */
                KeyFunc.KeyType = KEY_TYPE_LONG;
                //log_w("KEY_TYPE_LONG.\r\n");
            }
        }
    }
    /* 没有按键按下或按键被释放 */
    else
    {
        /* 单击标志位 */
        static uint8_t ClickFlag = 0;
        if(TrigFlag != 0)
        {
            /* 按键按下到松开的时间 */
            uint32_t KeyTime = GetSysTickVal() - KeyStartTime;
            /* 按键按下时间小于消抖时间--消抖处理 */
            if(KeyTime <= DEBOUNCE_TIME) 
                return; 
            /* 按键按下的时间小于长按--短按(区分双击和单击) */
            else if(KeyTime <= CLICK_MAX_TIME)
            {
                if(GetSysTickVal() - LastClickTime <= DOUBLE_CLICK_TIME)
                {
                    /* 置位双击标志位 */
                    DoubleClickFlag = 1;
                    /* 双击 */
                    KeyFunc.KeyType = KEY_TYPE_DOUBLE_CLICK;
                    //log_w("KEY_TYPE_DOUBLE_CLICK.\r\n");
                }
                /* 单击 */
                else
                {
                    /* 置位单击标志位 */
                    ClickFlag = 1;
                    /* 清除双击标志位 */
                    DoubleClickFlag = 0;
                    /* 更新单击释放的时间 */
                    ClickStartTime = GetSysTickVal();
                }
                LastClickTime = GetSysTickVal();
            }
        }
        /* 单击标志位 且 大于双击的时间内没有双击的标志位被触发被认为是单击事件 */
        if((ClickFlag == 1) && ((GetSysTickVal() - ClickStartTime) > DOUBLE_CLICK_TIME) && (DoubleClickFlag == 0))
        {
            /* 单击抬起 */
            KeyFunc.KeyType = KEY_TYPE_SHORT_UP;
            /* 清除单击标志位 */
            ClickFlag = 0;
            /* 清除双击标志位 */
            DoubleClickFlag = 0;
            //log_w("KEY_TYPE_SHORT_UP.\r\n");
        }
        TrigFlag = 0;
    }  
}

使用前实现一个获取当前软件运行的时间即可,我这里使用的是Systick实现的。如下:每1ms中断一次

/*
* 函数名称:SystemTickConfig
* 输入参数:None
* 返 回 值:None
* 作    者:Barry
* 功能描述:初始化系统工作时钟
* 修改记录:None
*/
void SystemTickConfig(void)
{
  uint32_t Reload = 0;
  uint16_t TickRate = 1000u;
  SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
  /* 配置PCLK1时钟 */  
  RCC_ConfigPclk1(RCC_HCLK_DIV1);
  /* 配置HCLK时钟 */
  RCC_ConfigHclk(RCC_SYSCLK_DIV1);
  /* 配置PCLK2时钟 */
  RCC_ConfigPclk2(RCC_HCLK_DIV1);
  Reload = SystemCoreClock / TickRate;    
  /* 开启SYSTICK中断 */                             
  SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; 
  /* 每1/TickRate Hz中断一次 */       
  SysTick->LOAD = Reload;                 
  /* 使能SysTick */  
  SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;         
}
/*
* 函数名称:SysTick_Handler
* 输入参数:None
* 返 回 值:None
* 作    者:Barry
* 功能描述:滴答定时器中断服务函数
* 修改记录:None
*/
void SysTick_Handler(void)
{
    uwTick++;
}
/*
* 函数名称:GetSysTickVal
* 输入参数:None
* 返 回 值:None
* 作    者:Barry
* 功能描述:获取当前SysTick计数值
* 修改记录:None
*/
uint32_t GetSysTickVal(void)
{
    return uwTick;
}

       如果觉得本篇文章多少有点帮助的话大家不要忘了,点赞、关注、评论、转发哦,创作不易!你们的支持是小编创作的最大动力。

相关文章
|
10月前
|
安全 数据可视化 数据安全/隐私保护
【Win32】资源文件(对话框),逆向对话框回调函数,消息断点(附带恶意软件源码)(上)
【Win32】资源文件(对话框),逆向对话框回调函数,消息断点(附带恶意软件源码)
|
10月前
|
安全
【Win32】资源文件(对话框),逆向对话框回调函数,消息断点(附带恶意软件源码)(下)
【Win32】资源文件(对话框),逆向对话框回调函数,消息断点(附带恶意软件源码)
|
Linux Windows
Qt6 防止程序多重启动,并实现双击图标显示已运行的程序
欢迎来到我的博客,希望这篇文章对你有所帮助,如果觉得不错,请点赞搜藏哈。
471 0
|
Python
Python 技术篇-pyhook暂停键盘鼠标监听事件,停止键盘鼠标监听事件且不关闭程序
Python 技术篇-pyhook暂停键盘鼠标监听事件,停止键盘鼠标监听事件且不关闭程序
412 0
|
Python
Python 技术篇-pyHook键盘鼠标监听事件,监测鼠标键盘按键实例演示
Python 技术篇-pyHook键盘鼠标监听事件,监测鼠标键盘按键实例演示
348 0
Python 技术篇-pyHook键盘鼠标监听事件,监测鼠标键盘按键实例演示
|
C#
C#引用CefSharp并屏蔽鼠标右键和禁止拖动放置事件
原文:C#引用CefSharp并屏蔽鼠标右键和禁止拖动放置事件 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013564470/article/details/78339957 ...
4118 0
|
Web App开发 JavaScript Java