概要

本文将系统介绍一种基于摩尔型状态机的按键消抖硬件设计方案。文章从机械按键的抖动原理出发,深入分析传统消抖方法的局限性,重点阐述为何选择摩尔型状态机——因其输出稳定、状态明确、抗干扰性强,特别适合按键消抖场景。通过四个核心状态(空闲态、按下消抖态、等待释放态、释放消抖态)的划分和状态转移图绘制,结合完整的Verilog实现代码与仿真验证,展示如何设计一个可靠、可配置的按键消抖模块。文章不仅提供可直接复用的工程代码,还详细讨论亚稳态处理、计数器同步、参数化扩展等实际问题,是一篇从理论到实践全面覆盖的硬件设计实战指南。

整体架构流程

一、引言

在嵌入式系统或FPGA开发中,按键输入是最常见的人机交互方式之一。然而,由于机械结构的物理特性,按键在按下和释放的瞬间会产生电平抖动,这种抖动会导致系统误判多次按键动作,影响系统稳定性。因此,按键消抖(Debouncing) 是嵌入式系统中必不可少的基础模块。

本文将从按键抖动原理出发,详细讲解基于状态机的按键消抖设计方法,包括状态划分、状态转移图绘制、Verilog代码实现,以及通过仿真验证设计功能的全过程。

二、按键抖动原理分析

2.1 按键结构与抖动产生原因
轻触按键由塑料外壳、金属触点、弹簧片等组成。按下时,弹簧片变形使触点接通;释放时,弹簧片回弹使触点断开。由于金属触点的弹性振动,会在接通或断开的瞬间产生多次短暂的接触与分离,表现为电平的快速跳变,即“抖动”。
在这里插入图片描述
抖动特点:

  • 持续时间通常在 1ms ~ 20ms 之间
  • 按下抖动比释放抖动更明显
  • 抖动期内会出现多个上升沿和下降沿

2.2 抖动的危害
在FPGA或单片机系统中,系统时钟频率往往在几十MHz,周期仅为几十纳秒。而抖动信号的周期在毫秒级,远大于系统时钟周期。若不处理,一次物理按键会被误检测为多次按键事件,导致功能异常,例如:

  • 串口重复发送
  • 菜单多次跳转
  • 计数器误触发等

三、按键消抖方法对比

3.1 传统延时采样法
检测到按键边沿后,固定延时20ms再采样电平状态。缺点明显:

  • 无法适应抖动时间超过20ms的劣质按键
  • 采样时刻可能仍处于抖动阶段,结果不可靠

3.2 基于稳定时间的判定法(本文采用)
核心思想: 以电平持续时间为判定条件,而非固定延时。

  • 按下判定: 检测到下降沿后开始计时,低电平持续≥20ms才确认为有效按下,期间若出现上升沿则视为抖动。
  • 释放判定: 检测到上升沿后开始计时,高电平持续≥20ms才确认为有效释放。

该方法自适应性强,可靠性高,适用于各类环境。

四、状态机建模与状态转移图

4.1 状态划分
将一次完整的按键过程划分为四个状态:

状态说明
IDLE空闲态,等待按键按下
P_FILTER按下消抖态,检测按下抖动
WAIT_R等待释放态,按键稳定按下
R_FILTER释放消抖态,检测释放抖动

4.2 状态转移图

        +-------------------+
        |       IDLE        |
        | (等待下降沿)      |
        +-------------------+
                | neg_edge
                v
        +-------------------+
        |    P_FILTER       |
        | (计时20ms,检测上升沿)|
        +-------------------+
                | time_20ms & !pos_edge
                v
        +-------------------+
        |    WAIT_R         |
        | (等待上升沿)      |
        +-------------------+
                | pos_edge
                v
        +-------------------+
        |    R_FILTER       |
        | (计时20ms,检测下降沿)|
        +-------------------+
                | time_20ms & !neg_edge
                v
              返回 IDLE

转移条件说明:

  • neg_edge:下降沿信号
  • pos_edge:上升沿信号
  • time_20ms:计时满20ms标志
    在这里插入图片描述

五、Verilog实现详解

主程序编写:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: HZ
// 独立按键消抖设计与验证
// Create Date: 2026/01/01 17:16:22
// Design Name: 
// Module Name: key_ffilter
// Project Name: 
//////////////////////////////////////////////////////////////////////////////////
module key_filter(
    input           Clk,         // 系统时钟,50MHz
    input           Reset_n,       // 复位信号,低电平有效
    input           Key,         // 原始按键输入(低电平按下)
    output reg      Key_p_flag,  // 按键按下稳定标志(单周期高脉冲)
    output reg      Key_r_flag   // 按键释放稳定标志(单周期高脉冲)
);
//闲状态 (IDLE)、按下抖动滤除状态 (FILTER0)、按下稳定状态 (DOWN) 以及释放抖动滤除状态 (FILTER1)
// --------------------------- 1. 异步信号同步(防亚稳态) ---------------------------
reg sync_d0k;  // 第一级同步寄存器
reg sync_d1k;  // 第二级同步寄存器
reg rk;        // 用于边沿检测的前一拍信号

always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        sync_d0k <= 1'b1;  // 按键默认高电平(未按下)
        sync_d1k <= 1'b1;
        rk <= 1'b1;
    end else begin
        sync_d0k <= Key;          // 第一级同步:采集原始按键信号
        sync_d1k <= sync_d0k;     // 第二级同步:稳定信号
        rk <= sync_d1k;           // 存储同步后的前一拍信号,用于边沿检测
    end
end

// --------------------------- 2. 边沿检测(上升沿/下降沿) ---------------------------
wire neg_edge_k;  // 按键下降沿标志(按下瞬间)
wire pos_edge_k;  // 按键上升沿标志(释放瞬间)

// 下降沿:前一拍高,当前拍低(按键按下)
assign neg_edge_k = (rk == 1'b1) && (sync_d1k == 1'b0);
// 上升沿:前一拍低,当前拍高(按键释放)
assign pos_edge_k = (rk == 1'b0) && (sync_d1k == 1'b1);

// --------------------------- 3. 状态机定义与编码 ---------------------------
// 状态枚举:空闲态、按下消抖态、等待释放态、释放消抖态
localparam IDLE      = 2'b00,
           P_FILTER  = 2'b01,
           WAIT_R    = 2'b10,
           R_FILTER  = 2'b11;

reg [1:0] curr_state;  // 当前状态
reg [1:0] next_state;  // 下一状态

// --------------------------- 4. 状态机时序逻辑(状态寄存器) ---------------------------
always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        curr_state <= IDLE;  // 复位后进入空闲态
    end else begin
        curr_state <= next_state;  // 时钟沿更新当前状态
    end
end

// --------------------------- 5. 状态机组合逻辑(下一状态判断) ---------------------------
always @(*) begin
    next_state = curr_state;  // 默认保持当前状态
    case (curr_state)
        // 空闲态:等待按键按下(检测下降沿)
        IDLE: begin
            if (neg_edge_k) begin
                next_state = P_FILTER;  // 检测到按下,进入按下消抖态
            end
        end

        // 按下消抖态:计时20ms,判断是否稳定按下
        P_FILTER: begin
            if (time_20ms_reach) begin
                next_state = WAIT_R;  // 20ms稳定,进入等待释放态
            end else if (pos_edge_k) begin
                next_state = IDLE;    // 20ms内检测到释放,判定为抖动,返回空闲态
            end
        end

        // 等待释放态:等待按键释放(检测上升沿)
        WAIT_R: begin
            if (pos_edge_k) begin
                next_state = R_FILTER;  // 检测到释放,进入释放消抖态
            end
        end

        // 释放消抖态:计时20ms,判断是否稳定释放
        R_FILTER: begin
            if (time_20ms_reach) begin
                next_state = IDLE;    // 20ms稳定,返回空闲态
            end else if (neg_edge_k) begin
                next_state = WAIT_R;  // 20ms内检测到按下,判定为抖动,返回等待释放态
            end
        end
    endcase
end

// --------------------------- 6. 20ms计时器(消抖核心) ---------------------------
reg [19:0] cnt;  // 计数器:50MHz时钟下,20ms = 50e6 * 0.02 = 1e6个时钟周期
wire time_20ms_reach;  // 20ms计时完成标志

// 计数器逻辑:仅在消抖态(P_FILTER/R_FILTER)计数,其他状态清零
always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        cnt <= 20'd0;
    end else begin
        case (curr_state)
            P_FILTER, R_FILTER: begin  // 按下/释放消抖态:计数
                if (time_20ms_reach) begin
                    cnt <= 20'd0;  // 计时完成,清零计数器
                end else begin
                    cnt <= cnt + 1'b1;
                end
            end
            default: cnt <= 20'd0;  // 其他状态:计数器清零
        endcase
    end
end

// 20ms计时完成判断(50MHz时钟:1e6个周期 = 20ms)
assign time_20ms_reach = (cnt >= 20'd1000000);

// --------------------------- 7. 输出标志信号(按下/释放) ---------------------------
// 按下标志:P_FILTER态计时完成时,输出单周期高脉冲
always @(posedge Clk or negedge Reset_n) begin
    if (Reset_n) begin
       Key_p_flag <= 1'b0;
    end else begin
        if (curr_state == P_FILTER && time_20ms_reach) begin
            Key_p_flag <= 1'b1;  // 按下稳定,置1
        end else if (curr_state == WAIT_R) begin
            Key_p_flag <= 1'b0;  // 进入等待释放态,拉低
        end
    end
end

// 释放标志:R_FILTER态计时完成时,输出单周期高脉冲
always @(posedge Clk or negedge Reset_n) begin
    if (!Reset_n) begin
        Key_r_flag <= 1'b0;
    end else begin
        if (curr_state == R_FILTER && time_20ms_reach) begin
            Key_r_flag <= 1'b1;  // 释放稳定,置1
        end else if (curr_state == IDLE) begin
            Key_r_flag <= 1'b0;  // 回到空闲态,拉低
        end
    end
end

endmodule

六、仿真验证与波形分析

Testbench设计

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: HZ
// 独立按键消抖设计与验证
// Create Date: 2026/01/01 17:16:22
// Design Name: 
// Module Name: key_ffilter
// Project Name: 
//////////////////////////////////////////////////////////////////////////////////
module key_filter_tb;
// --------------------------- 2. 信号定义 ---------------------------
reg Clk;         // 测试时钟
reg Reset_n;       // 测试复位
reg Key;         // 模拟按键输入
wire Key_p_flag; // 模块输出:按下标志
wire Key_r_flag; // 模块输出:释放标志
// --------------------------- 3. 实例化待测试模块 ---------------------------
key_filter uut (
    .Clk(Clk),
    .Reset_n(Reset_n),
    .Key(Key),
    .Key_p_flag(Key_p_flag),
    .Key_r_flag(Key_r_flag)
);
    initial Clk=1;
    always#10 Clk = ~Clk;
    
    initial begin
        Reset_n=0;
        Key=1;
        #201;
        Reset_n=1;
        
        //第一次按下测试
        //空闲稳定
        Key=1;
       #100000000//空闲 稳定 100ms
        
        //按下抖动
        Key=0; #18000000//按下 抖动 低18ms
        Key=1; #2000000//按下 抖动 高2ms
        Key=0; #1000000//按下 抖动 低1ms
        Key=1; #200000//按下 抖动 高0.2ms
        Key=0; #20000000//按下 抖动 低20ms
        
        //按下稳定
        Key=0; #50000000//按下 稳定 稳定50ms
        
        //释放抖动
        Key=1; #2000000//释放 抖动 高2ms
        Key=0; #1000000//释放 抖动 低1ms
        Key=1; #2000000//释放 稳定 高20ms
        
         //释放稳定
        Key=1; #50000000//释放 稳定 继续50ms
       
       //第二次按下测试
       //空闲稳定
       Key=1; #100000000//释放 稳定 继续100ms
       
       //按下抖动
        Key=0; #18000000//按下 抖动 低18ms
        Key=1; #2000000//按下 抖动 高2ms
        Key=0; #1000000//按下 抖动 低1ms
        Key=1; #200000//按下 抖动 高0.2ms
        Key=0; #20000000//按下 抖动 低20ms
        
       //按下稳定
        Key=0; #50000000//按下 稳定 稳定50ms
        
        //释放抖动
        Key=1; #2000000//释放 抖动 高2ms
        Key=0; #1000000//释放 抖动 低1ms
        Key=1; #2000000//释放 稳定 高20ms
        
         //释放稳定
        Key=1; #50000000//释放 稳定 继续50ms
        $stop;
       end
       
endmodule

七、仿真波形分析

在这里插入图片描述
在这里插入图片描述

key_p_flag 仅在低电平稳定20ms后产生单周期脉冲
key_r_flag 仅在高电平稳定20ms后产生单周期脉冲
key_state 输出干净无抖动的按键状态信号
状态机转换符合预期,抖动期间无错误标志输出

八、总结

本文系统介绍了基于状态机的按键消抖设计与实现,从原理分析、状态建模、代码实现到仿真验证,提供了一个完整、可靠且易于移植的解决方案。该设计具有以下优点:

✅ 结构清晰,状态转换明确

✅ 自适应抖动时长,可靠性高

✅ 输出干净的状态信号与标志脉冲

✅ 易于扩展为多按键或参数化设计

适用于FPGA、ASIC及高性能单片机系统中的按键输入处理,可直接用于实际项目。

FSM状态机不懂的同学可以看上一篇文章:

FPGA 学习必备:有限状态机(FSM)原理、编码方式与一段式 / 二段式 / 三段式详解

https://blog.csdn.net/2301_77513383/article/details/156517303?spm=1001.2014.3001.5501

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐