C语言中的开关去抖动逻辑

Switch Debouncing Logic in C

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

#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;
        }
    }
}

Why does he check 10 msec for button press and 100 msec for button release.

正如博客post所说,"Respond instantly to user input.""A 100ms delay is quite noticeable".

所以,主要的原因似乎是要强调 make-debounce 应该保持短,以便 make 被人类感知 "immediately",并且 break debounce 对时间不那么敏感。

这也得到了 post 末尾的段落的支持:"As I described in the April issue, most switches seem to exhibit bounce rates under 10ms. Coupled with my observation that a 50ms response seems instantaneous, it's reasonable to pick a debounce period in the 20 to 50ms range."

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

Can't he just check 10 msec for press and release?

当然可以,为什么不呢?正如他所写的那样,它应该可以工作,即使他写道(如上所述)他更喜欢更长的去抖周期(20 到 50 毫秒)。

Is polling this function every 5 msec from main the most effecient way to execute it

没有。正如作者所写,"All of these algorithms assume a timer or other periodic call that invokes the debouncer." 换句话说,这只是 一种 实现软件去抖的方法,所示示例基于在常规定时器中断上,仅此而已。

此外,5 毫秒并没有什么神奇之处;正如作者所说,"For quick response and relatively low computational overhead I prefer a tick rate of a handful of milliseconds. One to five milliseconds is ideal."

or should I check for an interrupt in the pin and when there is a interrupt change the pin to GPI and go into the polling routine and after we deduce the value switch the pin back to interrupt mode?

如果您在代码中实现它,您会发现让一个中断一次阻塞代码的正常 运行 10 - 50 毫秒是相当令人讨厌的。如果检查输入引脚状态是唯一要做的事情,那没关系,但如果硬件做任何其他事情,比如更新显示器,或者闪烁一些闪烁的灯,你在中断处理程序中的去抖动例程将导致明显的 jitter/stutter。换句话说,你的建议并不是一个实际的实现。

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

您可以将周期性定时器中断和输入引脚(状态改变)中断结合起来,但是由于许多基于定时器中断的软件去抖的开销很小,因此通常不值得尝试将两者结合起来——代码变得非常非常复杂,复杂的代码(尤其是在嵌入式设备上)往往需要 hard/expensive 维护。

我能想到的唯一情况(但我只是一个爱好者,无论如何都不是 EE!)电池供电操作,并使用输入引脚中断使设备从睡眠或类似状态进入部分或全功率模式。

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


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

请注意,我不想暗示这是世界上任何意义上的 "best";我只希望您展示一种我不常使用但在某些用例中可能具有一些有用属性的方法。在这里,忽略输入更改的扫描周期数(毫秒)([=92= 为 10],break/on-to-OFF 为 10)只是示例值;使用示波器或反复试验来找到您用例中的最佳值。如果这是您发现比其他无数替代方案更适合您的用例的方法,那就是。

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

这样做的好处是可以立即响应更改。它还允许不同的 make-debounce 和 break-debounce 持续时间(在此期间不检查引脚状态)。

缺点是,如果您的 switches/inputs 有任何故障(去抖持续时间之外的误读),它们会显示为清晰的 make/break 事件。

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

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

对于每个按钮,您有一个字节的状态,下面是变量 state;初始化为 0。假设 (PORT & BIT) 是您用来测试特定输入引脚的表达式,对 true(非零)表示 ON,对 false(零)表示关闭。在每次扫描期间(在你的定时器中断中),你做

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),因此对输入引脚状态的变化不敏感。

除了已接受的答案之外,如果您只想每隔 n 毫秒从某个地方轮询一个交换机,则不需要那篇文章中的所有混淆和复杂性.只需这样做:

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 个样本添加一个简单的中值滤波器。

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