一、按键抖动原理
按键抖动原理:按键存在一个反作用弹簧,因此当按下或者松开时均会产生额外的物理抖动,物理抖动会产生电平的抖动。
消抖方法:一般情况下,抖动的总时间会持续20ms以内,按下按键后,等20ms过去了再取键值就行了。
二、第1种按键消抖
只对按下侧的抖动进行消除,弹起的就不管了,因为我们使用按键时要的也是按下后的键值。输出为1clk的按键值。
//======================================================================
// --- 名称 : key_filter
// --- 作者 : 木子
// --- 日期 : 2024-4-10
// --- 描述 : 按键消抖,输出为1个clk的输入,只关注按下侧的消抖
//======================================================================
module key_filter
//---------------------<参数定义>---------------------------------------
#(
parameter TIME_20MS = 1000000 , //20ms时间
parameter TIME_W = 20 , //20ms时间位宽
parameter KEY_W = 4 //按键个数
)
//---------------------<端口声明>---------------------------------------
(
input clk , //时钟,50Mhz
input rst_n , //复位,低电平有效
input [KEY_W-1:0] key , //按键输入
output reg [KEY_W-1:0] key_vld //按键消抖后的输出
);
//---------------------<信号定义>---------------------------------------
reg [TIME_W-1:0] cnt ;
wire add_cnt ;
wire end_cnt ;
reg [KEY_W -1:0] key_r0 ;
reg [KEY_W -1:0] key_r1 ;
reg flag ;
//----------------------------------------------------------------------
//-- 信号同步 + 消除亚稳态
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= 0;
key_r1 <= 0;
end
else begin
key_r0 <= key; //信号同步
key_r1 <= key_r0; //打拍,防亚稳态
end
end
//----------------------------------------------------------------------
//-- 20ms计时
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
else
cnt <= cnt;
end
assign add_cnt = flag==0 && key_r1!=0 ; //允许计数 且 按键按下
assign end_cnt = add_cnt && cnt==TIME_20MS-1; //计到20ms
//计满指示
always @(posedge clk or negedge rst_n)begin
if(!rst_n) //复位
flag <= 0; //flag=0允许计数
else if(end_cnt) //20ms到
flag <= 1; //flag=1不再计数
else if(key_r1==0) //按键松开
flag <= 0; //flag=0,为下次计数做准备
else //否则
flag <= flag; //维持自身
end
//----------------------------------------------------------------------
//-- 按键消抖完成,输出按键有效信号
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
key_vld <= 0;
else if(end_cnt) //20ms到
key_vld <= key_r1; //按键已消抖,可以使用
else
key_vld <= 0;
end
endmodule
下面是编写的仿真代码。由代码可以看到这里使用了task任务,用其定义一个完整的按下弹起的按键过程。 在task任务中,模拟抖动时采用了随机数发生函数来产生抖动。$random这一系统函数可以产生一个有符号的32位随机整数。一般的用法是“$random%b”,其中b > 0。这样就会生成一个范围在 [-(b-1),b-1] 中的随机数。如果只得到正数的随机数,这可采用“{$random}%b”来产生,这样就会生成一个方位在 [0,b-1] 中的随机数。
`timescale 1ns/1ps //时间精度
`define Clock 20 //时钟周期
module key_filter_tb;
//---------------------<端口声明>---------------------------------------
reg clk ;
reg rst_n ;
reg [3:0] key ;
wire [3:0] key_vld ;
//----------------------------------------------------------------------
//-- 模块例化
//----------------------------------------------------------------------
key_filter
#( //参数传递
.TIME_20MS (100 )
)
u_key_filter //模块例化
(
.clk (clk ),
.rst_n (rst_n ),
.key (key ),
.key_vld (key_vld )
);
//----------------------------------------------------------------------
//-- 时钟信号和复位信号
//----------------------------------------------------------------------
initial begin
clk = 1;
forever
#(`Clock/2) clk = ~clk;
end
initial begin
rst_n = 0; #(`Clock*20+1);
rst_n = 1;
end
//----------------------------------------------------------------------
//-- task函数编写,模拟按键抖动
//----------------------------------------------------------------------
reg [15:0] rand ;
task press_key;
begin
repeat(50) begin //50次按下随机时间抖动
rand = {$random}%70;
#rand;
key = ~key;
end
key = 4'b1001;
#10000;
repeat(50) begin //50次释放随机时间抖动
rand = {$random}%70;
#rand;
key = ~key;
end
key = 0;
#10000;
end
endtask
//----------------------------------------------------------------------
//-- 设计输入信号
//----------------------------------------------------------------------
initial begin
#1;
key = 0 ; #(`Clock*20+1); //初始化完成
press_key; #10000;
press_key; #10000;
press_key; #10000;
$stop;
end
endmodule
仿真波形如下:
三、按键消抖二
按下和弹起的抖动都消除掉。
//======================================================================
// --- 名称 : key_filter
// --- 作者 : 木子
// --- 日期 : 2024-4-11
// --- 描述 : 按键消抖,输出为消抖后的输入,计数器一直在工作
//======================================================================
module key_filter
//---------------------<参数定义>---------------------------------------
#(
parameter TIME_20MS = 1000000 , //20ms时间
parameter TIME_W = 20 , //20ms时间位宽
parameter KEY_W = 4 //按键个数
)
//---------------------<端口声明>---------------------------------------
(
input clk , //时钟,50Mhz
input rst_n , //复位,低电平有效
input [KEY_W-1:0] key , //按键输入
output reg [KEY_W-1:0] key_vld //消抖后的按键输出
);
//---------------------<信号定义>---------------------------------------
reg [TIME_W-1:0] cnt ;
wire add_cnt ;
wire end_cnt ;
reg [KEY_W -1:0] key_r0 ;
reg [KEY_W -1:0] key_r1 ;
reg [KEY_W -1:0] key_r2 ;
wire key_press ;
//----------------------------------------------------------------------
//-- 边沿检测
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= 0;
key_r1 <= 0;
key_r2 <= 0;
end
else begin
key_r0 <= key; //信号同步
key_r1 <= key_r0; //打拍,防亚稳态
key_r2 <= key_r1;
end
end
assign key_press = key_r1 ^ key_r2; //按键状态变化检测
//----------------------------------------------------------------------
//-- 20ms计时
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
else
cnt <= cnt;
end
assign add_cnt = 1 ; //一直处于计数状态
assign end_cnt = key_press || (cnt== TIME_20MS-1); //按键仍在抖动或计到了20ms,则清0
//----------------------------------------------------------------------
//-- 按键消抖完成,输出按键有效信号
//----------------------------------------------------------------------
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
key_vld <= 0;
else if(cnt==TIME_20MS-1) //cnt计到20ms
key_vld <= key_r2; //按键已消抖,可以使用
else
key_vld <= key_vld;
end
endmodule
仿真代码与第一种略有不同。采用仿真模型进行仿真。
实现代码:
`timescale 1ns/1ps //时间精度
`define Clock 20 //时钟周期
module key_filter_tb;
//---------------------<端口声明>---------------------------------------
reg clk ;
reg rst_n ;
wire [3:0] key ; //本是输入现在变成了内部信号,故改成wire型
wire [3:0] key_vld ;
//----------------------------------------------------------------------
//-- 模块例化
//----------------------------------------------------------------------
//按键消抖仿真模型
key_module u_key_module
(
.key (key )
);
//按键消抖设计文件
key_filter
#( //参数传递
.TIME_20MS (100 )
)
u_key_filter //模块例化
(
.clk (clk ),
.rst_n (rst_n ),
.key (key ),
.key_vld (key_vld )
);
//----------------------------------------------------------------------
//-- 时钟信号和复位信号
//----------------------------------------------------------------------
initial begin
clk = 1;
forever
#(`Clock/2) clk = ~clk;
end
initial begin
rst_n = 0; #(`Clock*20+1);
rst_n = 1;
end
endmodule
//======================================================================
//--名称 : key_module
//--作者 : 木子
//--日期 : 2024-4-11
//--描述 : key按键消抖模块的仿真模型
//======================================================================
`timescale 1ns/1ps
module key_module
//---------------------<端口声明>---------------------------------------
(
output reg [15:0] key
);
//----------------------------------------------------------------------
//-- task函数编写,模拟按键抖动
//----------------------------------------------------------------------
reg [15:0] rand ;
task press_key;
begin
repeat(50) begin //50次按下随机时间抖动
rand = {$random}%70;
#rand;
key = ~key;
end
key = 4'b1001;
#10000;
repeat(50) begin //50次释放随机时间抖动
rand = {$random}%70;
#rand;
key = ~key;
end
key = 0;
#10000;
end
endtask
//----------------------------------------------------------------------
//-- 设计输入信号
//----------------------------------------------------------------------
initial begin
#1;
key = 0 ; #401; //初始化完成
press_key; #10000;
press_key; #10000;
press_key; #10000;
$stop;
end
endmodule
仿真波形: