按键消抖实现

news2024/11/19 2:16:58

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

在这里插入图片描述
可将按键按下整个过程看做四个状态:按键空闲状态,按下抖动状态,稳定按下状态,释放抖动状态。
代码实现:

/*
 * @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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/799592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

echarts timeline时间轴鼠标移入停止

echarts timeline时间轴鼠标移入停止 修改autoplay 变量无用&#xff0c;需修改option配置项里autoplay 然后setoption重新渲染生效

Swiper横向循环焦点图实现与解析

一、实现效果 二、样式与结构代码 html: <div class"item center"><div id"certify" class"col-md-12"><div class"swiper-container"><div class"swiper-wrapper"><div class"swiper-sli…

2:SpringIOC

文章目录 一&#xff1a;Spring_IOC概念引入_重要1&#xff1a;Spring解耦合的原理2&#xff1a;创建一个spring项目并实现IOC基本功能 二&#xff1a;Spring_IOC原理分析 ***1&#xff1a;XML解析技术读取配置文件**2**&#xff1a;反射技术实例化对象,放到容器中3&#xff1a…

【C语言】—— 可变参数列表

C语言中的可变参数是一种特殊的函数参数&#xff0c;允许在函数定义中传递可变数量的参数。使用可变参数机制可以使函数更加灵活&#xff0c;可以根据不同的需求接受不同数量或类型的参数。 目录 &#xff08;一&#xff09;概念理解 &#xff08;二&#xff09;代码展示 1…

Games虚拟现实人本计算研究

晚上八点(北京时间)的 GAMES Webinar 《VR/AR专题》第二期即将开始&#xff01;&#x1f525;&#x1f525;&#x1f525; &#x1f4dc;本期主题&#xff1a;VR人本计算及交互 &#x1f468;&#x1f3fb;‍&#x1f3eb;嘉宾1&#xff1a;北京大学的李胜老师 &#x1f468;…

纪录片《打铁文艺社》:从全美高中生电影节到多项国际赞誉,聚焦城市公共艺术的蜕变之路

7月21日&#xff0c;在全美高中生电影节&#xff08;All American High School Film Festival&#xff0c;AAHSFF&#xff09;公布的入围名单中&#xff0c;一部取材于中国深圳的纪录片《打铁文艺社Datie: The Art Tribe of Tiegang》以其深刻的主题和精良的制作&#xff0c;引…

git 合并非关联分支

面对的场景&#xff1a;现在有三个仓库&#xff0c;一个是本地的仓库1&#xff0c;第二个是和仓库1关联的在github上的仓库2&#xff0c;第三个是把仓库1拷贝到一个无网络环境中持续开发一段时间的仓库3. 分析 基本想法是把仓库3作为仓库1的远程仓库&#xff0c;然后在仓库1上…

Clion配置与使用记录

Clion与ROS2、docker 为了能够在Docker中使用ROS2环境&#xff0c;同时Clion能够在编辑代码时可以有代码提示以及函数、变量跳转等功能 Docker配置 参考&#xff1a;Docker SSH配置 启动Docker&#xff0c;需要将端口映射&#xff0c;添加如下参数&#xff1a; -p 8024:22&…

Redis-基于内存的key-value结构数据库

读写性高&#xff0c;适合存储热点性高的数据 也称为结构化的NoSql数据库 redis依赖环境&#xff1a;gcc NoSql 非关系型数据库&#xff0c;是关系型数据库的补充 关系型(RDBMS)非关系型(NoSql)MySqlRedisOracleMongo dbDB2MemCachedSQLServer 常用命令 Redis 教程_redi…

手机中有三个特别耗电的功能,半天就耗掉一半电量,记得关掉

在现代社会&#xff0c;手机已经成为我们生活中不可或缺的伙伴。然而&#xff0c;随着手机功能的日益强大和应用的多样化&#xff0c;电池续航成为了许多人的困扰。您是否曾经经历过使用手机一半时间&#xff0c;电量却已经耗掉一半的情况&#xff1f;若是如此&#xff0c;可能…

Pytorch图像处理注意力机制SENet CBAM ECA模块解读

目录 1. 注意力机制 1.1 SENet&#xff08;Squeeze-and-Excitation Network&#xff09; 1.1.1 SENet原理 1.1.2 SENet代码示例 1.2 CBAM&#xff08;Convolutional Block Attention Module&#xff09; 1.2.1 CBAM原理 1.2.2 CBAM代码示例 1.3 ECA&#xff08;Efficien…

Vue中TodoLists案例_底部统计

与上一篇Vue中TodoList案例_删除有俩个文件变化了 App.vue&#xff1a;向儿子组件MyFooter传递参数todos <template><div id"root"><div class"todo-container"><div class"todo-wrap"><MyHeader :addTodo"add…

每天一道C语言编程(递归:斐波那契数,母牛的故事)

递归的条件 递归函数必须有一个可直接退出的条件&#xff0c;否则会进入无限递归&#xff0c;例如 #include<stdio.h> void f(int n) {if(n<0)return;f(n-1);printf("%d ",n); }int main() {int n5;f(n);return 0;}//递归的出口 if(n<0) retur…

【docker】docker部署nginx

目录 一、步骤二、示例 一、步骤 1.搜索nginx镜像 2.拉取nginx镜像 3.创建容器 4.测试nginx 二、示例 1.搜索nginx镜像 docker search nginx2.拉取nginx镜像 docker pull nginx3.创建容器&#xff0c;设置端口映射、目录映射 # 在root目录下创建nginx目录用于存储nginx数据…

花色更潮的夜光飞盘,手感也很出色

飞盘运动因为易于入门&#xff0c;方便操作&#xff0c;一直以来都备受大家的喜爱&#xff0c;而近几年更是成为了一项非常流行的户外运动。每天玩上一局飞盘&#xff0c;不仅可以锻炼身体&#xff0c;还能够增强团队合作意识&#xff0c;让大家在运动中尽情享受乐趣。 这两天我…

【树上点对问题】Tree Problem

Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a; 一个很裸的树形DP 对于树上的一对点&#xff0c;我们往往考虑更换枚举对象 如果关注的是路径经过边&#xff0c;那么就考虑一条边两端的连通分量 如果关注的是路径经过点&#xff0c;那么分成两部分考虑 一部…

郑州如何为SSL证书续期

SSL数字证书的主要作用就是保护网站传输数据安全&#xff0c;而我们要知道SSL证书是有时间限制&#xff0c;到期之后就会失效&#xff0c;对网站传输数据的加密保护也会失效&#xff0c;这就需要我们在失效前为SSL证书续期。那么我们该如何为SSL证书续期呢&#xff1f;今天随SS…

uniapp实现带参数二维码

view <view class"canvas"><!-- 二维码插件 width height设置宽高 --><canvas canvas-id"qrcode" :style"{width: ${qrcodeSize}px, height: ${qrcodeSize}px}" /></view> script import uQRCode from /utils/uqrcod…

断路器分合闸速断试验

试验目的 高压断路器的分、 合闸速度是断路器的重要特性参数, 反映出断路器的操动机构 与传动机构在分、 合闸过程中的运动特征。 断路器分、 合闸速度超出或者低于规定值 均会影响断路器的运行状态和使用寿命。 断路器合闸速度不足, 将会引起触头合闸振 颤, 预击穿时间过长。…

互联网医院小程序源码|互联网+智慧医院解决方案

随着互联网技术的迅猛发展&#xff0c;互联网医院系统开发逐渐受到人们的关注和需求。互联网医院系统是将传统医院服务与互联网技术相结合的创新模式&#xff0c;通过互联网平台为患者提供在线诊疗、预约挂号、药品配送等医疗服务。下面就来介绍一下互联网医院系统开发的优势。…