本文参考小梅哥的独立按键消抖视频
1,实验原理:
这里是黑金开发板教程中的图,可以看出,按键未按下时的状态是高电平,按下为低电平。下边是小梅哥画的图解。
因为是机械按键,按下时候有一个不稳定的抖动期,这个时间大概在20ms以内。
2,设计思路:利用状态机实现独立按键的消抖
- 未按下时空闲状态
- 按下抖动滤除状态
- 按下稳定状态
- 释放抖动滤除状态
在第一个状态时,等待按键按下,一旦有按键按下(按键下降沿到来),便跳转到第二个状态,抖动滤除状态。在第二个状态,有检测到高电平(上升沿),就会被认为是毛刺,进而返回第一个状态继续等待下降沿。等毛刺被滤除后(计数满)则进入按下稳定状态。在按下稳定状态,等待释放(上升沿),同按键按下的状态,再次滤除释放按键的抖动。等抖动滤除后,恢复到第一个状态。
配个图吧。聊胜于无。
这段话可能有些啰嗦,结合代码看会比较清楚。当然这段话并没有牵扯到计数器的关闭与开启,会在后面代码注释中详细说明。
3,仿真图
注意看state的第一个状态,是0001,这个是第一个下降沿等待状态。后面才开始有了状态的跳变。每一个计数满就意味着一次滤波的完成。第一次代表按下抖动释放,第二次代表松开抖动释放。
仿真图很重要!!!可以帮助理解
RTL图可以自己去看,对应代码还是很清楚的。
4,代码
module filter(clk,rst_n,key_in,key_flag,key_state
);
//端口声明
input clk; //时钟50MHz
input rst_n; //复位信号
input key_in; //按键输入
output key_state; //按键状态,高电平为未按下,低电平为按下状态
output key_flag; /*完成滤波信号(消抖后的按键),这里有很有趣的一件事,我们在生活中发现,有些按键是按下时产生效果的,有些是按下松开后起作用的,在这段代码中,依据这个信号来产生*/
//定义
parameter IDLE = 4'b0001,
FILTER0 = 4'b0010,
DOWN = 4'b0100,
FILTER1 = 4'b1000;
//内部信号声明
reg [3:0] state;
reg key_flag;
reg key_state;
reg cnt_full;
reg [19:0] cnt;
reg en_counter;
//边沿检测模块,将输入信号寄存一个节拍,分别按键上升沿和下降沿的产生
reg key_tmp0,key_tmp1;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
key_tmp0 <= 1'b1;
key_tmp1 <= 1'b1;
end
else
begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
end
wire pedge,nedge;
assign nedge = (!key_tmp0) & key_tmp1; //下降沿
assign pedge = key_tmp0 & (!key_tmp1); //上升沿
//状态机模块
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
state <= IDLE;
en_counter <= 1'b0;
key_state <= 1'b1;
key_flag <= 1'b0;
end
else
begin
case(state)
IDLE:
begin
key_flag <= 1'b0;
key_state <= 1'b1;
en_counter <= 1'b0;
if(nedge) //检测到下降沿,进入下一个状态同时打开计数器
begin
state <= FILTER0;
en_counter <= 1'b1;
end
else
state <= state;
end
FILTER0:
if(cnt_full) //计数满,说明达到稳定状态,关闭计数器
begin
state <= DOWN;
en_counter <= 1'b0;
key_flag <= 1'b1;
key_state <= 1'b0;
end
else if(pedge) //检测到上升沿(毛刺),跳回idle状态同时关闭计数器
begin
en_counter <= 1'b0;
state <= IDLE;
end
else
state <= state;
DOWN:
begin
key_flag <= 1'b0;
if(pedge)
begin
state <= FILTER1;
en_counter <= 1'b1;
end
else
state <= DOWN;
end
FILTER1:
if(cnt_full)
begin
state <= IDLE;
//key_flag <= 1'b1;
key_state <= 1'b0;
end
else
if(nedge)
begin
en_counter <= 1'b0;
state <= DOWN;
end
else
state <= state;
default:
state <= IDLE;
endcase
end
end
//20ms计数器
//这里有一个计数使能信号,只有当计数使能为高电平的时候,计数器才会计数,数数到999_999计数满时间到
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= 20'd0;
else if(en_counter)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end
//计数满信号
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_full <= 1'b0;
else if(cnt == 20'd999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
endmodule
5,仿真代码
代码就直接贴上来了,理解应该没什么问题,这里的毛刺产生笔者随便模拟产生,没有用视频中讲的随机数产生函数。仿真时间比较长,不要着急。
module tb_filter;
// Inputs
reg clk;
reg rst_n;
reg key_in;
// Outputs
wire key_flag;
wire key_state;
// Instantiate the Unit Under Test (UUT)
filter uut (
.clk(clk),
.rst_n(rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
always #10 clk = ~clk;
initial begin
// Initialize Inputs
clk = 1'b0;
rst_n = 1'b0;
key_in = 1'b1;
// Wait 100 ns for global reset to finish
#200;
rst_n = 1'b1;
#(20*10);
key_in = 1'b0;
#1000;
key_in = 1'b1;
#140;
key_in = 1'b0;
#2000;
key_in = 1'b1;
#3000;
key_in = 1'b0;
#100;
key_in = 1'b1;
#20;
key_in = 1'b0;
#(20*10000000);
key_in = 1'b1;
#1000;
key_in = 1'b0;
#140;
key_in = 1'b1;
#2000;
key_in = 1'b0;
#3000;
key_in = 1'b1;
#100;
key_in = 1'b0;
#20;
key_in = 1'b1;
// Add stimulus here
end
endmodule
6,小总结
这里的按键消抖其实更多的是用来理解运用状态机的,具体个人感觉并不实用。要是多个按键就必须每一个按键分别调用模块,资源多,小点的还好,一旦太多,实例化就很麻烦了。所以真正可以用的代码我将在下一篇博客里分享。其次就是这里key_flag信号很有意思。仿真图很重要!!!可以帮助理解!仿真图很重要!!!可以帮助理解!仿真图很重要!!!可以帮助理解!