【FPGA】实战之按键消抖

一、按键消抖的原理

一般开发板上的按键都是机械按键,所以在按下的时候,会产生回弹的时刻,称为机械的弹性开关,这导致你按键按下但不一定真的按下了,按键弹起来了但不一定真的弹起来了,所以我们常常能看到这样的两幅图片
1.第一幅图是实际波形图,产生这样的毛刺
在这里插入图片描述
2.第二幅图是理想中的毛刺图
在这里插入图片描述
当然专业的术语还是的得看专业的,我这里只是笼统地讲一讲。

二、按键消抖的设计思路

按键消抖,顾名思义,就是当按键不再抖动的时候,整体的设计思路就是下降沿出现开始计数20ms,如果在计数20ms中又出现了一个上升沿,则计数清零,等待下一个下降沿,然后接着计数,反复如此,直到在20ms内未出现上升沿,算是真正的稳定了,也就是所谓的按键消抖了。

所以我们需要下降沿或者上升沿的检测,就拿下降沿来说,下降沿前一个周期的信号为低电平,后一个周期的信号为高电平,所以我们需要一些逻辑去判断这个是否为下降沿。

//下降沿
assign	n_edge = key_r0 & ~key_r1;
//上升沿
assign	p_edge = ~key_r0 & key_r1;

这里的key_r1与key_r0是一个信号,但是相差了一个周期,你想想,边沿就是信号在一个周期后的变化。

所以这个时候要借助打拍器了

何为打拍器,我也不能很好的告诉你,以我现在的水平只能告诉你打拍器是为了信号的稳定性,因为信号从0到1其实是有一个小斜坡的,如下图
在这里插入图片描述
在小斜坡上的值叫压摆率,中间的状态叫亚稳态,如果只取按下去的key的当前信号和key前一个周期信号,作为你检测边沿的条件,万一正好取在亚稳态上就不对了,所以需要借助打拍器,确保信号的稳定性,让数据更可靠。
然后第一个打拍是为了同步时钟信号,因为按键是异步信号,所以需要一个第一个打拍,当时钟周期为上升沿的时候,进行同步
具体参考:为什么需要打拍器

我们这里打三拍,也可以打两拍
这里按下复位键取-1,其实就是相当于2’b11,简单写法

// 对输入按键进行打拍,异步信号同步并检测边沿
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        key_r0 <= -1;//负数以补码的方式存放,对原码取反加1
        key_r1 <= -1;
        key_r2 <= -1;
    end 
    else begin 
        key_r0 <= key_in;
        key_r1 <= key_r0;
        key_r2 <= key_r1;
    end 
end

这里定义一个filter_flag,来判断下降沿的,如果是下降沿为1,不是则为0

//当检测到下降沿,filter_flag为1
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        filter_flag <= 1'b0;
    end 
    //检测到下降沿
    else if(n_edge)begin 
        filter_flag <= 1'b1;
    end 
    else if(end_cnt)begin
        filter_flag <= 1'b0;
    end
    else begin 
        filter_flag <= filter_flag;
    end 
end

对上一个模块的filter_flag标志来判断是否开始计数,如果出现下降沿的filter_flag标志开始计数,如果在计数中出现上升沿,则立马清零,计数的时间为20ms

//当检测到filter_flga为1的时候开始计数
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt <= 0;
    end 
    else if(add_cnt)begin 
            if(end_cnt || p_edge)begin 
                cnt <= 0;
            end
            else begin 
                cnt <= cnt + 1;
            end 
    end
   else  begin
       cnt <= cnt;
    end
end 

assign add_cnt = filter_flag;
assign end_cnt = add_cnt && cnt == DELAY_TIME-1;

最后一个模块是key_down的值,用来取最后抖动消除后并且计数达到20ms后稳定的key值,取的是最后一个周期的key_r2的值
(我也不知道为啥取key_r2的值,反正取最后一个就对了)
往后你就可以用这个稳定的key_down的值来使用

//key_down取的值是最后当前周期的key_r2值,是个稳定的值
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        key_down <= 0;
    end 
    else if(end_cnt)begin 
        key_down <= ~key_r2;
    end 
    else begin 
        key_down <= 0;
    end 
end

三、代码部分

这里定义一个常量KEY_W是按键个数,以后直接改变这个KEY_W的值来改变按键个数即可

module key_filter #(parameter KEY_W = 2,DELAY_TIME = 1_000_000)( 
    input				                clk		,
    input				                rst_n	,
    input	            [KEY_W-1:0]	    key_in	,
    output	    reg 	[KEY_W-1:0]	    key_down
);	

//计数器
reg     [19:0]      cnt;
wire                add_cnt;
wire                end_cnt;

// 标志信号
reg                 filter_flag; 

reg      [KEY_W-1:0]  key_r0;      
reg      [KEY_W-1:0]  key_r1; 
reg      [KEY_W-1:0]  key_r2;    

wire                  n_edge;
wire                  p_edge;


// 对输入按键进行打拍,异步信号同步并检测边沿
// 打几拍就是延时几个周期
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        key_r0 <= -1;//负数以补码的方式存放,对原码取反加1
        key_r1 <= -1;
        key_r2 <= -1;
    end 
    else begin 
        key_r0 <= key_in;
        key_r1 <= key_r0;
        key_r2 <= key_r1;
    end 
end

// assign  n_edge = {!key_r1[1] && key_r2[1],!key_r1[0] && key_r2[0]};//第一种检测边沿
//三目运算符
assign  n_edge = ~key_r1 & key_r2?1'b1:1'b0;//第二种检测边沿
// assign  p_edge = {key_r1[1] && !key_r2[1],key_r1[0] && !key_r2[1]};
assign  p_edge = key_r1 & ~key_r2?1'b1:1'b0;


//当检测到下降沿,filter_flag为1
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        filter_flag <= 1'b0;
    end 
    //检测到下降沿
    else if(n_edge)begin 
        filter_flag <= 1'b1;
    end 
    else if(end_cnt)begin
        filter_flag <= 1'b0;
    end
    else begin 
        filter_flag <= filter_flag;
    end 
end

//当检测到filter_flga为1的时候开始计数
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt <= 0;
    end 
    else if(add_cnt)begin 
            if(end_cnt || p_edge)begin 
                cnt <= 0;
            end
            else begin 
                cnt <= cnt + 1;
            end 
    end
   else  begin
       cnt <= cnt;
    end
end 

assign add_cnt = filter_flag;
assign end_cnt = add_cnt && cnt == DELAY_TIME-1;

//key_down取的值是最后当前周期的key_r2值,是个稳定的值
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        key_down <= 0;
    end 
    else if(end_cnt)begin 
        key_down <= ~key_r2;
    end 
    else begin 
        key_down <= 0;
    end 
end
endmodule

四、仿真验证

1.成功消抖的波形图
在这里插入图片描述
2.未成功消抖的波形图(中间出现上升沿)
在这里插入图片描述

五、总结

这些就是我对按键消抖的理解,可能有很多不对的,但是思路应该是这样的一步一步地设计的,接下来我会用按键去实现别的功能。

  • 13
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值