我有一个连接到中断的硬件按钮,但我的问题是它弹跳了一下,使按钮按下不可靠。我认为这些问题中的很多都可以通过在主循环中采样来解决,但这在技术上感觉是错误的。
中断更适合电路内通信还是中断也适合硬件开关?如果是这样,我可以使用什么去抖动技术?
我尝试保留一个计时器变量并根据当前时间、延迟和其他技术检查它。似乎反弹是如此之快,这并不重要。
我有一个连接到中断的硬件按钮,但我的问题是它弹跳了一下,使按钮按下不可靠。我认为这些问题中的很多都可以通过在主循环中采样来解决,但这在技术上感觉是错误的。
中断更适合电路内通信还是中断也适合硬件开关?如果是这样,我可以使用什么去抖动技术?
我尝试保留一个计时器变量并根据当前时间、延迟和其他技术检查它。似乎反弹是如此之快,这并不重要。
去抖动是一个常见问题。您应该能够找到...几乎无限数量的关于该主题的网页。史密斯也评论了 Jack Ganssle 广泛阅读的关于该主题的 PDF。有了所有这些答案,您就拥有了硬件和软件方法。
我将通过主要谈论尚未很好地涵盖的想法来稍微补充一下这个“文献”。但在我这样做之前,有一两点:
由于我已经提到了使用 74121 进行脉冲展宽,并且 Jack Ganssle没有提到它,而且这里也没有人提到它,所以我不妨提供这个附加链接,作为关于使用 74121 或 555 作为一次性使用的附加建议阅读用于去抖开关的定时器。
现在,通过使用微控制器进行观察来做到这一点。
我通常使用状态机来处理去抖动。这几乎总是由我设置的常规“心跳”计时器驱动, 在可能的情况。(出于几个原因,我通常不使用边沿触发的中断事件。)
状态机如下所示:
开关的 DEBOUNCED 值可以采用“非活动”、“活动”和“未知”值。这样,您可以确保您的软件在初始化后等到开关值稳定下来。但通常情况下,我不会为此烦恼。我用一些默认值替换“未知”值,而只使用二进制值系统。
通过首先将去抖值设置为其默认值,然后进入状态机的“CHANGING”状态,进入状态机。在每个时间间隔(通常如果我能摆脱它),我将读取当前开关值并更新当前状态,可能还有去抖值。然后我就退出了。高级代码然后只访问去抖动状态。
如果这对我很重要,我也可以保持之前的去抖动状态。在这些情况下,当更新去抖动状态本身时,我将首先将该状态复制到“先前去抖动状态”。然后,我可以使用这对值来确定是否存在去抖动转换。有时,我不关心过渡。有时,我会。所以这取决于。但在所有情况下,我只想知道已消除抖动的转换。我从不关心矮小的过渡。因此,高级代码从不使用状态机用于其自身工作的任何内部状态。
这种方法的好处之一是我可以一次消除整个交换机端口的抖动。我也可以在没有中断代码中的一个分支的情况下做到这一点。这意味着在微控制器的端口宽度(通常为 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
现在,此示例显示了完整的交易,包括先前和当前的去抖开关值。它还执行所有必要的状态转换。我没有展示这段代码的初始化。但是上面的内容说明了状态机的操作是多么容易,并且需要多少代码来操作。它非常快速和简单,并且不需要分支(有时涉及额外的周期以及额外的代码空间。)
我更喜欢使用时间安排是因为对使用我过去工作过的设备的各种不同的人进行的长期测试将我带到了那里。我尝试过更长的时间,当我这样做时,我开始让人们告诉我“响应能力”不够“敏捷”。(如今,随着孩子们在实时“射击”游戏中长大,我什至可能会进一步缩短它。他们会抱怨现代数字电视在设置和显示框架时甚至会造成轻微的延迟。)
有些人会对系统应该有多清晰和响应速度有非常清楚的感觉。清脆和响应意味着更频繁地采样,而不是更少。但就个人而言,我发现观察期可接受。(不过,即使对我来说,我也觉得更长的时间也不够好。)
请注意,我提到的状态机必须首先进入 SETTLED 状态,然后再在该状态停留一个采样时间,然后更新 DEBOUNCED 的值。因此,即使在最好的情况下按下并按住按钮,也需要进行以下转换:
因此,一个新的去抖动状态需要至少 3 个采样时间段才能实现。
一个按钮将需要至少 6 个采样时间才能从非活动状态变为活动状态,然后再返回非活动状态。
我提到了上述细节,以便绝对清楚意味着它介于两者之间从非活动到公认的活动去抖结果。这将需要另一个在状态可以返回到非活动状态之前。这是至少经历一个完整的按钮循环。
使用较长的采样时间将相应地具有较长的周期。使用我已经提到对我来说是“可以接受的”,然后意味着在某个地方整个按钮循环。这正逐渐进入人们确实倾向于注意到的领域。如果它比这更长,我当然不喜欢这种“感觉”。
如果你走这条路,不要随意使用更长的采样时间。如果必须,那么我认为您还必须对用户/消费者进行大量测试。
如果你正在为打字键盘开发代码,那么使用更短的时间。几十年前,打字员的记录是 217 wpm。这会导致大约每一个键. 像这样的打字员以受控的顺序敲击多个键。为了让使用水银湿簧继电器开关系统的打字速度非常快的打字员获得良好的性能,我发现运作良好。
去抖可以在软件中通过屏蔽 IRQ 的反弹时间或在硬件中通过添加一个保持电容来完成,您的 RC =T > 反弹时间范围为 1 到 15ms,具体取决于开关的大小。
要进行 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 毫秒的计时器进行去抖动,同时检查按钮的释放。确保在去抖动时也暂时禁用按钮中断,因此不会多次执行中断例程。
如果您仍然遇到问题,请在此处发布代码,以便我们提供帮助。