问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

按键消抖及按键控制 LED 灯

创作时间:
作者:
@小白创作中心

按键消抖及按键控制 LED 灯

引用
CSDN
1.
https://m.blog.csdn.net/weixin_50628347/article/details/139118170

按键是人机交互中常见的元素,常用于系统复位或控制设置等。然而,当按键被按下时,其产生的信号波形并不稳定,会出现抖动现象。为了解决这个问题,本实验使用状态机技术来消除按键抖动,并通过稳定的按键信号来控制LED灯的亮灭。

前言

按键是人机交互中常见的元素,常用于系统复位或控制设置等。然而,当按键被按下时,其产生的信号波形并不稳定,会出现抖动现象。如果直接检测波形的下降沿作为按键按下的标志,可能会因为抖动而产生多个错误标志。

为了解决这个问题,实验中使用状态机技术来消除按键抖动。状态机能够监测按键保持某一电平状态(高电平或低电平)的时间,当按键稳定在一个电平状态超过20ms时,即可判断按键已被按下或释放。最后,通过稳定的按键信号来控制LED灯的亮灭。

一、程序设计

本实验设计了一个状态机,通过状态机实现了按键消抖,然后再使用这个按键消抖模块来控制 led 灯的亮灭。

主要用了两个模块,key_debuounce 模块用于按键信号进行消除抖动,当按键按下的时候,key_down信号产生一个高电平信号用来控制 led的翻转,实现按键按一下 led 灯的亮灭状态发生改变。

IDLE 状态表明处于空闲状态,FILTER0 表明按键按下的抖动状态,DOWN 表明按键按下的稳定状态,FILTTER1 表明按键处于抬起的抖动状态。按键还没按下的时候处于 IDLE 空闲状态,按检测到按键按下的下降沿的时候进入 FILTER0状态,在 FILTER0检测到按键信号稳定 20ms后进入 DOWN状态,检测到按键抬起的上升沿的时候进入 FILTER1 状态,检测到按键抬起稳定 20ms 后进入 IDLE 状态,表明一次按键按下完成。

二、Verilog代码编写

1.按键消抖模块代码

// 定义一个名为 key_debounce 的模块,用于消抖按键输入信号
module key_debounce(
    input clk,          // 时钟信号输入
    input rst_n,        // 复位信号输入,低电平有效
    input key_in,       // 按键输入信号
    output key_down,    // 按键按下事件输出
    output key_up       // 按键释放事件输出
);
// 定义状态机的状态编码
localparam 
    IDLE=4'b0001,    // 空闲状态
    FILTER0=4'b0010,  // 首次滤波状态
    DOWN=4'b0100,    // 按键按下状态
    FILTER1=4'b1000;  // 按键释放滤波状态
// 定义20毫秒计数器的最大计数值,用于消抖延时
localparam CNT_20MS=20_000_000/20-1;
// 定义内部寄存器和信号
reg[31:0]cnt;            // 用于消抖延时的计数器
reg[1:0]key_filter;      // 按键滤波寄存器,用于消除按键输入的抖动
reg[1:0]key_in_d;        // 按键输入的双缓存寄存器
reg[3:0]state;           // 状态机当前状态
wire nedge;               // 按键从未按下变为按下的边沿信号
wire pedge;               // 按键从未按下变为释放的边沿信号
wire end_cnt;             // 计数器达到20毫秒的信号
// key_filter 寄存器,用于存储按键输入的抖动
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)
        key_filter<=2'b11;  // 复位时清零
    else
        key_filter<={key_filter[0],key_in};  // 非复位时,实现按键输入的双缓冲
end
// key_in_d 寄存器,用于存储 key_filter 的第二位
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)
        key_in_d<=2'b11;  // 复位时清零
    else
        key_in_d<={key_in_d[0],key_filter[1]};  // 非复位时,实现key_filter 的第二位的双缓冲
end
// 检测按键从未按下变为按下的边沿信号 nedge
assign nedge=(!key_in_d[0])&key_in_d[1];
// 检测按键从未释放变为按下的边沿信号 pedge
assign pedge=key_in_d[0]&(!key_in_d[1]);
// 计数器逻辑,用于实现20毫秒的消抖延时
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)
        cnt<='d0;
    else if(state==FILTER0)begin
        if(end_cnt|pedge)  // 如果计数器达到20毫秒或检测到按键释放,计数器清零
            cnt<='d0;
        else
            cnt<=cnt+1'b1;  // 否则计数器递增
    end
    else if(state==FILTER1)begin
        if(end_cnt|nedge)  // 如果计数器达到20毫秒或检测到按键按下,计数器清零
            cnt<='d0;
        else
            cnt<=cnt+1'b1;  // 否则计数器递增
    end
    else
        cnt<='d0;  // 其他状态下计数器清零
end
// 判断计数器是否达到20毫秒
assign end_cnt=cnt==CNT_20MS;
// 状态机逻辑,用于处理按键事件
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)
        state<=IDLE;  // 复位时,状态机置为空闲状态
    else begin
        case(state)  // 根据当前状态和输入信号进行状态转换
            IDLE:begin
                if(nedge)  // 如果检测到按键按下边沿,进入 FILTER0 状态
                    state<=FILTER0;
                else
                    state<=IDLE;  // 否则保持空闲状态
            end
            FILTER0:begin
                if(end_cnt)  // 如果计数器达到20毫秒,进入 DOWN 状态
                    state<=DOWN;
                else 
                    state<=FILTER0;  // 否则保持 FILTER0 状态
            end
            DOWN:begin
                if(pedge)  // 如果检测到按键释放边沿,进入 FILTER1 状态
                    state<=FILTER1;
                else
                    state<=DOWN;  // 否则保持 DOWN 状态
            end
            FILTER1:begin
                if(end_cnt)  // 如果计数器达到20毫秒,返回 IDLE 状态
                    state<=IDLE;
                else
                    state<=FILTER1;  // 否则保持 FILTER1 状态
            end
            default:state<=IDLE;  // 任何未定义的状态都视为空闲状态
        endcase
    end
end
// 定义按键按下事件输出信号,当状态机处于 FILTER0 状态且计数器达到20毫秒时输出
assign key_down=state==FILTER0&&end_cnt;
// 定义按键释放事件输出信号,当状态机处于 FILTER1 状态且计数器达到20毫秒时输出
assign key_up=state==FILTER1&&end_cnt;
endmodule

2.顶层代码

// 定义一个名为 key_led 的模块,用于控制LED灯随按键输入信号进行闪烁
module key_led(
    input clk,          // 时钟信号输入
    input rst_n,        // 复位信号输入,低电平有效
    input key_in,       // 按键输入信号
    output reg led      // LED输出信号,用于指示按键操作
);
// 定义一个内部信号 key_down,用于接收消抖后的按键按下事件
wire key_down;
// 实例化消抖模块 key_debounce,用于稳定按键输入信号
key_debounce key_debounce(
    .clk(clk),         // 连接时钟信号
    .rst_n(rst_n),     // 连接复位信号
    .key_in(key_in),   // 连接原始按键输入信号
    .key_down(key_down)  // 连接消抖后的按键按下事件输出
);
// 定义一个 always 块,用于处理LED灯的状态
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        // 当复位信号为低时,将LED灯设置为初始状态(这里为高电平)
        led<=1'b1;
    end else if(key_down) begin
        // 当检测到消抖后的按键按下事件时,切换LED灯的状态
        led<=~led;
    end else begin
        // 如果没有检测到按键按下事件,保持LED灯当前状态不变
        led<=led;
    end
end
endmodule

3.仿真代码

`timescale 1ns / 1ps // 定义仿真时间单位为1纳秒,时间精度为1皮秒
module tb_key_debounce(); // 定义测试模块 tb_key_debounce
    reg clk;            // 定义时钟信号 clk
    reg rst_n;          // 定义复位信号 rst_n,低电平有效
    reg key_in;         // 定义按键输入信号 key_in
    reg[15:0]my_rand;  // 定义一个16位的随机数发生器寄存器 my_rand
    wire key_down;      // 定义按键按下事件输出信号 key_down
    wire key_up;        // 定义按键释放事件输出信号 key_up
    // 实例化待测试的消抖模块 key_debounce
    key_debounce key_debounce(
        .clk(clk),
        .rst_n(rst_n),
        .key_in(key_in),
        .key_down(key_down),
        .key_up(key_up)
    );
    // 生成时钟信号的 initial 块
    initial begin
        clk=1;            // 初始化时钟信号为高电平
        forever begin      // 创建一个永久循环,以产生连续的时钟信号
            #10 clk=~clk;  // 每10纳秒翻转一次时钟信号
        end
    end
    
    // 生成复位信号的 initial 块
    initial begin
        rst_n=0;          // 初始化复位信号为低电平
        repeat(20) @(posedge clk);  // 等待20个时钟周期
            rst_n=1;      // 将复位信号置为高电平,结束复位
    end
    // 定义一个任务 bounce,用于模拟按键的抖动
    task bounce;
        begin
            repeat(50)begin
                my_rand=5_000_000+({$random}%5_000_000);  // 生成随机数,模拟按键抖动时间
                #my_rand;                                // 等待随机时间
                key_in=~key_in;                          // 翻转按键输入信号,模拟按键抖动
            end
        end
    endtask
    
    // 定义一个任务 press_key,用于模拟按键的按下和释放
    task press_key;
        begin
            key_in=1;                                 // 将按键输入信号置为高电平,模拟按键按下
            #25_000_000;                               // 等待25毫秒
            bounce;                                    // 调用 bounce 任务,模拟按键抖动
            key_in=0;                                  // 将按键输入信号置为低电平,模拟按键释放
            #50_000_000;                               // 等待50毫秒
            bounce;                                    // 再次调用bounce 任务,模拟按键抖动
            key_in=1;                                  // 将按键输入信号置回高电平,准备下一次按键事件
            #25_000_000;                               // 等待25毫秒
        end
    endtask
    
    // 主要的 initial 块,用于初始化信号并启动测试
    initial begin
        my_rand=0;                                    // 初始化随机数发生器寄存器
        key_in=1;                                     // 将按键输入信号置为高电平
        @(posedge rst_n);                            // 等待复位信号变为高电平
        repeat(50)@(posedge clk) begin               // 进行50次循环,每次循环等待一个时钟周期
            press_key;                                // 调用 press_key 任务,模拟一次按键按下和释放
            press_key;                                // 再次调用 press_key 任务
            #50_000_000;                              // 等待50毫秒
            $stop;                                    // 停止仿真
        end
    end
endmodule
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号