一、关于按键
按键监测一般有两种:按键扫描和按键中断。按键扫描是间隔很短时间反复查询GPIO状态,从而得知是否有按键动作,这种方式代码简单,但比较耗资源。按键中断而是通过按键产生中断信号,从而实现按键的检测,这种方式需要使用到中断机制,需要对MCU了解深入一点,效果是最好的。
按键一般占用一个GPIO口,通过监测该GPIO的电平变化得知按键操作,典型的电路如图 12.1.1 所示。当所需按键比较多时,则可以采用矩阵按键减少GPIO的占用。矩阵按键需要通过编程扫描等方式实现对多个按键的监控。
常用的按键都是机械触点式按键,机械式按键在按下或释放的过程中,由于机械弹性作用的影响,会伴随机械抖动,如图 12.1.2 所示。
抖动的时长与机械开关特性相关,一般为5-10ms。在这抖动过程中,会产生多次高低电平,导致被识别为多次按键操作。为了避免机械触点按键检测误判,必须消抖处理。按键消抖可以硬件上处理,即在按键旁并联电容,吸收抖动的电平。也可以软件处理,即通过延时,避开抖动。
由此,首先获取对应引脚的电平得知按键状态,再硬件或软件消除抖动。
二、硬件设计
如下图 12.2.1 所示,是一种常见轻触按键,该按键有四个脚,①和②脚连接,③和④脚连接,按钮按下后,四脚全相连,实现导通效果。
开发板有四个用户按键,在开发板左下角,如图 12.2.2 所示。为了方便区分,以按键所处位置命名,分别为KEY1_U(up,上)、KEY2_D(down,下)、KEY3_L(left,左)、KEY4_R(right,右)。
左边的KEY1为例,E2的TVS二极管用于静电保护,可以看作不存在;C36的电容用于硬件去抖,也可以看作不存在。按键松开时,VDD_3V3经过上拉电阻R25,再经过限流电阻R29到GPIO KEY1(PA0),此时PA0读取电平为高电平;按键按下时,VDD_3V3经过上拉电阻R25,再通过按键接地,此时PA0读取电平为低电平。
由此可知,按键按下,GPIO引脚电平变低,反之为高。四个按键所接GPIO分别为:KEY1(PA0)、KEY2 (PG15)、KEY3(PC13)、KEY4(PE3)。
三、软件设计
3.1 软件设计思路
实验目的:本实验通过轮询读方式取GPIO的输入电平判断按键是否按下,并操作对应LED。
- 按键初始化:GPIO端口时钟使能、GPIO引脚设置为输入(PA0, PG15, PC13, PE3);
- 封装每个按键处理函数:读取按键GPIO状态,操作对应LED灯亮灭;
- 主函数轮询按键状态:一直检测是否有按键被按下;
3.2 软件设计讲解
3.2.1 GPIO宏定义与接口宏定义
/*********************
* 按键引脚状态定义
**********************/
#define PUSH_DOWN GPIO_PIN_RESET
#define SPRING_UP GPIO_PIN_SET
/*********************
* 引脚宏定义
**********************/
#define KEY_UP_GPIO_PIN GPIO_PIN_0
#define KEY_UP_GPIO_PORT GPIOA
#define KEY_UP_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
#define KEY_DOWN_GPIO_PIN GPIO_PIN_15
#define KEY_DOWN_GPIO_PORT GPIOG
#define KEY_DOWN_GPIO_CLK_EN() __HAL_RCC_GPIOG_CLK_ENABLE()
#define KEY_LEFT_GPIO_PIN GPIO_PIN_13
#define KEY_LEFT_GPIO_PORT GPIOC
#define KEY_LEFT_GPIO_CLK_EN() __HAL_RCC_GPIOC_CLK_ENABLE()
#define KEY_RIGHT_GPIO_PIN GPIO_PIN_3
#define KEY_RIGHT_GPIO_PORT GPIOE
#define KEY_RIGHT_GPIO_CLK_EN() __HAL_RCC_GPIOE_CLK_ENABLE()
/*********************
* 函数宏定义
**********************/
/*
* 按键状态读取函数宏定义
*/
#define KEY_UP HAL_GPIO_ReadPin(KEY_UP_GPIO_PORT, KEY_UP_GPIO_PIN)
#define KEY_DOWN HAL_GPIO_ReadPin(KEY_DOWN_GPIO_PORT, KEY_DOWN_GPIO_PIN)
#define KEY_LEFT HAL_GPIO_ReadPin(KEY_LEFT_GPIO_PORT, KEY_LEFT_GPIO_PIN)
#define KEY_RIGHT HAL_GPIO_ReadPin(KEY_RIGHT_GPIO_PORT, KEY_RIGHT_GPIO_PIN)
根据硬件设计选定的四个按键的引脚,将其宏定义命名UP/DOWN/LEFT/RIGHT,且对他们的读取函数进行重命名。其中“HAL_GPIO_ReadPin()”原型“GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)”,参数依次是:引脚组,引脚号,返回的是0(低电平)或1(高电平)。
3.2.2 GPIO初始化
/*
* 函数名:void KeyInit(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:初始化按键的引脚,配置为输入
*/
void KeyInit(void)
{
// 定义GPIO的结构体变量
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能按键的GPIO对应的时钟
KEY_UP_GPIO_CLK_EN();
KEY_DOWN_GPIO_CLK_EN();
KEY_LEFT_GPIO_CLK_EN();
KEY_RIGHT_GPIO_CLK_EN();
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 设置为输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚反转速度设置为快
// 初始化'Up'键引脚配置
GPIO_InitStruct.Pin = KEY_UP_GPIO_PIN; // 选择按键的引脚
HAL_GPIO_Init(KEY_UP_GPIO_PORT, &GPIO_InitStruct);
// 初始化'Down'键引脚配置
GPIO_InitStruct.Pin = KEY_DOWN_GPIO_PIN; // 选择按键的引脚
HAL_GPIO_Init(KEY_DOWN_GPIO_PORT, &GPIO_InitStruct);
// 初始化Left'键引脚配置
GPIO_InitStruct.Pin = KEY_LEFT_GPIO_PIN; // 选择按键的引脚
HAL_GPIO_Init(KEY_LEFT_GPIO_PORT, &GPIO_InitStruct);
// 初始化'Right'键引脚配置
GPIO_InitStruct.Pin = KEY_RIGHT_GPIO_PIN; // 选择按键的引脚
HAL_GPIO_Init(KEY_RIGHT_GPIO_PORT, &GPIO_InitStruct);
}
将引脚初始化为上拉输入,此处使用了一个小技巧,因为各个按键的除了引脚号不同之外其余参数都是一致的,所以将GPIO结构体除引脚号外的参数只赋值一遍,最后只改变引脚号的那个成员参数的值进行初始化就可以了,不需要每个按键都将所有的成员参数重新赋值一遍,简化了代码量。
3.2.3 按键读取函数
因为四个按键这个函数的处理都几乎一致,所以此处只对KEY1,即UP键进行具体举例,其余的请查看具体代码。
/*
* 函数名:void UpKeyPolling(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:使用轮询方式查询向上键是否按下,通过按下控制三色灯绿灯亮灭
*/
static bool up_flag = false;
void UpKeyPolling(void)
{
if(KEY_UP == PUSH_DOWN) // 如果检测到向上键被按下
{
HAL_Delay(8); //延时8ms防按键抖动
if(KEY_UP == PUSH_DOWN) // 如果防抖动后向上键依然是处于被按下的状态,就认为向上键被按下过
{
up_flag = !up_flag; // 用一个标志位来判断向上键被按下次数,按下一次绿灯亮,再按一次绿灯灭,如此反复
RLED(OFF);
GLED(up_flag?OFF:ON);
BLED(OFF);
}
}
}
此时每按下一次UP键,绿色LED灯将亮灭交替。剩下的三个按键的效果分别是:DOWN->三个灯同时亮灭;LEFT->红灯亮灭;RIGHT->蓝灯亮灭。
3.2.4 主函数测试
// 初始化LED
LedGpioInit();
// 初始化按键
KeyInit();
while(1)
{
// 轮询向上键
UpKeyPolling();
// 轮询向下键
DownKeyPolling();
// 轮询向左键
LeftKeyPolling();
// 轮询向右键
RightKeyPolling();
}
四、代码工程
工程链接:GPIO-按键轮询