2. 按键消抖与单脉冲检测

任务目标

设计一个按键消抖与单脉冲检测电路,在FPGA上实现:当按键被按下时,消除机械抖动影响,仅输出一个周期宽度的单脉冲信号,用于后续逻辑的触发控制。

实现方案

采用计数器延时消抖法:检测到按键电平变化后启动计数器,经过约10ms稳定期后再次采样按键电平,若电平稳定则确认状态变化,并产生一个时钟周期的脉冲输出。该方案无需外部器件,资源占用少,适用于大多数机械按键场景。

完整代码

module key_debounce (
    input       clk,        // 系统时钟,50MHz
    input       rst_n,      // 异步复位,低有效
    input       key_in,     // 按键输入,按下为低电平
    output reg  key_pulse   // 单脉冲输出,高有效,宽度1个时钟周期
);

// 参数定义:10ms消抖时间 @50MHz = 500_000个时钟周期
parameter DELAY_CNT = 500_000;

// 状态编码
localparam IDLE     = 2'b00;  // 空闲态
localparam PRESS    = 2'b01;  // 按下确认中
localparam RELEASE  = 2'b10;  // 释放确认中

reg [1:0] state;
reg [18:0] cnt;          // 计数器,2^19 > 500_000
reg key_in_d1;           // 输入同步打拍

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

// 主状态机
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state <= IDLE;
        cnt <= 0;
        key_pulse <= 1'b0;
    end
    else begin
        // 默认输出为0
        key_pulse <= 1'b0;
        
        case (state)
            IDLE: begin
                // 检测到按键按下(低电平)
                if (key_in_d1 == 1'b0) begin
                    state <= PRESS;
                    cnt <= 0;
                end
            end
            
            PRESS: begin
                // 等待消抖时间
                if (cnt < DELAY_CNT) begin
                    cnt <= cnt + 1'b1;
                end
                else begin
                    // 消抖结束,再次采样
                    if (key_in_d1 == 1'b0) begin
                        // 确认按下,产生单脉冲
                        key_pulse <= 1'b1;
                        state <= RELEASE;
                        cnt <= 0;
                    end
                    else begin
                        // 抖动,回到空闲
                        state <= IDLE;
                    end
                end
            end
            
            RELEASE: begin
                // 等待释放
                if (key_in_d1 == 1'b1) begin
                    if (cnt < DELAY_CNT) begin
                        cnt <= cnt + 1'b1;
                    end
                    else begin
                        // 确认释放
                        state <= IDLE;
                    end
                end
                else begin
                    // 按键仍按下,重置计数器
                    cnt <= 0;
                end
            end
            
            default: state <= IDLE;
        endcase
    end
end

endmodule

仿真测试代码

module tb_key_debounce;

reg clk = 0;
reg rst_n = 0;
reg key_in = 1;
wire key_pulse;

// 50MHz时钟
always #10 clk = ~clk;

key_debounce uut (
    .clk(clk),
    .rst_n(rst_n),
    .key_in(key_in),
    .key_pulse(key_pulse)
);

// 模拟按键抖动
initial begin
    // 复位
    #20 rst_n = 1;
    
    // 模拟按下抖动:低-高-低-高-低
    #100 key_in = 0;
    #5   key_in = 1;
    #5   key_in = 0;
    #5   key_in = 1;
    #5   key_in = 0;  // 稳定按下
    
    // 等待消抖完成(10ms = 500_000周期 @50MHz)
    // 仿真中可减小DELAY_CNT以加速,此处等待脉冲
    wait (key_pulse == 1);
    $display("PASS: Key press detected, pulse generated at time %0t", $time);
    
    // 等待释放
    #100 key_in = 1;
    #5   key_in = 0;
    #5   key_in = 1;
    #5   key_in = 0;
    #5   key_in = 1;  // 稳定释放
    
    #100;
    $display("Test completed");
    $finish;
end

endmodule

运行与验证

仿真验证:在Vivado或ModelSim中运行仿真,观察key_pulse信号。当按键稳定按下约10ms后,key_pulse应输出一个高电平脉冲,宽度恰好为1个时钟周期(20ns @50MHz)。仿真中为加速可临时将DELAY_CNT改小(如100),验证逻辑正确后再恢复实际值。

板级验证:将key_pulse连接至LED,按下按键后LED应闪烁一次(亮一个时钟周期,人眼不可见),可再接一个计数器分频后观察。通过条件:每次按下按键,key_pulse恰好产生一个脉冲,无重复触发;快速连续按键时每个按下动作均能正确检测。

关键说明

  • 输入同步寄存器key_in_d1用于消除跨时钟域亚稳态风险
  • 状态机包含按下确认和释放确认两个稳定期,防止按键在释放时产生额外脉冲
  • 若系统时钟频率不同,需按公式DELAY_CNT = 10ms × 时钟频率重新计算参数