《DN8P1开发指南_V1.0》第十章 按键输入实验
第十章 按键输入实验
上一章,我们介绍了RA8P1的IO口作为输出的使用。本章,我们将向大家介绍如何使用 RA8P1的IO口作为输入。我们将利用板载的5个按键,来控制板载的两个LED灯亮灭。通过本章的学习,我们将了解到RA8P1的IO口作为输入的使用方法。
本章分为如下几个小节:
10.1 按键简介
10.2 硬件设计
10.3 程序设计
10.4 下载验证
10.1 按键简介
几乎每个开发板都会板载有独立按键,因为按键用处很多。常态下,独立按键是断开的,按下的时候才闭合。每个独立按键会单独占用一个IO口,通过IO口的高低电平判断按键的状态。但是按键在闭合和断开的时候,都存在抖动现象,即按键在闭合时不会马上就稳定的连接,断开时也不会马上断开。这是机械触点,无法避免。独立按键抖动波形图如下:

图9.1.1 按键抖动波形图
图中的按下抖动和释放抖动的时间一般为5~10ms,如果在抖动阶段采样,其不稳定状态可能出现一次按键动作被认为是多次按下的情况。为了避免抖动可能带来的误操作,我们要做的措施就是给按键消抖(即采样稳定闭合阶段)。消抖方法分为硬件消抖和软件消抖,我们常用软件的方法消抖。
软件消抖:方法很多,我们例程中使用最简单的延时消抖。检测到按键按下后,一般进行10ms 延时,用于跳过抖动的时间段,如果消抖效果不好可以调整这个10ms延时,因为不同类型的按键抖动时间可能有偏差。待延时过后再检测按键状态,如果没有按下,那我们就判断这是抖动或者干扰造成的;如果还是按下,那么我们就认为这是按键真的按下了。对按键释放的判断同理。
硬件消抖:利用RC电路的电容充放电特性来对抖动产生的电压毛刺进行平滑出来,从而实现消抖,但是成本会更高一点,本着能省则省的原则,我们推荐使用软件消抖即可。
10.2 硬件设计
1. 例程功能
BOOT与KEY0控制LED0开启与关闭;KEY1与KEY2控制LED1开启与关闭;KEY3控制LED0与LED1状态翻转。
2. 硬件资源
1)LED 灯
LED0 – P108
LED1 – PD06
2)按键
BOOT – P201
KEY0 – P003
KEY1 – P005
KEY2 – P010
KEY3 – P011
3. 原理图
按键硬件部分的原理图,如图 10.2.1 所示:

图 10.2.1 按键原理图
这里需要注意的是:KEY0、KEY1、KEY2、KEY3和BOOT采样到按键另一端的低电平为有效电平,由于按键外部没有上下拉电阻,所以需要在 RA8P1内部设置上下拉。
10.3 程序设计
10.3.1 FSP配置流程
注意:LED的FSP配置流程,我们已经在第八章节讲解过,这里我们不再赘述。
在FSP配置PinsPorts路径下配置P003管脚初始化为输入模式内部上拉,如下图所示。

其他按键的FSP配置一样,这里不在进行过多演示。
10.3.2 程序解析
1.按键驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。按键(KEY)驱动源码包括两个文件:key.c和key.h。
下面我们先解析key.h程序中的引脚定义与操作函数定义。
/* 按键按下返回值 */
#define BOOT_PRES 1
#define KEY0_PRES 2
#define KEY1_PRES 3
#define KEY2_PRES 4
#define KEY3_PRES 5
/* 函数声明 */
void key_init(void); /* 按键初始化 */
uint8_t key_scan(uint8_t mode); /* 按键扫描 */
/* BOOT端口读取定义 */
#define BOOT_READ() ({ bsp_io_level_t level; \
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_02_PIN_01, &level);\level; \
})
/* KEY0端口读取定义 */
#define KEY0_READ() ({ bsp_io_level_t level; \
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_03, &level); \level;\
})
/* KEY1端口读取定义 */
#define KEY1_READ() ({ bsp_io_level_t level; \
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_05, &level); \level; \
})
/* KEY2端口读取定义 */
#define KEY2_READ() ({ bsp_io_level_t level; \
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_10, &level); \level;\
})
/* KEY3端口读取定义 */
#define KEY3_READ() ({ bsp_io_level_t level; \
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_11, &level); \level;\
})
BOOT_READ() ~ KEY3_READ() 分别是读取对应按键状态的宏定义,用 R_IOPORT_PinRead 函数实现,该函数将引脚电平状态存入 level 变量,取值为高电平或低电平。BOOT_PRES、KEY0_PRES、KEY1_PRES、KEY2_PRES、KEY3_PRES 则是按键按下对应的键值宏定义标识符。
这里需要注意的是:BOOT、KEY0、KEY1、KEY2 和 KEY3 是低电平有效的(即一端接地),引脚已通过 FSP 配置工具设置为输入模式并开启内部上拉,系统启动时统一完成端口初始化,因此驱动中无需额外使能时钟与配置引脚模式。
下面我们再解析 key.c 的程序,这里有两个函数,先看按键初始化函数,其定义如下:
/**
* @brief 按键初始化函数
* @param 无
* @retval 无
*/
void key_init(void)
{
/* 无需再次初始化 */
}
所有按键引脚的输入模式、上拉配置均已通过 FSP 图形化配置工具完成设置,并在系统启动流程中统一初始化,因此 key_init 函数仅保留调用接口,无需额外硬件配置操作。
另一个函数是按键扫描函数,其定义如下:
/**
* @brief 按键扫描函数
* @note 该函数有响应优先级(同时按下多个按键):BOOT > KEY3 > KEY2 > KEY1 > KEY0!
* @param mode:0 / 1, 具体含义如下:
* @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
* 必须松开以后, 再次按下才会返回其他键值)
* @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
* @retval 键值, 定义如下:
* BOOT_PRES, 1, BOOT按下
* KEY0_PRES, 2, KEY0按下
* KEY1_PRES, 3, KEY1按下
* KEY2_PRES, 4, KEY2按下
* KEY3_PRES, 5, KEY3按下
*/
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
/* mode为1时允许连续触发,适合长按检测场景 */
if (mode) key_up = 1; /* 支持连按 */
/* 仅在按键释放后检测新的按下事件,避免一次按下被重复识别 */
if (key_up && (BOOT_READ() == 0 || KEY0_READ() == 0 || KEY1_READ() == 0 || KEY2_READ() == 0 || KEY3_READ() == 0))
{
R_BSP_SoftwareDelay(10, BSP_DELAY_UNITS_MILLISECONDS); /* 去抖动 */
key_up = 0; /* 确认按键有效后锁定状态 */
/* 按优先级依次读取各按键,后面的判断可覆盖前面的键值 */
if (KEY0_READ() == 0) keyval = KEY0_PRES;
if (KEY1_READ() == 0) keyval = KEY1_PRES;
if (KEY2_READ() == 0) keyval = KEY2_PRES;
if (KEY3_READ() == 0) keyval = KEY3_PRES;
if (BOOT_READ() == 0) keyval = BOOT_PRES;
}
else if (KEY0_READ() == 1 && KEY1_READ() == 1 && KEY2_READ() == 1 && KEY3_READ() == 1 && BOOT_READ() == 1)
{
key_up = 1; /* 所有按键均松开后恢复可触发状态 */
}
return keyval; /* 返回键值 */
}
key_scan 函数用于扫描这 5 个 IO 口是否有按键按下。该函数支持两种扫描方式,通过 mode 参数来设置。
当 mode 为 0 的时候,key_scan 函数不支持连续按,扫描某个按键时,该按键按下之后必须松开,才能第二次触发,否则不会再响应这个按键。这样的好处是可以防止按一次多次触发,坏处就是在需要长按的场景下不合适。
当 mode 为 1 的时候,key_scan 函数支持连续按,如果某个按键一直按下,则会一直返回这个按键的键值,可以方便地实现长按检测。
有了 mode 这个参数,大家就可以根据自己的需要选择不同的方式。这里要提醒大家,因为该函数内部有 static 静态变量,所以该函数不是可重入函数,在带 OS 的场景下需要留意。可以看到该函数的消抖延时是 10ms。同时还有一点要注意,该函数的按键扫描是有优先级的,最优先的是 BOOT,第二优先的是 KEY3,之后依次是 KEY2、KEY1,最后是 KEY0。该函数有返回值,如果有按键按下,则返回非 0 键值;如果没有按键按下或按键无效,则返回 0。
2.hal_entry.c 代码
在 hal_entry.c 里面编写如下代码:
void hal_entry(void)
{
uint8_t key = 0;
delay_init(1000000); /* 初始化systick */
led_init(); /* 初始化led */
key_init(); /* 初始化key */
while (1)
{
/* 扫描按键,mode=0表示按住不放时只触发一次 */
key = key_scan(0);
switch(key)
{
case BOOT_PRES:
LED0(0);
break;
case KEY0_PRES:
LED0(1);
break;
case KEY1_PRES:
LED1(0);
break;
case KEY2_PRES:
LED1(1);
break;
case KEY3_PRES:
/* 翻转LED0,提示主循环正在运行 */
LED0_TOGGLE();
/* 翻转LED1,提示定时或按键事件已触发 */
LED1_TOGGLE();
break;
}
/* 主循环短延时,用于控制按键扫描和LED刷新节奏 */
delay_ms(10);
}
}
hal_entry 是 FSP 工程的系统启动入口点。程序首先完成系统级初始化:初始化延时函数、LED 外设和按键外设。接下来在无限循环中调用 key_scan(0) 扫描获取键值,用键值判断最终到底是哪个按键按下了。
10.4 下载验证
在下载好程序后,我们可以按下 BOOT、KEY0、KEY1、KEY2、KEY3 来观察 LED 灯的变化,验证是否和预期结果一致:
• BOOT 按下时 LED0 点亮,KEY0 按下时 LED0 熄灭;
• KEY1 按下时 LED1 点亮,KEY2 按下时 LED1 熄灭;
• KEY3 按下时 LED0 与 LED1 状态同时翻转。
至此,本章的学习就结束了。本章学习了 RA8P1 的 IO 作为输入的使用方法,在前面 GPIO 输出的基础上又学习了一种 GPIO 使用模式,大家可以回顾前面 LED、蜂鸣器实验介绍的 GPIO 模式类型,巩固 GPIO 相关知识。

浙公网安备 33010602011771号