一、按键抖动
按键在按下时,活动触点击打固定触点会有机械振动,因而造成输出波形抖动,因按键形态和触点材料的不同,抖动的过程一般会持续数毫秒,金属触点的按键可能达到10ms,而软性触点(如导电橡胶或薄膜)则可能在1ms以内甚至没有抖动。
按键去抖动的实用做法是定时查询,定时器资源往往是极易复用的,一般10ms左右的查询间隔对于用户按键也是足够的,用户操作按键不可能达到50次每秒,按下的持续时间也不可能短于10ms。
二、解决思路
学过数字电路基础的同学应该都熟悉D触发器的特性,其输入端D和输出端Q的值相隔一个时钟周期,那么将两个信号取出来做如图所示的运算,就可以检测到按键输入端电平变化为上升还是下降:
检测到上升沿或下降沿时,我们可以开始计时,当电平变化保持时间超过按键抖动的理论最长时间时,我们可以认为抖动结束,进入稳定的按下状态。同理,松开按键时,当电平变化保持时间超过按键抖动的理论最长时间时,可以认为进入稳定的弹开状态。由于要对抖动时间进行计时,所以代码中必定包含一个计数器。
三、状态转换图
按键未检测到电平变化时为第一个状态S0,我们命名为IDLE(空闲)。当检测到第一个下降沿到来时,开始计时并进入第二个状态S1,我们命名为FILTER0。
进入第二个状态后,我们检查计数器的值——如果计数器计满了10ms,则说明此时抖动已经结束,进入稳定的闭合状态S2,我们将其命名为DOWN;如果计数器没有计满,而是迎来了上升沿变化,则说明遇到了抖动,经历的低脉冲无效,那么状态就返回到最初的IDLE状态,重新等待下降沿的到来。
进入第三个状态 DOWN后,我们可以设置各个管脚在按键按下时应有的变化,例如点亮LED,或者发送一帧数据。同时我们等待上升沿的到来,当上升沿到来时,开始计数并进入第四个状态S3,我们将其命名为FILTER1。
进入第四个状态后,我们检查计数器的值,如果计满了10ms,则说明抖动已经结束,回到最初的IDLE状态,如果计数器没有计满,反而是迎来了下降沿,则说明发生了抖动,重新回到DOWN的按下状态,等待上升沿的到来。
状态图如下:
四、按键消抖完整代码
module key_filter(
clk, //50M时钟输入
rst_n, //模块复位
key_in, //按键输入
key_flag, //按键标志信号
key_state, //按键状态信号
led //led调试端口
);
input clk;
input rst_n;
input key_in;
output reg key_flag;
output reg key_state;
output wire [3:0] led;
localparam
IDEL = 4'b0001,
FILTER0 = 4'b0010,
DOWN = 4'b0100,
FILTER1 = 4'b1000;
reg [3:0]state; //状态
reg [19:0]cnt; //计数器
reg en_cnt; //使能计数寄存器
assign led = (state == DOWN)?4'b0000:4'b1111; //点灯调试
//对外部输入的异步信号进行同步处理
reg key_in_sa,key_in_sb;
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
key_in_sa <= 1'b0;
key_in_sb <= 1'b0;
end
else begin
key_in_sa <= key_in;
key_in_sb <= key_in_sa;
end
reg key_tmpa,key_tmpb;
wire pedge,nedge;
reg cnt_full;//计数满标志信号
//使用D触发器存储两个相邻时钟上升沿时外部输入信号(已经同步到系统时钟域中)的电平状态
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
key_tmpa <= 1'b0;
key_tmpb <= 1'b0;
end
else begin
key_tmpa <= key_in_sb;
key_tmpb <= key_tmpa;
end
//产生跳变沿信号
assign nedge = !key_tmpa & key_tmpb;
assign pedge = key_tmpa & (!key_tmpb);
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
en_cnt <= 1'b0;
state <= IDEL;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(state)
IDEL :
begin
key_flag <= 1'b0;
if(nedge)begin
state <= FILTER0;
en_cnt <= 1'b1;
end
else
state <= IDEL;
end
FILTER0:
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
en_cnt <= 1'b0;
state <= DOWN;
end
else if(pedge)begin
state <= IDEL;
en_cnt <= 1'b0;
end
else
state <= FILTER0;
DOWN:
begin
key_flag <= 1'b0;
if(pedge)begin
state <= FILTER1;
en_cnt <= 1'b1;
end
else
state <= DOWN;
end
FILTER1:
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
state <= IDEL;
end
else if(nedge)begin
en_cnt <= 1'b0;
state <= DOWN;
end
else
state <= FILTER1;
default:
begin
state <= IDEL;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
endmodule
五、仿真
由于抖动的波形书写起来比较麻烦,本人跳过了仿真这一步,直接进行板级验证,开发板上的LED可以按照预期的效果进行点亮。
六、小结
值得说明的是,前文说的10ms(100Hz)只是一般的情况,根据不同的机械按键特性应设置为合适的值,给出的代码设置的是20ms,即抖动频率为50Hz。在开发时应当根据应用场景来设定合适的延时值。