前言
(1)首先感谢 李肯前辈的活动,从而申请到了RA2L1开发板的测评。
(2)本文主要介绍按键输入的内容。
(3)学习本文需要准备的前提,
【致敬未来的攻城狮计划】--RA2E1 开发板测评(1)keil环境配置;
【致敬未来的攻城狮计划】--RA2L1 开发板测评(2)LED闪烁;(4)本文主要介绍按键抖动问题,软硬件消抖方案,分析其中的优缺点。同时介绍如何进行模块移植。
开发板按键原理图分析
我们根据下面原理图可知,按键被按下的时候为低电平,否则为高电平。
按键抖动问题
(1)我们所看到的按键基本都是这样的,按下电平IO输入为低电平,否则为高电平。
(2)但是由于,按键所用的开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。
(3)这样会有什么影响呢?很简单,当我们只按下了一下按键,而由于单片机程序执行速度很快,所以可能会识别到抖动的高电平,导致单片机以为我们按下了很多次按键。因此,我们需要进行按键消抖。
注意:下面这张图表示按键按下时候是低电平!!!
硬件消抖
(1)我比较喜欢进行硬件消抖,因为硬件能够处理的事情,就别扔给搞软件的了。野火的指南者的按键可以供我们的学习。
(2)首先,为了防止IO口没有内部下拉,所以使用一个外部下拉保证按键没有被按下的时候,电平不是未知的。
(3)我们在按键两端并联一个104的电容,能够让按键按下时候的抖动电平被电容消除,使得电平上升的更平缓。这样就能够有效的消除按键抖动。
软件消抖
(1)介绍完硬件消抖了,如何进行软件消抖呢?首先,我们需要知道按键抖动的大概时间为5ms~20ms。之后我们可以先判断按键是否被按下,如果发现第一次被按下,单片机延时20ms。然后第二次判断按键是否被按下,如果此次依旧被按下,说明按键的确被按下了。(听不明白可以看后面代码)
阻塞式消抖
(1)因为我还没介绍RA2E1的函数。所以这里以文字进行表示。
(2)阻塞式消抖优缺点:
缺点:阻塞式消抖会让程序堵死在while,对CPU的资源浪费,而且会让我们无法执行 其他程序。
优点:如果我们按键按下需要执行的程序是记录按键按下次数,这样就不会导致我们误判明明只按下1次长时间不松手,而造成cpu认为按下多次。(如果按下按键为低电平,没有while,将会一直 执行程序)
cpp
//判断是否为低电平(按键按下为低电平)
//延时20ms
//while判断是否为低电平(按键松手为高电平,如果松手不满足while条件,退出循环)
//判断是否为低电平(按键按下为低电平)
//执行程序
//其他程序
非阻塞式消抖
优点:这种方法不会阻塞程序,如果我们按下按键不松手, 其他程序依旧可以跑。
缺点:如何我们需要的是记录按键按下的次数。因为人不可能将按键按下的时间精准控制在20ms之内。那么就会造成,我明明只按下了一次,而 执行程序部分却会执行多次。
cpp
//判断是否为低电平(按键按下为低电平)
//延时20ms
//判断是否为低电平(按键按下为低电平)
//执行程序
//其他程序
程序编写
rasc配置
(1)首先为我们在 keil环境配置那一章获得了一个文件夹了,此时打开。
(2)因为我们在上一章节已经将rasc添加进入keil中了,所以打开右上角的Tools,RA Smart Configurator。
(3)根据原理图我们知道P004为按键,P502为LED1。所以此时我们需要配置这两个引脚。
keil程序编写
函数介绍
(1)关于下面程序编写阶段R_IOPORT_PinWrite()函数的第三个参数,我是传入的自定义变量i。原因是因为BSP_IO_LEVEL_HIGH和BSP_IO_LEVEL_LOW本质就是0和1。为了方便我们反转电平,所以我建立了一个变量i,利用'!'对i进行反转,从而实现反转电平。
(2)对于按键输入,我们需要读取IO电平。所以需要用到R_IOPORT_PinRead()函数,以下为函数介绍。
cpp
/*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);
*参数1:
*固定为g_ioport_ctrl
*参数2:
*指定IO口,比如我们需要查看P004电平,就输入BSP_IO_PORT_00_PIN_04。如果是查看P411,就输入BSP_IO_PORT_04_PIN_11
*参数3:
返回的电平数值,这个需要自己定义。
*/
程序编写
四种按键输入的程序
(1) 我们需要注意一件事情,延时20ms是用于消抖。而阻塞式和非阻塞式者两种是按键扫描的方案,即使是硬件消抖也需要进行讨论!不过优缺点和软件消抖是一样的,唯一区别在于少了一个延时而已!
(2)以下为硬件消抖和软件消抖的方式。
cpp
/*************************************************/
/*************** 阻塞式硬件消抖 *****************/
/*************************************************/
void hal_entry(void)
{
bsp_io_level_t S1;
uint8_t i = 0;
/* TODO: add your own code here */
while(1)
{
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1 == BSP_IO_LEVEL_LOW)
{
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1 == BSP_IO_LEVEL_LOW)
{
while(S1 == BSP_IO_LEVEL_LOW)
{
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
}
i=!i;
R_IOPORT_PinWrite(&g_ioport_ctrl,BSP_IO_PORT_05_PIN_02,i);
}
}
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
/***************************************************/
/*************** 非阻塞式硬件消抖 *****************/
/***************************************************/
void hal_entry(void)
{
bsp_io_level_t S1;
uint8_t i = 0;
/* TODO: add your own code here */
while(1)
{
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1 == BSP_IO_LEVEL_LOW)
{
i=!i;
R_IOPORT_PinWrite(&g_ioport_ctrl,BSP_IO_PORT_05_PIN_02,i);
}
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
/*************************************************/
/*************** 阻塞式软件消抖 *****************/
/*************************************************/
void hal_entry(void)
{
bsp_io_level_t S1;
uint8_t i = 0;
/* TODO: add your own code here */
while(1)
{
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1 == BSP_IO_LEVEL_LOW)
{
R_BSP_SoftwareDelay(20,BSP_DELAY_UNITS_MILLISECONDS);
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1 == BSP_IO_LEVEL_LOW)
{
while(S1 == BSP_IO_LEVEL_LOW)
{
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
}
i=!i;
R_IOPORT_PinWrite(&g_ioport_ctrl,BSP_IO_PORT_05_PIN_02,i);
}
}
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
/*********************************************/
/************** 非阻塞式软件消抖 ***************/
/*********************************************/
void hal_entry(void)
{
bsp_io_level_t S1;
uint8_t i = 0;
/* TODO: add your own code here */
while(1)
{
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1 == BSP_IO_LEVEL_LOW)
{
R_BSP_SoftwareDelay(20,BSP_DELAY_UNITS_MILLISECONDS);
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1 == BSP_IO_LEVEL_LOW)
{
i=!i;
R_IOPORT_PinWrite(&g_ioport_ctrl,BSP_IO_PORT_05_PIN_02,i);
}
}
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
如果需要消抖,推荐的消抖程序
(1)对于学单片机的人来说,都知道野火和正点原子。两者都有其优点,我比较偏向于野火,但是我始终认为两者优点都需要学习。上述说了,野火的按键部分对按键抖动有很好的硬件处理。此时我介绍以下正点原子关于按键消抖的软件处理方案。
(2)当你真正研究透了这段代码,会发现编写这个代码的人真的牛逼。既做到了非阻塞,又成功避免了因为长时间按下不松手造成CPU误认按键按下多次,如果想按下按键保持高速递增也可以。
(3)因为直接讲解这个代码感觉很生硬,所以我举几个可能会出现的情况进行分析。
<1>按下按键不松手:
*我们知道mode我们设置的是0,所以第9行代码可以无视。
*此时我们按下按键,这个时候第11行可以成功执行。
*延时20ms之后,按键的抖动也将会消失,这个时候让S1_up=0,避免按下一次按键,CPU认为按下了多次。之后如果没有松手的话,第11行的代码就永远无法进去。
*执行完操作之后,因为S1_up=0,所以第11行的代码一直无法执行。此时我们松手,第22行的if将会让S1_up=1。这个时候我们又可以重新判断按键是否按下了。
<2>松手时刻的20ms按键抖动:
*假设我们是在CPU执行第19行代码进行松手的,那么第22行的if将会让S1_up=1。这个时候就有一个问题,第11行的if函数可以进来了。因为S1_up=1,同时因为松手时刻的按键抖动,可能会出现一个瞬间S1也为低电平。
*但是不用怕,我们有20ms的软件消抖,这样在第16行的if语句无法进去,因为我们已经松手了。
*这个时候有人就有疑惑了,因为松手时刻的按键抖动,进入了第11行的if,那么 S1_up=0,这样第11行的if将永远进不来了啊。不用担心,第22行的if函数会出手。
cpp
void hal_entry(void)
{
bsp_io_level_t S1;
uint8_t i = 0,mode = 0;
static uint8_t S1_up=1;//按键按松开标志
/* TODO: add your own code here */
while(1)
{
if(mode)S1_up=1; //mode=1表示支持连按
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1_up && (S1 == BSP_IO_LEVEL_LOW)) //如果按键被按下,同时在没有松手过就无法进来了
{
R_BSP_SoftwareDelay(20,BSP_DELAY_UNITS_MILLISECONDS);//延时20ms软件消抖
S1_up=0;
R_IOPORT_PinRead(&g_ioport_ctrl,BSP_IO_PORT_00_PIN_04,&S1);
if(S1 == BSP_IO_LEVEL_LOW) //软件消抖之后判断是否依旧为低电平,防止松手时候抖动造成第一个if通过了
{
i=!i;
R_IOPORT_PinWrite(&g_ioport_ctrl,BSP_IO_PORT_05_PIN_02,i);
}
}
else if(S1 == BSP_IO_LEVEL_HIGH) //松手了,即使是松手时候造成的抖动,也表示松手了
{
S1_up=1;
}
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
模块化
移植LED程序
(1)了解完了整个流程了,我们依旧要将其进行模块化。因为前面我们已经将led点灯进行了模块化了,所以现在先将它移植进来。
(2)建立key文件夹,并建立.c和.h文件。
(3)利用rasc将文件添加进入工程
(4)将文件路径添加进来
程序
key.c
注意:read_key()这个函数无法在key.c以外的文件中调用。
cpp
#include "key.h"
static uint8_t read_key(void);
/*函数说明:按键扫描
*传入参数:
* s1_Double_click :表示支持连按
* s1_NoDouble_click :不支持连按
*返回参数:
* s1_down :表示s1被按下
* s1_up : 表示s1没有被按下
*/
uint8_t KEY_Scan(uint8_t mode)
{
static uint8_t S1_up=1;//按键按松开标志
if(mode) S1_up=1; //mode=1表示支持连按
if(S1_up && read_key())
{
R_BSP_SoftwareDelay(20,BSP_DELAY_UNITS_MILLISECONDS);//延时20ms软件消抖
S1_up=0;
if(read_key()) return s1_down;
}
else if(!read_key()) S1_up=1;//松手了,即使是松手时候造成的抖动,也表示松手了
return s1_up;
}
/*函数说明:读取S1引脚的电平
*传入参数:无
*返回参数:
* s1_down :表示按键按下
* s1_up : 表示松手
*/
static uint8_t read_key(void)
{
bsp_io_level_t S1;
R_IOPORT_PinRead(&g_ioport_ctrl,s1,&S1);
if(S1 == BSP_IO_LEVEL_LOW) return s1_down;
else return s1_up;
}
key.h
cpp
#ifndef __key_H
#define __key_H
#include "hal_data.h"
/********* 参数宏定义 *********/
#define s1 BSP_IO_PORT_00_PIN_04
#define s1_down 1
#define s1_up 0
#define s1_Double_click 1
#define s1_NoDouble_click 0
/********* 函数宏定义 *********/
//暂无
/********* 函数声明 *********/
uint8_t KEY_Scan(uint8_t mode);
#endif
led.c
注意,此刻的led.c与上一章的led.c不同,增加了一个led_flip()函数,用于LED翻转电平用。
cpp
#include "led.h"
/*函数说明:LED翻转电平
*传入参数:
* LED1 :表示LED1进行翻转
* LED2 :表示LED2进行翻转
*返回参数:无
*/
void led_flip(uint16_t led)
{
if(led == LED1)
{
static uint8_t led1_flag = 0;
led1_flag = !led1_flag;
R_IOPORT_PinWrite(&g_ioport_ctrl,LED1,led1_flag);
}
if(led == LED2)
{
static uint8_t led2_flag = 0;
led2_flag = !led2_flag;
R_IOPORT_PinWrite(&g_ioport_ctrl,LED2,led2_flag);
}
}
/*函数说明:led1闪烁
*传入参数:无
*返回参数:无
*/
void led_1_flicker(void)
{
LED1_lighting_up;
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_SECONDS);
LED1_lighting_off;
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_SECONDS);
}
/*函数说明:led2闪烁
*传入参数:无
*返回参数:无
*/
void led_2_flicker(void)
{
LED2_lighting_up;
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_SECONDS);
LED2_lighting_off;
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_SECONDS);
}
led.h
cpp
#ifndef __led_H
#define __led_H
#include "hal_data.h"
/********* 参数宏定义 *********/
#define LED1 BSP_IO_PORT_05_PIN_02
#define LED2 BSP_IO_PORT_05_PIN_01
/********* 函数宏定义 *********/
#define LED1_lighting_off R_IOPORT_PinWrite(&g_ioport_ctrl,LED1,BSP_IO_LEVEL_LOW)
#define LED1_lighting_up R_IOPORT_PinWrite(&g_ioport_ctrl,LED1,BSP_IO_LEVEL_HIGH)
#define LED2_lighting_off R_IOPORT_PinWrite(&g_ioport_ctrl,LED2,BSP_IO_LEVEL_LOW)
#define LED2_lighting_up R_IOPORT_PinWrite(&g_ioport_ctrl,LED2,BSP_IO_LEVEL_HIGH)
/********* 函数声明 *********/
void led_flip(uint16_t led);
void led_1_flicker(void);
void led_2_flicker(void);
#endif