深度解析嵌入式开发中的按键去抖技术

 深度解析嵌入式开发中的按键去抖技术

现在的嵌入式设备是非常的高级了,嵌入式系统与外界的人机交互接口,现在流行是用触屏,甚至在AI的浪潮下,小巧的嵌入式设备干脆就直接语音输入然后进行AI识别了,一句“老wu同学,请帮我xxx”就可以搞定 ( ̄▽ ̄)”

但在工业自动化或者医疗设备等需要高可靠交互的场景,以机械开关和继电器为代表的传统机械触点器件,凭借其独特的优势,至今仍在各类设备中占据着不可或缺的地位。

将机械开关的信号作为输入时,其固有的物理特性——“抖动”是必须重视并妥善处理的首要问题。抖动是指机械触点在闭合或断开的瞬间,由于金属弹性碰撞而发生的一系列高速、不稳定的通断现象。对于微控制器的数字输入引脚而言,每一次通断都可能被误判为一次有效的信号边沿。例如,一次按键操作本应只触发一次中断或计数一次,但由于抖动,可能在几毫秒内被错误地识别为数十次,从而导致逻辑混乱或系统失控。

上图展示了机械触点在闭合瞬间产生的典型抖动波形。可以看到,在信号从高电平(OFF)稳定地切换到低电平(ON)之前,经历了一段持续数毫秒的不稳定期,期间信号电平在低电平和高电平之间剧烈振荡。这段振荡时间的长短和幅度因开关的类型、新旧程度、驱动力等因素而异,但对于高速响应的数字电路来说,其存在是致命的。

针对抖动问题,业界已发展出多种成熟的解决方案,主要分为硬件和软件两大类。

软件去抖

这可能是我们学习嵌入式开发首先接触到的方法,因为我们买的学习用的开发板,它不会去添加硬件去抖电路,没有额外的硬件成本所以软件去抖也是成本最低的去抖方法。其核心思想是通过软件实现“二次采样延时滤波”。当检测到输入引脚电平第一次变化后,程序并不立即响应,而是启动一个短暂的延时(通常为10-20ms,需长于最大抖动时间),延时结束后再次读取该引脚的电平。如果此时的电平与第一次变化后的电平一致,则确认为一次有效的状态切换。这种方法简单有效,但会占用CPU资源,并且在延时期间程序无法响应其他任务(除非使用中断或定时器),不适用于某些实时性要求极高的场合。

硬件去抖

使用数字逻辑器件,如由与非门构成的RS触发器(Set-Reset Latch),可以实现纯硬件的去抖。利用触发器的双稳态特性,触点的第一次接触即可锁定输出状态,后续的抖动不会影响输出结果,直到触点接触到另一端才发生状态翻转。这种方法响应速度快,不占用软件资源,但仅对具有常开(NO)和常闭(NC)两个触点的单刀双掷(SPDT)开关有效,且无法抑制来自电路外部的噪声干扰。

在设计去抖电路或编写去抖算法时,我们最常问的问题是:“抖动会持续多久?”网络上流传的许多设计往往基于一些经验法则或“祖传代码”,缺乏坚实的实验数据支撑。然而,抖动的特性远比一个简单的“最大抖动时间”要复杂得多。在抖动期间,输入引脚上出现的波形是怎样的?它的具体特征是什么?不理解输入信号的完整特性,就贸然编写处理函数,这对于构建高可靠性的系统来说是极其危险的。

因此,任何可靠的去抖方案都应建立在对开关抖动行为的深入理解之上,而这种理解必须源于实际的物理测量和数据分析。

为了获得精确的抖动数据,这个网站[1]的大神Jack Ganssle进行了一系列实验。实验对象涵盖了18种不同类型的开关,包括常用的瞬时按钮、拨动开关、滑动开关,甚至是从旧的游戏手柄和鼠标上拆下的微动开关。实验数据非常全面也很有代表性,能让我们充分理解抖动的物理现象,老wu这里引用了Jack Ganssle大神的实验成果。

网站所述的实验装置的核心是一块微控制器开发板和一个混合信号示波器(MSO)。最初的方案是使用微控制器直接采集开关引脚的电平变化,但Jack Ganssle很快发现这种方法存在局限。许多开关的抖动包含持续时间低于100纳秒的超窄脉冲,常规的微控制器采样速率难以精确捕捉这些瞬态细节。

因此,最终采用混合信号示波器(MSO)进行观测。MSO能够同时捕获模拟波形和数字逻辑电平,这使得我们不仅能看到电压的连续变化,还能理解数字电路会如何“解读”这个带有噪声的模拟信号。测试电路非常简单:一个5V电源通过一个1kΩ的上拉电阻连接到开关,示波器探头则直接测量开关引脚的电压。

实验结果揭示了开关抖动的几个关键特性:

1. 抖动时间差异巨大:大多数开关的抖动时间在几毫秒以内。在所有18个样本中,剔除两个极端异常值后,16个开关的平均抖动时间为1.557ms,最大值为6.2ms。然而,存在个例,例如一个看似普通的红色按钮开关(样本E),在触点断开时竟记录到了长达157ms的抖动时间。

2. 闭合与断开行为不一致:7个开关样本表现出闭合时的抖动时间远长于断开时。对于大多数开关而言,断开时的抖动通常非常短暂,甚至低于1微秒,但同一次测试中的下一次操作,其抖动时间又可能跃升至数百微秒,表现出高度的随机性。

3. 同型号开关存在个体差异:实验中测试了两对同型号的开关,结果表明它们的抖动特性差异可达两倍之多。这警示我们,在产品设计中不能完全依赖单个器件的测试数据,必须考虑元器件的个体差异性。

4. 操作方式影响抖动:开关的按压方式(如快慢、轻重)同样会影响抖动行为。例如,快速拨动一个拨动开关(样本G)产生的抖动时间是缓慢拨动时的3倍。这模拟了真实世界中的使用场景,例如用户情急之下用力敲击按钮,或是设备经受振动等情况。


图. 不同开关样本的抖动时间分布图

通过MSO的模拟通道,我们观察到了比纯数字逻辑更多的波形细节。少数开关能产生接近理想的0V和5V电平切换,但大多数开关的电气行为要复杂得多。

一个典型的现象是“触点擦拭”。在触点分离的瞬间,它们并非立刻断开,而是在彼此表面上有一个短暂的滑动或滚动过程。在这个过程中,接触电阻并非从零突变为无穷大,而是平滑地增加,表现为一个可变电阻。例如,开关C在断开时,其等效电阻在150µs的时间内从0Ω线性爬升至6Ω,然后才跃变为开路状态。这导致其输出电压也呈现一个缓慢的斜坡,而非陡峭的阶跃。

图. 开关C断开时,电压缓慢爬升,表现出触点擦拭行为

这种缓变的模拟信号对数字输入构成了严峻挑战。以TTL电平标准为例,0V至0.8V被识别为逻辑“0”,高于2.0V被识别为逻辑“1”,而0.8V至2.0V之间是“不确定区域”。当开关电压在这个不确定区域内徘徊时,数字输入端的逻辑门电路可能会发生振荡,输出一连串不确定的、快速变化的0和1,就如同开关在高速抖动一样。

图. 开关B的电压在TTL不确定区域徘徊,导致数字逻辑输出产生振荡

更有趣的是一些导电橡胶开关(常用于遥控器等消费电子产品)。它们的模拟输出是一条非常平滑的、从0V到5V的电压斜坡,几乎没有任何噪声或弹跳。然而,其对应的数字逻辑通道却显示出长达数毫秒的剧烈振荡。这正是因为平滑的电压斜坡缓慢地穿越了逻辑电平的“不确定区域”,导致了数字端的“虚拟抖动”。

图. 导电橡胶开关(K)的模拟电压平滑上升,但因缓慢穿越阈值区而在数字端引起剧烈振荡

因此,我们必须认识到,我们所处理的“抖动”,并不仅仅是物理触点的机械弹跳,还包含了因模拟信号特性不佳(如缓慢的边沿、在阈值附近徘徊)而导致的数字逻辑误判。这两种现象在系统层面产生的效果是相同的,都需要通过去抖来解决。这也警示我们,简单的去抖算法,如“连续读取两次相同的电平就认为是稳定”,可能并不可靠!!!

在抖动持续的整个时间窗口内,信号究竟是怎样的?每一次抖动都可以说是独一无二的!有些抖动表现为在最终稳定下来之前的一段高频噪声。而另一些则产生一系列会让简单去抖算法失效的逻辑脉冲。

以之前提到的抖动时间长达157ms的开关E为例。在一次测试中,它先是稳定输出逻辑“1”长达81ms,然后突然跳变为一个完美的逻辑“0”并保持了42ms,最后才稳定在正确的逻辑“1”状态。几乎任何基于简单延时或计数的去抖算法都会被这种“先稳定后反跳”的行为所欺骗!!!

图. 开关E在一次操作中表现出先长时间稳定在高电平,然后又跳变到低电平,最后才稳定的复杂行为

开关G的行为则揭示了另一种风险。在几次测试中,它会在最终稳定为逻辑“1”之前,先产生一个持续仅几微秒的窄脉冲“1”,随后是超过2ms的稳定“0”。如果这个开关输入连接到一个中断引脚,那个瞬时的窄脉冲足以触发中断服务程序(ISR)。但当ISR开始执行时,它去读取引脚状态,却会发现输入是稳定的低电平,从而造成误判的。

图. 开关G产生一个足以触发中断的窄脉冲后,进入了长达2ms的稳定低电平,之后才最终变为高电平

一个高质量的微动开关(样本O),虽然其总抖动时间从未超过1.18ms,但其抖动过程却是由一连串清晰的、宽度在几十微秒量级的0和1脉冲组成。这种信号模式对于那些仅仅寻找“N次连续稳定读数”的算法来说,是极大的挑战,因为在抖动结束前,信号可能在两种逻辑状态下都出现短暂的“稳定”。

图. 开关O产生的抖动由一连串清晰的、难以用简单计数法滤除的0和1脉冲构成

这些案例充分说明,设计一个真正可靠的去抖系统,必须考虑到抖动现象的多样性和复杂性,不能基于过于理想化的模型。

由于上述缺点,通常需要采用更复杂的软件去抖算法。但这又引入了新的问题:软件的复杂性,一个复杂的去抖动程序会增加代码维护的难度。采用软件去抖算法,对预选的开关进行实际的抖动特性测试是十分必要的。采购至少三个样品进行测量,可以为评估其统计特性提供一个更可靠的基础,从而判断机械触点的抖动时间是否具有一致性。

对于硬件去抖方案,其核心优势在于“实时性”和“独立性”:它在信号进入系统之前就已经将其“净化”,不占用任何CPU资源,并且其响应是即时的。对于某些高速或高可靠性应用,硬件去抖仍然是不可或缺的选择。

有两种经典的硬件去抖电路:SR锁存器方案和RC网络方案。

SR锁存器(Set-Reset Latch 置位-复位锁存器)是解决开关抖动最彻底、最可靠的硬件方法之一。其基本电路如下图所示,由两个交叉耦合的与非门(NAND Gate)构成。

图. 经典的SR锁存器去抖电路

这个电路设计的关键在于它需要一个双掷开关(Double-Throw Switch),即一个具有常开(NO)和常闭(NC)两个触点的开关。两个上拉电阻确保在开关悬空时,与非门的输入端为高电平。

其工作原理如下:

1. 初始状态:如图所示,当开关闭合到上方触点时,上方与非门的输入A被拉低至地(逻辑0)。无论B点的电平如何,根据与非门的逻辑特性,其输出Q都将为高电平(逻辑1)。这个高电平Q和下方上拉电阻提供的高电平共同作为下方与非门的输入,使其输出/Q为低电平(逻辑0)。这个低电平/Q又反馈到上方与非门的B输入端,进一步锁定了当前状态。此时,输出(Q, /Q)为(1, 0)。
2. 切换过程:当用户拨动开关,使其从上方触点向下方触点移动时,此时会有一个短暂的“悬空”阶段,两个触点都不与公共端连接。在此期间,由于两个上拉电阻的存在,A和B输入都为高电平。然而,由于下方与非门的输出/Q(逻辑0)反馈到了上方与非门的B输入端,即使A变为高电平,Q的输出依然保持为1。因此,整个锁存器的状态得以维持,输出保持不变。
3. 抖动发生:当开关的公共端接触到下方触点时,可能会发生弹跳。在接触的瞬间,下方与非门的输入被拉低,使其输出/Q变为高电平。这个高电平与A输入端的高电平(此时A已悬空)使上方与非门的输出Q变为低电平。这个低电平Q反馈到下方与非门的输入端,锁定了/Q的高电平状态。即使开关在下方触点上反复弹跳,只要它没有弹跳得足够远以至于重新接触到上方触点(这在物理上几乎不可能发生),电路状态就会稳定在(Q, /Q)=(0, 1),从而输出一个干净的、无抖动的电平翻转。

SR锁存器方案的输出是绝对干净的,因为它利用了状态反馈来“记忆”开关的第一次有效接触,并忽略后续的弹跳。

尽管SR锁存器方案效果绝佳,但由于双掷开关比普通的单掷开关(Single-Throw Switch)体积更大、成本更高,且很多按键无法实现双掷结构,因此在实际工程中并不常用。

基于RC网络的去抖电路

对于成本和体积更敏感的设计,尤其是使用单掷开关的场合,RC(电阻-电容)网络是最常见的硬件去抖方案。它利用电容器电压不能突变的特性来平滑开关抖动引起的电压跳变。

一个经过优化的RC去抖电路如下图所示。
图. 包含输入保护电阻R2的RC去抖电路

电路工作原理分析:

1. 开关断开(充电过程):当开关S1断开时,电源+V通过电阻R1和R2向电容C充电。电容两端的电压Vc会按照指数规律缓慢上升。在此期间,开关触点可能因抖动而瞬间闭合,但这只会短暂地将Vc拉低,一旦触点再次弹开,充电过程会继续。只要RC时间常数选择得当,在整个抖动期间,Vc的电压并不会上升到逻辑门识别为“高电平”的阈值。抖动结束后,Vc将稳定充电提升至+V电平。
2. 开关闭合(放电过程):当开关S1闭合时,电容C通过电阻R2放电。电压Vc会缓慢下降。抖动会导致触点瞬间断开,此时电源会通过R1+R2试图为电容充电,这会减缓放电过程,有助于在抖动期间将Vc维持在较高的电平。同样,只要RC参数合适,在抖动结束前,Vc都不会下降到逻辑“低电平”的阈值以下。

电阻R2的关键作用

许多教科书中的RC去抖电路会省略R2,让开关闭合时电容直接通过开关接地。这是一个危险的设计。当一个充满电的电容通过一个理想开关闭合时,会产生一个瞬时的、理论上无限大的放电电流(I = C * dV/dt)。这个巨大的浪涌电流不仅会损伤开关的金属触点,缩短其寿命,引线和PCB走线上的寄生电感(ESL)会与电容C形成一个LC振荡回路,产生比电源电压还高的振铃,这可能会造成EMI问题。

电阻R2在此处扮演两个重要角色:

限制放电电流:它限制了电容放电时的峰值电流,保护了开关触点。
确保缓慢放电:它与电容C共同决定了放电时间常数,确保了即使在抖动过程中,Vc也能缓慢下降,从而有效地滤除抖动。

施密特触发器 – RC防抖不可或缺的黄金搭档

RC网络的输出是一个缓慢变化的模拟电压。如上文所述,如果将这样一个缓变的信号直接输入到标准的数字逻辑门或微控制器引脚,当电压缓慢穿越逻辑阈值的不确定区域时,会导致输入级的逻辑振荡,产生一串高频噪声,反而使问题恶化。

因此,RC去抖电路的输出必须连接到一个带有施密特触发器输入的器件。

施密特触发器是一种具有“迟滞”特性的比较器。它有两个不同的翻转阈值:一个用于上升沿的“高阈值”(Vth+),一个用于下降沿的“低阈值”(Vth-)。

  • 当输入电压从低到高上升,只有超过Vth+时,输出才会翻转为高。
  • 当输入电压从高到低下降,只有低于Vth-时,输出才会翻转为低。

在Vth-和Vth+之间的电压范围内,输出状态保持不变。这种迟滞特性可以完美地“净化”RC网络输出的缓变信号。即使信号在阈值附近有小幅度的噪声波动,只要波动范围没有穿越两个阈值之差(即迟滞电压),输出就会保持稳定,从而得到一个干净、无抖动的数字信号。常见的74HC14/74AHCT14等芯片是集成了六个施密特触发反相器的常用选择。

设计准则:永远不要将RC滤波网络的输出直接连接到普通微处理器或逻辑器件的I/O引脚,除非该引脚明确标注具有施密特触发器输入特性。

RC去抖电路的参数设计

RC电路的设计核心是选择合适的R和C值,以确保在预估的最大抖动时间内,电容电压不会穿越施密特触发器的逻辑阈值。

根据实验数据,大多数开关的抖动时间远低于10ms。为了留出足够的裕量,我们可以选择一个20ms的去抖时间(T_debounce)。

1. 放电过程(开关闭合)

电容放电的电压方程为:

其中,V_initial是初始电压(此处为Vcc,例如5V)。我们需要确保在t = T_debounce时,Vcap仍然高于施密特触发器的下降沿阈值Vth-

重新排列时间常数公式以求解 R(电容的成本和尺寸差异很大,因此最好选择一个 C 值,然后计算 R)得到:

R * C = -t / ln(Vth / V_initial)

以74AHCT14为例,其在5V供电下的最差情况Vth-(数据手册中称为V_T-)约为1.7V。

代入t = 20ms, Vth= 1.7V, V_initial = 5V

R * C = -0.02 / ln(1.7 / 5) = -0.02 / (-1.078) ≈ 0.0185

通常,我们先选择一个常用的电容值,再计算电阻。选择C = 0.1µF,这是一个常见且便宜的容值。求解开关刚好闭合的条件。电容通过 R2 放电。如果电源为 5 伏(因此 Vinitial 为 5),那么 R2 为 185K。当然,你实际上买不到这个阻值的电阻,所以使用 180K。

但是,该分析忽略了栅极的输入漏电流。像 74AHCT14 这样的 CMOS 器件会从输入端泄漏出大约一个微安的电流。那个 180K 的电阻会将输入偏置到 0.18 伏,这与栅极最佳情况下的 0.5 伏开关点非常接近,令人不安。将 C 更改为 1 µF,R2 现在是 18K。

R1 + R2 控制电容的充电时间,从而设置开关断开情况下的去抖周期。充电方程为:

Vth = V_final * (1 - e^(-t / ((R1 + R2) * C))) 其中,V_final是最终电压(Vcc,5V)。我们需要确保在t = T_debounce时,Vth仍然低于施密特触发器的上升沿阈值Vth+

对于74AHCT14,其最差情况Vth+(数据手册中称为V_T+)约为0.9V(注意,这是反相器,所以输入低,输出高)。

整理公式求解(R1+R2)*C:

(R1 + R2) * C = -t / ln(1 - Vth / V_final)

代入t = 20ms, Vth = 0.9V, V_final = 5V

(R1 + R2) * C = -0.02 / ln(1 - 0.9 / 5) = -0.02 / ln(0.82) = -0.02 / (-0.198) ≈ 0.101

R1 + R2 = 0.101 / (1 * 10^-6) = 101kΩ

已知R2约为18kΩ,则R1 = 101kΩ - 18kΩ = 83kΩ。选择一个标称值为82kΩ的电阻即可。

设计考量:

  • 输入漏电流:CMOS器件(如74AHCT14)的输入漏电流通常在1µA左右。这个电流流过R1+R2时会产生一个小的压降。如果电阻值过大(例如几百kΩ以上),这个压降可能会影响逻辑电平的判断。因此,在选择C时,不宜过小,以免计算出的R值过大。C = 1µF和几十kΩ的电阻是一个比较均衡的选择。

  • 元件容差:普通电阻的容差为±5%,而电容的容差范围可能更大,特别是电解电容(可达+80%/-20%)。在设计时,应使用最差情况的元件值和逻辑阈值进行计算,以保证设计的可靠性。

硬件设计中的其他注意事项

  • 避免直连时钟或中断:永远不要将未经过去抖处理的开关信号连接到触发器的时钟输入端(Clock)或微处理器的中断引脚。抖动信号中的高频噪声和窄脉冲很容易违反触发器的最小脉宽、建立时间和保持时间等时序要求,导致亚稳态或错误的锁存。大多数MCU数据手册并未详细说明中断引脚内部的电气特性,但通常它们会连接到内部的某个时序逻辑单元,因此同样存在风险。

  • 专用去抖IC:市面上存在一些专用的去抖芯片,如MC14490,它内部集成了多个去抖单元。这类芯片使用简单,性能可靠,但成本相对较高。在许多现代设计中,使用一个廉价的微控制器(如PIC或AVR Tiny系列)来实现软件去抖,其总成本可能比使用专用去抖IC更低。

  • 未使用的逻辑输入:这是一个通用的数字电路设计规则:任何未使用的逻辑门输入端都不能悬空。必须将它们连接到Vcc或地,以防止输入端因静电等干扰而在逻辑阈值附近浮动,导致不必要的功耗增加和逻辑振荡。

固件去抖方案

随着MCU的性能越来越强、成本越来越低,使用固件来实现开关去抖已成为绝大多数应用的首选。软件方案不仅省去了外部硬件元器件,降低了BOM成本和PCB面积,还提供了无与伦比的灵活性,可以轻松调整去抖参数,甚至针对不同开关实现不同的去抖策略。

固件去抖的基本原则

在编写代码之前,我们需要建立一个清晰的设计策略。一个优秀的去抖固件,不仅要能处理机械抖动,还应能应对电磁干扰等外部噪声,同时保证系统的响应性和资源的有效利用。

  1. 处理EMI噪声:电路板上的引线,特别是连接到外部开关的线缆,就像天线一样,会从环境中拾取电磁噪声。这些噪声可能表现为短暂的电压尖峰,足以让MCU的输入引脚产生错误的电平翻转,其效果与机械抖动非常相似。一个鲁棒的去抖算法必须能够同时滤除这两种干扰。

  2. 最小化CPU开销:去抖动是系统中的一个低优先级后台任务。在等待开关状态稳定时,绝不能通过for循环或while循环等方式进行阻塞式延时。这种“死等”会极大地浪费CPU周期,甚至可能影响到系统中更重要的实时任务。所有可靠的去抖算法都应基于定时器中断或操作系统的调度机制,以非阻塞的方式运行。

  3. 使用普通I/O而非中断引脚:如上文所述,未经处理的开关信号不应连接到MCU的中断引脚。抖动产生的混乱信号序列可能会违反内部触发器的时序要求。因此,开关应连接到普通的GPIO引脚,由软件以固定的频率进行轮询,待状态稳定后,再由软件层面触发相应的逻辑动作。

  4. 避免同步采样:采样率的选择应避免与环境中可能存在的周期性噪声源同步。例如,在市电环境下,应避免以50Hz或60Hz的整数倍频率进行采样,以防工频干扰被错误地识别为开关动作。在汽车电子等应用中,甚至需要考虑避开与发动机或转向柱振动频率相关的采样率。

  5. 保证快速响应:用户体验至关重要。从用户按下按钮到系统给出反馈,这个延时必须尽可能短。实验表明,低于50ms的延时对于人类来说几乎是“瞬时”的,而超过100ms的延时则会产生明显的“迟滞感”。因此,我们的去抖算法应力求在保证可靠性的前提下,尽快确认用户的操作。

基于定时器和状态机的去抖算法

这是最经典且最通用的去抖方法之一,它将去抖过程抽象为一个基于时间的状态机。该算法由一个周期性调用的函数(通常由定时器中断触发)来驱动。

算法解析:

  • 该函数 DebounceSwitch1() 必须以固定的时间间隔 CHECK_MSEC(例如5ms)被调用。

  • 它使用静态变量 countbutton_state 来保存两次调用之间的状态。

  • 当检测到引脚电平变化时,就认为可能进入了抖动期,此时将 button_state 更新为当前状态,并重置 count

  • 如果引脚电平保持不变,则 count 持续增加。

  • 通过 count * CHECK_MSEC 将计数值转换为稳定持续的时间。当这个时间超过预设的阈值(按下PRESS_MSEC或释放RELEASE_MSEC)时,才最终确认开关的状态(Key_Pressed)。

  • Key_Changed 标志用于向主程序通知状态的有效改变,方便实现事件驱动的逻辑。

该算法最大的优点是基于时间而非次数,这使得它非常健壮且易于维护。无论CPU时钟频率或编译器如何变化,去抖动的延迟都是由宏定义 PRESS_MSECRELEASE_MSEC 明确决定的。此外,为按下和释放设置不同的去抖时间也提供了额外的灵活性,例如可以快速响应用户按下(较小的PRESS_MSEC),同时对释放后的EMI干扰提供更强的防护(较大的RELEASE_MSEC)。

移位寄存器去抖算法

这是一种更为精简和高效的算法,特别适合资源受限的MCU或需要翻译成汇编语言的场合。其核心思想是用一个整型变量作为“移位寄存器”,记录引脚最近N次的采样历史。

算法解析:

  • 同样,此函数也需要被定时器周期性地调用。

  • 静态变量 state_history 的每一位代表一次采样的结果。每次调用时,将最新的采样值(0或1)移入变量的最低位。

  • 通过与一个特定的掩码(如0xFFF0)比较,我们可以判断开关是否在最近的N次采样中都保持了稳定的状态。例如,state_history == 0xFFF0 意味着,在经历了一段不确定的时间(高位为1)后,最近4次采样值均为0。这精确地捕捉到了从抖动/释放到稳定按下的边缘

  • 这种方法巧妙地将历史信息压缩到一个变量中,通过位运算进行高效判断。它不仅能滤除抖动,还能天然地实现边缘检测,即只在状态稳定转换的那个瞬间返回TRUE一次。

此算法非常高效,几乎没有分支,执行时间固定,因此也非常适合在FPGA或ASIC中用硬件逻辑实现。

并行处理多个开关

当需要处理一整个端口(例如8个)的开关时,逐个对它们应用上述算法是低效的。我们可以将并行思想融入去抖算法中,同时处理多个输入。

算法解析:

  • 此算法维护一个循环队列 `State`,用于存储最近 `DEBOUNCE_SAMPLES` 次的整个端口的读数。
  • 核心思想是:如果一个开关(对应一个位)在最近的所有采样中其状态都保持不变,那么它就是稳定的。
  • 通过将队列中所有的字节进行按位与(`all_and`)和按位或(`all_or`),我们可以判断出哪些位是稳定的。如果一个位在`all_and`和`all_or`中的值相同,则说明该位在整个历史窗口中没有变过。
  •  `stable_mask` 计算出所有稳定位的掩码。
  • 最后,只用稳定的位来更新最终的 `Debounced_State`,而不稳定的位则保持其上一次的稳定状态。
    要检测状态变化,只需将当前的 `Debounced_State` 与上一次的值进行异或(XOR)运算。

这个算法用极少的代码实现了对整个端口的高效并行去抖,是处理矩阵键盘等应用的理想选择。

软件去抖的要点

所有可靠的软件去抖算法都有一个共同点:它们依赖于一个周期性的、由定时器驱动的采样机制。

  • 采样率:为了获得良好的响应性,同时不过度占用CPU,一个1ms到5ms的定时器中断(即采样周期)是比较理想的选择。

  • 去抖时间:结合实验数据(大多数抖动在10ms内结束)和用户体验(大于50ms的响应会被人类感知),一个20ms到50ms的去抖确认时间是比较合理的。这意味着,需要连续稳定20ms到50ms,才认为开关状态发生了有效改变。

工业级高可靠性输入电路设计

一个稳健的工业级输入电路,其设计目标不仅是消除抖动,更重要的是将外部恶劣的电磁环境与内部脆弱的数字控制核心有效隔离,并滤除各类高频噪声。下图展示了一个综合运用了光电隔离、RC滤波和施密特触发的经典电路,能够出色地完成这一任务。

该电路主要由三级构成:光电耦合器、RC低通滤波器(积分电路)和施密特触发器。

(1) 光电耦合器:实现电气隔离与噪声阻断

电路的第一级是光电耦合器。它的核心作用是电气隔离,即在信号传输过程中,将输入侧(外部)电路与输出侧(内部)电路的地线完全断开。这带来了两大好处:

  • 阻断共模噪声:工业现场中,不同设备间的地线可能存在电位差(地环路),或者整个地网可能受到强大的噪声干扰。光耦通过光信号传输,物理上切断了地线连接,有效阻止了这些共模噪声通过地线传导至微控制器侧。
  • 保护核心电路:外部输入线缆可能受到高压浪涌或静电的冲击。光耦的绝缘层能够承受数千伏的瞬态高压,从而保护后端低压的微控制器免遭损坏。

为了进一步增强抗噪声能力,该电路的输入侧(LED驱动侧)通常采用比微控制器逻辑电平更高的电压,如12V或24V。更高的驱动电压意味着在噪声幅度不变的情况下,信号的信噪比(SNR)更高。同时,为了确保LED可靠发光,驱动电流也应设置得相对较大(如10-20mA)。这样的“强信号”设计,不仅能抵抗噪声,还能有效克服开关触点因长期使用或环境氧化而导致的接触电阻增大的问题,保证了接触的长期可靠性。

(2) RC低通滤波器:滤除抖动与高频噪声

信号经过光耦隔离后,进入由电阻R和电容C组成的RC去抖电路,这部分功能也可以滤除高频成分

RC时间常数(τ = R × C)的选取至关重要。它必须大于预期的最大抖动时间,但又要小于系统所能允许的最小信号响应时间。在不需要高速响应的通用开关量输入场合,可以适当增大时间常数,以获得更强的抗干扰性能。

(3) 施密特触发器:波形整形与逻辑电平恢复

经过RC滤波后的信号虽然消除了抖动,但其边沿变得非常平缓。如果将这样的“慢边沿”信号直接送入标准的数字逻辑门,可能会引发两个问题:一是逻辑门在输入电压穿越其阈值区域时,可能因微小的噪声而产生多次输出翻转(振荡);二是输入电平长时间处于不确定区域会导致逻辑门内部的CMOS管同时导通,增加功耗甚至损坏器件。

施密特触发器正是解决这一问题的理想器件。能将缓慢变化的输入信号整形为具有陡峭边沿的标准数字信号,确保后级数字电路能够稳定、可靠地进行处理。

参考来源

很强的一个网站:https://www.ganssle.com/debouncing.htm

❤️ 如果这篇文章对您有帮助,欢迎打赏支持

微信打赏二维码

扫描上方二维码,用微信打赏

吴川斌

吴川斌

Leave a Reply