2. 按键消抖模块
任务目标
实现一个基于边沿检测的按键消抖模块,消除机械抖动,输出稳定的按键脉冲。按键按下和释放时均会产生约5~20ms的机械抖动,本模块通过20ms稳定计数窗口滤除抖动,仅在按键状态稳定后输出单周期脉冲。
设计原理
采用“采样+计数”方式:当检测到按键电平与稳定状态不一致时,启动计数器;若电平在连续20ms内保持不变,则判定状态翻转并输出脉冲;若中途变化则清零重新计数。该方案无需精确时钟分频,仅需一个计数器即可实现。
模块接口
| 信号 | 方向 | 位宽 | 说明 |
|---|---|---|---|
| clk | input | 1 | 系统时钟,50MHz |
| rst_n | input | 1 | 异步复位,低有效 |
| key_in | input | 1 | 按键原始输入(低电平表示按下) |
| key_pulse | output | 1 | 消抖后按键脉冲,高有效,持续1个时钟周期 |
| key_state | output | 1 | 消抖后稳定电平(低=按下,高=释放) |
关键完整代码
module key_debounce #(
parameter CLK_FREQ = 50_000_000, // 时钟频率Hz
parameter DEBOUNCE_MS = 20 // 消抖时间ms
)(
input wire clk,
input wire rst_n,
input wire key_in,
output reg key_pulse,
output reg key_state
);
// 计算计数上限
localparam integer CNT_MAX = CLK_FREQ / 1000 * DEBOUNCE_MS - 1;
reg [31:0] cnt;
reg key_state_d; // 上一拍稳定状态
// 异步复位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 32'd0;
key_state <= 1'b1; // 默认高电平(释放)
key_state_d <= 1'b1;
key_pulse <= 1'b0;
end else begin
// 保存上一拍状态
key_state_d <= key_state;
// 按键电平与当前稳定状态不一致 → 启动计数
if (key_in != key_state) begin
if (cnt == CNT_MAX) begin
// 计数满,状态翻转
key_state <= key_in;
cnt <= 32'd0;
end else begin
cnt <= cnt + 32'd1;
end
end else begin
// 电平一致,清零计数器
cnt <= 32'd0;
end
// 边沿检测:稳定状态发生变化时产生脉冲
if (key_state_d != key_state)
key_pulse <= 1'b1;
else
key_pulse <= 1'b0;
end
end
endmodule
运行与验证
Testbench要点:
// 在testbench中:
// 1. 初始化key_in=1,运行100us
// 2. 模拟按下抖动:key_in先拉低2us,再拉高1us,再拉低并保持
// 3. 等待约20ms,观察key_pulse是否出现一个高脉冲
// 4. 模拟释放抖动:key_in拉高2us,拉低1us,再拉高并保持
// 5. 等待约20ms,再次观察key_pulse
仿真通过条件:
- 按下抖动期间(前3us内)
key_pulse保持为0,无毛刺输出 - 按键稳定约20ms后,
key_state变为0(按下),同时key_pulse输出1个时钟周期的高脉冲 - 释放抖动期间无脉冲输出,稳定20ms后
key_state回到1,key_pulse再次输出单周期脉冲
综合与上板验证:将key_pulse连接至LED取反逻辑,每按一次按键LED翻转一次。若LED状态稳定翻转且无抖动闪烁,则模块工作正常。
注意事项
- 参数
CLK_FREQ必须与实际时钟匹配,否则消抖时间会偏移 - 计数器位宽需满足
CNT_MAX最大值,50MHz/20ms时计数上限为999999,需20位以上,此处使用32位安全余量 - 本模块适用于机械按键,若按键为自锁开关(状态保持型),则无需脉冲输出,只需
key_state即可