12. GPIO输入——按键输入检测¶
本章配套视频介绍:
《14-GPIO输入–按键输入检测》
https://www.bilibili.com/video/BV15N4y1R7sS/
按键检测使用到 IOPORT 外设的基本输入功能,本章中不再赘述 IOPORT 外设的概念, 如您忘记了可重新回到前面的章节进行阅读。
12.1. 按键检测原理¶
按键机械触点断开、闭合时,由于触点的弹性作用,按键开关不会马上稳定接通或一下子断开,使用按键时会产生 图12_1 中的带波纹信号,需要用软件消抖处理滤波,不方便输入检测。
本实验用到的野火启明6M5/启明4M2/启明2L1开发板的按键带硬件消抖功能, 它利用电容充放电的延时,消除了波纹,从而简化软件的处理,软件只需要直接检测引脚的电平即可。
瑞萨设计的微处理器(MCU)拥有硬件数字滤波的功能用来实现去除按键带来的纹波影响, 不过前提是按键用在外部中断作为按键信号输入的情况下使用, 通过使用数字滤波的方式能够替代掉用电容式滤波的方法来消除纹波, 从而减少我们在硬件上的开发成本。
12.2. 硬件设计¶
野火启明6M5开发板的按键电路图如下图所示, 图中RA6M5芯片的P004、P005引脚分别通过一个10KΩ的贴片电阻连接到电源的正极,所以按键在没有被按下的时候,GPIO引脚的输入状态为高电平状态, 分别又通过串联一个100Ω的贴片电阻和一个按键接地,所以按键在被按下的时候,GPIO引脚的输入状态为低电平状态。 只要我们检测引脚的输入电平,即可判断按键是否被按下。
野火启明4M2开发板的按键电路图如下图所示。
野火启明2L1开发板的按键电路图如下图所示。
若您使用的实验板按键的连接方式或引脚不一样,只需根据我们的工程修改引脚即可,程序的控制原理相同。
12.3. 软件设计¶
注:本小节所述的软件代码是以启明6M5开发板为例, 使用启明4M2开发板和启明2L1开发板的读者可打开配套的“12_GPIO_Key”例程查看相应的代码, 其中的差别并不大。
12.3.1. 新建工程¶
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “11_GPIO_LED”, 然后将工程文件夹重命名为 “12_GPIO_Key”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “11_GPIO_LED”, 然后将工程文件夹重命名为 “12_GPIO_Key”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “key” 文件夹, 再进入 “key” 文件夹里面新建 Key 驱动的源文件和头文件:“bsp_key.c” 和 “bsp_key.h”。 工程文件结构如下。
12_GPIO_Key
├─ ......
└─ src
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ key
│ ├─ bsp_key.c
│ └─ bsp_key.h
└─ hal_entry.c
警告
注意:对于使用 Keil 开发环境的用户,将代码文件放到 “src” 文件夹下之后, Keil 软件并不会自动将它们加入到工程,这时候需要打开 RASC FSP 配置界面, 点击一次单击右上角的 “Generate Project Content” 按钮,从而 “src” 文件夹下的代码文件就会被自动加入进工程中。 接着关闭 FSP 配置界面返回到 Keil,然后进行一次编译后会弹出一个提示框提示工程结构发生了变化,点击确定即可。 对于使用 e2 studio 的用户则不需要如此。
12.3.2. FSP配置¶
首先打开 “12_GPIO_Key” 项目的 FSP 配置界面,接下来我们要在这个界面里配置芯片的引脚。 以下的配置过程是以启明6M5开发板为例,启明4M2/2L1开发板的配置方法也是一样的,只是需要配置的按键引脚不同。
在 FSP 配置界面里面点开 “Pins”-> “Ports”-> “P0”-> “P004”, 然后将连接到按键1的IO引脚的 “Mode” 属性配置为 “Input Mode”,其他的属性默认即可。 按键2的引脚 “P005” 也是按照这样进行配置。
配置完成之后的配置界面如下图所示。 “P004”和“P005”引脚的工作模式(Mode)都设置为输入模式,外部中断(IRQ)不启用。外部中断功能会在后面的章节介绍。
配置完成之后按下快捷键“Ctrl + S”保存,最后点右上角的 “Generate Project Content” 图标, 让软件根据我们的设置自动生成配置代码即可。
对于 Keil 这边 RASC 的 FSP 配置也是一样的,需要先通过 RASC 软件打开 Keil 工程相关的 FSP 配置界面。 具体的方法在本教程前面的章节已经详述过了,这里不再重复说明。
12.3.3. Key_Scan按键扫描函数¶
按键程序设计思路可以非常简单:想要知道某个按键是否被按下, 只需检测连接到改按键的IO引脚是高电平还是低电平,若是低电平,说明按键正处于被按下的状态。
通过使用 R_IOPORT_PinRead 函数,我们可以获取某个IO引脚的电平状态。 R_IOPORT_PinRead 函数原型如下:
fsp_err_t R_IOPORT_PinRead (ioport_ctrl_t * const p_ctrl, bsp_io_port_pin_t pin, bsp_io_level_t * p_pin_value);
说明:
通过 bsp_io_port_pin_t 枚举类型的变量 pin 传入要读取的 IO 引脚的端口号和引脚号;
通过 bsp_io_level_t 枚举类型的指针 p_pin_value 来获取该 IO 引脚的电平状态。
bsp_io_level_t 枚举类型的定义如下:
/* 可以为单个引脚设置电平和读取电平 */
typedef enum e_bsp_io_level
{
BSP_IO_LEVEL_LOW = 0, ///< Low
BSP_IO_LEVEL_HIGH ///< High
} bsp_io_level_t;
Key_Scan 按键扫描函数如下:
/* 定义宏 KEY_ON 表示按键按下
定义宏 KEY_OFF 表示按键没有按下
*/
#define KEY_ON 1
#define KEY_OFF 0
/* 按键扫描函数(阻塞式)
* key: KEY1_SW2_PIN 用户按键1(丝印SW2)的引脚
* KEY2_SW3_PIN 用户按键2(丝印SW3)的引脚
*/
uint32_t Key_Scan(bsp_io_port_pin_t key)
{
bsp_io_level_t state;
// 读取按键引脚电平
R_IOPORT_PinRead(&g_ioport_ctrl, key, &state);
if (BSP_IO_LEVEL_HIGH == state)
{
return KEY_OFF; //按键没有被按下
}
else
{
do //等待按键释放
{
R_IOPORT_PinRead(&g_ioport_ctrl, key, &state);
} while (BSP_IO_LEVEL_LOW == state);
}
return KEY_ON; //按键被按下了
}
这是一个简单的按键扫描函数,当调用该函数并且检测到按键被按下的时候,就会在 do-while 语句里重复检测按键是否被松开, 一直到当手松开按键的时候才能跳出循环,并返回 KEY_ON 数值表明按键被按下; 而当按键没有被按下的时候 Key_Scan 函数则返回 KEY_OFF 的数值。
值得注意的是,若程序在 do-while 循环里一直检测到手没有松开按键, 则程序会被一直阻塞在这里,因此这是一个阻塞式的扫描函数。
12.3.4. hal_entry入口函数¶
在 hal_entry 函数里,首先初始化了 LED 和按键, 接着进入了 while 主循环,在该循环下反复地调用 Key_Scan 函数对两个用户按键进行实时扫描。 若按键1按下,则翻转LED1的状态;若按键2按下,则翻转LED2的状态。
需要注意的是,KEY1_SW2_PIN 和 KEY2_SW3_PIN 宏定义了两个用户按键的引脚,不同的板子按键引脚是不一样的。
/* 启明6M5开发板,两个按键引脚定义 */
#define KEY1_SW2_PIN BSP_IO_PORT_00_PIN_04
#define KEY2_SW3_PIN BSP_IO_PORT_00_PIN_05
/* 启明4M2开发板,两个按键引脚定义 */
#define KEY1_SW2_PIN BSP_IO_PORT_04_PIN_03
#define KEY2_SW3_PIN BSP_IO_PORT_04_PIN_04
/* 启明2L1开发板,两个按键引脚定义 */
#define KEY1_SW2_PIN BSP_IO_PORT_00_PIN_01
#define KEY2_SW3_PIN BSP_IO_PORT_00_PIN_02
hal_entry 入口函数如下:
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "key/bsp_key.h"
void hal_entry(void)
{
/* TODO: add your own code here */
LED_Init(); // LED 初始化
Key_Init(); // 按键初始化
while(1)
{
if( Key_Scan(KEY1_SW2_PIN) == KEY_ON ) //扫描按键1
{
LED1_TOGGLE; //翻转LED1状态
}
if( Key_Scan(KEY2_SW3_PIN) == KEY_ON ) //扫描按键2
{
LED2_TOGGLE; //翻转LED2状态
}
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}