在物联网 (IoT) 领域的工作很快让我意识到,我不能再仅仅依靠我对软件工程的理解,而对所有与硬件相关的事物一无所知,并希望它能由一个聪明的硬件来处理工程师。显然,我必须学习很多关于硬件的知识——但没关系,我实际上非常喜欢学习新事物。本周末的学习围绕使用 RC 电路的去抖动开关展开。而且我想我会为所有其他像我一样试图赶上硬件的软件工程师分享一些细节
好的,这不是我连接到去抖动电路的开关,但每个人都喜欢红色的大按钮
真正的工作。让我们首先讨论一下硬件按钮去抖动到底是什么。想象一下我们有下面的简单电路。
当我们关闭电路时,引脚上的电压应该从高值转变为低值,当然会。然而,从连接到引脚 (PIN) 的微控制器的角度来看,它可能不是我们期望的干净的高到低转换。这是因为该引脚测量的是数字信号,而开关控制的是模拟电压。模拟电压被解释为基于两个阈值电压的高或低数字值。电压输入高 (V IH ) 指的是可以被视为数字高的最小电压。电压输入低电平 (V IL ) 指的是可视为数字低电平的最大电压。当开关闭合时,快速变化的电流可能导致引脚电压在最终驱动至 GND 之前多次通过 V IH 和 V IL 转换。等效地,当开关打开时,在引脚上感受到的电压为 VDC 之前,可能会发生一组类似的转换。因此,开关上的单个状态转换(打开/关闭)可能会导致数字引脚上的多个状态转换(高/低)。这就是所谓的反弹,如果开发人员或工程师没有考虑到它,它可能会导致问题。
按钮去抖指的是在数字引脚上移除这些额外的状态转换。这可以通过硬件或软件来实现。出于本教程的目的,我们将使用 RC(电阻电容)电路对按钮进行去抖动。通过仔细选择电路中电阻器和电容器的正确值,我们可以平滑数字引脚上的电压转换。让我们看一下我们将使用的RC电路。
电路顶部接3.3V,电路底部接地。电阻器 R1 和 R2 将控制电容器 C 的充电和放电。标有 PIN 的电路元件连接到 Raspberry Pi 上的数字输入引脚 - 请注意施加的电压为 3.3V,因为 Raspberry Pi 使用 3.3V 逻辑电平.该电路相对容易构建,但我们仍需要计算要使用的 R1、R2 和 C 的值。在我们深入研究方程式之前(我喜欢方程式)让我们花点时间看看这个电路在开关打开和关闭时的行为。
当开关打开时,电容器两端存在电位差。这种差异允许电容器以由 R1、R2 和 C 确定的速率充电。充电时,引脚的读数应为 3.3V。
当开关闭合时,电容器上积累的电荷会流失——因为电容器两端不再存在电位差。电荷消耗的速率将由 R2 和 C 的值决定。
我们将从选择 C 的值开始。电容为 1µF 的电容器是常见且合适的元件。通过设置 C,我们可以计算 R2 的值。随着开关打开,电容器 C 将被充电。当开关闭合时,电容器将通过 R2 放电。描述该放电的方程由下式给出:
其中 V cap (t) 是时间 t 时电容器两端的电压,R 是电路的电阻,C 是电路的电容。重新排列这个等式我们有:
要计算 R 的值,我们需要做的最后一件事是为 t 选择一个值。我们为 t 选择的值应该足够大,以便所有弹跳都发生在该时间之前。如果您没有示波器,选择这个值可能会很棘手——示波器可以让您真正清楚地了解电压信号和时序。我无权访问示波器,所以我上网并找到了一篇由这样做的人撰写的文章。 Ganssle Group 的文章 建议将 20 毫秒作为他们测试的一系列常见引脚发生所有反弹的合理时间。
有了 t (20ms)、C (1µF)、V cap 的值——用于放电的是 V IL - (1.8V) 和 V 0 (3.3V),我们可以计算出 R——或者更具体地说,我们上面电路中的 R2——为 (约)21.5kΩ。我将其四舍五入为 22kΩ,因为我手头有一个。
为了计算 R1 的值,我们查看为电容器充电所需的方程式。那是开关打开的时候。描述电容器中电荷的方程式是:
其中 V cap 是电容器两端的电压,V 0 是电容器最终将上升到的电压,C 是电容,t 是时间,R 是电阻 - 在本例中为 R1+R2。重新排列我们有:
代入值 t (20ms)、C (1µF)、V cap - 用于充电的是 V IH - (1.8V) 和 V 0 (3.3V) 我们可以计算 R - 或者更具体地说 R1+R2 - 作为(大约.) 73kΩ。这意味着 R1 必须为 51kΩ。我手头有一个 47kΩ 和一个 3,900Ω 电阻器,因此将它们串联起来以创建一个 50,900Ω 电阻的 R1。
根据 R1、R2 和 C 的值,我构建了电路。然后我将它连接到 Arduino Due(它与 Raspberry Pi 具有相同的逻辑电平和非常相似的阈值电压)进行测试,并编写了一个简短的 Sketch 来使用硬件中断检测电压的上升和下降。
正如预期的那样(或至少希望如此),按下按钮时我得到了干净的过渡。现在剩下的就是看一下代码并查看仅使用电阻器和阻容电路的结果。享受!
volatile int rises = 0;
volatile int falls = 0;
int prev_rises = rises;
int prev_falls = falls;
void rise(void)
{
rises++;
}
void fall(void)
{
falls++;
}
void setup() {
// put your setup code here, to run once:
attachInterrupt(2,rise,RISING);
attachInterrupt(3,fall,FALLING);
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
int i;
Serial.println("**** Begin loop ****");
for(i=prev_rises;
i<rises;
i++){
Serial.println("A rise");
}
prev_rises = rises;
for(i=prev_falls;
i<falls;
i++){
Serial.println("A fall");
}
prev_falls = falls;
Serial.println("**** End loop ****");
delay(1000);
}
输出:
左图是纯电阻电路的输出,右图是电阻-电容电路的输出。两个图像都对应于单击一次按钮。