3、FPGA消抖原理:基于状态机的消抖思想
按键消抖,说白了就是跟机械抖动做斗争。你想想看,一个按键按下去,你以为只是一次干净的电平变化,实际上在几毫秒内,它会来回弹跳好几次。我刚开始做FPGA项目时,就被这个坑过——明明按了一下,结果系统检测到了五六次,直接乱套了。
那怎么解决?核心思路就四个字:稳定采样。我们不去管它抖不抖,只要在足够长的时间里,采样到的电平值一直没变,我们就认为它稳定了。这个思想,就是状态机消抖的基础。
3.1 状态机消抖的核心思想
我个人习惯把消抖状态机分成三个状态:
- 空闲态(IDLE):等待按键按下,检测到低电平(假设按下为低)就进入下一个状态
- 消抖态(DEBOUNCE):开始计时,持续采样,如果采样值一直稳定,就进入确认态
- 确认态(CONFIRM):输出稳定的按键值,等待按键释放后回到空闲态
嗯,这里要注意:消抖态里如果采样到抖动,计数器要清零重新计时。这个细节很多人会忽略,结果消抖效果大打折扣。
核心原则:消抖不是简单地延时,而是“持续稳定才确认”。只要中间有一次跳变,计时器就归零重来。
3.2 采样时钟的选择
采样时钟怎么选?我见过有人直接用系统时钟(比如50MHz)去采样,结果发现消抖计数器要计到几百万才能覆盖20ms的抖动期。这太浪费资源了。
我的建议是:单独生成一个低频采样时钟,或者用系统时钟分频出一个使能信号。常用的采样频率在1kHz到10kHz之间,也就是采样周期100us到1ms。
| 采样频率 | 采样周期 | 适用场景 |
|---|---|---|
| 1 kHz | 1 ms | 普通按键,抖动期20ms |
| 5 kHz | 200 us | 响应要求较高的按键 |
| 10 kHz | 100 us | 快速按键,或机械开关 |
为什么选这个范围?因为机械按键的抖动期通常在5ms到20ms之间。采样周期太短,容易捕捉到抖动;采样周期太长,响应延迟又太大。我在项目中一般用5kHz,也就是200us采样一次,消抖计数到100次(20ms)就确认稳定。这个组合用起来比较顺手。
3.3 稳定时间的确定
稳定时间,就是你要等多久才认为按键不再抖动了。这个值没有标准答案,得看实际用的按键型号。
我曾经在一个项目中用过一批便宜的轻触开关,抖动期居然长达30ms。一开始我按20ms来设计,结果偶尔还是会出现误触发。后来我改成30ms,问题就解决了。所以我的经验是:先查数据手册,再留20%~30%的余量。
经验值:一般按键用20ms,质量差一点的用30ms,高品质的用10ms就够了。如果你不确定,就选20ms,这是业界最通用的值。
3.4 边沿检测与消抖的结合
消抖之后,我们通常还需要检测按键的边沿——是按下(下降沿)还是释放(上升沿)。这两个动作往往对应不同的功能。
怎么结合?我常用的做法是:先消抖,再边沿检测。流程是这样的:
- 用采样时钟对按键输入进行消抖处理,得到稳定的按键值
- 将稳定后的按键值打一拍,得到上一周期的值
- 比较当前值和上一周期的值,产生边沿信号
举个例子,假设按键按下为低电平:
// 伪代码示意
always @(posedge clk_slow) begin
key_stable_prev <= key_stable; // 打一拍
key_stable <= debounced_key; // 消抖后的值
// 下降沿检测:按下
if (key_stable_prev == 1 && key_stable == 0)
key_falling <= 1;
else
key_falling <= 0;
// 上升沿检测:释放
if (key_stable_prev == 0 && key_stable == 1)
key_rising <= 1;
else
key_rising <= 0;
end
注意:边沿检测一定要在消抖之后做,否则你检测到的边沿全是抖动产生的假边沿。我见过有人把顺序搞反了,结果消抖白做了。
3.5 状态机消抖的完整流程
把上面这些串起来,就是一套完整的状态机消抖方案。我画了一张流程图,方便你理解整个逻辑:
这张图里有个关键点:消抖态中如果采样到抖动,计数器会清零,然后继续采样。这个回环保证了只有真正稳定的信号才能进入确认态。我在实际项目中,还会在确认态里加一个“释放检测”分支,确保按键完全释放后才回到空闲态,避免一次按下被误判为多次。
小技巧:如果你想让响应更灵敏,可以在确认态里直接输出边沿信号,不用等按键释放。但要注意,这样可能会把长按和短按混淆。具体怎么选,看你的应用场景。
好了,关于状态机消抖的原理就聊到这里。说白了就是三个状态、一个计数器、一个采样时钟,组合起来就能搞定机械抖动。下一节我们会用Verilog把这段逻辑写出来,到时候你就能看到它到底有多实用了。