一、按键状态
按键未按下时:空闲态,FPGA的引脚通过上拉电阻置为高电平。
按键按下时:FPGA的引脚接地,变为低电平。
**在按键按下和释放的过程中:**会出现一段时间的抖动,可以通过示波器观察抖动时间,不超过20ms(如果是50M的晶振,就会经过多个时钟周期,前一个时钟是高电平,下一个可能就是低电平,也就是会出现抖动的现象,这是我们不想要的)
IDEL:空闲态
DOWN:按下状态
***消抖原理:***当按键按下时,检测到下降沿进入FILTER,设置FILTER时间(信号抖动的最大时间)为20ms,也就是说20ms内检测到的上升沿下降沿的抖动不计入引脚状态,直到抖动的最后一次一定是下降沿(因为按下是低电平),然后等待20ms时间到来,就认为信号稳定了,后面同理。
下图为时序逻辑图:
**边沿检测:**前一个时刻低电平,后一个时刻高电平,那么就一定有上升沿。
reg1记录前一个时刻的状态,reg0记录后一个时刻的状态。reg0为‘0’时,经取反之后为‘1’,reg1为‘1’时,与门(两者为1,输出才为1)输出为才为‘1’,检测到上升沿。
!是逻辑取反,将不是0的数变为0,0110->0
~是按位取反,每一个bit都取反,0110->1001
二、代码
在这里,可以查看状态图。
module key_filter(Rst_n,Clk,key_in,key_flag,key_state);
input Rst_n,Clk,key_in;
output reg key_flag,key_state;//在always@中进行赋值就要加上reg
localparam
IDEL =4'b0001,
FILTER0 =4'b0010,
DOWN =4'b0100,
FILTER1 =4'b1000;
reg [3:0]state; //表示上述的四种状态
//寄存器类型必须放在最前面
reg key_tmp0,key_tmp1; //边沿检测(前一个时刻高电平,下一个时刻低电平,就是出现了下降沿)
reg [19:0]cnt;
reg en_cnt; //使能计数器,检测到下降沿,开始计数
reg cnt_full; //计数满的标志位
wire pedge,nedge;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
assign nedge = !key_tmp0 & key_tmp1;// !是逻辑取反,将不是0的数变为0,0110->0
assign pedge = key_tmp0 & (!key_tmp1);// ~是按位取反,每一个bit都取反,0110->1001
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 :
if(nedge)begin
state <= FILTER0;
en_cnt <= 1'b1;
end
else
state <= IDEL;
FILTER0:
if(cnt_full)begin
state <= DOWN;
en_cnt <= 1'b0;
key_flag <= 1'b1;
key_state <= 1'b0;
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
en_cnt <= 1'b0;
state <= IDEL;
key_state <= 1'b1;
end
else if(nedge)
state <= DOWN;
//en_cnt <= 1'b0;
else
state <= FILTER1;
default: begin//独热编码只表示4种状态,还有其他状态。
state <= IDEL;
en_cnt <= 1'b0;
key_state <= 1'b1;
key_flag <= 1'b0;
end
endcase
end
//20ns的晶振
//20ms / 20ns = 1000000 共需要20bit
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 == 20'd999999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
endmodule
`timescale 1ns/1ps
`define clock_period 20
module key_filter_tb;
reg Rst_n;
reg Clk;
reg key_in;
wire key_flag;
wire key_state;
key_filter filter1(
.Rst_n(Rst_n),
.Clk(Clk),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk = 1'b1;
always#(`clock_period/2) Clk = ~ Clk;
initial begin
Rst_n = 1'b0;
key_in = 1'b0;
#(`clock_period*20+1) Rst_n = 1'b1;
#(`clock_period*20+1)
press_key;
#10000;
press_key;
#10000;
$stop;
end
//改进
reg [15:0]myrand;
task press_key;
begin
repeat(50)begin
myrand = {$random}%65536;//产生随机数
#myrand key_in = ~key_in;
end
key_in = 0;//按下时,最后状态为0
#50000000;//20ms内是抖动状态,20-50ms是按下状态
repeat(50)begin
myrand = {$random}%65536;//产生随机数
#myrand key_in = ~key_in;
end
key_in = 1;//释放时,最后状态为1
#50000000;
end
endtask
endmodule
三、结果图
四、仿真模型module
(1)module类似于一个函数,就是进行了一次封装。
(2)module中不写时钟和复位,时钟和复位写在tb文件中
(3)
(4)在仿真时,必须把自己写的module和tb文件都加上。
`timescale 1ns/1ps
module key_module(key);
output reg key;
initial begin
key = 1'b0; //在module中,不需要时钟和复位
press_key;
#10000;
press_key;
#10000;
$stop;
end
reg [15:0]myrand;
task press_key;
begin
repeat(50)begin
myrand = {$random}%65536;//产生随机数
#myrand key = ~key;
end
key = 0;//按下时,最后状态为0
#50000000;//20ms内是抖动状态,20-50ms是按下状态
repeat(50)begin
myrand = {$random}%65536;//产生随机数
#myrand key = ~key;
end
key = 1;//释放时,最后状态为1
#50000000;
end
endtask
endmodule
`timescale 1ns/1ps
`define clock_period 20
module key_module_tb;
reg Rst_n;
reg Clk;
//reg key_in;
wire key_in; //当key_in进行连接时,用wire类型
wire key_flag;
wire key_state;
key_filter filter1(
.Rst_n(Rst_n),
.Clk(Clk),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
key_module mod0(.key(key_in));
initial Clk = 1'b1;
always#(`clock_period/2) Clk = ~ Clk;
initial begin
Rst_n = 1'b0;
//key_in = 1'b0;
#(`clock_period*20+1) Rst_n = 1'b1;
#(`clock_period*20+1);
end
endmodule