2. 按键消抖与单脉冲生成

任务说明

机械按键在按下与释放过程中会产生约5~20 ms的抖动,若直接作为时钟或触发信号,会导致LED翻转多次或逻辑误触发。本案例设计一个基于计数器采样的按键消抖电路,在按键状态稳定后产生一个单时钟周期脉冲,用于控制LED翻转。

设计原理

采用“连续采样确认”法:以系统时钟对按键输入持续采样,当检测到按键电平与稳定状态不同时启动计数器;若计数器计满设定的消抖时间(约20 ms)且期间电平未变化,则确认按键状态改变,输出一个单周期脉冲。该脉冲作为LED翻转的触发信号。

完整代码

module key_debounce_pulse (
    input       clk,        // 系统时钟 50MHz
    input       rst_n,      // 异步复位,低有效
    input       key_in,     // 按键输入(按下为低电平)
    output reg  led_out,    // LED输出(翻转控制)
    output reg  pulse_out   // 单周期脉冲输出
);

// 参数定义:20ms消抖时间 @50MHz = 1_000_000 个时钟周期
parameter CNT_MAX = 20'd1_000_000;

// 内部信号
reg  key_reg1, key_reg2;        // 两级同步寄存器
reg  key_stable;                // 当前稳定状态
reg  [19:0] cnt;                // 消抖计数器
reg  cnt_en;                    // 计数使能
wire key_changed;               // 按键电平变化标志

// 两级同步,消除亚稳态
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_reg1 <= 1'b1;
        key_reg2 <= 1'b1;
    end else begin
        key_reg1 <= key_in;
        key_reg2 <= key_reg1;
    end
end

// 检测电平变化(以上一拍稳定值为基准)
assign key_changed = (key_reg2 != key_stable);

// 消抖计数器控制
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt <= 20'd0;
        cnt_en <= 1'b0;
    end else if (key_changed) begin
        cnt <= 20'd0;          // 检测到变化,重新计数
        cnt_en <= 1'b1;
    end else if (cnt_en) begin
        if (cnt == CNT_MAX - 1) begin
            cnt_en <= 1'b0;    // 计数完成,关闭使能
        end else begin
            cnt <= cnt + 1'b1;
        end
    end
end

// 稳定状态更新与脉冲产生
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_stable <= 1'b1;    // 默认高电平(未按下)
        pulse_out <= 1'b0;
    end else if (cnt_en && (cnt == CNT_MAX - 1)) begin
        key_stable <= key_reg2; // 更新稳定状态
        pulse_out <= 1'b1;      // 产生单周期脉冲
    end else begin
        pulse_out <= 1'b0;      // 其余时间脉冲为低
    end
end

// LED翻转控制
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        led_out <= 1'b0;
    else if (pulse_out)
        led_out <= ~led_out;    // 每次有效按键翻转一次
end

endmodule

运行与验证

环境要求:Vivado或Quartus(Verilog仿真),或ModelSim/QuestaSim。

仿真验证步骤:

  1. 将上述代码保存为 key_debounce_pulse.v。
  2. 编写testbench,提供50MHz时钟,模拟按键抖动(在按下/释放时加入随机毛刺),观察 pulse_out 与 led_out。
  3. 编译并运行仿真。

通过条件:

  • 按下按键(key_in拉低)后,经过约20ms(即1,000,000个时钟周期),pulse_out 输出一个且仅一个高电平脉冲,宽度为1个时钟周期。
  • 释放按键后同样只产生一个脉冲。
  • 每次有效脉冲后 led_out 电平翻转一次。
  • 抖动期间(计数未完成时)pulse_out 保持低电平,无额外脉冲产生。

仿真激励要点:在testbench中,将 key_in 在按下和释放时各加入若干次10μs~1ms间隔的随机跳变(模拟机械抖动),再保持稳定电平。观察最终输出是否符合上述条件。

常见问题

  • 脉冲宽度不正确:检查 CNT_MAX 是否与时钟频率匹配。若时钟为25MHz,则需将参数改为 500_000。
  • LED翻转多次:确认 pulse_out 只在计数完成的那一个周期为高,其余时间必须为低。若在 else 分支中未将 pulse_out 清零,会导致持续高电平。
  • 亚稳态问题:两级同步寄存器 key_reg1/key_reg2 不可省略,否则在异步按键输入下可能采样到不稳定电平。