基于状态机的按键消抖模块

本次案例是按着小梅哥的思路来写的,部分截图和文字来自其教学视频。
这次设计的是一个能把按键信号输入转换为一个按键信号下降沿和上升沿检测输出。

1、状态机的设定

image
 

  • 空闲态:等待按键信号的下降沿,若出现则进入下一个状态。
  • 按下滤波状态:进行20ms的计时,若在20ms的计时内出现的上升沿则表示按键还在抖动,回到空闲态:否则进入下一个状态,并生成按键按下信号。
  • 等待释放状态:如果在该状态下出现上升沿信号进入释放滤波状态。
  • 释放滤波状态:进行20ms的计时,若在20ms的计时内出现的下降沿则表示按键还在抖动,回到等待释放状态:否则进入空闲态,并生成按键释放信号。
     

2、模块代码

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer:Lclone
// 
// Create Date: 2023/01/14 20:44:54
// Design Name: 
// Module Name: key_filter
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module key_filter(
    input           Clk,
    input           Rst_n,
    input           Key_in,
    output  reg    Key_press,
    output  reg    Key_release
    );
    
    reg [2:0]   key_sync;
    always @(posedge Clk or negedge Rst_n) begin
        if(Rst_n == 0)
            key_sync <= 0;
        else
            key_sync <= {key_sync[1:0],Key_in};//延迟3拍,减少亚稳态出现的概率的同时,还可以捕获上升沿和下降沿
    end
    
    reg key_nedge;
    reg key_pedge;
    always @(posedge Clk or negedge Rst_n) begin
        if(Rst_n == 0)
            begin
                key_nedge <= 0;
                key_pedge <= 0;
            end
        else if(key_sync[2:1] == 2'b10 )//下降沿捕获
            key_nedge <= 1'b1;
        else if(key_sync[2:1] == 2'b01 )//上降沿捕获
            key_pedge <= 1'b1;
        else
            begin
                key_nedge <= 0;
                key_pedge <= 0;
            end
    end
    
    parameter  CNT_20MS = 1_000_000;
    reg [19:0]  cnt_20ms;
    reg [ 1:0]  state;
    always @(posedge Clk or negedge Rst_n) begin
        if(Rst_n == 0)
            begin
                state <= 0;
                cnt_20ms <= 0;
                Key_press <= 0;
                Key_release <= 0;
            end
        else case(state)
            0:
                begin
                    Key_release <= 0;
                    if(key_nedge == 1)
                        state <= 1;
                    else
                        state <= 0;
                end
            1:
                begin
                    if(cnt_20ms < CNT_20MS & key_pedge == 1)
                        begin
                            state <= 0;
                            cnt_20ms <= 0;
                        end
                    else if(cnt_20ms == CNT_20MS - 1)
                        begin
                            state <= 2;
                            cnt_20ms <= 0;
                            Key_press <= 1;
                        end
                    else
                        cnt_20ms <= cnt_20ms + 1'b1;
                end
            2:
                begin
                    Key_press <= 0;
                    if(key_pedge == 1)
                        state <= 3;
                    else
                        state <= 2;
                end
            3:
                begin
                    if(cnt_20ms < CNT_20MS & key_nedge == 1)
                        begin
                            state <= 2;
                            cnt_20ms <= 0;
                        end
                    else if(cnt_20ms == CNT_20MS - 1)
                        begin
                            state <= 0;
                            cnt_20ms <= 0;
                            Key_release <= 1;
                        end
                    else
                        cnt_20ms <= cnt_20ms + 1'b1;
                end
            default:;
        endcase
    end
endmodule

3、仿真

(1)$random函数的使用

rand = {$random(seed)} % 10_000_000;

  • 表示生成0-9_999_999范围内的随机数并赋值给rand;
  • 1个seed数对应着1个组有特定顺序排列的随机数,可以通过设定seed的值来复现一组随机数;
  • 可以去掉花括号后使生成的范围变成(-9_999_999) - 9_999_999;

(2)仿真代码

`timescale 1ns / 1ps

module key_filter_tb();

reg clk_50m;
initial clk_50m <= 1;
always #10 clk_50m <= ~clk_50m;

reg rst_n;
initial begin
    rst_n <= 0;
    #200
    rst_n <= 1;
end

reg         key_in;
wire        key_press;
wire        key_release;
key_filter  key_filter_inst(
    .Clk                (clk_50m),
    .Rst_n              (rst_n),
    .Key_in             (key_in),
    .Key_press          (key_press),
    .Key_release        (key_release)    
    );
    
initial begin
    key_in <= 1;
    #400
    press_key(1);
    #20
    press_key(2);
    #20
    press_key(3);
end

reg [31:0]rand;
task press_key;
    input [3:0]seed;
    begin
        key_in = 1;
        #20_000_000;
        repeat(5)begin
            rand = {$random(seed)} % 10_000_000;
            #rand key_in = ~key_in;
        end
        
        key_in = 0;
        #40_000_000;
        
        repeat(5)begin
            rand = {$random(seed)} % 10_000_000;
            #rand key_in = ~key_in;
        end
        key_in = 1;
        #40_000_000;
    end
endtask
endmodule

(3)仿真结果

image
 
实验结果:每次按下都能准确发出Key_press和Key_release信号,实验初步验证成功。

4、参考文献

[1]【零基础轻松学习FPGA】小梅哥Xilinx FPGA基础入门到项目应用培训教程】  https://www.bilibili.com/video/BV1va411c7Dz/?p=20&share_source=copy_web&vd_source=c6135c3b3a9878c08e2ddc91acdf6853&t=0
posted @ 2023-01-15 00:18  Lclone  阅读(205)  评论(0编辑  收藏  举报