您是否有一个简单的去抖动例程可以方便地处理单个开关输入?
这是一个没有任何操作系统的简单裸机系统。
我想避免使用特定计数的循环结构,因为处理器速度可能会波动。
我想你可以在这里学到很多:http ://www.ganssle.com/debouncing.pdf
如果可能的话,最好的办法是始终在硬件中执行此操作,但也有一些关于软件的想法。
来自 TFA 的简单示例代码:
#define CHECK_MSEC 5 // Read hardware every 5 msec
#define PRESS_MSEC 10 // Stable time before registering pressed
#define RELEASE_MSEC 100 // Stable time before registering released
// This function reads the key state from the hardware.
extern bool_t RawKeyPressed();
// This holds the debounced state of the key.
bool_t DebouncedKeyPress = false;
// Service routine called every CHECK_MSEC to
// debounce both edges
void DebounceSwitch1(bool_t *Key_changed, bool_t *Key_pressed)
{
static uint8_t Count = RELEASE_MSEC / CHECK_MSEC;
bool_t RawState;
*Key_changed = false;
*Key_pressed = DebouncedKeyPress;
RawState = RawKeyPressed();
if (RawState == DebouncedKeyPress) {
// Set the timer which allows a change from current state.
if (DebouncedKeyPress) Count = RELEASE_MSEC / CHECK_MSEC;
else Count = PRESS_MSEC / CHECK_MSEC;
} else {
// Key has changed - wait for new state to become stable.
if (--Count == 0) {
// Timer expired - accept the change.
DebouncedKeyPress = RawState;
*Key_changed=true;
*Key_pressed=DebouncedKeyPress;
// And reset the timer.
if (DebouncedKeyPress) Count = RELEASE_MSEC / CHECK_MSEC;
else Count = PRESS_MSEC / CHECK_MSEC;
}
}
}
最简单的解决方案通常是最好的,我发现仅每 N 毫秒读取一次开关状态(10 到 50 之间,取决于开关)一直对我有用。
我已经去掉了损坏和复杂的去抖动例程,并用一个简单的慢速轮询代替了它们,结果总是这样好。
要实现它,您需要在系统上设置一个简单的周期性定时器中断(假设不支持 RTOS),但如果您习惯在裸机上对其进行编程,那么安排起来应该不难。
请注意,这种简单的方法增加了检测状态变化的延迟。如果一个开关需要 T 毫秒才能达到新的稳定状态,并且每 X 毫秒轮询一次,那么检测压力的最坏情况延迟是 T+X 毫秒。您的轮询间隔 X必须大于最坏情况的反弹时间 T。
没有适用于所有类型按钮的单一简单解决方案。无论这里有人告诉您使用什么,您都必须使用您的硬件进行尝试,看看它的效果如何。并查看示波器上的信号,以确保您真的知道发生了什么。Rich B 的 pdf 链接看起来是个不错的起点。
我使用多数投票方法来消除输入的抖动。我建立了一个简单的三态移位寄存器类型的数据结构,并对每个样本进行移位并取三个中最好的两个作为“正确”值。这显然是您的中断处理程序或轮询器的功能,具体取决于实际读取硬件的方法。
但是,最好的建议是请您友好的硬件设计师“锁定”该值,并允许您在达到该值时清除该值。
要消除抖动,您需要忽略任何持续低于某个阈值的切换。您可以在开机时设置硬件计时器,或使用通过周期性中断设置的标志。
如果你能摆脱它,最好的硬件解决方案是让开关有两个不同的状态,中间没有状态。也就是说,使用 SPDT 开关,每个极点都馈入触发器的 R 或 S 线。以这种方式接线,触发器的输出应该被去抖动。
我通常做的是输入寄存器的宽度有三个左右的变量。每次轮询,通常来自中断,都会将值向上移动一个,以便为新样本让路。然后我有一个通过设置样本的逻辑与并清除逆逻辑或来形成的去抖动变量。即(未经测试,来自记忆)
input3 = input2;
input2 = input1;
input1 = (*PORTA);
debounced |= input1 & input2 & input3;
debounced &= (input1 | input2 | input3);
这是一个例子:
debounced 有 xxxx (其中“x”是“随便”)
input1 = 0110,
input2 = 1100,
input3 = 0100
有了上面的信息,
我们只需要将位 2 切换为 1,位 0 切换为 0。其余的仍在“弹跳”。
debounced |= (0100); //set only bit 2
debounced &= (1110); //clear only bit 0
结果是现在 debounced = x1x0
ganssle.com 的算法可能存在错误。我对以下行的印象
static uint8_t Count = RELEASE_MSEC / CHECK_MSEC;
应该读
static uint8_t Count = PRESS_MSEC / CHECK_MSEC;
为了正确地消除初始压力。
在硬件级别,基本的去抖动例程必须考虑物理键(或开关)行为的以下部分:
按键静置 -> 手指接触按键并开始向下推 -> 按键到达行程底部,手指保持在那里 -> 手指开始松开按键,弹簧将按键向上推 -> 手指松开按键,按键稍微振动直到它停止
所有这些阶段都涉及 2 块金属相互刮擦和碰撞,在几毫秒的时间内将电压从 0 上下波动,因此每一步都有电噪声:
(1) 按键未接触时的噪音,由湿度、振动、温度变化等环境问题引起,导致按键触点电压变化
(2) 按键时产生的噪音
(3) 按键时的噪音
(4) 按键释放时的噪音
(5) 按键松动后发出的声音
这是我们基本上猜测键被人按下的算法:
读取键的状态,可以是“可能被按下”、“肯定被按下”、“肯定没有被按下”、“可能没有被按下”(我们永远不确定)
循环而键“可能”被按下(如果处理硬件,这是一个大于某个阈值的电压样本),直到“绝对没有”被按下(低于阈值电压)(这是初始化,等待噪声停顿,“可能”和“绝对不是”的定义取决于具体应用)
循环,而键“绝对没有”被按下,直到键“可能”被按下
当键“可能”被按下时,开始循环并采样键的状态,并跟踪键“可能”被按下的时间 - 如果键回到“可能不是”或“绝对没有”被按下在一定时间之前的状态,重新启动程序 - 在您选择的特定时间(毫秒数)(通常通过尝试不同的值),您确定样本值不再由噪声引起,但很可能由人的手指实际按住键而导致返回值“按下”
while(keyvalue = maybepressed){
//loop - wait for transition to notpressed
sample keyvalue here;
maybe require it to be "notpressed" a number of times before you assume
it's really notpressed;
}
while(keyvalue = notpressed){
//loop - wait for transition to maybepressed
sample keyvalue
again, maybe require a "maybepressed" value a number of times before you
transition
}
while(keyvalue=maybepressed){
presstime+=1;
if presstime>required_presstime return pressed_affirmative
}
}
return pressed_negative
使用集成,您将成为一个快乐的露营者。适用于所有开关。
只需在读取为高时增加一个计数器,并在读取为低时将其递减,并且当积分器达到限制(上限或下限)时调用状态(高或低)。
Jack Ganssle 很好地描述了整个概念。他作为原始问题的答案发布的解决方案非常好,但我发现其中一部分不太清楚它是如何工作的。
处理开关弹跳的主要方法有三种: - 使用轮询 - 使用中断 - 中断和池的组合。
由于我主要处理低功耗或倾向于低功耗的嵌入式系统,因此 Keith 对集成的回答对我来说非常合理。
如果您使用具有一个机械稳定位置的 SPST 按钮式开关,那么我更喜欢使用中断和池组合的解决方案。
像这样:使用 GPIO 输入中断来检测第一个边沿(下降或上升,未驱动开关状态的相反方向)。在 GPIO 输入 ISR 下设置关于检测的标志。
使用另一个中断来测量时间(即通用定时器或 SysTick)来计算毫秒数。在每个 SysTick 增量(1 ms)上:
如果 buttonFlag 为真,则调用函数来轮询按钮的状态(轮询)。
对 N 个连续的 SysTick 增量执行此操作,然后清除标志。
当您轮询按钮状态时,请使用您希望确定按钮状态的逻辑,例如 M 个连续读数相同、平均值超过 Z、如果状态计数、最后 X 个读数相同等。
我认为这种方法应该受益于中断响应和更低的功耗,因为在 N SysTick 增量后不会有按钮轮询。各种中断之间没有复杂的中断修改,因此程序代码应该相当简单易读。
考虑以下事项:您是否需要“释放”按钮,是否需要检测长按以及按钮释放时是否需要操作。我不喜欢按钮释放时的按钮动作,但有些解决方案就是这样工作的。