【致敬未来的攻城狮计划】--RA2E1 开发板测评(3)按键输入

前言

(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,将会一直 执行程序)

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
相关推荐
Ustinian_3102 天前
【C++】 C++游戏设计---五子棋小游戏
c
一叶飘零_sweeeet3 天前
Spring Boot 的核心注解
java·spring boot·c
m1chiru5 天前
C++ 游戏开发:打造高效、性能优越的游戏世界
c
charlie1145141918 天前
从0开始的STM32之旅8 串口通信(II)
stm32·单片机·嵌入式硬件·c·串口通信
孤寂大仙v8 天前
【C++】AVL树
开发语言·数据结构·c++·c
charlie11451419110 天前
嵌入式Linux入门具备:C语言基础与基本驱动学习(2):Linux GIibc IO基础
c语言·学习·c·io·嵌入式软件
charlie11451419112 天前
从0开始的STM32之旅 7 串口通信(I)
stm32·单片机·嵌入式硬件·c
Java Fans12 天前
C++ 实现俄罗斯方块游戏
c
我要学脑机13 天前
电路知识的回顾
c·电路
Ljw...13 天前
C++游戏开发
c++·c·游戏开发