GD32 MCU机械按键状态的识别

控制/MCU

1864人已加入

描述

GPIO口的输入功能-机械按键状态的识别

硬件: 深圳标航科技有限公司 暴风 开发板

处理器:GD32F103VET6

开发环境:MDK(keil 5) + STM32CubeMX

1.1 GPIO口的输入的作用

输入,其意是指将处理器外部的逻辑信号0或者1输入到处理器的内部。输入是每一个处理器的IO引脚的基本功能。利用处理器的输入功能我们可以获取外部电路的状态,进而做出进一步的判断。GPIO的输入功能的典型应用是获取机械按键的状态—判断按键是按下还是弹起。

1.2 机械按键状态的识别

1.2.1 机械按键电路的设计

按键有两个状态,一个是按下一个是弹起。通过巧妙的电路设计,会使得按键的按下与弹起时IO引脚的逻辑电平不一样。通过GPIO引脚的输入功能将这些逻辑电平输入到内部供处理器识别,由此可知按键是按下还是弹起,并做出进一步的判断。

下面我们先来讨论按键电路的设计。常用的按键电路设计如图1的(a)和(b)所示。

mcu

(a)

mcu

(b)

图1 按键电路设计

先来看图1的(a)图,在(a)图中,按键的一端接地,另一端接IO引脚,接IO引脚这一端通过一个电阻连接到高电平VCC(这种电阻叫上拉电阻)。在没有按键按下时,由于处理器吸取的电流非常非常小,R1两端可以认为没有电流流动,所以它们两边电位一样,也即IO引脚的电平跟VCC基本一样,此时IO引脚端为高电平。当按键按下后,IO引脚和地端相连,IO引脚直接变为了低电平。通过这个分析,我们得出图1(a)按键电路的特点如下:

(1)没有按键按下,IO引脚为高电平;

(2)有按键按下,IO引脚为低电平。

所以,如果处理器某个时候读到这个引脚信号为0,说明此时按键按下了,如果读到为1,说明按键没有按下。

再来看图1的(b)图,图1(b)中,按键一端接高电平,另一端接IO引脚,其中接IO引脚这一端通过一个电阻接到地(这种电阻叫下拉电阻)。图1(b)按键电路的特点如下:

(1)没有按键按下时,IO引脚为低电平;

(2)有按键按下时,IO引脚为高电平。

所以,如果处理器某个时候读到这个引脚信号为1,说明此时按键按下了,如果读到为0,说明按键没有按下。

暴风开发板的按键电路如图2所示,可以看到,在标航的暴风开发板中特意将两个按键分别按图1的(a)和(b)来连接,以便读者学习这两种按键电路按键状态的识别,这个设计比较人性化,可以照顾不同开发者的不同应用场合。

mcu

图2 暴风开发板按键电路图

图2中要注意,此时电路设计没有在PE2和PA0两个引脚分别连一个电阻到VCC和地,所以我们得在GD32的内部这两个引脚这里分别使能这两个电阻(GD32和STM32的每个IO引脚内部都配有一对受控的上下拉电阻)。

1.2.2 机械按键状态识别的思路设计

通常,我们都是设计一个函数来单独判断按键是否按下,这个按键函数的思路设计如下:

uint8_t Key_Scan(void)
{
   if(KEY0_Status == 0) || (WK_UP1_Status == 1)  //说明有按键按下了
   {
      if(KEY0_Status == 0)     return KEY0_Value;
      if(WK_UP1_Status == 1)  return WK_UP1_Value;
}
return KEY0_NO;
}

在函数Key_Scan中,我们先判断KEY0的状态是不是0或者WK_UP1的状态是不是为1,如果KEY0的状态是0或者WK_UP1的状态是1,说明按键按下了,接下来进行细分,看看是KEY0按下还是WK_UP1按下,并返回对应的按键值。

对于Key_Scan函数的调用,我们可以在主函数中这样调用

int main(void)
{
      uint8  keyvalue = 0;
    系统初始化;
      while(1)
      {
         keyvalue = Key_Scan();
         if(keyvalue == KEY0_Value) LED0 = ~LED0; // LED0状态反转
         if(keyvalue == WK_UP1_Value) LED1 = ~LED1;// LED1状态反转
    }
}

在主函数中,我们循环执行按键扫描,如果发现按键扫描函数返回的是KEY0_Value,则将LED0的状态反转,如果返回的是WK_UP1_Value,则将LED1的状态反转。总的来说,我们是希望按下一次按键,对应的LED的状态就反转。

上面这两个函数的配合是否有问题呢?表面看来好像没有问题,但是当你用这个思路去完善程序并下载到开发板执行的时候,你会发现按键按下时,灯的状态是不受控的,这个不受控的原因是什么呢?我们看一下整个执行过程。

假设有按键KEY0按下,则整个过程为

①执行语句“keyvalue = Key_Scan();”此时返回KEY0_Value,接着执行判断并使得LED0状态反转一次,这个过程持续时间非常短,1ms内估计就能执行完。

②又回来执行语句“keyvalue = Key_Scan();”,此时由于按键仍然处于按下状态(人为按下时,按键的按下状态通常会超过100ms,典型的是600ms左右),所以又会返回KEY0_Value,接着执行判断并使得LED0状态又反转一次。

注意,此时按键的状态已经变化两次了,但是我们只是执行一次按下而已!!!!!

继续往下分析,你会发现按键按下一次时,这个判断系统会执行多次返回,这是错误的。错误的原因在哪里呢?在Key_Scan这个函数中,这个函数里面只要KEY0_Status等于0,它就会返回一次KEY0_Value,所以我们需要加一个变量,用于描述按键的当前状态,如果当前按键已经按下了,则这里就不需要再次判断了。由于这个变量描述按键的按下与弹起状态,在Key_Scan执行完后也不能释放它的存储空间,所以我们需要用static修饰它,此时的Key_Scan函数需要修改如下:

uint8_t Key_Scan(void)
{
   static  uint8_t  flag = 0; //flag =0说明当前是弹起,=1说明是按下
   /*如果刚才是弹起但现在有按键按下则判断是那个按键按下,同时将按键状态置为1*/
   if((flag == 0)&&((KEY0_Status == 0) || (WK_UP1_Status == 1)))     {
      flag = 1;
      if(KEY0_Status == 0)     return KEY0_Value;
      if(WK_UP1_Status == 1)  return WK_UP1_Value;
}
return KEY0_NO;
}

将Key_Scan函数修改为上面的样子后,解决了按下一次就执行一次返回,避免了按下一次则返回多次的问题。但是它仍然是有重大缺陷的,因为我们按下一次按键后,flag被设置为1了,当按键再次被按下时里面的按下判断再也得不到执行,也即刚刚修改后的函数只能判断一次按键按下。要想将flag恢复为0,我们要在Key_Scan中增加弹起的语句,如果弹起了,将flag设置为0,则就可以解决多次按下后都能触发判断的问题了。增加判断后的Key_Scan函数如下:

uint8_t Key_Scan(void)
{
   static  uint8_t  flag = 0; //flag =0说明当前是弹起,=1说明是按下
   /*如果刚才是弹起但现在有按键按下则判断是那个按键按下,同时将按键状态置为1*/
   if((flag == 0)&&((KEY0_Status == 0) || (WK_UP1_Status == 1)))  
   {
      flag = 1;
      if(KEY0_Status == 0)     return KEY0_Value;

      if(WK_UP1_Status == 1)  return WK_UP1_Value;
}
/*如果刚才按键按下,现在弹起了,则设置flag=0*/
   if((flag == 1)&&((KEY0_Status == 1) && (WK_UP1_Status == 0)))     {
      flag = 0;
  }
  return KEY0_NO;
}

注意,按键弹起指的是所有按键的弹起,所以(KEY0_Status == 1) && (WK_UP1_Status == 0)这里要用逻辑与,体现出“而且”之意。

至此,机械按键状态识别的关键问题就解决了。

1.2.3 机械按键的抖动及其消除

虽然关键问题解决了,但还有一些细节要注意,这个细节就是按键的抖动。

以图3的按键电路为例,当按键按下时,PE2引脚的电平状态如图4所示。

mcu

图3 按键电路图

mcu

图4 按键按下过程

由图4可见,按键按下时,PE2并不是马上变为低电平,而是有一个渐变过程,而在弹起时也不是马上变为高电平,它也有一个渐变过程。这些渐变过程我们叫做抖动。在进行按键的按下与弹起时,我们都要进行抖动的消除。消除的方法非常简单,就是延时。通常,抖动持续的时间在10ms之内,所有,我们只要进行10ms的延时可以解决掉绝大部分机械按键的抖动—如果解决不了,此时就要用示波器测一下你的按键按下与放开时的信号,看看具体的抖动是多少,然后增加延时消除它,笔者就曾遇到过需要延时20ms的情况……

这个消抖的过程如下:

1、按下的消抖

if(按键按下)
{
   Delay(10ms);
   if(按键按下)
   {
      按键是真的按下,执行相应的动作;
    }
}

2、弹起的消抖

if(按键弹起)
{
Delay(10ms);
if(按键弹起)
{
按键是真的弹起了,执行相应的动作;
}
}

1.2.4 完整的按键判断程序

加入消抖后,整个按键判断的函数可以修改如下

uint8_t KEY_Scan(void)
{
      static  uint8_t  flag=0; //按键弹起为0,按下为1
      if(( flag == 0) && ((KEY0_Status == 0)||(WK_UP1_Status == 1)))
      {  
         /*按键刚刚处于弹起状态,但现在有按下*/
         HAL_Delay(10);    //延时10ms,消除抖动
         if((KEY0_Status == 0)||(WK_UP1_Status == 1))
         {  
            /*确实有按键按下*/
            flag = 1;   //按键为按下状态
            if(KEY0_Status == 0)  return KEY0_Value;
            if(WK_UP1_Status == 1) return WK_UP1_Value;
         }  
      }
     if((flag == 1) && ((KEY0_Status == 1) && (WK_UP1_Status == 0)))
      {  
         /*按键处于弹起状态而且刚才是按下状态*/
         HAL_Delay(10);    //消除弹起抖动
         if((KEY0_Status == 1) && (WK_UP1_Status == 0))

         {
            flag = 0;   //按键弹起了
         }  
      }
      return  KEY_NO;   //没有按键按下返回KEY_NO
}

1.3 按键状态判断实验

下面我们通过一个例子来验证按键状态的识别。

【例1】已知按键电路和LED电路如图5所示,编写程序实现以下功能:

按下按键KEY0, LED0的状态反转;按下按键WK_UP1,LED1的状态反转。

mcu

图5 按键电路和LED电路示意图

【实现过程】

1.配置RCC的高速时钟来自于外部晶体陶瓷晶振,并且设置HCLK的频率为72Mhz。

2.设置调式方式为Serial Wire。

以上两步不懂如何设置的可以回头看一下【模块一 GPIO口的输出功能-LED的闪烁实验】这一部分的步骤介绍。

3.设置PE12、PE13引脚的工作模式为输出,PE12的User Label选项为LED0,PE13的User Label设置为LED1,以使能更加直观方便。另外,一开始我们将这两盏LED灯都点亮,以方便观察结果。PE12和PE13的设置结果如图6所示。

mcu

图6 PE12和PE13的设置过程

4.设置PE2和PA0为输入。PE2引脚上拉电阻使能,PA0引脚下拉电阻使能。同时设置PE2的用户标号为KEY0,设置PA0引脚的标号为WK_UP1,。PE2引脚的设置如图7所示,PA0引脚的设置如图8所示。

mcu

图7 PE2引脚的设置结果示意图

mcu

图8 PA0引脚的设置

5.设置好后,给工程取名,同时选择IDE,并生成工程代码。

6.添加代码。

①编写按键识别的C语言文件,其内容如图9所示。

mcu

图9 key.c中内容示意图

②在key.h中定义KEY0_Value等宏名,如图10所示。

mcu

图10 KEY0_Value等的定义示意图

③修改主函数,其内容如图11所示。

mcu

图11 主函数的内容示意图

在主函数中,注意要将头文件key.h包含进工程,如图12所示。

mcu

至此,工程的代码添加完毕,编译后下载到开发板,按复位键,然后按KEY0或者WK_UP0,可以看到对应的LED灯的状态反转,任务目标完成。

1.4 按键识别实验用到的HAL库的函数解读

1.引脚电平反转函数 HAL_GPIO_TogglePin()

在主函数main的while循环中,我们使用到了函数HAL_GPIO_TogglePin,这个函数的相关信息为

●作用:将某个IO引脚的输出电平反转。比如要反转PE12引脚的电平,我们可以采用如下的方式来调用该函数:

HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_12);

●函数参数,有两个,第一个用于指明要反转信号的引脚位于第几组GPIO口,第二个用于指明要反转的是哪一个引脚的信号。

是不是很方便呢?

最后要注意,函数HAL_GPIO_TogglePin要使用于将IO引脚已经配置为输出的场合。

2.读引脚信号函数HAL_GPIO_ReadPin()

在key.c函数中,有一个宏定义

#define KEY0_Status HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)

里面用到了一个函数HAL_GPIO_ReadPin,这个函数相关的信息如下:

●作用:读取某个引脚的状态。比如要读取PA0的状态,我们可以采用如下的方式来调用该函数

HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);

●函数参数,有两个,第一个用于指明要读取信号的引脚位于那组GPIO口,第二个参数用于指明是那个引脚。

注意,函数HAL_GPIO_ReadPin使用于IO引脚已经设置为输入的场合

1.5 GPIO输入功能总结

在使用GD32/STM32的IO引脚时要注意以下两点:

1.如果引脚外部没有上拉电阻或者下拉电阻,则可能需要在引脚内部使能上拉电阻或者下拉电阻。

2.对按键状态进行识别时,一定要注意防止按下一次时有多次返回值。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分