上一篇文章介绍了用定时扫描的办法判断按键是否按下。然而,由于按键有限,在现实生活中,仅靠几个按键当然无法完成很多指令,于是人们想到了通过一个按键的短按、长按与双击来区分不同的指令,这也是蓝桥杯的考点之一。接下来我们就来讲讲如何判断短按、长按与双击。

需要判断长短按和双击,首先需要判断按键是否按下,这通过上一讲内容就可以实现。此外,短按、长按和双击涉及到按键的时间长短问题,因此我们需要利用定时器的基本计时功能来计算按键按下的长短。因此,大体可以敲定代码的框架:利用0.01s的定时器中断扫描按键,并在按下按键时开始计时,以区分长短按;在松开按键时开始计时,判断是两次间隔较长的短按还是一次双击。

我们需要定义几个变量,来存储按键的状态以及判断过程。由于一共有四个按键,因此我们可以定义一个结构体,不同的结构体变量代表不同的按键。

在这个结构体中,首先我们需要三个标志变量:single_flaglong_flagdouble_flag来存储短按长按双击的信息;接着,我们需要一个状态变量:key_status来存储按键端口的电平状态。由于这4个变量的值只有0和1,我们使用bool的变量类型(需要调用stdbool.h)考虑到在按键按下或松开的瞬间,可能会出现机械抖动的情况(即按键状态发生改变的瞬间,其端口电平状态在高低之间反复跳动),我们需要等待电平状态稳定以后才开始对按键情况进行判断。在这里,我们设置了0.01s的中断周期,因此当按下按键时,在第二次进入定时器中断时,其电平状态就能稳定下来,故需要定义一个变量click_status来标志按键按下的状态是否稳定,以达到消抖的目的。因为要判断长短按,我们还需要一个变量click_time来存储按键时长;同时,要判断是两次短按还是一次双击。我们还需要一个状态变量double_status来判断是否已进入需要判断双击与否的状态否则一次长按与一次短按或两次长按也有可能被识别为双击),一个变量double_time来存储两次按键的时间间隔。经过分析,我们定义如下结构体,key[0]~key[3]分别表示B1~B4。

struct keys
{
    bool single_flag;
    bool long_flag;
    bool double_flag;
    bool key_status;
    uint8_t click_status;
    int click_time;
    uint8_t double_status;
    int double_time;
} key[4];

下面是以B1为例的一段代码:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM4)
    {
        key[0].key_status = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);    //读取PB0的电平状态
        
        switch (key[0].click_status)    //根据不同的状态进行不同的判断
        {
            case 0:                     //状态0:第一次按下B1
                if (key[0].key_status == GPIO_PIN_RESET)    key[0].click_status = 1;
                                        //跳转到状态1
                break;
            case 1:                     //状态1:电平已经稳定
                if (key[0].key_status == GPIO_PIN_RESET) //若此时为低电平,说明B1的确被按下
                {
                    key[0].click_status = 2;             //跳转到状态2
                    key[0].click_time = 0;               //开始计时
                }
                else    key[0].click_status = 0;         //B1没有被按下,可能是松开时的抖动
                break;
            case 2:
                if (key[0].key_status == GPIO_PIN_RESET)    key[0].click_time++;
                //若B1持续被按下,则计时一直增加

                else if (key[0].key_status==GPIO_PIN_SET && key[0].click_time>=70)
                //若B1已经松开,且按下的持续时间大于一定值
                {
                    key[0].long_flag = 1;       //判定为长按
                    key[0].click_status = 0;    //重置判断按键的状态
                }

                else if (key[0].key_status==GPIO_PIN_SET && key[0].click_time<70)
                //若B1已经松开,且按下的持续时间小于一定值,则有可能是短按,有可能是双击
                {
                    switch (key[0].double_status)      //根据不同的状态进行不同的判断
                    {
                        case 0:                        //状态0:第一次松开按键
                            key[0].double_status = 1;  //跳转到状态1
                            key[0].double_time = 0;    //开始计时
                            break;
                        case 1:                        //状态1:第二次松开按键
                            key[0].double_flag = 1;    //判定为双击
                            key[0].double_status = 0;  //重置判断双击的状态
                            break;
                    }
                /* 如果这一段看不明白,请继续往下看,再回头理解 */
                    key[0].click_status = 0;           //重置判断按键的状态
                }
                break;
        }

        if (key[0].double_status == 1)              //状态1:第一次松开后未按下按键
        {
            key[0].double_time++;                  //若一直未按下第二次按键,则计时增加
            if (key[0].double_time >= 35)          //若间隔时间大于一定值
            {
                key[0].single_flag = 1;            //判定为短按
                key[0].double_status = 0;          //重置判断双击的状态
            }
        }
    }
}

若要判断所有按键的情况,只需要将以上程序放置在循环中即可。为方便日后使用,将其封装在库中。

/* key.c */

#include "key.h"

struct keys key[4];

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM4)
    {
        key[0].key_status = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
        key[1].key_status = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
        key[2].key_status = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
        key[3].key_status = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);

        for (int i=0; i<4; i++)
        {
            switch (key[i].click_status)
            {
                case 0:
                    if (key[i].key_status == GPIO_PIN_RESET)    key[i].click_status = 1;
                    break;
                case 1:
                    if (key[i].key_status == GPIO_PIN_RESET)
                    {
                        key[i].click_status = 2;
                        key[i].click_time = 0;
                    }
                    else    key[i].click_status = 0;
                    break;
                case 2:
                    if (key[i].key_status == GPIO_PIN_RESET)    key[i].click_time++;

                    else if (key[i].key_status==GPIO_PIN_SET && key[i].click_time>=70)
                    {
                        key[i].long_flag = 1;
                        key[i].click_status = 0;
                    }

                    else if (key[i].key_status==GPIO_PIN_SET && key[i].click_time<70)
                    {
                        switch (key[i].double_status)
                        {
                            case 0:
                                key[i].double_status = 1;
                                key[i].double_time = 0;
                                break;
                            case 1:
                                key[i].double_flag = 1;
                                key[i].double_status = 0;
                                break;
                        }
                        key[i].click_status = 0;
                    }
                break;
            }

            if (key[i].double_status == 1)
            {
                key[i].double_time++;
                if (key[i].double_time >= 35)
                {
                    key[i].single_flag = 1;
                    key[i].double_status = 0;
                }
            }
        }
    }
}
/* key.h */

#ifndef __KEY_H
#define __KEY_H

#include "main.h"
#include <stdbool.h>

struct keys
{
    bool single_flag;
    bool long_flag;
    bool double_flag;
    bool key_status;
    uint8_t click_status;
    int click_time;
    uint8_t double_status;
    int double_time;
};

extern struct keys key[];

#endif /* __KEY_H */

在使用按键时,只需要判断key[i].single_flag、key[i].long_flag和key[i].double_flag,并在执行完任务后将其重新置零即可。

本文参考自B站up主01Studio,并附加了笔者自己的一些感悟。按键识别比较难,需要大家反复敲代码进行实践!!!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐