最通俗教程!硬件消抖,软件消抖,状态机法!

目录

一、按键抖动

二、按键抖动的消除方法

1.硬件消抖法

电容消抖法

2.软件消抖法

延时法

状态机法

一、按键抖动

按键:常规情况下的按键是一种机械式输入设备,通过按下按键来获取一个输入的按键按下状态。在最理想的情况下,按键按下时,我们的单片机及相关设备应立马收到一个“按键按下”的即时状态;按键松开时,我们的单片机也应立马检测到按键已经被松开。

但是!就像几乎所有的现实情况永远不会像"理想情况"那样理想,按键也并不会像我们想的这样工作。按键工作时总会存着这一个 “按键抖动” 的不理想状态!

由于按键的机械设计,按键在被按下以及被松开时,必定会存在一个 “抖动” 的状态。这个 “抖动”的状态通常会持续10~20ms。

(我们通常用 “按键处的电平变化” 来描述按键的按下与松开状态)

可以看到,按键被按下以及松开按键时,各会有一小段的小波浪式的不稳定状态,在这个不稳定状态中,按键会在被按下以及松开两种状态中快速且突然的跳变,这会让我们的系统检测到按键突然被按下或者松开了未知多次,导致按键的检测逻辑混乱,这显然不是我们想要的,当按键被按下或者松开时,我们都应各检测到一次状态变化,总共两次的状态变化才对,而不是好多次。

这个 “按键抖动” 的根本原因在于按键触发与松开时,其内机械元件的不稳定性导致

二、按键抖动的消除方法

如上的情况,我们必须对按键抖动进行处理,才能够使得按键按我们设想的理想状态进行工作。针对此,聪明的工程师们提出了两种方法:硬件消抖法软件消抖法

1.硬件消抖法

通过一定的电路设计,我们能消除掉按键本身按键抖动现象的不利影响。硬件消抖法通常是通过两种方式实现,分别是电容消抖法RS触发器消抖法。在此我们仅暂讨论电容消抖法,RS触发器待有时间再整理咯。

电容消抖法

在此暂不展开描述电容这一元器件的详细特性,我们仅需要明白的是,电容就像是一个小水桶,可以存储电能,在电路中,当电压突然升高或者降低时,电容会在其电路上进行吸收或释放电能,帮助稳定电压。

对于按键而言,我们通常在其旁边并联一个电容,以实现按键的消抖。

消抖后的按键电平变化波形↓可以看到按键被按下或松开时,按键状态能够被唯一确定!此即达到了我们想要的效果。

到这里,有些好奇的宝宝们就可能会问了,为什么电容要并联在按键旁边为什么电容不能与按键串联

实际上,当我们对单个按键进行消抖时,在交流电电路下(为什么是交流电电路,需要自行了解电容的工作原理,在此不详细展开),电容与按键串联以消除按键抖动是可行的但是!电容的充放电是具有延迟性的,这就导致了按键对按下与松开的反应的响应会不够灵敏!具体体现为当我们按下或者松开按键时,过了一会儿,按键才会发生明显的效果。当我们对多个串联的按键进行消抖时,电路上串联着一个电容,串联的按键里有一个发生抖动,虽然这个抖动最终会被电容消除掉,但是在这个按键发出抖动信号直到被电容消除的这段时间里,这段 “抖动” 的不稳定信号会影响串联着的其他按键,导致其上信号的不稳定。总之的总之,串联电容以消抖是并不好的,所以我们通常采用并联电容的方法以进行消抖!

2.软件消抖法

不动硬件,我们单纯凭借软件也可消除 “按键抖动”,其中原理的原理是:通过忽略按键抖动时的电平变化以达到消除按键抖动的目的。软件消抖法分为延时法状态机法

延时法

按键首次按下或松开时,一定会发生一次状态的跳变,由此我们可以进入一个判断当中,紧接着跟着一个延时,通过这个延时来跳过按键的抖动阶段,再进行按键的判断,此时的判断的结果方为按键稳定时的真实结果。在此贴出一个能描述基本逻辑的51单片机按键消抖代码:

#include <reg51.h>

void delay_ms(unsigned int t)	 //ms级延时
{
	unsigned int i,j;
	for(i=0; i<t; i++)
		for(j=0; j<120; j++);
}
void main(void)
{
	while(1)  // 单片机程序在此while循环内一直循环执行
	{
		if(key==1)	  // 当检测到按键首次被按下时
		{
			delay_ms(20); //延时10ms以消抖,即跳过按键抖动阶段
			if(key==1)	 // 跳过了按键抖动阶段后的再判断,此时判断的结果即为真实且稳定的状态
			{
				 /*
                    按键被按下时执行的一系列程序
                */
				 while(key==1);//等待按键松开,防止往下执行
			}
		}
	}
}

虽然延时法通俗易懂且实现起来方便、直接。但是注意delay_ms(20);,这个延时函数会阻塞程序的执行,使得程序在这20ms内空转,什么都不干,单纯的在这等待,不能够同时处理其他任务,浪费珍贵的系统资源。为此,我们还有一种更为灵活与可靠的方法用于解决按键抖动,即状态机法。

状态机法

这种方法不会阻塞程序流的执行,能够保证程序执行其他任务时仍能够完成按键消抖的任务。

由按键的状态变化图(如上)可知,当按键被按下或者被松开时,系统必定会检测到一次状态的变换,借由这个状态变换进入按键检测逻辑,设计一系列的逻辑以处理按键抖动。状态机法解决按键抖动且不阻塞程序执行其他任务的本质是:利用单片机执行任务的循环性,当按键状态变化时,开始在单片机程序流中一遍遍记录时间、累计距离上次按键状态变化的时间间隔,当时间间隔大于按键抖动时间时再进行按键状态监测,判断并跳过按键抖动。

状态机法将按键的状态分为4种,分别是:静默状态短按消抖状态长按状态、释放状态。针对各种状态及处于该状态下所面临的接下来的状态进行分析可得如下按键状态流程图。

在此首先贴出一个在ESP32上实现的基于状态机的单按键消抖代码(一个按键需要单独分配一个状态机函数),再进行逐一分析:

#include <Arduino.h>

const int buttonPin = 47; // 按键引脚
const int debounceDelay = 50; // 消抖延迟时间(毫秒)

enum ButtonState {
  BUTTON_IDLE,  // 静默状态
  BUTTON_DEBOUNCE,  // 短按消抖状态
  BUTTON_PRESSED,  // 长按状态
  BUTTON_RELEASED  // 释放状态
};

ButtonState buttonState = BUTTON_IDLE;
unsigned long lastDebounceTime = 0;
void handle_button(int pin, ButtonState &state, unsigned long &last_debounce_time)
{
  int reading = digitalRead(buttonPin);
  unsigned long currentTime = millis();

  switch (buttonState) {
    case BUTTON_IDLE:
      if (reading == HIGH) {
        buttonState = BUTTON_DEBOUNCE;
        lastDebounceTime = currentTime;
      }
      break;

    case BUTTON_DEBOUNCE:
      if ((currentTime - lastDebounceTime) > debounceDelay) {
        if (reading == HIGH) {
          buttonState = BUTTON_PRESSED;
          Serial.println("Button pressed");
        } else {
          buttonState = BUTTON_IDLE;
        }
      }
      break;

    case BUTTON_PRESSED:
      if (reading == LOW) {
        buttonState = BUTTON_RELEASED;
        lastDebounceTime = currentTime;
      }
      break;

    case BUTTON_RELEASED:
      if ((currentTime - lastDebounceTime) > debounceDelay) {
        if (reading == LOW) {
          buttonState = BUTTON_IDLE;
          Serial.println("Button released");
        } else {
          buttonState = BUTTON_PRESSED;
        }
      }
      break;
  }
}
void setup(void)
{
  Serial.begin(115200);
  tft_init();
  pinMode(buttonPin, INPUT);
}

void loop(void)
{
    handle_button(buttonPin,buttonState,lastDebounceTime);
}

代码分析:首先,先看全局的几个重要变量

定义出了按键的四种状态以用于后续的判断

enum ButtonState {
  BUTTON_IDLE,  // 静默状态
  BUTTON_DEBOUNCE,  // 短按消抖状态
  BUTTON_PRESSED,  // 长按状态
  BUTTON_RELEASED  // 释放状态
};

紧接着,在初始状态下,按键尚未被按下,我们将按键的状态设置为静默状态

ButtonState buttonState = BUTTON_IDLE;  // 初始为尚未被按下的静默状态

其次,我们开始关注最重要的按键状态机函数handle_button():

在函数中,首先会不间断的做两件事,第一件事是不断读取按键的当前状态;第二件事是不断记录当前的时间戳(即程序从开始运行到现在的流逝时间)

  int reading = digitalRead(buttonPin); // 读取按键状态
  unsigned long currentTime = millis();

紧接着开始按键的消抖与检测。要理解这个函数的运行过程,我们从按键未被按下到按下后释放这一完整过程中对按键状态进行分析,从而理解代码

刚开始时,按键未被按下,buttonState处于BUTTON_IDLE静默状态。此处往后有两种情况,一是继续保持此静默状态,二是按键被按下。第一种状态时,程序通过break来跳出switch,不会有仍和操作。

第二种状态是用户按下按键,此时进入一个if判断,即if按键被按下了,设置按键状态为短按消抖阶段,并且记录下此时时间戳为抖动时间点。

    case BUTTON_IDLE:
      if (reading == HIGH) {
        buttonState = BUTTON_DEBOUNCE;
        lastDebounceTime = currentTime;
      }
      break;

当用户按下按键后,之后会有两种状态,一是按键按下后在10~20ms内抖动;二是越过抖动阶段后的按键保持状态。此时我们处理的逻辑是,距离按键被按下的10~20ms内若按键突变又出现一个按下态,则清空计时,更新此时为抖动时间点。唯独10~20ms后,再进行按键的判断,此时若仍为按下态,则按键状态进入长按状态。

    case BUTTON_DEBOUNCE:
      if ((currentTime - lastDebounceTime) > debounceDelay) {
        if (reading == HIGH) {
          buttonState = BUTTON_PRESSED;
          Serial.println("Button pressed");
        } else {
          buttonState = BUTTON_IDLE;
        }
      }
      break;

按键长按状态下,之后会有两种状态,第一是继续保持长按状态;第二是按键松开状态。当检测到按键松开后,按键进入到按键松开状态。

    case BUTTON_PRESSED:
      if (reading == LOW) {
        buttonState = BUTTON_RELEASED;
        lastDebounceTime = currentTime;
      }
      break;

按键松开状态时,会有两种状态,一是抖动状态;二是按键完全释放的稳定状态。抖动状态的处理方法与上方基本类似。注意此处当按键状态抖动突变时,按键状态会回到BUTTON_IDLE静默状态一,之后松开完全后,程序流仍会一路重新执行至此,重新判断是否满足消抖时间间隔。

    case BUTTON_RELEASED:
      if ((currentTime - lastDebounceTime) > debounceDelay) {
        if (reading == LOW) {
          buttonState = BUTTON_IDLE;
          Serial.println("Button released");
        } else {
          buttonState = BUTTON_PRESSED;
        }
      }
      break;

若到此仍旧有些困惑,金阿姨结合代码并想着按键由按下到松开过程中每个状态下所面临的接下来的所处状态情况进行切入并反复理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值