按键消抖实现

一、使用状态机实现按键消抖

按键消抖实现_第1张图片
可将按键按下整个过程看做四个状态:按键空闲状态,按下抖动状态,稳定按下状态,释放抖动状态。
代码实现:

/*
 * @Description: 状态机方式按键消抖(多按键)
 * @Author: Fu Yu
 * @Date: 2023-07-27 15:03:36
 * @LastEditTime: 2023-07-27 19:35:59
 * @LastEditors: Fu Yu
 */

module keys_filter #(
    parameter   MAX_20ms = 20'd999_999,//20ms
                WIDTH = 3
)(
    input       wire                        clk         ,
    input       wire                        rst_n       ,
    input       wire  [WIDTH-1:0]           key_in      ,

    output      wire  [WIDTH-1:0]           key_down     
);

//状态机参数定义
localparam  IDLE        =   4'b0001       ,//空闲状态
            FILTER_DOWN =   4'b0010       ,//按键按下抖动状态
            HOLD_DOWEN  =   4'b0100       ,//按下按键稳定状态
            FILTER_UP   =   4'b1000       ;//按键释放抖动状态

//状态跳转条件定义
wire        idle2filter_down       ;//IDLE -> FILTER_DOWN
wire        filter_down2hold_down   ;//FILTER_DOWN -> HOLD_DOWN
wire        hold_down2filter_up     ;//HOLE_DOWN -> FILTER_UP
wire        filter_up2idle        ;//FILTER_UP -> IDLE

reg [19:0] cnt_20ms;
reg [3:0]   state_c;//现态
reg [3:0]   state_n;//次态
reg [WIDTH-1:0]    key_r0;//寄存
reg [WIDTH-1:0]    key_r1;//打拍
reg [WIDTH-1:0]    key_r2;
reg [WIDTH-1:0]    key_down_r;//寄存key_down

wire [WIDTH-1:0]   nedge;//下降沿
wire [WIDTH-1:0]   pedge;//上升沿
wire add_cnt_20ms;
wire end_cnt_20ms;

//****************************************************************
//--状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end

//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
    case (state_c) 
        IDLE : begin
            if(idle2filter_down)begin
                state_n = FILTER_DOWN;
            end
            else begin
                state_n = state_c;
            end
        end
        FILTER_DOWN : begin
            if(filter_down2hold_down) begin
                state_n = HOLD_DOWEN;
            end
            else begin
                state_n = state_c;
            end
        end
        HOLD_DOWEN : begin
            if(hold_down2filter_up) begin
                state_n = FILTER_UP;
            end
            else begin
                state_n = state_c;
            end
        end
        FILTER_UP : begin
            if(filter_up2idle) begin
                state_n = IDLE;
            end
            else begin
                state_n = state_c;
            end
        end
        default : state_n = IDLE;
    endcase
end

assign idle2filter_down = state_c == IDLE && nedge;
assign filter_down2hold_down = state_c == FILTER_DOWN && end_cnt_20ms;
assign hold_down2filter_up = state_c == HOLD_DOWEN && pedge;
assign filter_up2idle = state_c == FILTER_UP && end_cnt_20ms;

//第三段:描述输出,时序逻辑或组合逻辑皆可
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        key_down_r <= 'b0;
    end
    else if(filter_down2hold_down) begin
        key_down_r <= ~key_r2;
    end
    else begin
        key_down_r <= 'b0;
    end
end

assign  key_down = key_down_r;

//****************************************************************
//--上升沿下降沿检测
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        key_r0 <= {WIDTH{1'b1}};
        key_r1 <= {WIDTH{1'b1}};
        key_r2 <= {WIDTH{1'b1}};
    end
    else begin
        key_r0 <= key_in;
        key_r1 <= key_r0;
        key_r2 <= key_r1;
    end
end

assign nedge = ~key_r1 & key_r2;
assign pedge = ~key_r2 & key_r1;

//****************************************************************
//--20ms计数器
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_20ms <= 20'd0;
    end
    else if(add_cnt_20ms)begin
        if(end_cnt_20ms) begin
            cnt_20ms <= 20'd0;
        end
        else begin
            cnt_20ms <= cnt_20ms + 1'd1;
        end
    end
    else begin
        cnt_20ms <= cnt_20ms;
    end
end

assign add_cnt_20ms = state_c == FILTER_DOWN || state_c == FILTER_UP;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == MAX_20ms;


endmodule //key_filte

测试文件:

`timescale 1ns/1ns
module keys_filter_tb ();

reg tb_clk;
reg tb_rst_n;
reg [2:0] tb_key_in;
wire [2:0]  tb_key_down;

parameter CYCLE = 20;
defparam u_keys_filter.MAX_20ms = 20'd99;         

always #(CYCLE) tb_clk = ~tb_clk;

integer i,j;

initial begin
    tb_key_in = 3'b111;
    tb_clk = 0;
    tb_rst_n = 0;//开始复位
    #(CYCLE*2);
    tb_rst_n = 1;
    #11;
    tb_key_in[1] = 0;
    for(j=0;j<8;j=j+1)begin
        i = {$random}%500;
        #i;
        tb_key_in[1] = i;
    end
    tb_key_in[1] = 0;
    wait(u_keys_filter.MAX_20ms);
    #10000;

    tb_key_in[1] = 1;
    for(j=0;j<8;j=j+1)begin
        i = {$random}%500;
        #i;
        tb_key_in[1] = i;
    end
    tb_key_in[1] = 1;
    #1000;
    $stop;
end


 keys_filter #(
    .WIDTH(3)
 )u_keys_filter(
    .          clk       (tb_clk)  ,
    .          rst_n      (tb_rst_n) ,
    .          key_in     (tb_key_in) ,

    .          key_down     (tb_key_down)
);

endmodule //keys_filter_tb

二、非状态机方式消抖

此方法简单,当检测到下降沿时,进行一次20ms计数,20ms计数过后直接检测稳定信号并输出。
代码实现:

/*
 * @Description: 多位按键销抖
 * @Author: Fu Yu
 * @Date: 2023-07-27 11:05:30
 * @LastEditTime: 2023-07-27 12:19:49
 * @LastEditors: Fu Yu
 */


module key_filter #(
    parameter WIDTH = 3,//WIDTH表示位宽
    parameter MAX_20ms = 20'd999_999//20ms
)(
    input       wire                    clk         ,
    input       wire                    rst_n       ,
    input       wire [WIDTH - 1:0]      key_in      ,

    output      wire [WIDTH - 1:0]      key_down
);

reg [19:0] cnt_20ms;//20ms计数器
reg [WIDTH-1:0] key_r0;//同步
reg [WIDTH-1:0] key_r1;//打两排
reg [WIDTH-1:0] key_r2;
reg [WIDTH-1:0] key_down_r;//寄存key_down信号
reg flag;//计数器计数标志

wire add_cnt_20ms;//开始计数信号
wire end_cnt_20ms;//结束计数信号
wire [WIDTH-1:0] nedge;//下降沿信号

//****************************************************************
//--同步、打两拍
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        key_r0 <= {WIDTH{1'b1}};//3'b111
        key_r1 <= {WIDTH{1'b1}};
        key_r2 <= {WIDTH{1'b1}};
    end
    else begin
        key_r0 <= key_in;
        key_r1 <= key_r0;
        key_r2 <= key_r1;
    end
end

//****************************************************************
//--下降沿检测
//****************************************************************
assign nedge = ~key_r1 & key_r2;

//****************************************************************
//--flag
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        flag <= 1'b0;
    end
    else if(nedge) begin//检测到下降沿时,开始计数
        flag <= 1'b1;
    end
    else if(end_cnt_20ms)begin//计满20ms时,停止计数
        flag <= 1'b0;
    end
    else begin
        flag <= flag;
    end
end

//****************************************************************
//--20ms计数器
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt_20ms <= 20'd0;
    end
    else if(add_cnt_20ms) begin
        if(end_cnt_20ms) begin
            cnt_20ms <= 20'd0;
        end
        else begin
            cnt_20ms <= cnt_20ms + 1'd1;
        end
    end
    else begin
        cnt_20ms <= cnt_20ms;
    end
end

assign add_cnt_20ms = flag;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == MAX_20ms;

//****************************************************************
//--key_down赋值
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        key_down_r <= 'b0;
    end
    else if(end_cnt_20ms) begin
        key_down_r <= ~key_r2;
    end
    else begin
        key_down_r <= 'b0;
    end
end
assign key_down=key_down_r;

endmodule //key_filter

你可能感兴趣的:(fpga开发)