前言
(1)首先感谢 李肯前辈的活动,从而申请到了RA2L1开发板的测评。
(2)本文主要介绍按键输入的内容。
(3)学习本文需要准备的前提,
【致敬未来的攻城狮计划】--RA2E1 开发板测评(1)keil环境配置;
【致敬未来的攻城狮计划】--RA2L1 开发板测评(2)LED闪烁;
(4)本文主要介绍按键抖动问题,软硬件消抖方案,分析其中的优缺点。同时介绍如何进行模块移植。
(5)代码仓库: gitee仓库; GitHub仓库;
开发板按键原理图分析
我们根据下面原理图可知,按键被按下的时候为低电平,否则为高电平。
按键抖动问题
(1)我们所看到的按键基本都是这样的,按下电平IO输入为低电平,否则为高电平。
(2)但是由于,按键所用的开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。
(3)这样会有什么影响呢?很简单,当我们只按下了一下按键,而由于单片机程序执行速度很快,所以可能会识别到抖动的高电平,导致单片机以为我们按下了很多次按键。因此,我们需要进行按键消抖。
注意:下面这张图表示按键按下时候是低电平!!!
硬件消抖
(1)我比较喜欢进行硬件消抖,因为硬件能够处理的事情,就别扔给搞软件的了。野火的指南者的按键可以供我们的学习。
(2)首先,为了防止IO口没有内部下拉,所以使用一个外部下拉保证按键没有被按下的时候,电平不是未知的。
(3)我们在按键两端并联一个104的电容,能够让按键按下时候的抖动电平被电容消除,使得电平上升的更平缓。这样就能够有效的消除按键抖动。
软件消抖
(1)介绍完硬件消抖了,如何进行软件消抖呢?首先,我们需要知道按键抖动的大概时间为5ms~20ms。之后我们可以先判断按键是否被按下,如果发现第一次被按下,单片机延时20ms。然后第二次判断按键是否被按下,如果此次依旧被按下,说明按键的确被按下了。(听不明白可以看后面代码)
阻塞式消抖
(1)因为我还没介绍RA2E1的函数。所以这里以文字进行表示。
(2)阻塞式消抖优缺点:
缺点:阻塞式消抖会让程序堵死在while,对CPU的资源浪费,而且会让我们无法执行 其他程序。
优点:如果我们按键按下需要执行的程序是记录按键按下次数,这样就不会导致我们误判明明只按下1次长时间不松手,而造成cpu认为按下多次。(如果按下按键为低电平,没有while,将会一直 执行程序)
//判断是否为低电平(按键按下为低电平) //延时20ms //while判断是否为低电平(按键松手为高电平,如果松手不满足while条件,退出循环) //判断是否为低电平(按键按下为低电平) //执行程序 //其他程序
非阻塞式消抖
优点:这种方法不会阻塞程序,如果我们按下按键不松手, 其他程序依旧可以跑。
缺点:如何我们需要的是记录按键按下的次数。因为人不可能将按键按下的时间精准控制在20ms之内。那么就会造成,我明明只按下了一次,而 执行程序部分却会执行多次。
//判断是否为低电平(按键按下为低电平) //延时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()函数,以下为函数介绍。
/*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)以下为硬件消抖和软件消抖的方式。
/*************************************************/ /*************** 阻塞式硬件消抖 *****************/ /*************************************************/ 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函数会出手。
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以外的文件中调用。
#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
#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
ed.c
注意,此刻的led.c与上一章的led.c不同,增加了一个led_flip()函数,用于LED翻转电平用。
#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
#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