我有一个连接到中断的硬件按钮,但是我的问题是它会弹起一点,使按钮按下不可靠。我认为可以通过在主循环中进行采样来解决很多这些问题,但这在技术上感觉是错误的。
中断是否更适合于电路内通信,或者中断也适合于硬件开关?如果可以,我可以使用哪些去抖动技术?
我尝试过保留计时器变量,并根据当前时间,延迟和其他技术对其进行检查。看来跳动是如此之快,没关系。
我有一个连接到中断的硬件按钮,但是我的问题是它会弹起一点,使按钮按下不可靠。我认为可以通过在主循环中进行采样来解决很多这些问题,但这在技术上感觉是错误的。
中断是否更适合于电路内通信,或者中断也适合于硬件开关?如果可以,我可以使用哪些去抖动技术?
我尝试过保留计时器变量,并根据当前时间,延迟和其他技术对其进行检查。看来跳动是如此之快,没关系。
Answers:
防弹跳是一个常见问题。您应该能够找到...关于该主题的网页数量几乎不受限制。Smith也评论了Jack Ganssle在该主题上广泛阅读的PDF。通过所有这些答案,您可以获得硬件和软件方法。
我将主要通过谈论尚未很好涵盖的想法来补充这一“文学”。但在我这样做之前,有两点:
由于我已经提到过使用74121进行脉冲拉伸并且杰克·甘斯尔(Jack Ganssle)尚未提及,这里也没有人提及,因此,我也可以提供此附加链接,作为使用74121或555作为单次拍摄的其他建议读物。跳动开关的计时器。
现在,通过使用微控制器观察来完成此操作。
我通常使用状态机来处理反跳。这几乎总是由我设置为大约的常规“心跳”计时器驱动的, 在可能的情况。(出于多种原因,我通常不使用边沿触发的中断事件。)
状态机如下所示:
模拟此电路 –使用CircuitLab创建的原理图
开关的DEBOUNCED值可以采用“无效”,“有效”和“未知”值。这样,您可以确保软件在初始化后一直等到开关值稳定下来。但是通常,我不会为此而烦恼。我将“未知”值替换为一些默认值,而只是使用二进制值系统。
通过首先将去抖动的值设置为其默认值,然后进入状态机的“ CHANGING”状态来进入状态机。在每个时间间隔(通常(如果可以解决),我将读取当前的开关值并执行当前状态的更新,并可能执行去抖动的值。然后我退出。然后,高级代码仅访问去抖动状态。
如果对我来说很重要,我可能还会保留先前的反跳状态。在这些情况下,当更新去抖动状态本身时,我将首先将该状态复制到“先前的去抖动状态”。然后,我可以使用一对值来确定是否存在去抖动的过渡。有时候,我不在乎过渡。有时候,我愿意。因此,这取决于。但是在所有情况下,我只想知道已消除抖动的过渡。我从不关心欠缺过渡。因此,高级代码绝不会使用状态机用于其自身工作的任何内部状态。
这种方法的优点之一是,我可以立即对整个交换机端口进行防抖动处理。而且我也可以在中断代码中没有任何分支的情况下进行操作。这意味着可以在微控制器的端口宽度(通常为8位宽)内进行非常短的快速反跳代码。AtmelAT90的示例显示了如何使用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;
}
中断对于硬件开关也同样非常有用。通过使用中断,可以避免资源和能源的大量浪费,尤其是在处理电池供电的设备时。
另外,随着代码的变得越来越大,您会发现实现按钮的中断比在主循环中轮询按钮更容易。
至于防抖,可能是编码问题。我通常使用〜10ms计时器进行反跳,同时检查按钮的释放情况。确保在消除抖动时也暂时禁用按钮中断,以使中断例程不会执行多次。
如果仍有问题,请在此处发布代码,以便我们提供帮助。
这与Tony Stewart的《答案》非常相似,但我认为可以对其进行扩展。
最上面的原理图是针对低沿或下降沿的中断。底部的原理图用于高沿或上升沿的中断。
模拟此电路 –使用CircuitLab创建的原理图
就个人而言,考虑到电容器的成本,对我来说,简单地使用它是值得的,而不用担心我的软件防抖是否有问题。
请注意,正如托尼·斯图尔特(Tony Stewart)所说,此电路中的时间常数为10ms 要么 。这将需要三到五个时间常数(具体取决于微控制器对按钮进行自我复位的敏感度,因此,如果微控制器在重复中断功能方面遇到问题,则可能是原因,因此您可能需要调整电容/电阻以使中断不会多次发生(也就是说,仅当您的中断被设置为在高或低信号上起作用,而不是在上升沿或下降沿起作用时)。
人类很慢,我们不需要立即注意微秒级范围内的微秒。
当然,这不是始终执行此操作的唯一方法,也不是正确的方法,但是我发现通常更明智的方法是设置一个计时器(许多微控制器具有sys ticks)以固定的间隔触发中断并将引脚的状态转换为a变量供以后检查代码。您最终会在弹跳时得到一个充满灰分的var
10010110灰
但在某些时间点上,您将获得以下4个值:
01111111上升沿仅
在稳定状态下反跳11111111按钮处于上升状态
10000000下降沿仅
在稳定状态下反弹了000000000按钮下降
不过,在大多数情况下,我只是使用一个在弹跳过程中重置的计数器。它快速,经过测试且易于执行。
如果失败,那么我尝试其他人建议的Ganssle文档中更明智的方法!