【问题标题】:Switch Debouncing Logic in CC中的开关去抖动逻辑
【发布时间】:2018-07-04 05:05:40
【问题描述】:

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

  • 为什么他检查 10 毫秒按钮按下和 100 毫秒按钮释放。他不能只检查 10 毫秒的新闻和发布吗?
  • 从 main 中每 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;
        }
    }
}

【问题讨论】:

    标签: c embedded debouncing


    【解决方案1】:

    为什么他检查 10 毫秒按钮按下和 100 毫秒释放按钮。

    正如博文所说,“立即响应用户输入。”“100 毫秒的延迟非常明显”

    因此,主要原因似乎是强调 make-debounce 应该保持简短,以便人类感知“立即”记录 make,并且 break debounce 对时间不敏感。

    帖子末尾附近的一段也支持这一点:“正如我在 4 月号中所描述的,大多数交换机的跳出率似乎低于 10 毫秒。再加上我观察到 50 毫秒的响应似乎是瞬时的,选择 20 到 50 毫秒范围内的去抖动周期是合理的。”

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

    他不能只检查 10 毫秒的新闻和发布吗?

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

    每 5 毫秒从 main 轮询这个函数是最有效的执行方式

    没有。正如作者所写,“所有这些算法都假定有一个定时器或其他周期性调用来调用去抖动器。”换句话说,这只是实现软件去抖动的一种方法,并且所示示例基于常规定时器中断,仅此而已。

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

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

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

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

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

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

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


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

    请注意,我不想暗示这是世界上任何意义上的“最好”;我只希望您展示一种我没有看到经常使用的方法,但在某些用例中它可能具有一些有用的属性。在这里,也忽略了输入变化的扫描周期数(毫秒)(10 个用于接通/断开到接通,10 个用于断开/接通到断开)只是示例值;使用示波器或试错法在您的用例中找到最佳值。如果这是一种您发现比其他无数替代方案更适合您的用例的方法,那就是。

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

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

    不利的一面是,如果您的开关/输入有任何故障(在去抖动持续时间之外的误读),它们会显示为明确的通断事件。

    首先,您定义输入在中断后和合闸后脱敏的扫描次数。这些范围从 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(零)离开。在每次扫描期间(在您的计时器中断中),您会这样做

    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) 测试按钮状态。 0 表示关闭,1 表示开启。此外,如果(state > 1),则此按钮最近打开(如果state & 1)或关闭(如果state & 0),因此对输入引脚状态的变化不敏感。

    【讨论】:

    • 感谢您的解释,帮助很大。我阅读了博客文章,并注意到您重新引用了 20 到 50 毫秒的时间段。我应该更好地解释我的困惑。似乎他在按钮开关的情况下设置了 100 毫秒的释放周期(或任何更长的周期值)?在摇杆开关的情况下,这并不重要,我可以将释放和按下周期保持为短周期值(10ms或什么)?尽管他发布了适用于所有应用程序的良好通用代码。或者可能让我知道我是否想太多:-P
    • @CodeModeOn:我认为您在示例中过于重视实际值。我认为它们只是明确设置的占位符,没有特别的含义...不是出于实际原因选择的(例如“这些是使用的好值”),而是为了使代码更易于理解和修改为自己使用。我自己也经常这样做,在我自己的示例代码 sn-ps 中。
    【解决方案2】:

    除了接受的答案之外,如果您只想每 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 毫秒。通过在任何直流电源和地之间连接开关(最好与限流电阻串联),您可以使用示波器轻松测量弹跳。

    【讨论】:

      猜你喜欢
      • 2016-03-26
      • 2015-04-12
      • 2017-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多