## 密码锁-----按键消抖模块详解
- 如上图所示为按键原理图,当按键不按下的时候为高电平,按键按下的时候为低电平。于是通过检测key信号电平,就可以判断按键状态。
但反作用弹簧会导致抖动现象,使得电平信号出现一段不确定波形
通过上图思考:如何判断是抖动?
一般情况下,抖动的电平信号为1的持续时间不会超过20ms,文中假定抖动为10ms,那么就可以通过对抖动波形进行计数,小于10ms的情况下,又出现了高电平或者低电平,就认为是一次抖动,否则高低电平的持续时间超过了10ms,那么才是真正的按键按下或者释放动作。 - 状态机实现按键消抖
2.1 状态分析
IDLE:空闲状态
F0:按下消抖状态
DOWN:按下稳定状态
F1:释放消抖状态
2.2 状态条件转换分析:
- 文中需要判断下降沿和上升沿,从而进行条件的设置,所以就要进行边沿检测。边沿检测一个直接的方法:设置两个寄存器,对前一状态和后一状态进行寄存,若前后两个状态不同,则检测到了边沿,对于上升沿还是下降沿的确定可以用组合逻辑比较来确定。若前一状态为高电平,后面状态为低电平,则为下降沿,反之为上升沿。下图为边沿检测的原理图:
按键消抖模块设计:
//10ms按键消抖
//IDLE 未按下时的空闲状态
//FILTER0 按下抖动滤除状态
//DOWN 按下稳定状态
//FILTER1 释放抖动滤除状态
module key_filter(clk,rst_n,key_in,key_state,key_p_flag,key_r_flag,key_flag);
input clk;
input rst_n;
input key_in;
wire nedge; //下降沿
wire pedge; //上升沿
reg key_in0,key_in1;
reg [3:0] state; //四种状态,空闲,按下消抖,释放,释放消抖
reg [23:0] cnt; //10ms
reg en_cnt;
reg cnt_full;
output reg key_p_flag; //按下标志
output reg key_r_flag; //释放标志
output key_flag; //按键切换的标志(有按键按下或者释放)
output reg key_state; //按键状态标志
assign key_flag = key_p_flag || key_r_flag;
localparam IDLE = 4'd0001;
localparam FILTER0 = 4'd0010;
localparam DOWN = 4'd0100;
localparam FILTER1 = 4'd1000;
//10ms计数器的设计
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 24'd0;
else if(en_cnt) //计数使能信号
cnt <= cnt + 1'b1;
else
cnt <= 24'd0;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_full <= 1'b0;
else if(cnt==24'd499999) //加满10ms则产生一个高脉冲
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
//用两级寄存器实现边沿检测
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_in0 <=1'b0;
key_in1 <=1'b0;
end
else begin
key_in0 <= key_in;
key_in1 <= key_in0;
end
end
assign nedge = ~key_in0 & key_in1;
assign pedge = key_in0 & !key_in1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n) begin
state <= IDLE;
en_cnt <= 1'b0;
key_p_flag <= 1'b0;
key_r_flag <= 1'b0;
key_state <= 1; //没按下的时候高电平
end
else begin
case(state)
IDLE:
begin
key_r_flag <= 1'b0;
if(nedge)begin
state <= FILTER0;
en_cnt <= 1'b1;
end
else
state <= IDLE;
end
FILTER0:
if(cnt_full)begin
key_p_flag <= 1'b1;
key_state <= 1'b0;
en_cnt <= 1'b0;
state <= DOWN;
end
else if(pedge)begin
state <= IDLE;
en_cnt <= 1'b0;
end
else
state <= FILTER0;
DOWN:
begin
key_p_flag <= 1'b0;
if (pedge)begin
state <= FILTER1;
en_cnt <= 1'b1;
end
else
state <= DOWN;
end
FILTER1:
if(cnt_full)begin
key_r_flag <= 1'b1;
key_state <= 1'b1;
state <= IDLE;
en_cnt <= 1'b0;
end
else if(nedge)begin
en_cnt <= 1'b0;
state <= DOWN;
end
else
state <= FILTER1;
default : state <= IDLE;
endcase
end
end
endmodule
tb测试
module key_filter_tb;
reg clk;
reg rst_n;
reg key_in;
wire key_flag;
wire key_state;
wire [3:0] state;
key_filter key_filter0(
.clk(clk),
.rst_n(rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_p_flag(key_p_flag),
.key_r_flag(key_r_flag),
.state(state),
.key_state(key_state)
);
initial clk= 1;
always#(`clk_period/2) clk = ~clk;
initial begin
rst_n = 1'b0;
key_in = 1'b1;
#(`clk_period*10) rst_n = 1'b1;
#(`clk_period*10 + 1); //与时钟边沿稍微延后1ns,减少门级仿真中亚稳态的产生
//模拟按下抖动 20ms 内
key_in = 1'b0;#1000;
key_in = 1'b1;#2000;
key_in = 1'b0;#1400;
key_in = 1'b1;#2600;
key_in = 1'b0;#1300;
key_in = 1'b1;#200;
key_in = 1'b0;#500100;
#50000100;
//产生一个低电平大于 20ms,代表按下稳定
//模拟释放抖动 20ms 内
key_in = 1'b1;#2000;
key_in = 1'b0;#1000;
key_in = 1'b1;#2000;
key_in = 1'b0;#1400;
key_in = 1'b1;#2600;
key_in = 1'b0;#1300;
key_in = 1'b1;#500100;
#50000100;
//产生一个高电平大于 20ms,代表释放稳定
//模拟按下抖动 20ms 内
key_in = 1'b0;#1000;
key_in = 1'b1;#2000;
key_in = 1'b0;#1400;
key_in = 1'b1;#2600;
key_in = 1'b0;#1300;
key_in = 1'b1;#200;
key_in = 1'b0;#500100;
#50000100;
//产生一个低电平大于 20ms,代表按下稳定
//模拟释放抖动 20ms 内
key_in = 1'b1;#2000;
key_in = 1'b0;#1000;
key_in = 1'b1;#2000;
key_in = 1'b0;#1400;
key_in = 1'b1;#2600;
key_in = 1'b0;#1300;
key_in = 1'b1;#500100;
#50000100;
//产生一个高电平大于 20ms,代表释放稳定
$stop;
end
endmodule
下降沿检测波形图:
上升沿检测波形图:
4. 按键消抖的测试
1ms = 1000000ns
在进行tb仿真的时候,需要下降沿或者上升沿持续至少10ms,(10ms = 10000000ns)(10000000 /20 = 500000) 所以就是至少#500000个周期才能得到真正的按键按下和释放
在tb里面加入状态的跳转切换进行测试