蓝桥杯嵌入式开发指南 03 - 按键配置

实例

通过配置按键和 LED 灯,使按键的长按和短按控制对应 LED 灯亮灭。

电路分析



打开蓝桥开发板的电路图,可以看到板载的 4 个可编程按键 K1-K4 通过 N K1-N K4 经由跳线帽连接到 M PA0、M PA8、M PB1 和 M PB2

工程建立

本次工程在上一次的工程上继续修改,复制上一次的工程作为本次的工程基础。

初始化按键

编写按键初始化函数,程序代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Key_Init(void)
{
//声明GPIO结构体变量
GPIO_InitTypeDef GPIO_InitStructure;

//使能按键PA和PB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);

//设置PA0、PA8为上拉输入模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

//设置PB1、PB2为上拉输入模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}

特别提醒


结合电路原理图,此处将 PA0、PA8、PB1、PB2 的模式均设置为上拉输入(GPIO_Mode_IPU),在按键松开状态下,由上拉电阻拉高,读取到高电平,当按键按下时,读取到引脚电平为低电平。

配置定时器

为消除抖动和能够准确的进行按键扫描,使用定时器以 50ms 为周期进行按键扫描,在上一例的基础上,修改定时器 1 程序:
声明 50ms 定时的全局变量:

1
u8 Flag50ms=0;

配置定时器 1 中断:

1
2
3
4
5
6
7
8
9
10
11
12
13
void TIM1_UP_IRQHandler(void)
{
static u16 cnt=0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update)==SET) //1ms到
{
TIM_ClearITPendingBit(TIM1,TIM_IT_Update); //清除中断等待标志
if(++cnt>=50) //计时是否到50ms
{
cnt=0; //清空计时
Flag50ms=1; //50ms到
}
}
}

定义按键输入变量

为方便后续获取各个按键的电平状态,此处利用宏定义来定义 KeyX 对应为各引脚读取到的电平状态。

1
2
3
4
#define Key1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
#define Key2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)
#define Key3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)
#define Key4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)

按键扫描程序

编写按键扫描程序,扫描识别各个按键的状态,通过定时器每 50ms 调用一次按键扫描程序,定时进行扫描,此处我们将按键超过 2 秒的按键动作定义为长按操作,否则为短按操作,通过松手后对按键的计数变量进行判断完成计时检测。

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
void Key_Scan(void)
{
//定义按键计数变量,用于判断长按短按
static u16 key1_count=0,key2_count=0,key3_count=0,key4_count=0;

//按键按下后,进行计数操作
if(!Key1)
{
key1_count++;
}
else if(!Key2)
{
key2_count++;
}
else if(!Key3)
{
key3_count++;
}
else if(!Key4)
{
key4_count++;
}

//按键松开后,根据计数值判断按键状态
else
{
//K1是否为长按?
if(key1_count>=40)
{
//Key 1 Long Press Function
}
//K1如果不是长按,是否为短按?
else if(key1_count>=1)
{
//Key 1 Short Press Function
}

else if(key2_count>=40)
{
//Key 2 Long Press Function
}
else if(key2_count>=1)
{
//Key 2 Short Press Function
}

else if(key3_count>=40)
{
//Key 3 Long Press Function
}
else if(key3_count>=1)
{
//Key 3 Short Press Function
}

else if(key4_count>=40)
{
//Key 4 Long Press Function
}
else if(key4_count>=1)
{
//Key 4 Short Press Function
}

//按键操作执行完毕后,清空按键计数
key1_count=0;
key2_count=0;
key3_count=0;
key4_count=0;
}
}

本初处以 K1 按钮来详细讲解代码,当 K1 按下后,计数器 key1_count 执行 ++ 操作,由于按键扫描频率为 50ms,当超过 2s 时,key1_count 此时也超过了 40,若没有超过 2s,则 key1_count 应位于 1-40 之间,当按键松开后,没有任何按键按下的情况时,进入 else 内的松手程序,比较 key1_count 是否为大于 40,若大于则执行 K1 按键的长按功能,若位于 1-40 间,则执行短按功能,若没有按下,则 key1_count 为 0,继续判断 key_2count 的功能,当所有按键判断和操作执行完毕后,清空计数,执行下一轮扫描操作。

特别提醒


为什么要在此处使用 else if 结构?
通常情况来讲,在读取按键这个动作中只会有一个按键按下,使用 else if 结构可以减少部分程序判断流程,提升代码运行效率。

该代码编写方式的优缺点?
使用如上结构进行判断按键长短按的操作时,可以发现我们的动作均是在按键松开后执行的,其主要的优点在于可以独立区别长按和短按操作,即长按执行时不会触发短按的操作,但也存在一定的缺点,无法执行长按连续的动作,比如要求长按按钮过程中对某一个变量进行跨度更大的加或者减,若需要实现此功能,则要对按键判断部分进行修改,具体修改方法请参见拓展延伸部分。

继续以 K1 为例,为 K1 按钮添加如下功能:

  • 短按 K1 控制 L1 开关
  • 长按 K1 控制 L2 开关

在按键扫描程序内定义 L1 和 L2 的状态变量:

1
2
u8 L1_Status=1;
u8 L2_Status=1;

修改 K1 的功能代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
......
//按键松开后,根据计数值判断按键状态
else
{
//K1是否为长按?
if(key1_count>=40)
{
//Key 1 Long Press Function
L2_Status=!L2_Status; //改变LED状态
GPIO_SetBits(GPIOD,GPIO_Pin_2); //使能573锁存器
GPIO_WriteBits(GPIOC,GPIO_Pin_9,(BitAction)L2_Status); //写入新的LED状态
GPIO_ResetBits(GPIOD,GPIO_Pin_2); //失能573锁存器,保持LED状态
}
//K1如果不是长按,是否为短按?
else if(key1_count>=1)
{
//Key 1 Short Press Function
L1_Status=~L1_Status; //改变LED状态
GPIO_SetBits(GPIOD,GPIO_Pin_2); //使能573锁存器
GPIO_WriteBit(GPIOC,GPIO_Pin_8,(BitAction)L1_Status); //写入新的LED状态
GPIO_ResetBits(GPIOD,GPIO_Pin_2); //失能573锁存器,保持LED状态
}
......

修改主程序

结合功能要求,修改主程序:

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
int main(void)
{
SysTick_Config(SystemCoreClock/1000);
Delay_Ms(200);
STM3210B_LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);

//调用定时器1初始化程序
Timer1_Init();
//调用LED初始化程序
LED_Init();
//调用按键初始化程序
Key_Init();

while(1)
{
if(Flag50ms) //50ms计时到
{
Flag50ms=0; //清除计时标志
Key_Scan(); //调用按键扫描程序
}
}
}

延伸拓展

按键长按过程中若要执行操作,以 K1 为例,则应修改代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Key_Scan(void)
{
static u16 key1_count=0;
if(!Key1)
{
key1_count++
if(key1_count==1)
{
//K1 Short Press Function
}
else if(key1_count>=40)
{
key1_count=40; //防止溢出
//K1 Long Press Function
}
}
eles
{
key1_count1=0;
}
}