MCU短按长按的功能

本文介绍了如何使用STM32的一个IO口和定时器,通过状态机实现按键的短按和长按功能。通过定时器每隔20ms读取按键状态,定义了三种状态(空闲、消抖、确认按下/长按)和三个事件(空闲、单击、长按)。按键动作由读取引脚电平确定,状态处理函数根据状态和动作更新事件。在定时器中断中调用状态机函数,根据KeyCfg.KEY_Event处理相应事件。
摘要由CSDN通过智能技术生成

.外设:一个io口、一个定时器。
为了降低上手的门槛,本文仅使用一个IO口作演示。

实现思路
使用定时器,定时20ms来读取简化的按键状态机。
这里简化了状态机,大家只需明白三个概念。

状态:数量为有限个,记录按键状态。可根据条件切换。对应的代码中,用switch case来匹配不同的状态
条件:就是一些简单的判断。对应的代码,用if来判断按键按下或释放,判断时间计数的长短
事件:在特定的状态和条件下,产生的事件。代码只有三个事件:Null(空闲)SingleClick(短按)LongPress(长按)
思路图解:

在这里插入图片描述

3. 长按、单击 定义
长按事件:按下时间大于1s,释放后响应。(不支持连按,若需要连按,进行简单修改即可)
单击事件:按下时间小于1s, 释放后响应。

4.代码分析
4.1 外设准备

4.2 类型、变量定义
4.2.1按键事件
枚举型名称:KEY_EventList_TypeDef
本文实现短按长按功能,故只有三种事件:无事件、短按事件、长按事件
对应代码,即:空闲、单击、长按

typedef enum _KEY_EventList_TypeDef 
{
    KEY_Event_Null            = 0x00, // 空闲
    KEY_Event_SingleClick  = 0x01, // 单击
    KEY_Event_LongPress    = 0x02 // 长按
}KEY_EventList_TypeDef;

4.2.2 按键电平、动作
因为有的电路按下时,引脚为低电平;有的按下时,引脚却为高电平。这里将电平、动作分开,更方便移植。
枚举型名称:KEY_PinLevel_TypeDef
即:高、低电平。

// 按键引脚的电平
typedef enum

    KKEY_PinLevel_Low = 0,
    KEY_PinLevel_High
}KEY_PinLevel_TypeDef;

枚举型名称:KEY_Action_TypeDef
按键只有按下和没按下俩个动作:
即:按下、释放

// 按键动作,
typedef enum

    KEY_Action_Press = 0,
    KEY_Action_Release
}KEY_Action_TypeDef;

4.2.3 按键状态
枚举型名称:KEY_StatusList_TypeDef
思路图解的分析,分为如下几个状态。
即:空闲、消抖、确认按下、确认长按

// 按键状态
typedef enum _KEY_StatusList_TypeDef 
{
    KEY_Status_Idle     = 0, // 空闲
    KEY_Status_Debounce ,    // 消抖
    KEY_Status_ConfirmPress    ,    // 确认按下    
    KEY_Status_ConfirmPressLong,    // 确认长按
}KEY_StatusList_TypeDef;

4.2.4 按键配置结构体
按键配置信息的结构体名称:KEY_Configure_TypeDef
打包好按键的基本属性。

typedef struct _KEY_Configure_TypeDef 
{
    uint16_t                        KEY_Count;                 // 按键长按时长计数
    KEY_Action_TypeDef             KEY_Action;                // 按键动作,按下或释放
    KEY_StatusList_TypeDef         KEY_Status;                // 按键状态
    KEY_EventList_TypeDef          KEY_Event;                 // 按键事件
    KEY_PinLevel_TypeDef           (*KEY_ReadPin_Fcn)(void);  // 读引脚电平函数
}KEY_Configure_TypeDef;


成员解释:

KEY_Count:计数,记一个数为20ms。
KEY_Action:按键动作,按下或者释放。
KEY_Status:记录按键的状态值。
KEY_Event:记录按键触发的事件。
KEY_ReadPin_Fcn:读取按键电平值的函数指针。方便移植。
4.3 变量、函数、宏定义
4.3.1宏定义
KEY_LONG_PRESS_TIME :
短按、长按的时长分界线。大于–>长按,小于–>短按。
KEY_PRESSED_LEVEL:
按键被按下的实际电平,我的电路里,按键按下引脚接地。所以为低电平。
/**************************************************************************************************** 
*                             长按、单击 定义
* 长按事件:按下时间大于 KEY_LONG_PRESS_TIME,释放后响应。(不支持连按,需要连按响应可自己配置)
* 单击事件:按下时间小于 KEY_LONG_PRESS_TIME 释放后响应。
****************************************************************************************************/
// 长按时长的宏定义
#define KEY_LONG_PRESS_TIME    50  // 20ms*50 = 1s
#define KEY_PRESSED_LEVEL      0   // 按键被按下时的电平

4.3.2 变量定义
KeyCfg:全局变量,打包了按键的各个属性。

/**************************************************************************************************** 
*                            按键配置信息的全局结构体变量
****************************************************************************************************/
KEY_Configure_TypeDef KeyCfg={
        
        0,                        // 按键长按时长计数
        KEY_Action_Release,        // 按键动作,按下或释放 
        KEY_Status_Idle,        // 按键状态
        KEY_Event_Null,         // 按键事件
        KEY_ReadPin             // 读引脚电平函数
};

4.3.3函数定义
局部函数:

//读取引脚的电平
static KEY_Action_TypeDef KEY_ReadPin(void) 
{
  return (KEY_Action_TypeDef)GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);
}

// 获取按键动作,按下或释放,保存到结构体
static void KEY_GetAction(void) 
{
    if(KeyCfg.KEY_ReadPin_Fcn() == KEY_PRESSED_LEVEL)
    {
        KeyCfg.KEY_Action = KEY_Action_Press;
    }
    else
    {
        KeyCfg.KEY_Action =  KEY_Action_Release;
    }
 
}


KEY_ReadPin:读取PA_0的电平状态。
KEY_GetAction:将读取到的电平状态转换为实际的按下或释放的动作。
状态处理函数
KEY_ReadStateMachine:编写,思路完全按照前文手绘的思路图像
首先读取按键的动作,再在switch case 里面匹配引脚的状态,case下用if判断按键动作或按下的时长,对状态、事件进行赋值。

void KEY_ReadStateMachine(void)
{

    
    KEY_GetAction();
    
    switch(KeyCfg.KEY_Status)
    {
        //状态:没有按键按下
        case KEY_Status_Idle:
            if(KeyCfg.KEY_Action == KEY_Action_Press)
            {
                KeyCfg.KEY_Status = KEY_Status_Debounce;
                KeyCfg.KEY_Event = KEY_Event_Null;
            }
            else
            {
                KeyCfg.KEY_Status = KEY_Status_Idle;
                KeyCfg.KEY_Event = KEY_Event_Null;
            }
            break;
            
        //状态:消抖
        case KEY_Status_Debounce:
            if(KeyCfg.KEY_Action == KEY_Action_Press)
            {
                KeyCfg.KEY_Status = KEY_Status_ConfirmPress;
                KeyCfg.KEY_Event = KEY_Event_Null;
            }
            else
            {
                KeyCfg.KEY_Status = KEY_Status_Idle;
                KeyCfg.KEY_Event = KEY_Event_Null;
            }
            break;    
            
        //状态:确认按下
        case KEY_Status_ConfirmPress:
            if( (KeyCfg.KEY_Action == KEY_Action_Press) && ( KeyCfg.KEY_Count >= KEY_LONG_PRESS_TIME))
            {
                printf("KEY_Status_ConfirmPressLong\r\n");
                KeyCfg.KEY_Count = 0;
                KeyCfg.KEY_Status = KEY_Status_ConfirmPressLong;
                KeyCfg.KEY_Event = KEY_Event_Null;
                
            }
            else if( (KeyCfg.KEY_Action == KEY_Action_Press) && (KeyCfg.KEY_Count < KEY_LONG_PRESS_TIME))
            {
                printf("继续按下 %d\r\n",KeyCfg.KEY_Count);
                KeyCfg.KEY_Count++;
                KeyCfg.KEY_Status = KEY_Status_ConfirmPress;
                KeyCfg.KEY_Event = KEY_Event_Null;
            }
            else
            {
                printf("突然gg,按短了 %d\r\n",KeyCfg.KEY_Count);
                KeyCfg.KEY_Count = 0;
                KeyCfg.KEY_Status = KEY_Status_Idle;
                KeyCfg.KEY_Event = KEY_Event_SingleClick;

            }
            break;    

            
        //状态:确认长按
        case KEY_Status_ConfirmPressLong:
            printf("KEY_Status_ConfirmPressLong\r\n");
            if(KeyCfg.KEY_Action == KEY_Action_Press) 
            {   // 一直等待其放开
                printf("一直按着 KEY_Status_ConfirmPressLong\r\n");
                KeyCfg.KEY_Status = KEY_Status_ConfirmPressLong;
                KeyCfg.KEY_Event = KEY_Event_Null;
                KeyCfg.KEY_Count = 0;
            }
            else
            {
                KeyCfg.KEY_Status = KEY_Status_Idle;
                KeyCfg.KEY_Event = KEY_Event_LongPress;
                KeyCfg.KEY_Count = 0;
            }
            break;    
        default:
            break;
    }

}


5.1定时器中断:
直接调用KEY_ReadStateMachine()函数即可,将读取到的事件保存到KeyCfg.KEY_Event变量。

extern KEY_Configure_TypeDef KeyCfg;
//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{

    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
            KEY_ReadStateMachine();  //调用状态机
            
            if(KeyCfg.KEY_Event == KEY_Event_SingleClick)
            {
                printf("单击\r\n");//事件处理
            }
            if(KeyCfg.KEY_Event == KEY_Event_LongPress)
            {
                printf("长按\r\n");//事件处理
            }
        }
}

5.2 main函数
这里main函数十分简洁。初始化即可。
实际应用时,事件处理的代码不建议放在定时器里面。

int main(void)
{    
    uart_init(115200); // 用于查看输出
    TIM3_Int_Init(200-1,7200-1); //调用定时器使得20ms产生一个中断
    //按键初始化函数
    KEY_Init();
    
    while(1);

}     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值