基于FPGA的数字钟——(二)按键消抖模块
1、硬件原理
按键按下时会有随机的抖动,因此需要在抖动期过后再进行按键状态的判断。
2、原理实现
按键引脚检测电平跳变,跳变时进行计数,计数一定时间后再判断电平·高低以判断是抖动还是真实按下。检测按键放开同理。
3、FPGA实现
设计一个状态机,有以下几个状态:IDLE(放开),FILTER0(下降沿滤波),DOWN(按下),FILTER1(上升沿滤波)。
消抖计数器计数周期为50000个时钟周期,即50000*20=1000000ns=1ms
verilog代码
//
// Company: NanJing University of Information Science & Technology
// Engineer: Yang Cheng Yu
//
// Create Date: 2019/12/29 15:55:40
// Design Name: key_debounce
// Module Name: key_debounce
// Project Name: Clock
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module key_debounce
#(
parameter BEBON_CNT = 50000//消抖计数器
)
(
input clk,
input rst,
input key_in,
output reg key_press,
output reg key_up
);
reg[15:0] debounce_cnt;//1ms计数器
reg cnt_en;
reg cnt_full;
reg key_temp0;
reg key_temp1;
wire nedge;//按键下降沿
wire pedge;//按键上升沿
reg[3:0] state;
localparam IDLE = 4'b0001;
localparam FILTER0 = 4'b0010;
localparam DOWN = 4'b0100;
localparam FILTER1 = 4'b1000;
always@(posedge clk or negedge rst)begin
if(!rst)begin
key_temp0 <= 1'b1;
key_temp1 <= 1'b1;
end
else begin
key_temp0 <= key_in;
key_temp1 <= key_temp0;
end
end
assign nedge = (!key_temp0)&(key_temp1);
assign pedge = (key_temp0)&(!key_temp1);
always@(posedge clk or negedge rst)begin
if(!rst)begin
debounce_cnt <= 16'd1;
cnt_full <= 1'b0;
end
else
if(cnt_en)
if(debounce_cnt==BEBON_CNT)begin
debounce_cnt <= 16'd1;
cnt_full <= 1'b1;
end
else begin
debounce_cnt <= debounce_cnt + 1'b1;
cnt_full <= 1'b0;
end
else begin
debounce_cnt <= 16'd1;
cnt_full <= 1'b0;
end
end
always@(posedge clk or negedge rst)begin
if(!rst)begin
state <= IDLE;
end
else
case(state)
IDLE:begin
if(nedge)
state <= FILTER0;
else
state <= IDLE;
end
FILTER0:begin
if(cnt_full)
if(key_temp0==1'b0)//计时结束按键还按着
state <= DOWN;
else
state <= IDLE;
else
state <= FILTER0;
end
DOWN:begin
if(pedge)
state <= FILTER1;
else
state <= DOWN;
end
FILTER1:begin
if(cnt_full)
if(key_temp0==1'b1)//计数结束后按键还放开
state <= IDLE;
else
state <= DOWN;
else
state <= FILTER1;
end
default:
state <= IDLE;
endcase
end
always@(posedge clk or negedge rst)begin
if(!rst)begin
key_press <= 1'b0;
key_up <= 1'b1;
cnt_en <= 1'b0;
end
else
case(state)
IDLE:begin
key_press <= 1'b0;
key_up <= 1'b1;
cnt_en <= 1'b0;
end
FILTER0:begin
key_press <= 1'b0;
key_up <= 1'b1;
cnt_en <= 1'b1;
end
DOWN:begin
key_press <= 1'b1;
key_up <= 1'b0;
cnt_en <= 1'b0;
end
FILTER1:begin
key_press <= 1'b1;
key_up <= 1'b0;
cnt_en <= 1'b1;
end
default:begin
key_press <= 1'b0;
key_up <= 1'b1;
cnt_en <= 1'b0;
end
endcase
end
endmodule
状态机转移图
testbench代码
仿真时缩短了计数器的计数周期为500个clk,只是为了让仿真跑快一点,计数50000个clk仿真要很久。
`timescale 1ns/1ps
`define clock_period 20
module key_debonce_tb;
reg clk;
reg rst;
reg key_in;
wire key_press;
wire key_up;
key_debounce
#(
.BEBON_CNT(500)//消抖计数器
)
key_debounce
(
.clk(clk),
.rst(rst),
.key_in(key_in),
.key_press(key_press),
.key_up(key_up)
);
initial clk = 1;
always#(`clock_period/2)clk = ~clk;
initial begin
rst = 0;
key_in = 1;
#(`clock_period*20);
rst = 1;
key_in = 1;
#(`clock_period*100);
key_in = 0;
#(`clock_period*10);//一次抖动
key_in = 1;
#(`clock_period*500)
key_in = 0;
#(`clock_period*1000);//一次成功按下
key_in = 1;
#(`clock_period*1000);//一次成功放开
$stop;
end
endmodule