1.补充方法原理
-
原理:仍然是采用延时方法
-
时序图如下
-
该时序图实现的是一位按键的情况。在使用消抖后按键信号的时候,可以在顶层调用消抖模块多次;也可以写多位按键消抖情况
2.补充方法源码及仿真
- 上文说到我们可能会需要多位按键消抖的情况,由于笔者常用的EP4CE6F17C8器件通常是四个按键,下文展示了4个按键的消抖模块
/**************************************功能介绍***********************************
Date :2023年7月27日10:42:24
Author : Alegg xy.
Version : 2.0
Description: 按键消抖(4个按键)
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module key_debounce (
input clk ,
input rst_n ,
input [3:0] key_in ,
output reg [3:0] key_out //输出脉冲信号
);
//---------<参数定义>---------------------------------------------------------
parameter TIME_20MS = 20'd1000_000;//20ms
//---------<内部信号定义>-----------------------------------------------------
reg [3:0] key_r0 ;//同步
reg [3:0] key_r1 ;//打两拍
reg [3:0] key_r2 ;
wire nedge ;//下降沿
reg flag ;//计数器计数的标志信号(按键按下抖动标志)
reg [19:0] cnt_20ms ;//20ms计数器
wire add_cnt_20ms ;
wire end_cnt_20ms ;
//****************************************************************
//--同步打拍
//****************************************************************
//同步,将key_in信号,同步到clk时钟域下面
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= 4'b1111;
end
else begin
key_r0 <= key_in;
end
end
//打两拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r1 <= 4'b1111;
key_r2 <= 4'b1111;
end
else begin
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
//****************************************************************
//--nedge
//****************************************************************
assign nedge = (~key_r1 & key_r2) != 0 ;//下降沿检测
//****************************************************************
//--cnt_20ms
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = flag;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
//****************************************************************
//--flag
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 'd0;
end
else if(nedge)begin
flag <= 1'b1;
end
else if(end_cnt_20ms)begin
flag <= 1'b0;
end
end
//****************************************************************
//--key_out
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out <= 4'd0000;
end
else if(end_cnt_20ms)begin
key_out <= ~key_r2;
end
else begin
key_out <= 4'b0000;
end
end
endmodule
- 仿真文件
`timescale 1ns/1ns
module tb_key_debounce();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
reg [3:0] tb_key_in ;
//输出信号定义
wire [3:0] tb_key_out ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
key_debounce u_key_debounce(
.clk (tb_clk ),
.rst_n (tb_rst_n ),
.key_in (tb_key_in ),
.key_out (tb_key_out )
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
defparam u_key_debounce.TIME_20MS = 100;
integer i,j;
//产生激励
initial begin
tb_rst_n = 1'b1;
tb_key_in = 4'b1111;
#(CLOCK_CYCLE*2);
tb_rst_n = 1'b0;
#(CLOCK_CYCLE*20);
tb_rst_n = 1'b1;
//****************************************************************
//--模拟按下key[0]
//****************************************************************
#1;
tb_key_in[0] = 1'b0;
for (j =0 ; j<8 ; j=j+1) begin//模拟抖动
i = {$random}%500;
#i;
tb_key_in[0] = i;
end
tb_key_in[0] = 1'b0;
wait(u_key_debounce.end_cnt_20ms);
#1000;
tb_key_in[0] = 1'b1;
//****************************************************************
//--模拟同时按下key[1]和key[2]
//****************************************************************
#1;
tb_key_in = 4'b1001;
for (j=0 ; j<8 ; j=j+1) begin
i = {$random}%500;
#i;
tb_key_in[1] = i;
tb_key_in[2] = i;
end
tb_key_in = 4'b1001;
wait(u_key_debounce.end_cnt_20ms);
#1000;
tb_key_in = 4'b1111;
#1000;
$stop;
end
endmodule
- 仿真效果
3.扩展方法:状态机实现消抖
-
状态转移图
-
代码
/**************************************功能介绍***********************************
Date : 2023年7月27日15:52:18
Author : Alegg xy.
Version : 3.0
Description: 状态机实现按键消抖
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module fsm_key_debounce(
input clk ,
input rst_n ,
input [3:0] key_in ,
output reg [3:0] key_out
);
//---------<参数定义>---------------------------------------------------------
parameter MAX_20ms = 20'd1_000_000;
reg [3:0] key_r0 ;
reg [3:0] key_r1 ;
reg [3:0] key_r2 ;
wire nedge ;
wire pedge ;
reg flag ;
reg [19:0] cnt_20ms ;
wire add_cnt_20ms ;
wire end_cnt_20ms ;
//---------<内部信号定义>-----------------------------------------------------
//同步,将key_in信号,同步到clk时钟域下面
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= 4'b1111;
end
else begin
key_r0 <= key_in;
end
end
//打两拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r1 <= 4'b1111;
key_r2 <= 4'b1111;
end
else begin
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
//****************************************************************
//--nedge和pedge定义
//****************************************************************
assign nedge = (~key_r1 & key_r2) != 0;//下降沿检测
assign pedge = (key_r1 & ~key_r2) != 0;//上升沿检测
//****************************************************************
//--cnt_20ms
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 19'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 19'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = flag;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == MAX_20ms - 1;
//****************************************************************
//--flag
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 'd0;
end
else if(nedge || pedge)begin
flag <= 1'b1;
end
else if(end_cnt_20ms)begin
flag <= 1'b0;
end
end
//****************************************************************
//--状态机
//****************************************************************
//状态机参数定义
localparam IDLE = 4'b0001,//初始状态
FILTTER_DOWN = 4'b0010,//按下抖动
HOLD_DOWN = 4'b0100,//稳定信号
FILTER_UP = 4'b1000;//松开抖动
reg [3:0] cstate ;//现态
reg [3:0] nstate ;//次态
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE :
if (nedge) begin
nstate = FILTTER_DOWN;
end
else begin
nstate = cstate;
end
FILTTER_DOWN :
if (end_cnt_20ms) begin
nstate = HOLD_DOWN;
end
else begin
nstate = cstate;
end
HOLD_DOWN :
if (pedge) begin
nstate = FILTER_UP;
end
else begin
nstate = cstate;
end
FILTER_UP :
if (end_cnt_20ms) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
default : nstate = cstate;
endcase
end
//第三段:描述输出,时序逻辑或组合逻辑皆可
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out <= 4'b0000;
end
else begin
case (cstate)
IDLE :key_out <= 4'b0000 ;
FILTTER_DOWN:key_out <= 4'b0000 ;
HOLD_DOWN :key_out <= ~key_r2 && pedge;
FILTER_UP :key_out <= 4'b0000 ;
default :key_out <= 4'b0000 ;
endcase
end
end
endmodule
-
代码理解的关键之处
1、4位按键信号赋值需要4位位宽的二进制数
2、上升沿和下降沿如何求值
3、何时计数开始
4、状态转换条件
5、如何赋值可以实现输出的脉冲信号
-
仿真文件
`timescale 1ns/1ns
module tb_fsm_key_debounce();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
reg [3:0] tb_key_in ;
//输出信号定义
wire [3:0] tb_key_out ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
defparam u_fsm_key_debounce.MAX_20ms = 10;
//模块例化
fsm_key_debounce u_fsm_key_debounce(
.clk (tb_clk ),
.rst_n (tb_rst_n ),
.key_in (tb_key_in ),
.key_out (tb_key_out )
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
//产生激励
initial begin
tb_rst_n = 1'b1;
tb_key_in = 4'b1111;
#(CLOCK_CYCLE*2);
tb_rst_n = 1'b0;
#(CLOCK_CYCLE*20);
tb_rst_n = 1'b1;
#7;
tb_key_in = 4'b1110;
#13;
tb_key_in = 4'b1111;
#8;
tb_key_in = 4'b1110;
#10;
tb_key_in = 4'b1111;
#5;
tb_key_in = 4'b1110;
#1000;
tb_key_in = 4'b1111;
#1000;
$stop;
end
endmodule
- 仿真效果