用C语言切换Debouncing Logic

时间:2018-01-25 02:10:51

标签: c embedded debouncing

关于开关去抖,我遇到了this code by Ganssle。代码看起来非常有效,而我提出的几个问题可能非常明显,但我希望澄清一下。

  • 为什么他会按下按钮10毫秒,按钮释放100毫秒。难道他不能只检查10毫秒进行按压和释放吗?
  • 每5毫秒从主要轮询此函数执行它的最有效方法,或者我应该检查引脚中的中断,当有中断时将引脚更改为GPI并进入轮询例程并在我们推断之后该值将引脚切换回中断模式?

<子>

#define CHECK_MSEC  5   // Read hardware every 5 msec
#define PRESS_MSEC  10  // Stable time before registering pressed
#define RELEASE_MSEC    100 // Stable time before registering released
// This function reads the key state from the hardware.
extern bool_t RawKeyPressed();

// This holds the debounced state of the key.
bool_t DebouncedKeyPress = false;

// Service routine called every CHECK_MSEC to
// debounce both edges
void DebounceSwitch1(bool_t *Key_changed, bool_t *Key_pressed)
{
    static uint8_t Count = RELEASE_MSEC / CHECK_MSEC;
    bool_t RawState;
    *Key_changed = false;
    *Key_pressed = DebouncedKeyPress;
    RawState = RawKeyPressed();
    if (RawState == DebouncedKeyPress) {
        // Set the timer which will allow a change from the current state.
        if (DebouncedKeyPress) Count = RELEASE_MSEC / CHECK_MSEC;
        else                 Count = PRESS_MSEC / CHECK_MSEC;
    } else {
        // Key has changed - wait for new state to become stable.
        if (--Count == 0) {
            // Timer expired - accept the change.
            DebouncedKeyPress = RawState;
            *Key_changed=true;
            *Key_pressed=DebouncedKeyPress;
            // And reset the timer.
            if (DebouncedKeyPress) Count = RELEASE_MSEC / CHECK_MSEC;
            else                 Count = PRESS_MSEC / CHECK_MSEC;
        }
    }
}

2 个答案:

答案 0 :(得分:2)

  

为什么他按下按钮10毫秒,按钮按下100毫秒。

正如博客文章所说,&#34;立即响应用户输入。&#34; &#34; 100毫秒延迟非常明显&#34;

所以,主要原因似乎是要强调制造 - 去抖应该保持简短,以便制造注册&#34;立即&#34;通过人的感觉,破坏性去抖对时间敏感度较低。

这也得到了帖子末尾附近段落的支持:&#34;正如我在4月刊中所描述的那样,大多数交换机似乎都表现出低于10毫秒的跳出率。再加上我观察到50ms响应似乎是瞬间的,在20到50ms范围内选择去抖时间是合理的。&#34;

换句话说,示例中的代码示例值重要得多,并且要使用的正确值取决于所使用的开关;您应该根据具体用例的具体情况自行决定。

  

他只能检查10毫秒进行按压和释放吗?

当然,为什么不呢?正如他所写,它应该有效,即使他写道(如上所述)他更喜欢更长的去抖时间(20到50毫秒)。

  

从主要的每5毫秒轮询此功能是最有效的执行方式

没有。正如作者所写,&#34;所有这些算法都假设一个定时器或其他定期调用来调用去抖动。&#34; 换句话说,这只是一个实现软件去抖动的方法,所示示例基于常规定时器中断,这都是。

此外,5毫秒没有什么神奇之处;正如作者所说,&#34;对于快速响应和相对较低的计算开销,我更喜欢几毫秒的滴答速率。一到五毫秒是理想的。&#34;

  

或者我应该检查引脚中的中断,当有中断时,将引脚更改为GPI并进入轮询程序,在我们推断出值后,将引脚切换回中断模式?

如果你在代码中实现它,你会发现有一个中断阻止代码正常运行10-50ms一次是相当讨厌的。如果检查输入引脚状态是唯一的事情是可以的,但如果硬件做了其他事情,比如更新显示器,或闪烁一些blinkenlights,你在中断处理程序中的debouncing例程将导致明显的抖动/断续。换句话说,你的建议,不是一个实际的实现。

基于周期性定时器中断的软件去抖动例程(在原始博客文章和其他地方显示)的工作方式,它们只需要很短的时间,只需几十个周期左右,并且不会中断其他代码任何大量的时间。这很简单,也很实用。

您可以将周期性定时器中断和输入引脚(状态变化)中断结合起来,但由于许多基于定时器中断的软件去抖的开销很小,因此通常不值得尝试组合这两者 - 代码变得非常非常复杂,并且复杂的代码(特别是在嵌入式设备上)往往难以维护。

我能想到的唯一一个案例(但我只是一个业余爱好者,而不是任何一个EE!),如果你想最大限度地减少电力使用,例如电池供电操作,并使用输入引脚中断使器件从睡眠或类似状态进入部分或全功率模式。

(实际上,如果你还有毫秒或亚毫秒计数器(不一定基于中断,但可能是循环计数器或类似),你可以使用输入引脚中断和循环计数器来更新输入状态在第一次更改时,然后通过将循环计数器值存储在状态更改中来对其进行特定持续时间的脱敏。但是,您确实需要处理计数器溢出,以避免很久以前事件似乎发生的情况很久以前,由于反击溢出。)

我发现Lundin的答案非常有用,并决定编辑我的答案以显示我自己对软件去抖的建议。如果您的RAM非常有限,但是多个按钮被多路复用,并且您希望能够以最小的延迟响应按键和释放,这可能会特别有趣。

请注意,我不希望暗示这是最好的&#34;在任何世界意义上;我只希望你展示我经常使用的一种方法,但在某些用例中可能会有一些有用的属性。在这里,忽略输入变化的扫描周期数(毫秒)(10为make / off-to-ON,10为break / on-to-OFF)只是示例值;使用示波器或反复试验来查找用例中的最佳值。如果这是一种方法,你发现它比其他无数的替代品更适合你的用例,那就是。

这个想法很简单:每个按钮使用一个字节来记录状态,最低有效位描述状态,其他七个位是去敏感(去抖持续时间)计数器。每当发生状态变化时,下一次更改仅被视为稍后的扫描周期。

这有利于立即响应变化。它还允许不同的make-debounce和break-debounce持续时间(在此期间不检查引脚状态)。

缺点是,如果您的开关/输入有任何故障(在去抖持续时间之外的误读),它们会显示为明确的制造/中断事件。

首先,您可以定义中断后和make之后输入不敏感的扫描次数。这些范围从0到127,包括0和127。您使用的确切值完全取决于您的用例;这些只是占位符。

#define  ON_ATLEAST   10  /* 0 to 127, inclusive */
#define  OFF_ATLEAST  10  /* 0 to 127, inclusive */

对于每个按钮,您有一个字节的状态,下面是变量state;初始化为0.让我们说(PORT & BIT)是用于测试特定输入引脚的表达式,为ON评估 true (非零), false (零)表示OFF。在每次扫描期间(在您的定时器中断中),您执行

if (state > 1)
    state -= 2;
else
if ( (!(PORT & BIT)) != (!state) ) {
    if (state)
        state = OFF_ATLEAST*2 + 0;
    else
        state = ON_ATLEAST*2 + 1;
}

在任何时候,您都可以使用(state & 1)测试按钮状态。 OFF为0,ON为1。此外,如果(state > 1),则此按钮最近打开(如果state & 1)或关闭(如果state & 0),因此对输入引脚状态的更改不敏感。

答案 1 :(得分:0)

除了接受的答案之外,如果您只想从每个 n ms的某个位置轮询交换机,则不需要该文章的所有混淆和复杂性。只需这样做:

static bool prev=false;
...

/*** execute every n ms ***/
bool btn_pressed = (PORT & button_mask) != 0;
bool reliable = btn_pressed==prev;
prev = btn_pressed;

if(!reliable)
{
  btn_pressed = false; // btn_pressed is not yet reliable, treat as not pressed
}

// <-- here btn_pressed contains the state of the switch, do something with it

这是消除开关的最简单方法。对于任务关键型应用程序,您可以使用相同的代码,但为最后3个或5个样本添加一个简单的中值过滤器。

如文章所述,开关的机电反弹通常小于10ms。您可以通过连接任何直流电源和地之间的开关(最好与限流电阻串联),使用示波器轻松测量弹跳。