一、概述
我们在进行各种硬件开发时通常都会实现多效果的综合和实现,而在实际的开发中,每个时刻只会显示单个效果,这就需要涉及到效果之间的切换了,而要实现状态切换最好的就是使用按键进行按键,所以按键在我们的日常硬件开发中占据很大的作用。
二、相关理论
我们在使用按键之前,首先应该考虑到的就是按键的抖动的问题,所以在使用按键的过程中按键消抖的成功与否是确保最终结果的最关键的过程。以下是按键抖动过程中波形的具体示意图:
三、实现方法
在FPGA的按键使用过程中我们有两种方法对按键进行消抖,一种是直接采用计数器搭配打拍的方式进行按键的消抖,最后使用一个输出电平标志按键消抖的完成。另外一种是采用状态机的方式进行按键消抖的实现。我们这里分别从两种实现方法进行讨论。
1、计数器的方法
基本思路
最初波形图构造
其中的key_in就是按键的状态,代表按键按下与否,key_in_r是key_in打一拍之后的波形图,与key_in相比相位相差了一个周期。nedge就是下降沿触发的标志,也就是按键抖动的标志位,key_out是输出一个电平代表按键消抖成功。
2、状态机的方法
状态机的实现方法就是在计数器的转移条件之间增加了下降沿以及计数器进行搭配检测,实现状态机状态之间的转移 ,从而达到按键消抖的目的
最初波形图构造
这里就是比前面计数器多了一个现态和次态,以及上升沿标志,将整个按键从按下到释放划分成四个状态,通过各个条件转换成状态机状态转移的条件,通过状态的切换达到按键消抖。
四、代码编写
1、计数器实现
设计文件
//对一位按键输入进行消抖,最终输出消抖过后的按键信号,输出一个周期高电平表示按键按下一次
module key(
input clk,
input rst_n,
input key_in,
output reg key_out
);
//参数定义
parameter TIME_20MS = 1_000_000;
//内部信号定义
reg key_in_r;
reg [23:0] cnt;
wire add_cnt;
wire end_cnt;
wire nedge;
reg add_flag;
//检测抖动,对信号打一拍
always @(posedge clk or negedge rst_n )begin
if(!rst_n)
key_in_r<=1'b1;
else
key_in_r<=key_in;
end
assign nedge = !key_in && key_in_r;
//从抖动的地方开始计数,这里实现计数标志位的书写
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
add_flag<=0;
else if(nedge)
add_flag<=1;
else if(end_cnt)
add_flag<=0;
else
add_flag<=add_flag;
end
//开始计数,在计数结束时采样点平
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt<=0;
else if(add_cnt)begin
if(end_cnt)
cnt<=0;
else
cnt<=cnt+1;
end
else
cnt<=0;
end
assign add_cnt=add_flag;
assign end_cnt= (add_cnt) && (cnt==TIME_20MS-1);
//电平输出
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
key_out <= 0;
else if(end_cnt)
key_out <= 1'b1;
else
key_out <= 0;
end
endmodule
2、状态机实现
//状态机实现
module key (
input clk ,
input rst_n ,
input [ key_in , //输入原始的按键信号
output reg key_out //输出处理之后的按键信号
);
//参数定义
localparam IDLE = 4'b0001,//空闲
JITTLE0 = 4'b0010,//滤除第一次抖动
DOWN = 4'b0100,//稳定
JITTLE1 = 4'b1000;//滤除第二次抖动
parameter TIME_20MS = 1_000_000;//需要计数的值,20ms
//内部信号
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
reg [19:0] cnt_20ms;//计数20ms
wire add_cnt_20ms;
wire end_cnt_20ms;
wire nedge ;//下降沿信号
wire pedge ;//上升沿信号
reg key_in_r1 ;//打两拍 同步打拍
reg key_in_r2 ;
//状态转移 同步时序逻辑描述状态转移
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
state_c <= IDLE;
else
state_c <= state_n;
end
//状态转移条件 组合逻辑
always @(*) begin
case (state_c)//一定是case 现态
IDLE:begin
if(nedge)
state_n = JITTLE0;
else
state_n = state_c;
end
JITTLE0:begin
if(end_cnt_20ms)
state_n = DOWN;
else
state_n = state_c;
end
DOWN:begin
if(pedge)
state_n = JITTLE1;
else
state_n = state_c;
end
JITTLE1 :begin
if(end_cnt_20ms)
state_n = IDLE;
else
state_n = state_c;
end
default: state_n = IDLE;
endcase
end
//20ms计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_20ms <= 0;
else if(add_cnt_20ms)begin
if(end_cnt_20ms)
cnt_20ms <= 0;
else
cnt_20ms <= cnt_20ms + 1;
end
end
assign add_cnt_20ms = (state_c == JITTLE0) || (state_c == JITTLE1);
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
//下降沿 上升沿
//同步 打拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_in_r1 <= 1'b1;
key_in_r2 <= 1'b1;
end
else begin
key_in_r1 <= key_in; //同步按键输入信号
key_in_r2 <= key_in_r1; //打拍
end
end
//r1当前状态,r2上一个状态
assign nedge = ~key_in_r1 && key_in_r2;
assign pedge = key_in_r1 && ~key_in_r2;
//key_out
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
key_out <= 0;
else if(end_cnt_20ms &&(state_c== JITTLE1))
key_out <= 1'b1;//有效脉冲 20ns
else
key_out <= 0;
end
endmodule
3、测试文件
因为两种方法所需要实现的效果是一致的,所以这里我们采用同一个测试文件。
`timescale 1ns/1ns
module key_tb ;
reg clk ;
reg rst_n ;
reg key_in ;
wire key_out ;
defparam key_inst.TIME_20MS = 1000;
key key_inst(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key_in ), //输入原始的按键信号
.key_out (key_out ) //输出处理之后的按键信号
);
//激励信号产生
parameter CLK_CLY = 20;
//时钟
initial clk=1;
always #(CLK_CLY/2)clk=~clk;
//复位
initial begin
rst_n= 1'b0;
#(CLK_CLY*3);
#5;//复位结束避开时钟上升沿
rst_n= 1'b1;
end
//激励
integer i;
initial repeat(5)begin
key_in = 1;//模拟按键未按下
i ={$random}%6;//给i赋值0-5
#(CLK_CLY*500);//等待复位时间结束
#3;
repeat (3)begin
key_in = 0;//前按键抖动开始
#(CLK_CLY*1);
//一个5-10ms的抖动时间
repeat ((i+5)*50)begin
key_in = $random;
#(CLK_CLY*1);
end
key_in = 0;//按键稳定
#(CLK_CLY*100*50);
//后抖动开始
key_in = 1;
#(CLK_CLY*1);
repeat ((i+5)*50)begin
key_in = $random;
#(CLK_CLY*1);
end
key_in = 1;//按键稳定
#(CLK_CLY*10*500);
end
//模拟意外抖动
repeat (3)begin
repeat ((i+5)*50)begin
key_in = $random;
#(CLK_CLY*1);
end
key_in = 1;//按键稳定
#(CLK_CLY*500);
end
$stop;
end
endmodule
五、仿真波形图
1、计数器方法
2、状态机方法
通过仿真结果得知两种方法实现的波形图其实都是差不多的,就是第二种使用状态机的缘故导致波形图看着比第一种的负责一点,其实两种方法基本的实现原理都是一致的,只不过就是换了一种实现方法。在图中我们可以看到两种方法的key_out输出位置不一致,这是我们设置的输出条件有点小差别,这个不会影响最终结果。