文章目录
一、按键消抖的原理
一般开发板上的按键都是机械按键,所以在按下的时候,会产生回弹的时刻,称为机械的弹性开关,这导致你按键按下但不一定真的按下了,按键弹起来了但不一定真的弹起来了,所以我们常常能看到这样的两幅图片
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.未成功消抖的波形图(中间出现上升沿)
五、总结
这些就是我对按键消抖的理解,可能有很多不对的,但是思路应该是这样的一步一步地设计的,接下来我会用按键去实现别的功能。