【FPGA】按键消抖

一、按键消抖概述

1、为何要进行按键消抖

在这里插入图片描述

2、消抖的方式

在这里插入图片描述

二、系统设计

1、系统模块划分

在这里插入图片描述

按键和LED是不同的外设,这里划分模块时,可以将按键消抖设计为一个模块,LED控制设计为一个模块。当然,这个工程比较简单,可以将两者划分为一个模块。

2、系统时序图

在这里插入图片描述

三、代码实现

文件结构:
在这里插入图片描述

1、按键消抖模块(key_debounce)

/**
 *
 * 按键消抖模块
*/
module key_debounce(
    input   wire    clk,
    input   wire    rst_n,
    input   wire    key_in,  // 按键输入

    output   reg     key_out  // 消抖后的按键, 1为按下
);


    parameter TIME_DELAY = 1000_000;  // 延迟时间,20ms

    reg [19:0]   cnt_delay;  // 计数器
    wire         add_cnt;    // 是否计数
    wire         end_cnt;    // 是否完成一次计数

    reg          key_before;  // 前一个状态电平
    reg          key_now;     // 当前按键电平

    wire         flag_fall;   // 按下(开始抖动)标志
    reg          flag_timing;  // 在计时标志,1表示在(可)计时


    // 当前按键电平设置
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            key_now <= 1'b1;
        else
            key_now <= key_in;
    end

    // 前一个按键状态电平设置
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            key_before <= 1'b1;
        else
            key_before <= key_now;
    end

    // 前一状态为1,后一状态为0,开始抖动
    assign flag_fall = key_before & (~key_now);


    // 计时标志判断
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            flag_timing <= 1'b0;
        else if (flag_fall) // 开始计时
            flag_timing <= 1'b1;
        else if (end_cnt)  // 计时结束
            flag_timing <= 1'b0;
    end



    // 计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt_delay <= 20'd0;
        else if (add_cnt) begin   // 计数
            if (end_cnt)
                cnt_delay <= 20'd0;
            else
                cnt_delay <= cnt_delay + 1'd1;
        end
    end

    assign add_cnt = flag_timing;  // 在计时状态 计时
    assign end_cnt = ((cnt_delay==TIME_DELAY) && add_cnt);  // 计时结束标志




    // 消抖后的按键
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            key_out <= 1'b0;
        else if (end_cnt)
            key_out <= ~key_now;
        else
            key_out <= 1'b0;
    end
    



endmodule

2、呼吸灯模块(led_breath)

/**
 *
 * 呼吸灯
*/
module led_breath(
    input   wire        clk,
    input   wire        rst_n,
    input   wire        flag_state_change,  // 状态切换标志

    output  reg[3:0]    led_b
);


    parameter TIME_US = 50;  // 1us (微秒)
    parameter TIME_MS = 1000;  // 1000次us --- 1ms
    parameter TIME_S = 1000; // 1000次毫秒 --- 1s

    // 信号定义
    reg [5:0]     cnt_us; // 微秒计数器
    reg [9:0]    cnt_ms; // 毫秒计数器
    reg [9:0]     cnt_s;  // 秒计数器

    wire add_cnt_us;
    wire end_cnt_us;
    wire add_cnt_ms;
    wire end_cnt_ms;
    wire add_cnt_s;
    wire end_cnt_s;

    reg flag;  // 满1s时,flag取反, flag 为0 时由灭变亮,flag为1时由亮变灭

    // us计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt_us <= 6'd0;
        else if (flag_state_change)
            cnt_us <= 6'd0;
        else if (add_cnt_us) begin
            if (end_cnt_us)
                cnt_us <= 6'd0;
            else 
                cnt_us <= cnt_us + 1'd1;
        end
    end


    assign add_cnt_us = 1'b1;
    assign end_cnt_us = ((cnt_us==TIME_US-1) && add_cnt_us);


    // ms 计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt_ms <= 10'd0;
        else if (flag_state_change)
            cnt_ms <= 6'd0;
        else if (add_cnt_ms) begin
            if (end_cnt_ms)
                cnt_ms <= 10'd0;
            else if(end_cnt_us) // 每1us,+1
                cnt_ms <= cnt_ms + 1'd1;
        end
    end


    assign add_cnt_ms = 1'b1;
    assign end_cnt_ms = ((cnt_ms==TIME_MS-1) && add_cnt_ms);


    // s 计数器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt_s <= 10'd0;
        else if (flag_state_change)
            cnt_s <= 6'd0;
        else if (add_cnt_s) begin
            if (end_cnt_s)
                cnt_s <= 10'd0;
            else if(end_cnt_ms) // 每1ms,+1
                cnt_s <= cnt_s + 1'd1;
        end
    end


    assign add_cnt_s = 1'b1;
    assign end_cnt_s = ((cnt_s==TIME_S-1) && add_cnt_s);


    // flag判断
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            flag <= 1'b0;
        else if (flag_state_change)
            flag <= 1'b0;
        else if(end_cnt_s)
            flag <= ~flag;
    end

    
    // led判断
    // flag 为0 时由灭变亮,flag为1时由亮变灭
    integer i;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            led_b <= 4'b0000;
        else if (flag_state_change)
            led_b <= 4'b0000;
        else begin
            led_b <= {4{(cnt_s > cnt_ms) ? ~flag : flag}};
        end
    end









endmodule

3、流水灯模块(led_waterfall)

/**
 *
 * 流水灯模块
*/
module led_waterfall(
    input   wire        clk,
    input   wire        rst_n,
    input   wire        flag_state_change,  // 状态切换标志

    output  reg[3:0]    led_w
);


    parameter T_WATER = 25'd24_999_999;  // 一个变化时间--0.5s

    reg [24:0]  cnt_period;  // 周期计时器
    wire    add_cnt;
    wire    end_cnt;

    // 计时器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt_period <= 25'd0;
        else if (flag_state_change)  // 状态切换时,从头开始
            cnt_period <= 25'd0;
        else if (add_cnt) begin
            if (end_cnt)
                cnt_period <= 25'd0;
            else
                cnt_period <= cnt_period + 1'd1;
        end
    end

    assign add_cnt = 1'b1;
    assign end_cnt = ((T_WATER==cnt_period) && add_cnt);


    // 流水灯输出
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            led_w <= 4'b0001;
        else if (flag_state_change) // 切换状态,从头开始
            led_w <= 4'b0001;
        else if (end_cnt)
            led_w <= {led_w[2:0], led_w[3]};
        else
            led_w <= led_w;
    end





endmodule

4、按键控制模块

/**
 *
 * 按键控制模块
*/
module led_control(
    input   wire            clk,
    input   wire            rst_n,
    input   wire[1:0]       key,  // 1为按下

    output  reg[3:0]        led  // LED, 1为亮
);


    // LED两种状态
    parameter WATERFALL_LIGHT = 0; // 流水灯
    parameter BREATH_LIGHT = 1;    // 呼吸灯

    reg led_state = WATERFALL_LIGHT;  // LED状态,初始为流水灯
    reg flag_state_change;  // 状态切换标志

    /* 按键改变LED状态,
       key[0] 为流水灯
       key[1] 为呼吸灯
    */
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            led_state <= WATERFALL_LIGHT;
        else if (key[0]) begin
            led_state <= WATERFALL_LIGHT;
            flag_state_change = 1'b1;
        end
        else if (key[1]) begin
            led_state <= BREATH_LIGHT;
            flag_state_change = 1'b1;
        end
        else begin
            led_state <= led_state;
            flag_state_change <= 1'b0;
        end
    end


    wire[3:0] led_w;
    wire[3:0] led_b;


    led_waterfall u_led_waterfall(
        .clk                    (clk),
        .rst_n                  (rst_n),
        .flag_state_change      (flag_state_change),  // 状态切换标志

        .led_w                  (led_w)
    );

    led_breath u_led_breath(
        .clk                    (clk),
        .rst_n                  (rst_n),
        .flag_state_change      (flag_state_change),  // 状态切换标志

        .led_b                  (led_b)
    );



    // led输出
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            led <= 4'b1111;
        else if (led_state == WATERFALL_LIGHT)
            led <= led_w;
        else if (led_state == BREATH_LIGHT)
            led <= led_b;
        else
            led <= led;
    end

5、顶层文件

/**
 *
 * 顶层文件
*/
module led_control_top(
    input   wire        clk,
    input   wire        rst_n,
    input   wire[1:0]   key_in,

    output  wire[3:0]   led
);


    wire [1:0] key;  // 消抖后的key

    // 按键消抖模块实例化
    key_debounce k0(
        .clk            (clk),
        .rst_n          (rst_n),
        .key_in         (key_in[0]),  // 按键输入
    
        .key_out        (key[0])  // 消抖后的按键, 1为按下
    );
    key_debounce k1(
        .clk            (clk),
        .rst_n          (rst_n),
        .key_in         (key_in[1]),  // 按键输入
    
        .key_out        (key[1])  // 消抖后的按键, 1为按下
    );



    // led控制模块实例化
    led_control u_led_control(
        .clk            (clk),
        .rst_n          (rst_n),
        .key            (key),  // 1为按下

        .led            (led)  // LED, 1为亮
    );










endmodule

四、效果展示

按键消抖控制LED

五、仿真设计

1、仿真文件

/**
 *
 * 仿真文件
*/
`timescale 1ns/1ps
module led_control_top_tb;

    reg          tb_clk;
    reg          tb_rst_n;
    reg [1:0]    tb_key_in;

    wire [3:0]   tb_led;

    parameter CYCLE = 20;  // 时钟周期
    // 修改消抖持续时间
    defparam test_led_control_top.k0.TIME_DELAY = 10;
    defparam test_led_control_top.k1.TIME_DELAY = 10;
    // 修改呼吸灯周期
    defparam test_led_control_top.u_led_control.u_led_breath.TIME_US = 5;
    defparam test_led_control_top.u_led_control.u_led_breath.TIME_MS = 5;
    defparam test_led_control_top.u_led_control.u_led_breath.TIME_S = 5;
    // 修改流水灯周期
    defparam test_led_control_top.u_led_control.u_led_waterfall.T_WATER = 5;


    // 时钟变化
    always #(CYCLE/2) tb_clk = ~tb_clk;


    // 初始化
    integer i,j;
    initial begin
        tb_clk = 1'b1;
        tb_rst_n = 1'b0;
        tb_key_in = 2'b11;
        #(CYCLE * 10);
        tb_rst_n = 1'b1;
        #(CYCLE * 100);   // 默认为流水灯

        // 按下key1,切换为呼吸灯
        for (i=0 ;i<5; i=i+1) begin
            tb_key_in[1] = {$random};
            j = {$random}%20;  // 按键产生的时差
            #(CYCLE * j);

        end
        tb_key_in = 2'b11;


        #(CYCLE *120);
        // 按下key0,切换为流水灯
        for(i= 0; i<5; i=i+1) begin
            tb_key_in[0] = {$random};
            j = {$random}%20;
            #(CYCLE * j);
        end
        tb_key_in = 2'b11;
        #(CYCLE *120);
        $stop;


    end




    // 模块实例化
    led_control_top test_led_control_top(
        .clk        (tb_clk),
        .rst_n      (tb_rst_n),
        .key_in     (tb_key_in),

        .led        (tb_led)
    );









endmodule

2、波形图

在这里插入图片描述

  • 11
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值