基于状态机的按键消抖设计与Verilog实现:原理、状态转移与仿真验证
概要
本文将系统介绍一种基于摩尔型状态机的按键消抖硬件设计方案。文章从机械按键的抖动原理出发,深入分析传统消抖方法的局限性,重点阐述为何选择摩尔型状态机——因其输出稳定、状态明确、抗干扰性强,特别适合按键消抖场景。通过四个核心状态(空闲态、按下消抖态、等待释放态、释放消抖态)的划分和状态转移图绘制,结合完整的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
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)