搜索
热搜: ROHM 模拟 车载
查看: 1886|回复: 1

按键长短按检测

[复制链接]

该用户从未签到

2

主题

115

帖子

0

精华

高级会员

最后登录
2019-8-12
发表于 2019-1-13 21:09:24 | 显示全部楼层 |阅读模式
本帖最后由 林光光1号 于 2019-1-13 21:15 编辑

1.环境介绍

1.1软件环境介绍

PC环境:Windows10 64-bit  
IDE环境:MDK-ARM 5.24.2.0  
工程名称:STM32_KeyTest  
工程下载地址:[github](http://github.com/LGG001/MyApplication_Program)  



1.2硬件环境介绍

MCU平台:STM32F103C8T6   
工程名称:STM32 Display Board V1.0  
工程下载地址:[github](http://github.com/LGG001/LCEDA_Hardware)  



1.3 应用环境介绍


按键检测是属于一个基础的单片机IO口应用,在消费级产品中如:洗衣机、抽油烟机、MP3...等等,几乎任何一个产品都会带有按键,因此在研发人员开发中就会遇到检测按键功能设计,本文从硬件原理到软件实现介绍如何检测按键的长短按,并触发相应的事件,可以轻松移植到任何MCU平台上。硬件平台使用的是以STM32F103C8T6位核心的一块STM32 Display Board V1.0开发板,开源在github上,可通过上面相应链接进行下载。软件工程也开源在github上可通过上面相应链接进行下载


2.原理介绍


2.1硬件过程分析

按键电路一般是:一端接上拉电阻(IO口带上拉电阻配置可直接配置),一点接GND,当按键没有按下时位高电平,按下位低电平。按键在实际工作中会存在许多的干扰,如在按下的一瞬间会有抖动、误触发等。因为按键本身的特性原因,在按下的时候会有一个抖动电平,这个抖动会触发多个高低电平,因此会使按键无法正常直接检测。


2.2状态与时间分析



本文章讨论按键长短按,上图就是一个按键长短按的整一个过程,在这个过程中可以把按键划分为如下几个状态:  

- A:IDLE-空闲状态,没有按键按下或按键释放后的状态  
- B:Debounce-消抖状态,按键按下瞬间的抖动状态  
- C:Pressed-按键按下状态,为低电平  
- D:Long_Pressed-按键长按状态,按键按下超过一定的时间  
- E:Release-按键释放状态,按键松开的状态  

按键的触发过程是一个从左向右的单向过程,在这每个过程都对应着一个时间段,不过在检测长短按过程中只需重点要关注B:消抖时间、D:长按时间、E:释放时间  

其他状态时间A:空闲状态时间、C:按键按下的时间可以有B、D、E的时间以及状态确定  

在按键过程分析中,主要分析下图的三个过程:  
- 按键消抖过程(或误触发)  
- 按键短按过程  
- 按键长按过程   





3.程序设计


3.1数据结构定义


数据结构的定义位于button.h文件。从上面的分析可以知道,在按键长短按过程中主要涉及五个状态与三个重要时间段,因为在一个时间段内只能存在一种状态,程序中可以把状态定义为枚举类型:  
```C
typedef enum
{
    IDLE,                    //空闲
    Debounce,            //消抖
    Pressed,            //按下
    Long_Pressed,    //长按
    Release,            //释放
}key_status;        //按键状态
```

同时使用结构体类型定义按键长短按的时间变量:  
```C
typedef struct
{
    unsigned int Time_Debounce;            //消抖时间
    unsigned int Time_LongPressed;        //长按时间
    unsigned int Time_Release;            //释放时间
}key_time;                                //消抖、长按&释放时间
```

硬件上有六个按键,每一个按键都有长按短按两种情况,应为一次只能检测一个按键的长短按,所以使用枚举的方式定义KEY1~KEY6的长短按键值:  
```
typedef enum
{
    NULL_Pressed=0,        //无按键按下
    KEY1_Pressed,        //KEY1按下(短按)
    KEY2_Pressed,        //KEY2按下(短按)
    KEY3_Pressed,        //KEY3按下(短按)
    KEY4_Pressed,        //KEY4按下(短按)
    KEY5_Pressed,        //KEY5按下(短按)
    KEY6_Pressed,        //KEY6按下(短按)
    KEY1_LongPressed,    //KEY1长按
    KEY2_LongPressed,    //KEY2长按
    KEY3_LongPressed,    //KEY3长按
    KEY4_LongPressed,    //KEY4长按
    KEY5_LongPressed,    //KEY5长按
    KEY6_LongPressed,    //KEY6长按
}key_value;                //键值
```

为了能方便的找到是哪个按键处于那个状态,还是短按(或长按),可以定义一个键表,方便在扫描过程中找对对应按键:  
```
typedef struct
{
    unsigned int Index;                //按键索引(按键按下的键值)
    key_value Value1;                //键值1(KEYx按下)
    key_value Value2;                //键值2(KEYx长按)
    key_status State1;                //按键状态1(按下状态)
    key_status State2;                //按键状态2(长按状态)
}KEY_Table;
```

索引值指的是按键按下时对应的值,硬件中KEY1~KEY6都使用PB上的IO口,每个按键按下的键值(十六进制)都不一样,使用宏定义:  
```C
#define KEY_VALUE        0x3c03            //没按键按下的键值
#define KEY1_VALUE        0x3c02            //KEY1按下的键值
#define KEY2_VALUE        0x3c01            //KEY2按下的键值
#define KEY3_VALUE        0x3803            //KEY3按下的键值
#define KEY4_VALUE        0x3403            //KEY4按下的键值
#define KEY5_VALUE        0x2c03            //KEY5按下的键值
#define KEY6_VALUE        0x1c03            //KEY6按下的键值
```

时间参数一般消抖采用50ms,释放3ms,长按2s,按键的检测周期位10ms,对应时间可以使用宏定义:  
```C
#define    RELEASE_TIMES            3            //按键释放时间
#define    DEBOUNCE_TIMES            5            //按键消抖时间
#define    LONG_PRESSED_TIMES        200            //按键长按时间
```

3.2按键扫描过程程序设计

按键扫描过程程序位于button.c文件。在扫描前先定义一个键值表存放每个按键的索引、键值、状态:  
```C
//键值表
const KEY_Table key_tab[]=
{
    {KEY1_VALUE,    KEY1_Pressed,    KEY1_LongPressed,    Pressed, Long_Pressed},        //KEY1-短按-长按
    {KEY2_VALUE,     KEY2_Pressed,    KEY2_LongPressed,    Pressed, Long_Pressed},        //KEY2-短按-长按
    {KEY3_VALUE,     KEY3_Pressed,    KEY3_LongPressed,    Pressed, Long_Pressed},        //KEY3-短按-长按
    {KEY4_VALUE,    KEY4_Pressed,    KEY4_LongPressed,    Pressed, Long_Pressed},        //KEY4-短按-长按
    {KEY5_VALUE,    KEY5_Pressed,    KEY5_LongPressed,    Pressed, Long_Pressed},        //KEY5-短按-长按
    {KEY6_VALUE,    KEY6_Pressed,    KEY6_LongPressed,    Pressed, Long_Pressed},        //KEY6-短按-长按
};
```

按键扫描过程:
* 读取键值,如果有按键按下则赋值释放时间,然后判断上一次的状态
    * 如果为空闲则证明按键第一次按下,赋值消抖时间并把状态改为消抖
    * 如果在消抖状态并消抖完成,赋值长按时间并在键值表中找出对应的键值,把状态改为按下;如果没有找到则判断为误触发或多按键同时按下
    * 如果在按下状态并检测到长按时间完成,在键值表中找出对应的键值,把状态改为长按;如果没有找到则判断为误触发或多按键同时按下
    * 如过在长按状态下则不处理
    * 其他情况把状态改为空闲并退出
* 读取键值后,发现是没有按键按下,则贩毒案释放时间是否完成,如果完成则判断状态
    * 如果为按下状态,则找出对应按键并返回它的值(按下)
    * 如果为长按状态,则找出对应按键并返回它的值(长按)
    * 其他状态,如空闲、消抖等等;把状态置为空闲后退出
*注:按键短按长按要在释放后才有效;函数中有多处是使用static修饰的变量*

```C
/**
  * @函数名       check_user_key
  * @功  能       按键扫描
  * @参  数       无
  * @返回值       key_value:键值
  */
key_value KEY_Scan(void)
{
    static key_status state;                            //按键状态
    static key_time     time;                            //按键消抖和释放的时间
    volatile key_value KEY_Value = NULL_Pressed;        //键值,初始为空
    static unsigned char i;                                //扫描键值表计数:i
    unsigned int key_temp;
   
    key_temp = (GPIO_ReadInputData(GPIOB) & KEY_VALUE);    //读取键值
    if(key_temp != KEY_VALUE)                            //判断是否有按键按下
    {
        time.Time_Release = RELEASE_TIMES;                //有按键按下(在按下的状态),赋值释放时间   
        switch(state)                                    //判断上一次按键的状态
        {
            case IDLE:                                    //空闲状态
                time.Time_Debounce = DEBOUNCE_TIMES;    //赋值消抖时间
                state = Debounce;                        //将状态改为消抖状态
                break;                                    //退出
               
            case Debounce:                                //消抖状态
                if(--time.Time_Debounce == 0)            //消抖完成
                {
                    time.Time_LongPressed = LONG_PRESSED_TIMES;    //长按的时间赋值
                    
                    for(i=0; ;i++)                                //循环查询键值表
                    {
                        if(key_temp == key_tab.Index)        //循环查询索引
                        {
                            state = key_tab.State1;            //对应索引的状态为按下(PRESSED)
                            break;                                //退出循环
                        }
                        else if(i >= 6)                            //超出按键扫描范围,或同时按到其他按键
                        {
                            state=IDLE;                            //把状态重新配置为空闲
                            return NULL_Pressed;                //返回空值
                        }
                    }
                }
                break;
               
            case Pressed:                                    //按键按下状态
                if(--time.Time_LongPressed == 0)            //长按消抖完成
                {
                    for(i=0; ;i++)                            //循环查询键值表
                    {
                        if(key_temp == key_tab.Index)    //重新循环查询索引
                        {
                            state = key_tab.State2;        //对应索引的状态为长按(LONG_PRESSED)
                            break;                            //退出循环
                        }
                        else if(i > 6)                        //超出按键扫描范围,或同时按到其他按键
                        {
                            state=IDLE;                        //把状态重新配置为空闲
                            return NULL_Pressed;            //返回空值
                        }
                    }
                }
                break;
               

            case Long_Pressed:                            //一直保持长按,退出,不处理
                break;
               
            default:                                    //其他情况,将状态设为空闲,退出
                state=IDLE;
                break;
        }
    }
    else
    {
        if(time.Time_Release != 0)                        //判断按键是否已经释放完成(空闲状态时stkey.release一直等于0)
        {
            if(--time.Time_Release == 0)                //如果按键已经释放
            {
                switch(state)
                {
                    case Pressed:                        //按键按下,返回单击键值
                        KEY_Value=key_tab.Value1;
                        state=IDLE;                        //将状态设为空闲
                        break;
                        
                    case Long_Pressed:                    //按键长按,返回按键长按键值
                        KEY_Value=key_tab.Value2;
                        state=IDLE;                        //将状态设为空闲
                        break;
                        
                    default:                            //其他情况(消抖期间释放,或一直没按键按下),将状态设为空闲,退出
                        state=IDLE;
                        break;
                }
            }
        }
    }
    return KEY_Value;                                    //返回键值
}
```

3.3键值处理过程程序设计

键值处理过程程序位于button.c文件。将上面获取的键值,根据不同的键值进行不同的处理,需要注意的是:程序中使用窗口打印信息,一般不建议在处理函数中做复杂的处理,这会影响程序运行的时间,这里主要作为演示效果使用:
```C
/**
  * @函数名       KeyProcess
  * @功  能       按键处理,根据键值来执行相应的操作
  * @参  数       key_val:键值
  * @返回值       无
  */
void KEY_Process(key_value key_value)
{
    switch(key_value)
    {
        case KEY1_Pressed:                    //KEY1按下(短按)
            printf("KEY1_Pressed! \r\n");    //打印信息
            break;

        case KEY2_Pressed:                    //KEY2按下(短按)
            printf("KEY2_Pressed! \r\n");    //打印信息
            break;
            
        case KEY3_Pressed:                    //KEY3按下(短按)
            printf("KEY3_Pressed! \r\n");    //打印信息
            break;
            
        case KEY4_Pressed:                    //KEY4按下(短按)
              printf("KEY4_Pressed! \r\n");   //打印信息                                                                                                                                                                                                                                                                                                            
            break;
            
        case KEY5_Pressed:                    //KEY5按下(短按)
            printf("KEY5_Pressed! \r\n");    //打印信息
            break;
        
        case KEY6_Pressed:                    //KEY6按下(短按)
            printf("KEY6_Pressed! \r\n");    //打印信息
            break;
            
        case KEY1_LongPressed:                    //KEY1长按
            printf("KEY1_LongPressed! \r\n");    //打印信息
            break;
            
        case KEY2_LongPressed:                    //KEY2长按
            printf("KEY2_LongPressed! \r\n");    //打印信息
            break;
            
        case KEY3_LongPressed:                    //KEY3长按
            printf("KEY3_LongPressed! \r\n");    //打印信息
            break;
            
        case KEY4_LongPressed:                    //KEY4长按
            printf("KEY4_LongPressed! \r\n");    //打印信息
            break;
            
        case KEY5_LongPressed:                    //KEY5长按
            printf("KEY5_LongPressed! \r\n");    //打印信息
            break;
        
        case KEY6_LongPressed:                    //KEY6长按
            printf("KEY6_LongPressed! \r\n");    //打印信息
            break;

        default:                                //其他
            break;
    }
}
```

3.4主函数处理

在初始化好相应的接口后,只需要提供10ms作为按键扫描的时基,如下:
```C
/**
  * @函数名       main
  * @功  能       主函数入口
  * @参  数       无
  * @返回值       无
  */
int main(void)
{
    key_value temp;        //键值变量
   
    LED_Init();            //LED初始化
    KEY_Init();            //KEY初始化
    USART1_Init();        //串口初始化
    Delay_Init();        //延迟初始化
   
    GPIO_ResetBits(LED1_PORT,LED1_PIN);        //LED1亮
    GPIO_ResetBits(LED2_PORT,LED2_PIN);        //LED2亮
   
   
    while(1)
    {   
        temp = KEY_Scan();            //获取键值
        if(temp != NULL_Pressed)    //如果键值非空(有按键按下)
            KEY_Process(temp);        //处理键值
        Delay_ms(10);                //时间单位10ms
    }
}
```

4.实现效果

使用USB转TTL把串口链接PC,打开上位机(这里使用的是SecureCRT 8.3,其他的串口助手也一样),配置波特率115200、停止位1位,数据位8位,无奇偶校验,无流控。效果中首先短按KEY1、KEY3,然后长按KEY2、KEY4:  


回复

使用道具 举报

该用户从未签到

1347

主题

6657

帖子

0

精华

论坛元老

最后登录
2020-7-26
发表于 2020-5-29 19:20:47 | 显示全部楼层
超级实用学习学习了
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条

Archiver|手机版|小黑屋|罗姆半导体技术社区

GMT+8, 2024-4-20 02:33 , Processed in 0.099079 second(s), 12 queries , MemCache On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表