Timer 与按键消抖的应用(复盘总结)

         因为通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。

         消抖是为了避免在按键按下或是抬起时电平剧烈抖动带来的影响。按键的消抖,可用硬件或软件两种方法,在实际的设计中,为了节省硬件成本,大多数情况会选择使用软件消抖。 

延时消抖

        软件消抖,即检测出按键闭合后执行一个延时程序,5ms~10ms 的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。当检测到按键释放后,也要给 5ms~10ms 的延时,待后沿抖动消失后才能转入该键的处理程序。

        以前常用的消抖算法往往就是:判断按下->延时->再次判断是否按下->是,执行/否,退出。

        最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。

//按键处理函数 
void ButtonScan(void)
{     
    static int ButtonFlag = 0;//按键标志,1代表松开,0代表按下

    if(Button == 0)//第一次判断按键按下
    {
        delay_ms(10);//延时10ms,去抖动
        if(Button == 0)//第二次判断按键按下
        {
            ButtonFlag = 1;    //确认按键为按下,按键标志置1
        }        
}

 利用定时器消抖

        利用定时器消抖算法:通过 SysTick 中断每1ms对按键进行扫描,当检测到连续的稳定无抖动电平信号(长度可设置)之后,才进行相应的逻辑操作。

        启用一个定时中断,每 1ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描 25 次后,看看这连续 25 次的按键状态是否是一致的。25 次按键的时间是 25ms,这 25ms 内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,而非处于抖动的阶段。

对 SYS 进行了配置

在 STM32CubeMX 中,默认 SysTick 中断为 1ms。 我们可以打开 MDK-ARM 工程,在左侧 Drivers/STM32F1xx_HAL_Driver 文件夹的 stm32f1xx_hal.c 文件中找到 SysTick 的初始化。

/**
  * @brief This function configures the source of the time base.
  *        The time source is configured  to have 1ms time base with a dedicated
  *        Tick interrupt priority.
  * @note This function is called  automatically at the beginning of program after
  *       reset by HAL_Init() or at any time when clock is reconfigured  by HAL_RCC_ClockConfig().
  * @note In the default implementation, SysTick timer is the source of time base.
  *       It is used to generate interrupts at regular time intervals.
  *       Care must be taken if HAL_Delay() is called from a peripheral ISR process,
  *       The SysTick interrupt must have higher priority (numerically lower)
  *       than the peripheral interrupt. Otherwise the caller ISR process will be blocked.
  *       The function is declared as __weak  to be overwritten  in case of other
  *       implementation  in user file.
  * @param TickPriority Tick interrupt priority.
  * @retval HAL status
  */
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  /* Configure the SysTick to have interrupt in 1ms time basis*/
  if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
  {
    return HAL_ERROR;
  }

  /* Configure the SysTick IRQ priority */
  if (TickPriority < (1UL << __NVIC_PRIO_BITS))
  {
    HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
    uwTickPrio = TickPriority;
  }
  else
  {
    return HAL_ERROR;
  }

  /* Return function status */
  return HAL_OK;
}

函数里面有一句注释: /* Configure the SysTick to have interrupt in 1ms time basis*/,翻译过来就是,将 SysTick 配置为 1ms 中断。也就是说,经过此函数,SysTick 定时器被初始化为 1ms 的时基单元,即每毫秒进入一次 Systick 中断。

我们可以在左侧 Application/User 文件夹的 stm32f1xx_it.c 文件中找到 SysTick 中断服务函数。

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

里面只有一个 HAL_IncTick() 函数,我们右键转跳到该函数可以看到该函数的定义。

 

/**
  * @brief This function is called to increment  a global variable "uwTick"
  *        used as application time base.
  * @note In the default implementation, this variable is incremented each 1ms
  *       in SysTick ISR.
  * @note This function is declared as __weak to be overwritten in case of other
  *      implementations in user file.
  * @retval None
  */
__weak void HAL_IncTick(void)
{
  uwTick += uwTickFreq;
}

每毫秒 uwTick 加一,用于计算 Systick 产生的节拍数。

在 HAL_IncTick 函数的下方有一个 HAL_GetTick(void) 函数,主要用于获取当前节拍,如果内核正常运行,则 uwTick 的值即为已经经过的 ms 数。

这里顺便提及一下 HAL 库的延时函数 HAL_Delay() 。在左侧 Drivers/STM32F1xx_HAL_Driver 文件夹的 stm32f1xx_hal.c 文件中找到 HAL_Delay() 函数。

/**
  * @brief This function provides minimum delay (in milliseconds) based
  *        on variable incremented.
  * @note In the default implementation , SysTick timer is the source of time base.
  *       It is used to generate interrupts at regular time intervals where uwTick
  *       is incremented.
  * @note This function is declared as __weak to be overwritten in case of other
  *       implementations in user file.
  * @param Delay specifies the delay time length, in milliseconds.
  * @retval None
  */
__weak void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  while ((HAL_GetTick() - tickstart) < wait)
  {
  }
}

可以看到 HAL_Delay() 函数其实就是通过 HAL_GetTick() 函数数节拍以确定精准的延时。

        回到按键消抖的主题,既然 STM32CubeMX 已经默认设置 Systick 为 1ms 中断,那么我们可以直接利用起来。打开 MDK-ARM 工程,按下组合键 Ctrl+N(按住 Ctrl 键再按 N 键),新建一个文件,再按下组合键 Ctrl+S,文件名改为 button.c,保存到 MiaowLabs-DEMO 的 Src 文件夹里。来到这里,我们虽然新建了button.c 源文件,但是还没有把该文件加入到 MDK-ARM 工程里。我们在 MDK-ARM 工程界面左侧 Project 栏目双击 Application/User 文件夹,把 button.c 加进来。

双击 button.c 文件,把下面代码敲进去。

#include "button.h"
#include "main.h"
#include "stm32f1xx_it.h"

int iButtonCount;//i代表int型变量,ButtonCount表示按键计数变量
int iButtonFlag;//i代表int型变量,ButtonFlag表示重按键标志,1代表重新按键,0为没有重新按键
int g_iButtonState;//g是globle代表全局变量,会在其他地方引用;i代表int型变量,ButtonState表示按键标志,1代表按下,0代表松开
void ButtonScan(void){
  if(   HAL_GPIO_ReadPin(Button_GPIO_Port,Button_Pin) == GPIO_PIN_RESET )//如果引脚检测到低电平
  {
      iButtonCount++;                         //按键按下,计数iButtonCount加1
      if(iButtonCount>=30)                    //1ms中断服务函数里运行一次,iButtonCount大于等于30,即按键已稳定按下30ms
         {
            if(iButtonFlag==0)                  //判断有没有重按键,1为有,0为没有
                {
                    g_iButtonState=1;                 //设置按键标志
                    iButtonCount=0;
                    iButtonFlag=1;                  //设置重按键标志
                    }
                else                              //如果重按键,则重新计数
                    iButtonCount=0;
                }
    else                                  //如果没有稳定按下30ms,则代表没有按下按键
         g_iButtonState=0;

         }
else                                      //如果一直无检测到低电平,即一直无按键按下
    {
         iButtonCount=0;                  //清零iButtonCount
         g_iButtonState=0;                  //清除按键标志
         iButtonFlag=0;                   //清除重按键标志
    }
}

        再新建一个文件 button.h 头文件,把文件保存到 Inc 文件夹。然后,把下面代码敲进去。

#ifndef __BUTTON_H
#define __BUTTON_H

extern int g_iButtonState;//声明外部变量,方便其他地方引用

void ButtonScan(void);//声明按键扫描函数

#endif

        打开 stm32fxx_it.c 文件,在 /* USER CODE BEGIN Includes */ 与 /* USER CODE END Includes */ 之间加入 #include "button.h"。

        在 stm32fxx_it.c 文件里面 SysTick_Handler() 函数里加入按键扫描函数 ButtonScan(),更重要的是,要在后面加上 HAL_SYSTICK_IRQHandler() 函数,STM32CubeMX v5.x 之后的版本为避免 HAL_SYSTICK_Callback() 执行时间超过 1ms 或产生循环依赖时出现阻塞,默认不调用 HAL_SYSTICK_IRQHandler(),所以我们要手动加上这个语句。

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
  ButtonScan();//按键扫描函数
  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
  HAL_SYSTICK_IRQHandler(); //必须加这个语句,不加按键会失效
  /* USER CODE END SysTick_IRQn 1 */
}

        在左侧 Application/User 文件夹里打开 main.c 函数,在主循环里面加入以下代码:

    if(g_iButtonState == 1)
    {            
        HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);//翻转LED引脚(PB12)的电平
        while(HAL_GPIO_ReadPin(Button_GPIO_Port,Button_Pin)==GPIO_PIN_RESET);//按键松手检测
    }

        这段代码的意思就是:如果按键状态标志为 1 ,则代表按键被按下了,这时候进行 LED 电平翻转。

        在左侧 Application/User 文件夹里点开 main.c 函数左侧的加号,找到 main.h 头文件,并把#include "button.h"头文件加进去。

        代码已经编写好了。这时候,点击编译按钮,会提示没有错误和警告。

      (完成)

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是一个定时器按键消抖的代码解释: ```c #include <reg52.h> #define uchar unsigned char #define uint unsigned int uchar keyvalue; // 存储按键状态 uchar keylock; // 锁定标志位 uchar keynum; // 消抖计数器 /* 定时器0初始化函数 */ void Init_Timer0() { TMOD = 0x01; // 定时器0工作模式1 TH0 = 0x3C; // 定时器初值,65536-50000=15536=3CB0H TL0 = 0xB0; ET0 = 1; // 开启定时器0中断 TR0 = 1; // 启动定时器0 } /* 定时器0中断处理函数 */ void Timer0_isr() interrupt 1 { TH0 = 0x3C; TL0 = 0xB0; // 重新给定时器赋初值 if (keynum < 250) // 如果按键按下且持续时间小于10ms { keynum++; // 计数器加1 if (keynum >= 200) // 经过一段时间后 { keylock = 0; // 解锁按键 if (keynum == 250) // 如果按键最后一次还是按下状态 { keyvalue = 1; // 表示按键按下 } } } } /* 按键扫描函数 */ uchar Key_Scan() { uchar temp; if (keylock == 0) // 如果按键没有被锁定 { temp = P1 & 0x0F; // 读取按键状态 if (temp != 0x0F) // 如果有按键按下 { keynum = 0; // 清零计数器 keylock = 1; // 锁定按键 } } else // 如果按键被锁定 { temp = P1 & 0x0F; // 读取按键状态 if (temp == 0x0F) // 如果按键已经松开 { keylock = 0; // 解锁按键 if (keynum < 250) // 如果按键按下时间小于10ms { keyvalue = 0; // 表示按键松开 } else // 如果按键按下时间大于10ms { keyvalue = 2; // 表示按键无效 } } } return keyvalue; } /* 主函数 */ void main() { Init_Timer0(); // 初始化定时器0 while (1) { if (Key_Scan()) // 如果有按键按下 { // 处理按键事件 } } } ``` 这段代码中,定时器0被初始化为工作模式1,计数器的初值被设为65536-50000=15536=3CB0H,也就是10ms。当按键按下时,按键状态会被存储在keyvalue变量中,然后启动定时器0,并在定时器0中断处理函数中检查按键状态和持续时间,从而消除按键抖动。如果按键被检测到了,Key_Scan函数会返回按键状态,然后在主函数中处理按键事件。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值