一、按键抖动现象
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
按键抖动的时间一般为5~10ms,不会超过20ms,软件消抖的方案通常采用延时的方法对按键进行消抖,等待按键抖动结束后,再判断按键的状态。
二、按键抖动与亚稳态问题解决方案

以上图电路为例,按键未按下时,连接3.3V电源,当按键按下时,IO口接地,即按键按下为低电平,按键未按下为高电平。因此IO口检测到下降沿时,按键按下,检测到上升沿时,按键松开。

如图所示,根据D触发器的特性,输入信号和输出信号间隔一个时钟周期,可将按键Key值输入D触发器,并将其与Q值(按键前一状态值Key_pre)做逻辑运算,即可判断按键的上升沿和下降沿。
当Key=0,Key_pre=1时,为下降沿nedge;当Key=1,Key_pre=0时,为上升沿pedge。
亚稳态问题是指触发器无法在某个规定时间段内达到一个可确认的状态。当一个触发器进入亚稳态时,既无法预测该单元的输出电平,也无法预测何时输出才能稳定在某个正确的电平上。在这个稳定期间,触发器输出一些中间级电平,或者可能处于振荡状态,并且这种无用的输出电平可以沿信号通道上的各个触发器级联式传播下去。按键在抖动过程中如果出现亚稳态问题,则无法正确判断下降沿和上升沿,就无法正确判断按键是否按下。
亚稳态不能从根本上消除,但是可以采取一定的措施,降低其对电路的影响。可通过改进上述电路,将一级D触发器改成两级D触发器,以应对亚稳态问题。

三、按键消抖模块设计
1.设计思路
模块端口说明:
| 端口名称 | 方向 | 说明 |
|---|---|---|
| Clk | input | 系统时钟 |
| Rst_n | input | 系统复位 |
| key_in | input | 按键输入端 |
| key_flag | output | 按键消抖标志 |
| key_state | output | 按键状态标志 |
消抖完成时,key_flag置1,若为按键按下消抖,则|key_state=0,表示按键已按下,反之表示按键已抬起。
可利用状态机实现按键消抖过程,状态定义如下:
S0:空闲状态,按键未按下
S1:按键按下,抖动滤波
S2:按键稳定按下
S3:按键抬起,抖动滤波
状态转换图:

2.按键消抖代码设计
(1)亚稳态问题应对与上升沿下降沿判断
定义3位宽的寄存器key_sync_reg,key_sync_reg[0]为按键输入key_in,key_sync_reg[1]为第一级D触发器的Q端,key_sync_reg[2]为第二级D触发器的Q端,即key_pre。定义wire型的pedge和nedge,根据上述电路计算上升沿和下降沿。
reg [2:0]key_sync_reg;
//二级寄存器解决亚稳态问题, 2:key_pre 1:key_reg 0:key_in
wire pedge,nedge;//定义上升沿下降沿
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
key_sync_reg <= 3'd0;
else
key_sync_reg <= {key_sync_reg[1:0],key_in};
assign pedge = key_sync_reg[2:1] == 2'b01;
assign nedge = key_sync_reg[2:1] == 2'b10;
(2)20ms计数器
在cnt_en被使能时,开始计数;20ms计数完成,dly_done置1,状态机根据dly_done跳转到下一状态。
reg dly_done,cnt_en;//延时完成标志,计数器使能
reg [19:0]cnt;//计数器
localparam cnt_max = 20'd999_999;//20ms
always@(posedge Clk or negedge Rst_n)//计数器
if(!Rst_n) begin
cnt <= 20'd0;
dly_done <= 1'b0;
end
else if(cnt_en) begin
if(cnt == cnt_max) begin//计时20ms
dly_done <= 1'b1;
cnt <= 20'd0;
end
else
cnt <= cnt + 1'b1;
end
else begin
cnt <= 20'd0;
dly_done <= 1'b0;
end
(3)按键消抖状态机
由S0跳转到S1时,使能cnt_en,在S1状态根据dly_done判断20ms延时是否结束,若结束,按键消抖标志置1,跳转到S2状态,完成按键按下消抖。按键抬起消抖过程类似。
always@(posedge Clk or negedge Rst_n)
if(!Rst_n) begin
state <= S0;
key_flag <= 1'b0;
cnt_en <= 1'b0;
key_state <= 1'b1;
end
else
case(state)
S0: begin
key_flag <= 1'b0;
if(nedge) begin
state <= S1;
cnt_en <= 1'b1;
end
else
state <= S0;
end
S1:
if(dly_done) begin
state <= S2;
cnt_en <= 1'b0;
key_flag <= 1'b1;
key_state <= 1'b0;//按键按下为低电平
end
else if(pedge) begin
state <= S0;
cnt_en <= 1'b0;
end
else
state <= S1;
S2: begin
key_flag <= 0;//抖动滤除完成,标志清零
if(pedge) begin
state <= S3;
cnt_en <= 1'b1;
end
else
state <= S2;
end
S3:
if(dly_done) begin
state <= S0;
cnt_en <= 1'b0;
key_flag <= 1'b1;
key_state <= 1'b1;
end
else if(nedge) begin
state <= S2;
cnt_en <= 1'b0;
end
else
state <= S3;
default: begin
state <= S0;
key_flag <= 1'b0;
cnt_en <= 1'b0;
key_state <= 1'b1;
end
endcase
按键消抖模块源代码
//本模块用于实现按键消抖与检测,按下为低电平
module key_filter(
input Clk, //系统时钟
input Rst_n, //系统复位
input key_in, //按键输入
output reg key_flag, //按键消抖标志
output reg key_state //按键状态标志
);
reg [1:0]state;
localparam
S0 = 2'd0, //空闲状态,按键未按下
S1 = 2'd1, //按键按下,抖动滤波
S2 = 2'd2, //按键稳定按下
S3 = 2'd3; //按键抬起,抖动滤波
reg [2:0]key_sync_reg;
//二级寄存器解决亚稳态问题, 2:key_pre 1:key_reg 0:key_in
wire pedge,nedge;//定义上升沿下降沿
reg dly_done,cnt_en;//延时完成标志,计数器使能
reg [19:0]cnt;//计数器
localparam cnt_max = 20'd999_999;//20ms
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
key_sync_reg <= 3'd0;
else
key_sync_reg <= {key_sync_reg[1:0],key_in};
assign pedge = key_sync_reg[2:1] == 2'b01;
assign nedge = key_sync_reg[2:1] == 2'b10;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n) begin
state <= S0;
key_flag <= 1'b0;
cnt_en <= 1'b0;
key_state <= 1'b1;
end
else
case(state)
S0: begin
key_flag <= 1'b0;
if(nedge) begin
state <= S1;
cnt_en <= 1'b1;
end
else
state <= S0;
end
S1:
if(dly_done) begin
state <= S2;
cnt_en <= 1'b0;
key_flag <= 1'b1;
key_state <= 1'b0;//按键按下为低电平
end
else if(pedge) begin
state <= S0;
cnt_en <= 1'b0;
end
else
state <= S1;
S2: begin
key_flag <= 0;//抖动滤除完成,标志清零
if(pedge) begin
state <= S3;
cnt_en <= 1'b1;
end
else
state <= S2;
end
S3:
if(dly_done) begin
state <= S0;
cnt_en <= 1'b0;
key_flag <= 1'b1;
key_state <= 1'b1;
end
else if(nedge) begin
state <= S2;
cnt_en <= 1'b0;
end
else
state <= S3;
default: begin
state <= S0;
key_flag <= 1'b0;
cnt_en <= 1'b0;
key_state <= 1'b1;
end
endcase
always@(posedge Clk or negedge Rst_n)//计数器
if(!Rst_n) begin
cnt <= 20'd0;
dly_done <= 1'b0;
end
else if(cnt_en) begin
if(cnt == cnt_max) begin//计时20ms
dly_done <= 1'b1;
cnt <= 20'd0;
end
else
cnt <= cnt + 1'b1;
end
else begin
cnt <= 20'd0;
dly_done <= 1'b0;
end
endmodule
四、仿真
1.按键抖动模拟
将模拟按键抖动过程封装成task任务,在仿真时,只需要调用任务,可提高代码的简洁性和可阅读性。
按键抖动过程利用repeat和random随机函数模拟,random随机函数是为了模拟按键抖动过程中高低电平的随机时间,抖动模拟代码如下:
task key_press();//模拟按键按下
reg [23:0]rand;
begin
repeat(5) begin
key_in = 1'b0;
rand = {$random} % 1001;
#(rand * 2000);
key_in = 1'b1;
#(rand * 2000);
end
key_in = 1'b0;
#40_000_000;
repeat(5) begin
key_in = 1'b1;
rand = {$random} % 1001;
#(rand * 2000);
key_in = 1'b0;
#(rand * 2000);
end
key_in = 1'b1;
#30_000_000;
end
endtask
2.仿真文件
在仿真时,直接调用封装好的按键抖动任务,模拟按键按下过程。
`timescale 1ns/1ns
module key_filter_tb();
reg Clk,Rst_n,key_in;
wire key_flag,key_state;
key_filter key_filter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk = 1'b1;
always #10 Clk = ~Clk;
initial begin
Rst_n = 1'b1;
key_in = 1'b1;
#201;
key_press();
$stop;
end
3.仿真结果

210

被折叠的 条评论
为什么被折叠?



