基于FPGA的按键消抖
前言
按键消抖属于一个经典问题了,放一张图
大概就是根据经验,如果按键在20ms内没有变化则判定为按下。
一、按键状态机
本次是参考小梅哥的文档使用状态机写的按键消抖,状态转移图如下
二、按键消抖模块
1.模块框图
其中 :key_pose是在按键按下时刻产生一个时钟周期的高电平。
key_nege是在按键抬起时刻产生一个时钟周期的高电平。
key_out 是在按键消抖后的实际状态。
2.模块代码
开发平台:Vivado 2018.3
仿真平台:Modusim SE-64 10.5
开发平台:ALINX AX7Z020
代码如下:状态机的是按照野火的新式两段式状态机格式编写的
`timescale 1ns / 1ns
module key
#(
parameter CNT_20MS_MAX = 1_000_000
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_in ,
output reg key_pose ,
output reg key_nege ,
output reg key_out
);
reg key_in_reg1;
reg key_in_reg2;
wire key_in_pose;
wire key_in_nege;
reg key_out_reg;
reg cnt_en;
reg cnt_20ms_full;
reg [19:0] cnt_20ms;
reg [3:0] state;
localparam IDLE = 4'b0001,
DOWN_FILTER = 4'b0010,
DOWN = 4'b0100,
UP_FILTER = 4'b1000;
//将按键信号同步至系统时钟地域,同时打拍缓解亚稳态
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in_reg1 <= 1'b1;
else
key_in_reg1 <= key_in;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in_reg2 <= 1'b1;
else
key_in_reg2 <= key_in_reg1;
//捕获按键按下的上升沿,下降沿
assign key_in_nege = (~key_in_reg1)&&(key_in_reg2);
assign key_in_pose = (key_in_reg1)&&(~key_in_reg2);
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'd0;
else if((cnt_20ms == CNT_20MS_MAX - 1'b1) && (cnt_en == 1'b1))
cnt_20ms <= cnt_20ms;
else if((cnt_20ms != CNT_20MS_MAX - 1'b1) && (cnt_en == 1'b1))
cnt_20ms <= cnt_20ms + 1'b1;
else
cnt_20ms <= 20'd0;
//按键处于状态2、4时开启20ms定时器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_en <= 1'b0;
else if((state == DOWN_FILTER) || (state == UP_FILTER))
cnt_en <= 1'b1;
else
cnt_en <= 1'b0;
//按键记满将标志置1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms_full <= 1'b0;
else if(cnt_20ms == CNT_20MS_MAX - 2'd2)
cnt_20ms_full <= 1'b1;
else
cnt_20ms_full <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE : if(key_in_nege == 1'b1)
state <= DOWN_FILTER;
else
state <= IDLE;
DOWN_FILTER : if(cnt_20ms_full == 1'b1)
state <= DOWN;
else if(key_in_pose == 1'b1)
state <= IDLE;
else
state <= DOWN_FILTER;
DOWN : if(key_in_pose == 1'b1)
state <= UP_FILTER;
else
state <= DOWN;
UP_FILTER : if(cnt_20ms_full == 1'b1)
state <= IDLE;
else if(key_in_nege == 1'b1)
state <= DOWN;
else
state <= UP_FILTER;
default : state <= IDLE;
endcase
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_out <= 1'b1;
else if(state == DOWN)
key_out <= 1'b0;
else if(state == IDLE)
key_out <= 1'b1;
else
key_out <= key_out;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_out_reg <= 1'b1;
else
key_out_reg <= key_out;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_pose <= 1'b0;
else
key_pose <= ~key_out_reg & key_out;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_nege <= 1'b0;
else
key_nege <= key_out_reg & ~key_out;
endmodule
2.模块仿真代码
参考了野火的代码
`timescale 1ns / 1ns
module tb_key();
parameter CNT_1MS = 20'd19 ,
CNT_11MS = 21'd69 ,
CNT_41MS = 22'd149 ,
CNT_51MS = 22'd199 ,
CNT_60MS = 22'd249 ;
reg sys_clk;
reg sys_rst_n;
reg key_in;
wire key_pose;
wire key_nege;
wire key_out;
key
#(
.CNT_20MS_MAX(20'd25)
)
key_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n) ,
.key_in (key_in ) ,
.key_pose (key_pose ) ,
.key_nege (key_nege ) ,
.key_out (key_out )
);
reg [21:0] tb_cnt;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
key_in <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk =~ sys_clk;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tb_cnt <= 22'b0;
else if(tb_cnt == CNT_60MS)
tb_cnt <= 22'b0;
else
tb_cnt <= tb_cnt + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in <= 1'b1;
else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)
|| (tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
key_in <= {$random} % 2;
else if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS)
key_in <= 1'b0;
else
key_in <= 1'b1;
endmodule
三、总结(含文件)
工程文件点这里~~
提取码:bemh
黑金的板子到了,真滴帅,赶紧点个灯试试手。
打算基于黑金的板子,将以前做的仿真都重构下下。
但是最近事情有点多,进度缓慢,呜呜呜 >o<
此工程可以应用于实际项目,如果认为文章写的不错请点赞支持。
FPGA初学者,欢迎交流鸭。