硬件按钮的中断和采样之间的区别?

电器工程 中断 按钮 采样
2022-02-03 01:04:25

我有一个连接到中断的硬件按钮,但我的问题是它弹跳了一下,使按钮按下不可靠。我认为这些问题中的很多都可以通过在主循环中采样来解决,但这在技术上感觉是错误的。

中断更适合电路内通信还是中断也适合硬件开关?如果是这样,我可以使用什么去抖动技术?

我尝试保留一个计时器变量并根据当前时间、延迟和其他技术检查它。似乎反弹是如此之快,这并不重要。

4个回答

去抖动是一个常见问题。您应该能够找到...几乎无限数量的关于该主题的网页。史密斯也评论了 Jack Ganssle 广泛阅读的关于该主题的 PDF。有了所有这些答案,您就拥有了硬件和软件方法。

我将通过主要谈论尚未很好地涵盖的想法来稍微补充一下这个“文献”。但在我这样做之前,有一两点:

  1. 模拟硬件中的去抖动可以实现您无法通过仅通过轮询或硬件引脚更改事件定期以数字方式“观察”的开关来实现的结果。但是你可以在数字上为所有意图和目的做得“足够好”。这些天几乎没有人使用外部模拟去抖动解决方案。但我已经使用了从使用单次 (74121) 的脉冲拉伸到 Jack Ganssle在这里提到的技术的所有方法。
  2. 对于那些只从事嵌入式编程并且对学习电子产品完全不感兴趣的人来说,去抖动开关可能是所需的两个基本技能之一。操作 LED 可能是另一种。而且,我并不是说在这些方面只有一项技能。我的意思是能够以多种方式做到这一点。因此,您确实需要完全理解 Jack Ganssle 所写的内容,以及更多关于开关的内容。

由于我已经提到了使用 74121 进行脉冲展宽,并且 Jack Ganssle没有提到它,而且这里也没有人提到它,所以我不妨提供这个附加链接,作为关于使用 74121 或 555 作为一次性使用的附加建议阅读用于去抖开关的定时器。


现在,通过使用微控制器进行观察来做到这一点。

我通常使用状态机来处理去抖动。这几乎总是由我设置的常规“心跳”计时器驱动8ms, 在可能的情况。(出于几个原因,我通常不使用边沿触发的中断事件。)

状态机如下所示:

示意图

模拟此电路- 使用CircuitLab创建的原理图

开关的 DEBOUNCED 值可以采用“非活动”、“活动”和“未知”值。这样,您可以确保您的软件在初始化后等到开关值稳定下来。但通常情况下,我不会为此烦恼。我用一些默认值替换“未知”值,而只使用二进制值系统。

通过首先将去抖值设置为其默认值,然后进入状态机的“CHANGING”状态,进入状态机。在每个时间间隔(通常8ms如果我能摆脱它),我将读取当前开关值并更新当前状态,可能还有去抖值。然后我就退出了。高级代码然后只访问去抖动状态。

如果这对我很重要,我也可以保持之前的去抖动状态。在这些情况下,当更新去抖动状态本身时,我将首先将该状态复制到“先前去抖动状态”。然后,我可以使用这对值来确定是否存在去抖动转换。有时,我不关心过渡。有时,我会。所以这取决于。但在所有情况下,我只想知道已消除抖动的转换。我从不关心矮小的过渡。因此,高级代码从不使用状态机用于其自身工作的任何内部状态。

这种方法的好处之一是我可以一次消除整个交换机端口的抖动。我也可以在没有中断代码中的一个分支的情况下做到这一点。这意味着在微控制器的端口宽度(通常为 8 位宽)内非常快速和短的去抖动代码。来自 Atmel AT90 的一个示例显示了如何使用 Timer0 中断事件来实现这一点:

.equ    SWPORTPINS  =   PINB
.def    SwRawCurr   =   r4
.def    SwRawPrev   =   r5
.def    SwState     =   r6
.def    SwDebCurr   =   r7
.def    SwDebPrev   =   r8

            ; Debounce the input switches.

                mov     SwRawPrev, SwRawCurr
                in      SwRawCurr, SWPORTPINS
                mov     Timer0Tmp1, SwRawCurr
                eor     Timer0Tmp1, SwRawPrev
                mov     Timer0Tmp0, Timer0Tmp1
                or      Timer0Tmp1, SwState
                mov     SwState, Timer0Tmp0
                mov     Timer0Tmp0, Timer0Tmp1
                com     Timer0Tmp0
                and     Timer0Tmp1, SwDebCurr
                and     Timer0Tmp0, SwRawCurr
                or      Timer0Tmp1, Timer0Tmp0
                mov     SwDebPrev, SwDebCurr
                mov     SwDebCurr, Timer0Tmp1

现在,此示例显示了完整的交易,包括先前和当前的去抖开关值。它还执行所有必要的状态转换。我没有展示这段代码的初始化。但是上面的内容说明了状态机的操作是多么容易,并且需要多少代码来操作。它非常快速和简单,并且不需要分支(有时涉及额外的周期以及额外的代码空间。)


我更喜欢使用8ms时间安排是因为对使用我过去工作过的设备的各种不同的人进行的长期测试将我带到了那里。我尝试过更长的时间,当我这样做时,我开始让人们告诉我“响应能力”不够“敏捷”。(如今,随着孩子们在实时“射击”游戏中长大,我什至可能会进一步缩短它。他们会抱怨现代数字电视在设置和显示框架时甚至会造成轻微的延迟。)

有些人会对系统应该有多清晰和响应速度有非常清楚的感觉。清脆和响应意味着更频繁地采样,而不是更少。但就个人而言,我发现20ms观察期可接受。不过,即使对我来说,我也觉得更长的时间也不够好。

请注意,我提到的状态机必须首先进入 SETTLED 状态,然后再在该状态停留一个采样时间,然后更新 DEBOUNCED 的值。因此,即使在最好的情况下按下并按住按钮,也需要进行以下转换:

  1. 从 SETTLED 变为 CHANGING
  2. 从 CHANGING 变为 SETTLED
  3. 留在SETTLED,更新DEBOUNCED

因此,一个新的去抖动状态需要至少 3 个采样时间段才能实现。

一个按钮将需要至少 6 个采样时间才能从非活动状态变为活动状态,然后再返回非活动状态。


我提到了上述细节,以便绝对清楚8ms意味着它介于两者之间16ms<t24ms从非活动到公认的活动去抖结果。这将需要另一个24ms在状态可以返回到非活动状态之前。这是至少40ms<t48ms经历一个完整的按钮循环。

使用较长的采样时间将相应地具有较长的周期。使用20ms我已经提到对我来说是“可以接受的”,然后意味着在某个地方100ms<t120ms整个按钮循环。这正逐渐进入人们确实倾向于注意到的领域。如果它比这更长,我当然不喜欢这种“感觉”。

如果你走这条路,不要随意使用更长的采样时间。如果必须,那么我认为您还必须对用户/消费者进行大量测试。

如果你正在为打字键盘开发代码,那么使用更短的时间。几十年前,打字员的记录是 217 wpm。这会导致大约每一个键45ms. 像这样的打字员以受控的顺序敲击多个键。为了让使用水银湿簧继电器开关系统的打字速度非常快的打字员获得良好的性能,我发现2ms运作良好。

去抖可以在软件中通过屏蔽 IRQ 的反弹时间或在硬件中通过添加一个保持电容来完成,您的 RC =T > 反弹时间范围为 1 到 15ms,具体取决于开关的大小。

  • 例如 100k 上拉和 0.1μF 跨开关 = 10ms @63% 或 ~8ms 在 50%Vdd 或如果使用施密特触发器门 @1.33V=Vil 从 5V 或 ~73% V+ ~12ms

要进行 SW 去抖动,请记录当前事件的时间戳并检查上一个有效事件的延迟:

#define DELAY_DEBOUNCE       150

uint32_t    __ts_lastpress = 0;

ISR(some_vector)
{
    uint32_t    now = millis(); // some timer tick counter

    if ( now - __ts_lastpress < DELAY_DEBOUNCE )
        return; // ignore it

    __ts_lastpress = now;
    // do the job here
}

UPD:只需稍加修改,您就可以注册双击:

#define DELAY_DEBOUNCE       150
#define DELAY_DOUBLE_CLICK   600

uint32_t    __ts_lastpress = 0;

ISR(some_vector)
{
    uint32_t    now = millis(); // some timer tick counter

    if ( now - __ts_lastpress < DELAY_DEBOUNCE )
        return; // ignore it

    // do the job here
    if ( now - __ts_lastpress < DELAY_DOUBLE_CLICK )
    {
        // it is double click
    }
    else
    {
        // it is single click
    }

    __ts_lastpress = now;
}

中断对于硬件开关也非常有用。通过使用中断,您可以避免资源和能源的大量浪费,尤其是在处理电池供电设备时。

此外,随着您的代码变得越来越大,您会发现实现按钮的中断比在主循环中轮询它们更容易。

至于你的去抖动,它可能是一个编码问题。我通常使用约 10 毫秒的计时器进行去抖动,同时检查按钮的释放。确保在去抖动时也暂时禁用按钮中断,因此不会多次执行中断例程。

如果您仍然遇到问题,请在此处发布代码,以便我们提供帮助。