3、按键扫描检测处理

按键扫描
版本一、二都可使用,推荐使用最新版本
说明:本文处理按键的短按、长按检测执行,非矩阵按键
硬件可以类似如下连接即可,无需放置上下拉电阻;
在这里插入图片描述
按键动作分长按、短按(弹起时执行)两种
按下不放执行长按,但松开按键时不予执行短按函数
多个按键可以同时操作
按键引脚会自动配置软件上下拉

程序V1.0

1、驱动文件

Key_Driver.h

#ifndef _Key_Driver_H_
#define _Key_Driver_H_
#include "Common_Driver.h"

extern void Key_Init(void);
extern void Key_Scan(void);

#endif

Key_Driver.c

/**********************************************************************
*file:按键扫描处理文件
*author:残梦
*date:2023.2.15
*Version:V1.0
*note:  按键动作分长按、短按(弹起时执行)两种
        按下不放执行长按,但松开按键时不予执行短按函数
        多个按键可以同时操作
        按键引脚会自动配置软件上下拉
使用方法:
    1、修改宏按键个数值:KEY_NUM
    2、修改宏按键引脚对应时钟:KEYx_GPIO_CLK_ENABLE()
    3、修改按键编码枚举:KeyCode_EnumDef
    4、修改按键端口及引脚号变量数组:Key_gpioPort[]、Key_gpioPin[];端口和引脚需相对应,第一个为按键0
    5、修改按键引脚初始电平状态值:KeyInitStatus[]
    6、主函数调用按键初始化函数Key_Init()
    7、1ms调用1次扫描函数Key_Scan()
    8、修改按键长按、短按执行函数:Key_LongPressAction()、Key_Action()
**********************************************************************/
#include "Key_Driver.h"

#define LongPressThr 2000//长按LongPressThr毫秒标为长按按键动作
#define LongPressInc 500//长按LongPressInc毫秒执行一次长按动作函数

#define KEY_NUM 4//按键个数
#define KEY0_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOE_CLK_ENABLE()
#define KEY1_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOE_CLK_ENABLE()
#define KEY2_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOE_CLK_ENABLE()
#define KEY3_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()

typedef enum
{
    eKeyCode0 = 0,
    eKeyCode1 = 1,
    eKeyCode2 = 2,
    eKeyCode3 = 3
}KeyCode_EnumDef;//按键编码

static GPIO_TypeDef*    Key_gpioPort[KEY_NUM]   = {GPIOE,GPIOE,GPIOE,GPIOA};//按键引脚对应端口key0-key
static uint16_t         Key_gpioPin[KEY_NUM]    = {GPIO_PIN_4,GPIO_PIN_3,GPIO_PIN_2,GPIO_PIN_0};//按键引脚对应引脚号key0-key
static uint8_t          KeyInitStatus[KEY_NUM]  = {1,1,1,0};//按键引脚初始电平状态:0--低电平,1--高电平
static uint8_t KeyReset = 1;//无需用户更改

static void Key_LongPressAction(KeyCode_EnumDef key,uint32_t DownTime);
static void Key_Action(KeyCode_EnumDef key);

/****************************************************
@function:按键初始化(无需用户修改)
@param:void
@return:void
@date:2023.2.15
@note:
****************************************************/
void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    KEY0_GPIO_CLK_ENABLE();
    KEY1_GPIO_CLK_ENABLE();
    KEY2_GPIO_CLK_ENABLE();
    KEY3_GPIO_CLK_ENABLE();

    //外设引脚初始化
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    for(uint8_t i = 0;i < KEY_NUM;i++)
    {
        GPIO_InitStruct.Pull = KeyInitStatus[i]?GPIO_PULLUP:GPIO_PULLDOWN;
        GPIO_InitStruct.Pin = Key_gpioPin[i];
        HAL_GPIO_Init(Key_gpioPort[i], &GPIO_InitStruct);
    }
    KeyReset = 1;
}

/******************************
@function:按键扫描函数(无需用户修改)
@param:void
@return:void
@date:2023.2.15
@remark:需在定时中断中调用,推荐调用间隔1ms
        长按和短按分为两种
        按下不放执行长按,但是弹起时不予执行短按函数
******************************/
void Key_Scan(void)
{
    static uint8_t keybuf[KEY_NUM] = {0xFF};//按键扫描缓冲区赋值
    static uint8_t KeySta[KEY_NUM] = {1};//全部矩阵按键的当前状态
    static uint8_t backup[KEY_NUM] = {1};//按键值备份,保存前一次的值
    static uint32_t KeyDownTime[KEY_NUM] = {0};//长按累计时间
    static uint32_t KeyDownThr[KEY_NUM] = {LongPressThr};
    static uint8_t KeyDownFlag[KEY_NUM] = {0};
    uint8_t i = 0,pinSta = 0;

    if(KeyReset)
    {
        KeyReset = 0;
        for(i = 0;i < KEY_NUM;i++)
        {
            keybuf[i] = 0xFF;
            KeySta[i] = 1;
            backup[i] = 1;
            KeyDownTime[i] = 0;
            KeyDownThr[i] = LongPressThr;
            KeyDownFlag[i] = 0;
        }
    }

    //将按键值移入缓冲区
    for(i=0;i < KEY_NUM;i++)
    {
        pinSta = (uint8_t)HAL_GPIO_ReadPin(Key_gpioPort[i],Key_gpioPin[i]);
        if(!KeyInitStatus[i]){pinSta = pinSta?0:1;}
        keybuf[i] = (keybuf[i] << 1) | pinSta;
    }

    //消抖后更新按键状态
    for (i=0; i < KEY_NUM; i++)
    {
        if((keybuf[i] & 0x0F) == 0x00){KeySta[i] = 0;}//连续4次扫描值为0,即4ms内都是按下状态时,可认为按键已稳定的按下
        else if((keybuf[i] & 0x0F) == 0x0F){KeySta[i] = 1;}//连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起
    }

    for(i=0; i < KEY_NUM; i++)//循环检测按键
    {
        if(KeySta[i] == 0)//长按按键
        {
            KeyDownTime[i] += 1;
            if(KeyDownTime[i] >= KeyDownThr[i])
            {
                KeyDownFlag[i] = 1;		//长按标志
                KeyDownThr[i] += LongPressInc; //时间阈值增加LongPressInc毫秒,以准备下次执行
                Key_LongPressAction((KeyCode_EnumDef)i,KeyDownTime[i]); //调用长按执行函数
            }
        }

		//按键弹起 
		if(backup[i] != KeySta[i])//检测按键动作
        {
            if(backup[i] != 1)//按键按下时执行动作
            {
                if(KeyDownFlag[i] == 0)//长按弹起避免重复执行	
                {
                    Key_Action((KeyCode_EnumDef )i); //调用按键动作函数
                }
            }
            backup[i] = KeySta[i];     //刷新前一次的备份值
        }
					 
        if(KeySta[i]) 
        {
            KeyDownFlag[i] = 0;	//长按标志
            KeyDownTime[i] = 0;	//累加时间清零
            KeyDownThr[i] = LongPressThr;//恢复阈值
        }					
    }
}

/******************************
@function:长按按键动作函数
@param:key-按键键码
@return:void
@date:2023.2.15
@remark:void
******************************/
static void Key_LongPressAction(KeyCode_EnumDef key,uint32_t DownTime)
{
    printf("Key%d:%d\r\n",key,DownTime);
    switch(key)
    {
        case eKeyCode0:
        {
            
        }break;
        case eKeyCode1:
        {

        }break;
        case eKeyCode2:
        {

        }break;
        case eKeyCode3:
        {

        }break;
    }
}

/******************************
@function:短按按键动作函数
@param:key-按键键码
@return:void
@date:2023.2.16
@remark:void
******************************/
static void Key_Action(KeyCode_EnumDef key)
{
    printf("Action Key%d\r\n",key);
    switch(key)
    {
        case eKeyCode0:
        {

        }break;
        case eKeyCode1:
        {

        }break;
        case eKeyCode2:
        {

        }break;
        case eKeyCode3:
        {

        }break;
    }

}

2、使用方法

1、修改宏按键个数值:KEY_NUM
2、修改宏按键引脚对应时钟:KEYx_GPIO_CLK_ENABLE()
3、修改按键编码枚举:KeyCode_EnumDef
4、修改按键端口及引脚号变量数组:Key_gpioPort[]、Key_gpioPin[];端口和引脚需相对应,第一个为按键0
5、修改按键引脚初始电平状态值:KeyInitStatus[]
6、主函数调用按键初始化函数Key_Init()
7、1ms调用1次扫描函数Key_Scan()
8、修改按键长按、短按执行函数:Key_LongPressAction()、Key_Action()

测试效果:
在这里插入图片描述
注意:扫描时间1ms如觉得太长可以更久调用1次,程序中会检测4次引脚的状态值,如都为1或0则算为稳定状态
按键文件下载:
链接:https://pan.baidu.com/s/1sO_kFuJb6wtLiMlFbneFsA
提取码:sun5

版本V1.1

驱动程序

key_driver.h

#ifndef _key_driver_H_
#define _key_driver_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "stdlib.h"

typedef enum
{
    eKey_Enter = 0,//按键0
    eKey_Num//按键个数
}Key_List_EnumDef;//按键列表

typedef enum
{
    eKey_Event_Press = 0,//按键按下
    eKey_Event_LongPress,//按键长按
    eKey_Event_LongPress_Up,//按键长按弹起
    eKey_Event_Up,//按键单击弹起
    eKey_Event_Num
}Key_Event_EnumDef;//按键动作事件

extern const char Name_Key_List[eKey_Num][6];
extern const char Name_Key_Event[eKey_Event_Num][13];

void Key_Init(void);
void Key_Scan(void);

#ifdef __cplusplus
}
#endif
#endif

key_driver.c

/**********************************************************************
*file:按键扫描文件
*author:残梦
*date:2023.10.10
*Version:V1.1
*note:  按键动作分多个事件,见事件列表Key_Event_EnumDef
        多个按键可以同时操作
        按键引脚会自动配置软件上下拉
        按键扫描建议10ms一次
使用方法:
    1、修改枚举按键个数Key_List_EnumDef
    2、修改宏按键引脚对应时钟:KEY0_GPIO_CLK_ENABLE()
    4、修改按键端口及引脚号变量数组:key_gpioPort[]、key_gpioPin[];端口和引脚需相对应,第一个为按键0
    5、修改按键引脚初始电平状态值:key_InitStatus[]
    6、主函数调用按键初始化函数Key_Init()
    7、1ms调用1次扫描函数Key_Scan(),且修改相应的调用周期时间dScanPeriod的值
    8、修改Key_Event_Callback()函数,编写自己的应用程序
**********************************************************************/
#include "key_driver.h"
#include "gpio.h"
#include "stdio.h"

#define dScanPeriod   10     //单位:毫秒;按键扫描Key_Scan()周期
#define dLongPressThr (1000/dScanPeriod)    //单位:毫秒;按键长按动作阈值
#define dLongPressInc (100/dScanPeriod)    //单位:毫秒;长按递增时间量

#define KEY0_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOC_CLK_ENABLE()//KEY0 PC13
static GPIO_TypeDef*    key_gpioPort[eKey_Num]   = {GPIOC};//按键顺序对应Key_List_EnumDef
static uint16_t         key_gpioPin[eKey_Num]    = {GPIO_PIN_13};//按键顺序对应Key_List_EnumDef
static uint8_t          key_InitStatus[eKey_Num]  = {1};//按键引脚初始电平状态:0--低电平,1--高电平

const char Name_Key_List[eKey_Num][6] = {"Enter"};
const char Name_Key_Event[eKey_Event_Num][13] = {"Press","LongPress","LongPress_Up","Up"};
static uint8_t key_reset = 0;//按键初始化后复位扫描函数局部变量,无需用户更改
static void Key_Event_Callback(Key_List_EnumDef key,Key_Event_EnumDef event,uint32_t PressTime);

/****************************************************
@function:按键初始化(无需用户修改)
@param:void
@return:void
@note:
****************************************************/
void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    KEY0_GPIO_CLK_ENABLE();

    //外设引脚初始化
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    for(uint8_t i = 0;i < eKey_Num;i++)
    {
        GPIO_InitStruct.Pull = key_InitStatus[i]?GPIO_PULLUP:GPIO_PULLDOWN;
        GPIO_InitStruct.Pin = key_gpioPin[i];
        HAL_GPIO_Init(key_gpioPort[i], &GPIO_InitStruct);
    }
    key_reset = 1;
}

/******************************
@function:按键扫描函数(无需用户修改)
@param:void
@return:void
@remark:需在定时中断中调用,推荐调用间隔10ms
******************************/
void Key_Scan(void)
{
    static uint8_t keybuf[eKey_Num] = {0xFF};//按键扫描缓冲区赋值
    static uint8_t KeySta[eKey_Num] = {1};//全部矩阵按键的当前状态
    static uint8_t backup[eKey_Num] = {1};//按键值备份,保存前一次的值
    static uint32_t KeyDownTime[eKey_Num] = {0};//长按累计时间
    static uint32_t KeyDownThr[eKey_Num] = {dLongPressThr};
    static uint8_t KeyDownFlag[eKey_Num] = {0};
    static uint8_t KeyClearNum = 0x0F;//按键消抖次数
    uint8_t i = 0,pinSta = 0;

    if(key_reset)//复位扫描函数局部变量
    {
        key_reset = 0;
        for(i = 0;i < eKey_Num;i++)
        {
            keybuf[i] = 0xFF;
            KeySta[i] = 1;
            backup[i] = 1;
            KeyDownTime[i] = 0;
            KeyDownThr[i] = dLongPressThr;
            KeyDownFlag[i] = 0;
        }
        KeyClearNum = (dScanPeriod < 10)?0x0F:0x01;
    }

    //将按键值移入缓冲区
    for(i=0;i < eKey_Num;i++)
    {
        //扫描按键引脚状态
        pinSta = (uint8_t)HAL_GPIO_ReadPin(key_gpioPort[i],key_gpioPin[i]);
        if(!key_InitStatus[i]){pinSta = pinSta?0:1;}
        keybuf[i] = (keybuf[i] << 1) | pinSta;

        //消抖后更新按键状态:此处需注意:0x01--连续1次;0x03--连续2次;0x07--连续3次,0x0F连续4次
        //当低于10ms时建议扫描4次,否则1次
        if((keybuf[i] & KeyClearNum) == 0x00){KeySta[i] = 0;}//扫描值为0,即4*周期内都是按下状态时,可认为按键已稳定的按下
        else if((keybuf[i] & KeyClearNum) == KeyClearNum){KeySta[i] = 1;}//扫描值为1,即4*周期内都是弹起状态时,可认为按键已稳定的弹起
    
        //按键事件检测
		if(backup[i] != KeySta[i])//检测按键动作变化
        {
            if(KeySta[i] == 0)//按键按下:eKey_Event_Press
            {
                Key_Event_Callback((Key_List_EnumDef )i,eKey_Event_Press,0);
            }
            else
            {
                if(KeyDownFlag[i] == 0)//长按弹起避免重复执行	
                {
                    Key_Event_Callback((Key_List_EnumDef )i,eKey_Event_Up,0);
                }
                else
                {
                    KeyDownFlag[i] = 0;	//长按标志
                    KeyDownTime[i] = 0;	//累加时间清零
                    KeyDownThr[i] = dLongPressThr;//恢复阈值

                    Key_Event_Callback((Key_List_EnumDef )i,eKey_Event_LongPress_Up,KeyDownTime[i]);
                }
            }
            backup[i] = KeySta[i];//刷新前一次的备份值
        }
        else if(KeySta[i] == 0)//按键长按
        {
            KeyDownTime[i] += 1;
            if(KeyDownTime[i] >= KeyDownThr[i])
            {
                KeyDownFlag[i] = 1; //长按标志
                KeyDownThr[i] += dLongPressInc; //时间阈值增加LongPressInc毫秒,以准备下次执行
                Key_Event_Callback((Key_List_EnumDef )i,eKey_Event_LongPress,KeyDownTime[i]);
            }
        }
    }
}

/******************************
@function:按键响应事件
@param: key--动作按键
        event--动作事件
        PressTime--按下累计时间
@return:void
@remark:
******************************/
static void Key_Event_Callback(Key_List_EnumDef key,Key_Event_EnumDef event,uint32_t PressTime)
{
    printf("%s-%s-%d\n",Name_Key_List[key],Name_Key_Event[event],PressTime);
}

使用效果:

在这里插入图片描述
文件下载
链接:https://pan.baidu.com/s/1RT84Z-k_qET5Jb0eAxFn-w
提取码:j8g2

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值