2. 按键消抖模块
任务说明
机械按键在按下与释放时会产生约5~20ms的抖动,导致电平在高低之间多次跳变。本案例实现一个按键消抖模块,通过延时计数确认电平稳定后,再结合边沿检测输出单周期脉冲,确保每次按键动作只产生一次有效输出。
实现方案
采用“延时计数 + 边沿检测”的两级处理:
- 当检测到按键电平变化时,启动计数器,在抖动窗口(20ms)内持续采样;
- 若采样值在窗口内保持不变,则确认电平稳定,更新稳定后的按键状态;
- 对稳定后的状态做上升沿检测,输出单周期脉冲
key_pulse。
完整代码
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 // 按键稳定后的单周期脉冲
);
// 计算消抖所需计数最大值
localparam integer CNT_MAX = CLK_FREQ / 1000 * DEBOUNCE_MS - 1;
localparam integer CNT_WIDTH = $clog2(CNT_MAX + 1);
reg key_in_d1;
reg key_in_d2;
wire key_changed; // 输入电平发生变化
reg [CNT_WIDTH-1:0] cnt;
reg cnt_en; // 计数使能
reg key_stable; // 稳定后的按键电平(低有效)
// 两级同步,消除亚稳态
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_in_d1 <= 1'b1;
key_in_d2 <= 1'b1;
end else begin
key_in_d1 <= key_in;
key_in_d2 <= key_in_d1;
end
end
// 检测输入变化
assign key_changed = (key_in_d2 != key_stable);
// 消抖计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= {CNT_WIDTH{1'b0}};
cnt_en <= 1'b0;
end else if (key_changed) begin
// 电平变化,重新开始计数
cnt <= {CNT_WIDTH{1'b0}};
cnt_en <= 1'b1;
end else if (cnt_en) begin
if (cnt == CNT_MAX) begin
cnt_en <= 1'b0; // 计数完成
key_stable <= key_in_d2; // 锁定稳定电平
end else begin
cnt <= cnt + 1'b1;
end
end
end
// 边沿检测:稳定状态由高到低(按下)产生脉冲
reg key_stable_d;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
key_stable_d <= 1'b1;
else
key_stable_d <= key_stable;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
key_pulse <= 1'b0;
else
key_pulse <= (~key_stable) & key_stable_d; // 检测下降沿(按下)
end
endmodule
测试平台
`timescale 1ns/1ps
module tb_key_debounce;
reg clk;
reg rst_n;
reg key_in;
wire key_pulse;
// 50MHz时钟
localparam CLK_PERIOD = 20;
always #(CLK_PERIOD/2) clk = ~clk;
key_debounce #(
.CLK_FREQ(50_000_000),
.DEBOUNCE_MS(20)
) uut (
.clk(clk),
.rst_n(rst_n),
.key_in(key_in),
.key_pulse(key_pulse)
);
// 模拟机械抖动
task press_key;
begin
// 按下过程:先抖动再稳定
key_in = 1'b1;
#100;
key_in = 1'b0; #200; // 抖动
key_in = 1'b1; #150;
key_in = 1'b0; #180;
key_in = 1'b1; #120;
key_in = 1'b0; // 稳定按下
#(25_000_000); // 等待消抖完成(约25ms)
end
endtask
initial begin
clk = 0;
rst_n = 0;
key_in = 1'b1;
#100;
rst_n = 1;
#100;
press_key; // 第一次按下
#1000;
$display("key_pulse = %b (期望为1)", key_pulse);
// 释放过程
key_in = 1'b1;
#(25_000_000);
#1000;
$display("key_pulse = %b (期望为0)", key_pulse);
$finish;
end
// 监控脉冲
always @(posedge key_pulse)
$display("时间 %0t: 检测到按键脉冲", $time);
endmodule
运行与验证
- 仿真运行:在Vivado/ModelSim中编译两个文件,运行仿真。观察
key_pulse信号。 - 通过条件:
- 按下按键(含抖动)后,约20ms消抖窗口结束,
key_pulse输出一个时钟周期的高脉冲; - 释放按键时,
key_pulse保持低电平(本设计只检测按下动作); - 抖动期间
key_pulse不产生任何毛刺脉冲。
关键点说明
- 参数
CLK_FREQ和DEBOUNCE_MS可根据实际时钟与按键特性调整,计数器位宽由$clog2自动计算。 - 两级触发器同步用于消除亚稳态,是跨时钟域输入的标准处理方式。
- 本模块输出的是“按下”脉冲;若需同时检测“释放”,可增加对上升沿的判断。