EEJournal

专题文章
现在就订阅

开关反弹终极指南(第九部分)

是的!就是它了!最后,我们终于看到了这个史诗般的传奇故事的最后一部分,涉及到所有与开关反弹有关的事情,以及如何缓解这种现象;那就是,如何去揭发那些小流氓。

你可能只是偶然看到了这本不起眼的巨著,我们也许应该从注意到第1部分我们介绍了开关弹跳和入的基本概念第2部分我们深入研究了与单杆单掷(SPST)和单杆双掷(SPDT)开关相关的弹跳。

之后,在第3部分第4部分,第5部分我们考虑了如何分别使用RC网络、单稳态多谐振荡器和锁存器来弹跳我们的开关。在第6部分我们介绍了一些特殊的开关弹跳集成电路(ic)第7部分我们仔细思考了使用带重置的交换机、带中断的交换机以及轮询与中断驱动的技术。

最近,在第8部分,我们考虑FPGA(硬件)解决开关弹跳问题,我们也开始考虑MCU(软件)解决方案。在本专栏中,我们将更深入地探讨软件解决方案领域(没有人能听到您的尖叫),但首先请注意,如果您发现这里使用的任何开关术语不熟悉,您可能需要仔细阅读和思考相关的术语开关类型而且开关的术语列。

软件解决方案(概述)

在我们开始之前,让我们简单地提醒自己几点。首先,我是一名硬件设计工程师,所以你不允许嘲笑或批评我的代码。其次,出于讨论的目的,我们假设我们正在使用单个交换机—我们可以扩展我们的解决方案,以便稍后处理多个交换机。

在上一篇专栏文章中,我们讨论了SPST和SPDT开关环境中的快速和肮脏的软件解决方案。以SPST开关为例,我们首先考虑如下所示的波形:

SPST-NO拨动开关无噪声的开关反弹(图片来源:Max Maxfield)

我们简单得可笑的第一次通过解决方案是检查开关是否刚刚改变了状态,这将表明(潜在的反弹)过渡的前沿。作为其中的一部分,我们确定开关信号以何种方式转换(0- 1或1- 0),从而允许我们在开关关闭和打开时执行不同的操作。

我们的第一次通过解决方案的一个巨大问题是,在看到第一次转换时执行任何我们希望的操作之后,我们调用延迟()函数等待开关停止弹跳。在代码中,我们定义了一个名为MAX_BOUNCE并将其赋值为20,我们将其表示为20毫秒(ms)。因此,我们使用的延迟值为(max_bounce * 1000),相当于20毫秒(使用这个值的原因在前面的专栏中详细讨论过)。问题是延迟()函数是一个阻塞函数,它让微控制器在延迟期间玩弄它的比喻拇指,从而阻止它做任何更有用的事情。

太吵了!

关于我们上面描述的快速和肮脏的解决方案,你是否发现了汤里的苍蝇或房间里的大象(我感觉很慷慨,所以可以随意选择任何你喜欢的比喻)?问题是,正如在前面的专栏中讨论的那样,我们使用的任何开关反弹解决方案都应该能够处理有噪声的信号。考虑如下所示的波形,例如:

有噪声的SPST-NO拨动开关上的开关反弹(图片来源:Max Maxfield)

如果我们便宜的程序不幸在噪声峰值期间读取输入,它会错误地认为这是(潜在的)多反弹过渡的前沿,并相应地做出反应。

无论你怎么看,这几乎肯定会引起问题。作为一个简单的例子,假设噪声峰值和开关转换开始之间的时间大于我们的20毫秒值。在这种情况下,除了我们的程序在这种行为被证明是正确的之前就开始行动之外,在开关真正开始转换的时候,程序会错误地认为开关处于与现实世界状态相反的状态,这时混乱就会随之而来。

转储延迟()

在开始考虑更复杂的软件解决方案之前,我们需要做的第一件事是转储延迟()函数。在我的兴趣项目中,我使用的方法是定义一些主时钟“滴答”——比如每毫秒一次——并使用这个滴答作为执行任何操作的动力,包括检查任何开关的状态。记住,你不能笑,arduino启发的伪代码可能看起来像下面这样:

#定义TICK 1 // TICK时间= 1毫秒
#定义MAX_BOUNCE 20 // 20毫秒

uint32_t PreviousTime;

Void设置()

PreviousTime = millis();

Void循环()

uint32_t currentTime;

currentTime = millis();
if ((currentTime - PreviousTime) >= TICK)

//使用开关
//做其他事情

PreviousTime = currentTime;

Arduino的米尔斯()函数返回自程序开始运行以来的毫秒数。关于MAX_BOUNCE,这取决于我们如何决定玩东西,这可以用来说我们确定开关将在第一次开关转换后20毫秒停止反弹,或者它可以用来等待到最后一次开关反弹后20毫秒。

相信我!

一种非常有趣的反弹方法是使用计数器。为了使事情简单,让我们假设我们从寻找一个0到1的转换开始,并且我们确信在最终转换后,开关将在20毫秒内停止反弹。

这里的思想是,当我们看到开关信号上的第一个从0到1的转换时,我们将计数器清除为0。每次执行1ms循环时,我们都会重新读取开关上的值。如果该值为1,则增加计数器的值;如果值为0,则将计数器清除为0。

当计数器最终包含的值为20时,我们知道来自开关的信号在20毫秒内一直保持在1状态,此时我们可以执行任何我们认为合适的操作。

现在我们将开始寻找以1到0转换的形式以另一种方式切换的开关。当这种情况发生时,我们可以采取几种明显的方法。一种选择是像以前一样将计数器清除为0。但是这一次,每次我们循环一次,如果开关为0增量计数器,如果开关为1,我们清除计数器;我们等柜台数到20。另一种选择是在计数器上装载20个;在这种情况下,每次循环时,如果开关为0减量计数器,如果开关为1,我们重新加载计数器为20;我们等待计数器达到0。

基于计数器的解决方案也可以容忍噪声,这取决于我们编写代码的方式。例如,假设我们的代码像上面描述的那样工作。在这种情况下,如果我们正在等待0到1的转换,并且我们看到了1的噪声峰值,尽管这将启动计数,但在每次后续通过循环时,计数器将重复重置为0,直到交换机开始真正的0到1的跳跃转换。

垂直计数器

总有新东西要学。例如,读者本·库克空速电子好心地花时间告诉我,他个人最喜欢的开关反弹解决方案是“垂直计数器”。

好吧,这听起来确实很有趣,但是——在本的交流之前——如果一个垂直计数器爬上我的腿,把牙齿咬进我身体的一个不幸的部分,我是不会认出来的,所以我让本解释一下。

本回答道:“这个主意不是我的功劳。我不久前刚刚读到它,并在最近的项目中使用了它。这是一个巧妙的技巧。你可以点击这里了解更多关于垂直计数器的信息”。

不要太狡猾!

另一种有趣的方法是使用移位寄存器。假设我们有一个32位无符号整数变量,我们把它当作一个移位寄存器。让我们进一步假设我们正在等待开关上的0到1转换,因此我们开始将移位寄存器清除到0。

每次我们循环1毫秒的循环时,我们将当前的0或1值从开关移到移位寄存器的最低有效位(LSB)。接下来,我们使用&运算符在逻辑上将移位寄存器与0x000FFFFF(从而去除寄存器最高有效位中的任何“噪声”),并将结果与0x000FFFFF进行比较。

如果在开关开始0到1转换之前出现任何1噪声峰值,或者如果开关开始弹跳,任何随机的1和0将通过移位寄存器工作,这实际上是由我们的循环每毫秒记录一次。只有当移位寄存器和0x000FFFFF包含0x000FFFFF时,我们才知道我们已经有了21个1序列,因此我们的开关在20毫秒内一直处于稳定的1状态。

此时,我们开始在交换机上寻找1到0的转换。一种方法是用0x000FFFFF预加载移位寄存器。每次我们绕1毫秒的循环,我们将当前的1或0值从开关移到移位寄存器的LSB中。同样,我们使用&运算符在逻辑上将移位寄存器与0x000FFFFF(从而消除寄存器最高有效位中的任何“噪声”),并将结果与0x00000000进行比较。

在这种情况下,如果在开关开始1到0转换之前出现任何0噪声峰值,或者如果开关开始弹跳,任何随机的0和1将通过移位寄存器,这实际上是由我们的循环每毫秒记录一次。只有当移位寄存器和0x000FFFFF包含0x00000000时,我们才知道我们已经有了一系列的20个0,因此我们的开关在20毫秒内一直处于稳定的0状态。

但是等等,还有更多!

正如我前面提到的,总有新东西要学。举个例子,当我浏览好友Jacob Beningo的网站时,我看到了两篇与开关反弹相关的文章:一个可重复使用的按钮反弹模块而且7步创建一个可重用的Debounce算法

我不确定Jacob是否明确地说过这一点,但我从这些文章中得到的一点是,将跟踪按钮状态的任务与实际使用这些状态的任务分离开来。

我仍然试图把我的大脑围绕在这个问题上,但是-仍然从Arduino的角度考虑-我将继续让我的1毫秒循环循环读取任何开关的状态,并使用上面讨论的移位寄存器或基于计数器的技术执行开关反弹。但是,我要用一些GetSwitchState ()函数,我可以用来访问一个命名交换机的状态,如GetSwitchState (BigRedSwitch),它将返回SWITCH_IS_CLOSEDSWITCH_IS_OPENSWITCH_JUST_CLOSED,SWITCH_JUST_OPENED(或者,在按钮的情况下,BUTTON_IS_PRESSEDBUTTON_IS_RELEASEDBUTTON_JUST_PRESSED,BUTTON_JUST_RELEASED).

这样可以增加主程序的可读性和可理解性,因为我们可以使用这样的语句if (GetSwitchState(BigRedSwitch) == SWITCH_JUST_OPENED)启动一些动作,其中测试开关状态的行为也会改变它的状态SWITCH_JUST_OPENEDSWITCH_IS_OPEN

再说一次,我还在考虑这个问题的早期阶段,所以如果你有任何想法或建议,请在下面的评论中分享。下次见,祝你玩得愉快!

关于“Switch Debounce终极指南(第9部分)”的2个想法

  1. 关于上面显示的一种方法的代码片段,我知道它只是一个实际可用代码的原型,但有一个微妙的缺陷需要解决。

    我在上一个项目中遇到了这个问题,我在Windows下使用Visual Basic来控制一些测试设备。我需要对各种操作进行计时&在类似的过程中使用Windows计时器。

    如果计时器在操作开始和预期的结束时间之间溢出,就会出现问题。如果按“原样”使用代码,发生溢出,则弹跳时间将长得不可思议,导致开关操作不正确。

    解决方案是添加另一个定时器溢出测试。这方面的代码可以在参考硬件计时器操作的语言参考网站上找到。

  2. 我真的很喜欢这些关于开关跳脱的讨论,但我不确定所讨论的技术是否适用于我的问题。我有一个废弃的光谱仪,里面有一个非常复杂的齿轮驱动装置,可以旋转衍射光栅。每当光栅旋转10埃时,凸轮就会激活微开关。我有两根来自光谱仪的电线,由连接到微开关的继电器激活。我想保持10埃标记,以帮助我在波长校准过程中。当开关被激活时,我确实检测到弹跳,但我也注意到两根电线携带8伏p-p 60cps信号,我认为这是来自继电器线圈或机箱内的变压器。我不希望这个交流电压影响计数的微开关过渡。我在两根引线上连接了一个10 uF电解,工作了一段时间,但很快我就检测不到任何开关过渡。有人有什么建议可以帮助我解决这个问题吗?如果您有任何建议,我将不胜感激。 Thank you!

留下回复

有特色的博客
2023年2月2日
我们分享了对2023年高性能计算(HPC)的预测,包括边缘计算解决方案的增长以及人工智能和机器学习的兴起。文章“2023年五大高性能计算趋势”首先出现在“从硅到软件”....上
2023年2月2日
只需注册一次即可访问所有Cadence按需在线研讨会。叶尖间隙流动是通过叶轮机械旋转部件和静止部件之间的小间隙的流动。这种间隙的大小和形状直接与叶轮钦有关。
2023年1月30日
聪明地工作,而不是更努力地工作。大家不都是这么跟你说的吗?当然,这是很好的建议,... ...
2023年1月19日
你是否在调整表带或更换手表电池时遇到了问题?如果是这样,我是好消息的携带者....

有特色的视频

Synopsys 224G和112G以太网PHY IP OIF在ECOC 2022互操作

Synopsys对此

这段特色视频展示了Synopsys 224G和112G以太网PHY IP中长到达性能的四个演示,与第三方通道和serde互操作。

了解更多

特色粉笔谈话亚博里的电子竞技

自动基准调优

Synopsys对此

基准测试是衡量计算资源性能的好方法,但基准调优可能是一个非常复杂的问题。在这一集粉笔谈话中,来自Synopsy亚博里的电子竞技s的Nozar Nozarian和Amelia Dalton研究了Synopsys的Optimizer Studio,该工作室将进化搜索算法与强大的用户界面结合在一起,可以帮助您快速设置和运行基准测试实验,比以往任何时候都要少得多的努力和时间。

点击这里了解更多关于Synopsys Optimizer Runtime & Optimizer Studio的信息

Baidu