可将按键按下整个过程看做四个状态:按键空闲状态,按下抖动状态,稳定按下状态,释放抖动状态。
代码实现:
/*
* @Description: 状态机方式按键消抖(多按键)
* @Author: Fu Yu
* @Date: 2023-07-27 15:03:36
* @LastEditTime: 2023-07-27 19:35:59
* @LastEditors: Fu Yu
*/
module keys_filter #(
parameter MAX_20ms = 20'd999_999,//20ms
WIDTH = 3
)(
input wire clk ,
input wire rst_n ,
input wire [WIDTH-1:0] key_in ,
output wire [WIDTH-1:0] key_down
);
//状态机参数定义
localparam IDLE = 4'b0001 ,//空闲状态
FILTER_DOWN = 4'b0010 ,//按键按下抖动状态
HOLD_DOWEN = 4'b0100 ,//按下按键稳定状态
FILTER_UP = 4'b1000 ;//按键释放抖动状态
//状态跳转条件定义
wire idle2filter_down ;//IDLE -> FILTER_DOWN
wire filter_down2hold_down ;//FILTER_DOWN -> HOLD_DOWN
wire hold_down2filter_up ;//HOLE_DOWN -> FILTER_UP
wire filter_up2idle ;//FILTER_UP -> IDLE
reg [19:0] cnt_20ms;
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
reg [WIDTH-1:0] key_r0;//寄存
reg [WIDTH-1:0] key_r1;//打拍
reg [WIDTH-1:0] key_r2;
reg [WIDTH-1:0] key_down_r;//寄存key_down
wire [WIDTH-1:0] nedge;//下降沿
wire [WIDTH-1:0] pedge;//上升沿
wire add_cnt_20ms;
wire end_cnt_20ms;
//****************************************************************
//--状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case (state_c)
IDLE : begin
if(idle2filter_down)begin
state_n = FILTER_DOWN;
end
else begin
state_n = state_c;
end
end
FILTER_DOWN : begin
if(filter_down2hold_down) begin
state_n = HOLD_DOWEN;
end
else begin
state_n = state_c;
end
end
HOLD_DOWEN : begin
if(hold_down2filter_up) begin
state_n = FILTER_UP;
end
else begin
state_n = state_c;
end
end
FILTER_UP : begin
if(filter_up2idle) begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default : state_n = IDLE;
endcase
end
assign idle2filter_down = state_c == IDLE && nedge;
assign filter_down2hold_down = state_c == FILTER_DOWN && end_cnt_20ms;
assign hold_down2filter_up = state_c == HOLD_DOWEN && pedge;
assign filter_up2idle = state_c == FILTER_UP && end_cnt_20ms;
//第三段:描述输出,时序逻辑或组合逻辑皆可
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
key_down_r <= 'b0;
end
else if(filter_down2hold_down) begin
key_down_r <= ~key_r2;
end
else begin
key_down_r <= 'b0;
end
end
assign key_down = key_down_r;
//****************************************************************
//--上升沿下降沿检测
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
assign nedge = ~key_r1 & key_r2;
assign pedge = ~key_r2 & key_r1;
//****************************************************************
//--20ms计数器
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_20ms <= 20'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms) begin
cnt_20ms <= 20'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'd1;
end
end
else begin
cnt_20ms <= cnt_20ms;
end
end
assign add_cnt_20ms = state_c == FILTER_DOWN || state_c == FILTER_UP;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == MAX_20ms;
endmodule //key_filte
测试文件:
`timescale 1ns/1ns
module keys_filter_tb ();
reg tb_clk;
reg tb_rst_n;
reg [2:0] tb_key_in;
wire [2:0] tb_key_down;
parameter CYCLE = 20;
defparam u_keys_filter.MAX_20ms = 20'd99;
always #(CYCLE) tb_clk = ~tb_clk;
integer i,j;
initial begin
tb_key_in = 3'b111;
tb_clk = 0;
tb_rst_n = 0;//开始复位
#(CYCLE*2);
tb_rst_n = 1;
#11;
tb_key_in[1] = 0;
for(j=0;j<8;j=j+1)begin
i = {$random}%500;
#i;
tb_key_in[1] = i;
end
tb_key_in[1] = 0;
wait(u_keys_filter.MAX_20ms);
#10000;
tb_key_in[1] = 1;
for(j=0;j<8;j=j+1)begin
i = {$random}%500;
#i;
tb_key_in[1] = i;
end
tb_key_in[1] = 1;
#1000;
$stop;
end
keys_filter #(
.WIDTH(3)
)u_keys_filter(
. clk (tb_clk) ,
. rst_n (tb_rst_n) ,
. key_in (tb_key_in) ,
. key_down (tb_key_down)
);
endmodule //keys_filter_tb
此方法简单,当检测到下降沿时,进行一次20ms计数,20ms计数过后直接检测稳定信号并输出。
代码实现:
/*
* @Description: 多位按键销抖
* @Author: Fu Yu
* @Date: 2023-07-27 11:05:30
* @LastEditTime: 2023-07-27 12:19:49
* @LastEditors: Fu Yu
*/
module key_filter #(
parameter WIDTH = 3,//WIDTH表示位宽
parameter MAX_20ms = 20'd999_999//20ms
)(
input wire clk ,
input wire rst_n ,
input wire [WIDTH - 1:0] key_in ,
output wire [WIDTH - 1:0] key_down
);
reg [19:0] cnt_20ms;//20ms计数器
reg [WIDTH-1:0] key_r0;//同步
reg [WIDTH-1:0] key_r1;//打两排
reg [WIDTH-1:0] key_r2;
reg [WIDTH-1:0] key_down_r;//寄存key_down信号
reg flag;//计数器计数标志
wire add_cnt_20ms;//开始计数信号
wire end_cnt_20ms;//结束计数信号
wire [WIDTH-1:0] nedge;//下降沿信号
//****************************************************************
//--同步、打两拍
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};//3'b111
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
//****************************************************************
//--下降沿检测
//****************************************************************
assign nedge = ~key_r1 & key_r2;
//****************************************************************
//--flag
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
flag <= 1'b0;
end
else if(nedge) begin//检测到下降沿时,开始计数
flag <= 1'b1;
end
else if(end_cnt_20ms)begin//计满20ms时,停止计数
flag <= 1'b0;
end
else begin
flag <= flag;
end
end
//****************************************************************
//--20ms计数器
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_20ms <= 20'd0;
end
else if(add_cnt_20ms) begin
if(end_cnt_20ms) begin
cnt_20ms <= 20'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'd1;
end
end
else begin
cnt_20ms <= cnt_20ms;
end
end
assign add_cnt_20ms = flag;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == MAX_20ms;
//****************************************************************
//--key_down赋值
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
key_down_r <= 'b0;
end
else if(end_cnt_20ms) begin
key_down_r <= ~key_r2;
end
else begin
key_down_r <= 'b0;
end
end
assign key_down=key_down_r;
endmodule //key_filter