按键消抖实现

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

听GPT 讲K8s源代码--pkg(八)

k8s项目中 pkg/kubelet/envvars&#xff0c;pkg/kubelet/events&#xff0c;pkg/kubelet/eviction&#xff0c;pkg/kubelet/images&#xff0c;pkg/kubelet/kubeletconfig这些目录都是 kubelet 组件的不同功能模块所在的代码目录。 pkg/kubelet/envvars 目录中包含了与容器运行…

服务器用友数据库中了locked勒索病毒后怎么解锁数据恢复

随着信息技术的迅速发展&#xff0c;服务器成为现代企业中不可或缺的重要设备。然而&#xff0c;由于网络安全风险的存在&#xff0c;服务器在日常运作中可能遭受各种威胁&#xff0c;包括恶意软件和勒索病毒攻击。近日&#xff0c;我们收到很多企业的求助&#xff0c;企业的用…

Jenkins+Gitlab+Maven集成CI/CD

MavenGitlab集成 配置好下列环境 # Java环境 JAVA_HOME /usr/lib/jvm/java-11-openjdk-11.0.19.0.7-1.el7_9.x86_64# Maven环境 MAVEN_HOME /usr/local/maven# Maven环境变量 PATHEXTRA $MAVEN_HOME/bin1. 配置settings.xml路径 2. 安装maven插件 创建项目 配置gitlab地址和指…

19.2:纸牌问题

给定一个整型数组arr&#xff0c;代表数值不同的纸牌排成一条线 玩家A和玩家B依次拿走每张纸牌 规定玩家A先拿&#xff0c;玩家B后拿 但是每个玩家每次只能拿走最左或最右的纸牌 玩家A和玩家B都绝顶聪明 请返回最后获胜者的分数 方法一&#xff1a;暴力解法 自然智慧。 pack…

Redis 基础知识和核心概念解析:探索 Redis 的数据结构与存储方式

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

基于解析法和遗传算法相结合的配电网多台分布式电源降损配置(Matlab实现)

目录 1 概述 2 数学模型 2.1 问题表述 2.2 DG的最佳位置和容量&#xff08;解析法&#xff09; 2.3 使用 GA 进行最佳功率因数确定和 DG 分配 3 仿真结果与讨论 3.1 33 节点测试配电系统的仿真 3.2 69 节点测试配电系统仿真 4 结论 1 概述 为了使系统网损达到最低值&a…

1200*B. Vanya and Lanterns

Examples input 7 15 15 5 3 7 9 14 0 output 2.5000000000 input 2 5 2 5 output 2.0000000000 解析&#xff1a; 最大距离即为每相邻两盏灯之间的最大距离/2 注意起点没有灯&#xff0c;终点可能有灯&#xff0c;需要分别判断 #include<bits/stdc.h> using nam…

Cesium态势标绘专题-直线箭头(标绘+编辑)

标绘专题介绍:态势标绘专题介绍_总要学点什么的博客-CSDN博客 入口文件:Cesium态势标绘专题-入口_总要学点什么的博客-CSDN博客 辅助文件:Cesium态势标绘专题-辅助文件_总要学点什么的博客-CSDN博客 本专题没有废话,只有代码,代码中涉及到的引入文件方法,从上面三个链…

大语言模型LLM

目录 一、语言模型的发展 语言模型&#xff08;Language Model&#xff0c;LM&#xff09;目标是建模自然语言的概率分布&#xff0c;具体目标是构建词序列w1,w2,...,wm的概率分布&#xff0c;即计算给定的词序列作为一个句子出现可能的大小P(w1w2...wm)。但联合概率P的参数量…

0-虚拟机补充知识

虚拟机克隆 如果想要构建服务器集群&#xff0c;没有必要一台一台的去进行安装&#xff0c;只要通过克隆就可以。 快速获得多台服务器主要有两种方式&#xff0c;分别为&#xff1a;直接拷贝操作和vmware的克隆操作 直接拷贝 将之前安装虚拟机的所有文件进行拷贝&#xff0…

git撤销上一次的commit

一行命令 git reset --soft HEAD^如果在vscode上面&#xff0c;就可以

Oracle 截取指定字符到目标串的末尾

SQL&#xff1a; SELECT-- 目标字符串 目标字符串 指定符号 最后一个 最后一个字符位置1 substr( HG/2106010103/YG\FJSJ\SXKTFJ\FJ03_JPHD, instr( HG/2106010103/YG\FJSJ\SXKTFJ\FJ03_…

transformer 笔记

目录 目前在NLP领域当中&#xff0c;主要存在三种特征处理器——CNN、RNN 以及 Transformer&#xff0c;当前Transformer的流行程度已经大过CNN和RNN&#xff0c;它抛弃了传统CNN和RNN神经网络&#xff0c;整个网络结构完全由Attention机制以及前馈神经网络组成。 Transformer…

【蓝图】p40-p43对象引用、变量有效性、实现键盘控制物体自转、简单点名系统

p40-p43对象引用、变量有效性、实现键盘控制物体自转、简单点名系统 p40对象引用、变量有效性p41实现键盘控制物体自转创建bool值控制旋转实现通过键盘控制自转 p42p43简单点名系统Get All Actors Of Class&#xff08;获得场景中所有该类的actor演员&#xff09;getFor Each L…

Dart - 语法糖(持续更新)

文章目录 前言开发环境中间表示语法糖1. 操作符/运算符&#xff08;?./??/??/../?../.../...?&#xff09;2. 循环&#xff08;for-in&#xff09;3. 函数/方法&#xff08;>&#xff09;4. 关键字&#xff08;await for&#xff09; 最后 前言 通过将dill文件序列化…

【腾讯云 Cloud Studio 实战训练营】沉浸式体验编写一个博客系统

文章目录 前言项目中技术栈新建工作空间登录(注册)Cloud Studio 账号&#xff1a;进入 Cloud Studio 控制台&#xff1a;配置工作空间参数&#xff1a;确认并创建工作空间&#xff1a;项目搭建 配置nuxt 脚手架运行项目报错信息解决错误脚手架运行预览问题 开启博客代码配置lay…

法大大携手盘子女人坊,以数字化唤醒国风摄影新体验

第三方数据显示&#xff0c;目前&#xff0c;我国共有163万家摄影相关企业&#xff0c;有约1900个从事摄影相关业务的品牌&#xff0c;且预计到2025年艺术摄影市场规模将达到7063.18亿元。艺术摄影行业作为在时代进步、科技发展以及人民生活水平提高的推动下逐渐发展起来的行业…

GPT一键化身「AI助理」——自定义指令功能

最近GPT又更新了一个超实用的功能——自定义指令&#xff0c;启用后&#xff0c;你可以给GPT设置一些固定指令&#xff0c;让它记住或扮演某个角色&#xff0c;比如客服、律师、投资管理师、老师、营养师...... 这样&#xff0c;我们就不再需要每次都要打开新的聊天&#xff0c…

spring-websocket在SpringBoot(包含SpringSecurity)项目中的导入

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f96d;本文内容&#xff1a;spring-websocket在SpringBoot(包含SpringSecurity…

瓴羊Quick BI:可视化大屏界面设计满足企业个性需求

大数据技术成为现阶段企业缩短与竞争对手之间差距的重要抓手&#xff0c;依托以瓴羊Quick BI为代表的工具开展内部数据处理分析工作&#xff0c;也成为诸多企业持续获取竞争优势的必由之路。早年间国内企业倾向于使用进口BI工具&#xff0c;但随着瓴羊Quick BI等一众国内数据处…