前情提示: 我们之前简要做了LED和蜂鸣器的学习笔记。因此,本文程序涉及到LED和蜂鸣器的程序段不再重复出现。

零、按键基本认识

1、防抖

按键机械触点断开、闭合的时候,由于触点的弹性作用,按键开关不会马上稳定接通或一下子断开,而是会产生一些波纹信号,这些波纹信号会干扰高低电平的判断。如下图所示,在按键按下的前后均有信号抖动:

为了解决这个问题,有一些电路自带消抖功能,利用电容充放电的延时,消除了干扰波纹,从而简化了软件的处理,软件只需检测引脚的高低电平即可。这叫硬件消抖。

然而,我们的实验板却没有硬件消抖功能,因此,当用户按下按键时,软件需要延时一会儿(一般为10ms左右),待引脚的输入电平稳定后再判断高低电平。这叫软件消抖。

大致的思路如下:

1
2
3
4
5
6
7
8
9
10
11
12
uint8_t KEY_Scan(void)
{
if(KEY按下)
{
delay_ms(10);//延时10-20ms,防抖。
if(KEY确实按下)
{
return KEY_Value;
}
return 无效值;
}
}

返回的结果只有两种:要么按键确实被按下了,要么按键确实没被按下。

2、支持连续按

支持连续按的意思是,用户一直按着按键,相应的外设就会一直工作,直到用户松手,相应的外设才不工作。简单来说就是用户按一次不松手算很多次按下。其实,上面的程序就实现了这个功能,思路跟上面是一样的。

我们重复写一遍。大致的思路如下:

1
2
3
4
5
6
7
8
9
10
11
12
uint8_t KEY_Scan(void)
{
if(KEY按下)
{
delay_ms(10);//延时,防抖。
if(KEY确实按下)
{
return KEY_Value;
}
return 无效值;
}
}

3、不支持连续按

不支持连续按的意思是,即使用户一直按着按键,但相应的外设不会一直工作,响一下就不响了。简单来说就是用户按一次不松手只能算一次按下。

大致的思路如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uint8_t KEY_Scan(void)
{
static uint8_t iskey = 0; //记录之前按键有无被按过
//初始化iskey为0,表明按键之前没被按过
if(!iskey && KEY按下) //如果按键之前没被按过且现在按键被按下了
{
delay_ms(10);//延时,防抖
iskey = 1; //标记按键已经被按下
if(KEY确实按下)
{
return KEY_VALUE;
}
}else if(KEY没有按下)
iskey = 0; //如果按键没被按下(即松开),则标记按键没被按过
return 没有按下
}

4、STM32F103精英上按键的电路图

电路图如下如所示:

从电路图我们可以看到,精英版为我们提供了两种按键,WK_UP接VCC电源,而KEY0和KEY1接地。因此,WK_UP连接的端口为下拉输入模式,KEY0和KEY1连接的端口为上拉输入模式。

当单片机读到的电平为低电平时,说明KEY0和KEY1被按下,而WK_UP没有被按下;

当单片机读到的电平为高电平时,说明KEY0和KEY1没有被按下,而WK_UP被按下。

最后,WK_UP接的是PA0端口,KEY0接的是PE4端口,KEY1接的是PE3端口。

一、按键实验初体验

1.支持连续按

本程序实现功能:按下按键,相应外设会工作,若按键不松手则外设一直工作。换言之,外设工作与否取决于按键是否被按下。

程序如下:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/* =====key.h=====*/
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"

/* 检测按键电平情况的函数 */
#define KEY0 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)
#define KEY_UP GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)

/* 由上到下分别是:上拉输入式按下、下拉输入式按下 */
#define IPU_ON 0
#define IPD_ON 1

/* 由上到下均是不同按键对应的编号:全部未按下为0、key0为1、key1为2、wk_up为3 */
#define ALL_KEY_UNPRS 0
#define KEY0_PRS 1
#define KEY1_PRS 2
#define KEY_UP_PRS 3

void KEY_Init(void);
u8 KEY_Scan(void);

#endif


/* =====key.c===== */
#include "stm32f10x.h"
#include "key.h"
#include "delay.h"

void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE);

/* KEY0 & KEY1 -- Ground */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; /* PinE3 -- KEY1, PinE4 -- KEY0 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉输入 */
GPIO_Init(GPIOE, &GPIO_InitStructure);

/* KEY_UP(WK_UP) -- VCC */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /* PinA0 -- KEY_UP */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; /* 下拉输入 */
GPIO_Init(GPIOA, &GPIO_InitStructure);
}

u8 KEY_Scan(void)
{
if(KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON)
{
delay_ms(10);
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}
return ALL_KEY_UNPRS;
}


/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"

int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0_ON;
while(KEY_Scan() != ALL_KEY_UNPRS); /* 松手检测,这里的作用是:
/* 如果用户不松手,程序将会卡在这个地方,相应的外设也会持续工作 */
break;
case KEY1_PRS:
LED1_ON;
while(KEY_Scan() != ALL_KEY_UNPRS);
break;
case KEY_UP_PRS:
BEEP_ON;
while(KEY_Scan() != ALL_KEY_UNPRS);
break;
default:
LED0_OFF;
LED1_OFF;
BEEP_OFF;
break;
}
}
}

2.不支持连续按

本程序实现功能:按下按键,相应外设会工作,且经过300ms后不工作。这就是说若按键不松手,外设不会一直工作。

将以上部分程序修改如下:

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
/* =====key.c===== */
/* key_Scan函数修改如下,其他部分不变 */
u8 KEY_Scan(void)
{
static int isPrsBefore = 0;
if((isPrsBefore == 0) && (KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON))
{
delay_ms(10);
isPrsBefore = 1;
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}else if (KEY0 == !IPU_ON && KEY1 == !IPU_ON && KEY_UP == !IPD_ON)
isPrsBefore = 0;
return ALL_KEY_UNPRS;
}


/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"

int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0_ON;
delay_ms(300);
break;
case KEY1_PRS:
LED1_ON;
delay_ms(300);
break;
case KEY_UP_PRS:
BEEP_ON;
delay_ms(300);
break;
default:
LED0_OFF;
LED1_OFF;
BEEP_OFF;
break;
}
}
}

二、综合实验

以下我们实现一个功能:按下按键,相应外设工作状态将反转。按键不松手,工作状态不会改变(即不支持连续按)。

为实现工作状态反转功能,本程序使用位带操作读取IO口输入电平和输出电平。这种办法实现了与51类似的IO口控制功能,比如51里是这样用的:sbit LED0 = P2^6;
在STM32里是这样用的:#define LED0 PEin(5),意思是读取GPIOE.5口的输入电平;#define LED0 PEout(5),意思是读取GPIOE.5口的输出电平。

完整代码如下(头文件sys.h为正点原子资料盘自带文件):

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/* =====led.h===== */
#ifndef __LED_H
#define __LED_H

#define LED0_OFF GPIO_SetBits(GPIOB, GPIO_Pin_5)
#define LED0_ON GPIO_ResetBits(GPIOB, GPIO_Pin_5)
#define LED1_OFF GPIO_SetBits(GPIOE, GPIO_Pin_5)
#define LED1_ON GPIO_ResetBits(GPIOE, GPIO_Pin_5)

#define LED0 PBout(5) //位带操作
#define LED1 PEout(5)

void LED_Init(void);

#endif


/* =====led.c===== */
#include "stm32f10x.h"
#include "led.h"

void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* 推挽输出 */

/* Definition: void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) */
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_Init(GPIOE, &GPIO_InitStructure);

LED0_OFF;
LED1_OFF;
}


/* =====beep.h===== */
#ifndef __BEEP_H
#define __BEEP_H

#define BEEP_ON GPIO_SetBits(GPIOB, GPIO_Pin_8)
#define BEEP_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_8)

#define BEEP PBout(8)

void Beep_Init(void);

#endif


/* =====beep.c===== */
#include "stm32f10x.h"
#include "beep.h"

void Beep_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_Init(GPIOB, &GPIO_InitStructure);
BEEP_OFF;
}


/* =====key.h===== */
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"

/* Definition: uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) */
#define KEY0 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)
#define KEY_UP GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)

#define IPU_ON 0
#define IPD_ON 1

#define ALL_KEY_UNPRS 0
#define KEY0_PRS 1
#define KEY1_PRS 2
#define KEY_UP_PRS 3

void KEY_Init(void);
u8 KEY_Scan(void);

#endif


/* =====key.c===== */
#include "stm32f10x.h"
#include "key.h"
#include "delay.h"

void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE);

/* KEY0 & KEY1 -- Ground */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; /* PinE3 -- KEY1, PinE4 -- KEY0 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉输入 */
GPIO_Init(GPIOE, &GPIO_InitStructure);

/* KEY_UP(WK_UP) -- VCC */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /* PinA0 -- KEY_UP */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; /* 下拉输入 */
GPIO_Init(GPIOA, &GPIO_InitStructure);
}

u8 KEY_Scan(void)
{
static int isPrsBefore = 0;
if((isPrsBefore == 0) && (KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON))
{
delay_ms(10);
isPrsBefore = 1;
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}else if (KEY0 == !IPU_ON && KEY1 == !IPU_ON && KEY_UP == !IPD_ON)
isPrsBefore = 0;
return ALL_KEY_UNPRS;
}


/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"

int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0 = !LED0; //工作状态实现反转
break;
case KEY1_PRS:
LED1 = !LED1;
break;
case KEY_UP_PRS:
BEEP = !BEEP;
break;
default:
break;
}
}
}