环境介绍
软件环境介绍
PC环境:Windows10 64-bit
IDE环境:MDK-ARM 5.24.2.0
工程名称:STM32_KeyTest
工程下载地址:github
硬件环境介绍
MCU平台:STM32F103C8T6
工程名称:STM32 Display Board V1.0
工程下载地址:github
应用环境介绍
按键检测是属于一个基础的单片机IO口应用,在消费级产品中如:洗衣机、抽油烟机、MP3…等等,几乎任何一个产品都会带有按键,因此在研发人员开发中就会遇到检测按键功能设计,本文从硬件原理到软件实现介绍如何检测按键的长短按,并触发相应的事件,可以轻松移植到任何MCU平台上。硬件平台使用的是以STM32F103C8T6位核心的一块STM32 Display Board V1.0开发板,开源在github上,可通过上面相应链接进行下载。软件工程也开源在github上可通过上面相应链接进行下载
原理介绍
硬件过程分析
按键电路一般是:一端接上拉电阻(IO口带上拉电阻配置可直接配置),一点接GND,当按键没有按下时位高电平,按下位低电平。按键在实际工作中会存在许多的干扰,如在按下的一瞬间会有抖动、误触发等。因为按键本身的特性原因,在按下的时候会有一个抖动电平,这个抖动会触发多个高低电平,因此会使按键无法正常直接检测。
状态与时间分析
本文章讨论按键长短按,上图就是一个按键长短按的整一个过程,在这个过程中可以把按键划分为如下几个状态:
- A:IDLE-空闲状态,没有按键按下或按键释放后的状态
- B:Debounce-消抖状态,按键按下瞬间的抖动状态
- C:Pressed-按键按下状态,为低电平
- D:Long_Pressed-按键长按状态,按键按下超过一定的时间
- E:Release-按键释放状态,按键松开的状态
按键的触发过程是一个从左向右的单向过程,在这每个过程都对应着一个时间段,不过在检测长短按过程中只需重点要关注B:消抖时间、D:长按时间、E:释放时间
其他状态时间A:空闲状态时间、C:按键按下的时间可以有B、D、E的时间以及状态确定
在按键过程分析中,主要分析下图的三个过程:
- 按键消抖过程(或误触发)
- 按键短按过程
- 按键长按过程
程序设计
数据结构定义
数据结构的定义位于button.h文件。从上面的分析可以知道,在按键长短按过程中主要涉及五个状态与三个重要时间段,因为在一个时间段内只能存在一种状态,程序中可以把状态定义为枚举类型:1
2
3
4
5
6
7
8typedef enum
{
IDLE, //空闲
Debounce, //消抖
Pressed, //按下
Long_Pressed, //长按
Release, //释放
}key_status; //按键状态
同时使用结构体类型定义按键长短按的时间变量:1
2
3
4
5
6typedef struct
{
unsigned int Time_Debounce; //消抖时间
unsigned int Time_LongPressed; //长按时间
unsigned int Time_Release; //释放时间
}key_time; //消抖、长按&释放时间
硬件上有六个按键,每一个按键都有长按短按两种情况,应为一次只能检测一个按键的长短按,所以使用枚举的方式定义KEY1~KEY6的长短按键值:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16typedef 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; //键值
为了能方便的找到是哪个按键处于那个状态,还是短按(或长按),可以定义一个键表,方便在扫描过程中找对对应按键:1
2
3
4
5
6
7
8typedef 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口,每个按键按下的键值(十六进制)都不一样,使用宏定义:1
2
3
4
5
6
7
时间参数一般消抖采用50ms,释放3ms,长按2s,按键的检测周期位10ms,对应时间可以使用宏定义:1
2
3
按键扫描过程程序设计
按键扫描过程程序位于button.c文件。在扫描前先定义一个键值表存放每个按键的索引、键值、状态:1
2
3
4
5
6
7
8
9
10//键值表
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修饰的变量
1 | /** |
键值处理过程程序设计
键值处理过程程序位于button.c文件。将上面获取的键值,根据不同的键值进行不同的处理,需要注意的是:程序中使用窗口打印信息,一般不建议在处理函数中做复杂的处理,这会影响程序运行的时间,这里主要作为演示效果使用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62/**
* @函数名 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;
}
}
主函数处理
在初始化好相应的接口后,只需要提供10ms作为按键扫描的时基,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/**
* @函数名 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
}
}
实现效果
使用USB转TTL把串口链接PC,打开上位机(这里使用的是SecureCRT 8.3,其他的串口助手也一样),配置波特率115200、停止位1位,数据位8位,无奇偶校验,无流控。效果中首先短按KEY1、KEY3,然后长按KEY2、KEY4: