GPIO—按键轮询

12.1关于按键

前面控制LED灯是让GPIO输出高低电平,而获取按键则是读取GPIO电平,从而获知用户是否按下按键。

按键监测一般有两种:按键扫描和按键中断。按键扫描是间隔很短时间反复查询GPIO状态,从而得知是否有按键动作,这种方式代码简单,但比较耗资源。按键中断而是通过按键产生中断信号,从而实现按键的检测,这种方式需要使用到中断机制,需要对MCU了解深入一点,效果是最好的。

本节先介绍按键扫描,理解按键的基本原理,下一章再介绍按键中断,同时了解STM32F103的中断使用方法。

按键一般占用一个GPIO口,通过监测该GPIO的电平变化得知按键操作,典型的电路如图 12.1.1 所示。当所需按键比较多时,则可以采用矩阵按键减少GPIO的占用。矩阵按键需要通过编程扫描等方式实现对多个按键的监控,这里以最简单的独立按键为基础进行介绍。
GPIO—按键轮询
可以看到,在没有按下按键时,电源3.3V通过电阻连接到MCU的PA0脚上,此时MCU读取PA0的电平就是3.3V的高电平。在按键按下时,电源3.3V经过电阻,再经过按键连接到了地,此时PG3连接到接地的一端,读到的电平就是0V的低电平。由此,MCU就可用过读取对应引脚的电平值,得知按键的变化。

常用的按键都是机械触点式按键,机械式按键在按下或释放的过程中,由于机械弹性作用的影响,会伴随机械抖动,如图 12.1.2 所示。

GPIO—按键轮询
抖动的时长与机械开关特性相关,一般为5-10ms。在这抖动过程中,会产生多次高低电平,导致被识别为多次按键操作。为了避免机械触点按键检测误判,必须消抖处理。按键消抖可以硬件上处理,即在按键旁并联电容,吸收抖动的电平。也可以软件处理,即通过延时,避开抖动。

由此,首先获取对应引脚的电平得知按键状态,再硬件或软件消除抖动。

12.2硬件设计

如下图 12.2.1 所示,是一种常见轻触按键,该按键有四个脚,①和②脚连接,③和④脚连接,按钮按下后,四脚全相连,实现导通效果。
GPIO—按键轮询

开发板有四个用户按键,在开发板左下角,如图 12.2.2 所示。为了方便区分,以按键所处位置命名,分别为KEY1_U(up,上)、KEY2_D(down,下)、KEY3_L(left,左)、KEY4_R(right,右)。
GPIO—按键轮询
如图 12.2.3 为开发板用户按键部分的原理图,四个按键除了所接GPIO引脚不同外,其它一模一样。以最左边的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)。
GPIO—按键轮询

12.3软件设计

12.3.1标题软件设计思路

实验目的:本实验通过轮询读方式取GPIO的输入电平判断按键是否按下,并操作对应LED。

  1. 按键初始化:GPIO端口时钟使能、GPIO引脚设置为输入(PA0, PG15, PC13, PE3);
  2. 封装每个按键处理函数:读取按键GPIO状态,操作对应LED灯亮灭;
  3. 主函数轮询按键状态:一直检测是否有按键被按下;
    本实验配套代码位于“5_程序源码\5_GPIO—按键轮询\”。

12.3.2软件设计讲解

  1. GPIO宏定义与接口宏定
    代码段 12.3.1 引脚宏定义(driver_key.h)
/*********************
* 按键引脚状态定义
**********************/
#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(高电平)。

  1. GPIO初始化
    代码段 12.3.2 按键初始化(driver_key.c)
/*
* 函数名: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结构体除引脚号外的参数只赋值一遍,最后只改变引脚号的那个成员参数的值进行初始化就可以了,不需要每个按键都将所有的成员参数重新赋值一遍,简化了代码量。

  1. 按键读取函数
    因为四个按键这个函数的处理都几乎一致,所以此处只对KEY1,即UP键进行具体举例,其余的请查看具体代码。

代码段 12.3.3 按键读取函数(driver_key.c)

/*
* 函数名: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); } } }
  • 8行:定义了一个全局变量标志位“up_flag”,作为UP键被按下的标志;
  • 11行:获取该按键状态;
  • 13行:延时5-10ms,软件去抖;
  • 14行:再次获取该按键状态,此时依旧按下,说明是正常按键操作,非抖动;
  • 16行:将标志位置反,按键按一次置反一次(即0->1->0->1这样循环);
  • 17行:熄灭红色LED灯;
  • 18行:根据标志位“up_flag”的值,让绿色LED灯亮或灭;
  • 19行:熄灭蓝色LED灯;

此时每按下一次UP键,绿色LED灯将亮灭交替。剩下的三个按键的效果分别是:DOWN->三个灯同时亮灭;LEFT->红灯亮灭;RIGHT->蓝灯亮灭。

  1. 主函数测试
    代码段 12.3.4 主函数控制逻辑(main.c)
// 初始化 LED
LedGpioInit();
// 初始化按键
KeyInit();
while(1) {
// 轮询向上键
UpKeyPolling();
// 轮询向下键
DownKeyPolling();
// 轮询向左键
LeftKeyPolling();
// 轮询向右键
RightKeyPolling(); }
  • 1~4行:初始化LED灯和按键;
  • 6~16行:一直循环查询每个按键当前状态,从而判断对应按键是否按下;

12.4实验效果

本实验对应配套资料的“5_程序源码\5_GPIO—按键轮询\”。打开工程后,编译,下载。按下KEY3_L,红色亮/灭;按下KEY3_U,绿色亮/灭;按下KEY3_R,蓝色亮/灭;按下KEY3_D,三灯亮/灭。


百问网技术论坛:
http://bbs.100ask.net/

百问网嵌入式视频官网:
https://www.100ask.net/index

百问网开发板:
淘宝:https://100ask.taobao.com/
天猫:https://weidongshan.tmall.com/

技术交流群(鸿蒙开发/Linux/嵌入式/驱动/资料下载)
QQ群:869222007(已满)752871361

单片机-嵌入式Linux交流群:
QQ群:536785813

上一篇:QMI8658C惯性模块开发记录


下一篇:pi4j与Spring Boot