一、简介
按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点 的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。按键消抖就是在按键按下抖动最后一个下降沿延时20ms。
二、时序图和状态图
这里按键消抖可以用计数器做和状态机做时序图我只画了一个
按键消抖时序图
状态机状态转换图
三、程序
计数器消抖
module key_filter #(parameter KEY_W = 2,DELAY_TIME = 1_000_000)(
input clk ,
input rst_n ,
input [KEY_W-1:0] key_in ,
output reg [KEY_W-1:0] key_down
);
reg [19:0] cnt;
wire add_cnt;
wire end_cnt;
reg filter_flag;
reg [KEY_W-1:0] key_r0;
reg [KEY_W-1:0] key_r1;
reg [KEY_W-1:0] key_r2;
wire n_edge;
wire p_edge;
//对输入按键进行打拍,异步信号同步并且检测边沿
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= -1; //负数以补码形式存放,对源码取反再加1
key_r1 <= -1;
key_r2 <= -1;
end
else begin
key_r0 <= key_in; //同步
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
//三目运算符 检测上下沿
assign n_edge = ~key_r1 & key_r2?1'b1:1'b0;
assign p_edge = key_r1 & ~key_r2?1'b1:1'b0;
//计数器开始的标志信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
filter_flag <= 1'b0;
end
else if(n_edge)begin
filter_flag <= 1'b1;
end
else if(end_cnt || p_edge)begin
filter_flag <= 1'b0;
end
else begin
filter_flag <= filter_flag;
end
end
//按键消抖延时20ms计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt || p_edge)begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
else begin
cnt <= cnt;
end
end
assign add_cnt = filter_flag;
assign end_cnt = add_cnt && cnt == DELAY_TIME-1;
//按键输出
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_down <= 0;
end
else if(end_cnt)begin
key_down <= ~key_r2;
end
else begin
key_down <= 0;
end
end
endmodule
状态机消抖
/****************************************
-----------------------------------------
Component name:key_filter
Author :lcyyds
time :2021/12/7
Description :Mealy型三段式状态机按键消抖
src :
-----------------------------------------
****************************************/
module key_filter # (parameter KEY_W = 3,TIME_20MS = 1000000)(
input clk ,
input rst_n ,
input [KEY_W - 1:0] key_in ,
output [KEY_W - 1:0] key_out
);
// 参数定义
localparam IDLE = 4'b0001; //初始状态
localparam DOWN = 4'b0010; //按键按下抖动
localparam HOLD = 4'b0100; //按键按下后稳定
localparam UP = 4'b1000; //按键上升抖动
// 信号定义
reg [3:0] state_c ; //现态
reg [3:0] state_n ; //次态
// 状态转移条件定义
wire idle2down ;
wire down2idle ;
wire down2hold ;
wire hold2up ;
wire up2idle ;
reg [KEY_W - 1:0] key_r0 ; //同步
reg [KEY_W - 1:0] key_r1 ; //打拍
wire [KEY_W - 1:0] nedge ; //下降沿
wire [KEY_W - 1:0] pedge ; //上升沿
// 20ms计数器
reg [19:0] cnt_20ms ;
wire add_cnt_20ms ;
wire end_cnt_20ms ;
reg [KEY_W - 1:0] key_out_r; // 输出寄存
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态转换条件
assign idle2down = (state_c == IDLE) && nedge; // 检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge && ~end_cnt_20ms); // 计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms); // 计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up = (state_c == HOLD) && (pedge); // 检测到上升沿跳转到上升态
assign up2idle = (state_c == UP) && end_cnt_20ms; // 计数器计数到20ms跳转到初始态
//次状态
always@(*)begin
case(state_c)
IDLE: begin
if(idle2down)begin
state_n = DOWN;
end
else begin
state_n = state_c;
end
end
DOWN: begin
if(down2idle)begin
state_n = IDLE;
end
else if(down2hold)begin
state_n = HOLD;
end
else begin
state_n = state_c;
end
end
HOLD: begin
if(hold2up)begin
state_n = UP;
end
else begin
state_n = state_c;
end
end
UP: begin
if(up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:state_n = state_c;
endcase
end
// 20ms计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 0;
end
else if(add_cnt_20ms)begin
if(pedge || end_cnt_20ms)begin
cnt_20ms <= 0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP; // 当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && (cnt_20ms == TIME_20MS - 1); // 当计数到最大值或检测到上升沿计数器清零
// 同步打拍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {KEY_W{1'b1}};
key_r1 <= {KEY_W{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
assign nedge = ~key_r0 & key_r1; // 检测下降沿
assign pedge = key_r0 & ~key_r1; // 检测上升沿
// 按键赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out_r <= {KEY_W{1'b0}};
end
else if(hold2up) begin
key_out_r <= ~key_r1;
end
else begin
key_out_r <= {KEY_W{1'b0}};
end
end
assign key_out = key_out_r;
endmodule