IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 【FPGA】day10-状态机实现按键消抖 -> 正文阅读

[嵌入式]【FPGA】day10-状态机实现按键消抖

一、项目分析

1、实现原理

将按键按下到释放的整个过程分为4个过程:
按键按下抖动状态
按键按下保持低电平状态
按键释放状态
按键释放保持高电平状态

由此,可以把按键被按下的过程划分为4个状态,
空闲状态、按下状态、保持状态、释放状态

2、状态转移图

IDLE:空闲状态,按键释放/未按下时保持高电平的状态;
FILETER_DOWN:按键按下抖动状态;
HOLD_DOWN:按键按下保持低电平状态;
FILETER_UP:按键释放抖动状态。
在这里插入图片描述

3、时序图

在这里插入图片描述

4、模块框图

在这里插入图片描述
按键消抖模块:检测两个按键的状态,输出一个单周期的脉冲信号,标志按键被按下。
LED控制模块:当接收到按键消抖模块发出的脉冲信号时,改变4个LED的流水状态。
按键和LED是不同的外设,这里划分模块时,可以把按键消抖设计为一个模块,LED控制设计为一个模块;当然,工程比较简单,也可以直接把按键消抖和LED控制放在一个模块设计。

二、项目源码

1、顶层模块

module key_top (
    input        clk   ,
    input        rst_n ,
    input  [1:0] key_in,

    output [3:0] led 
);

//信号定义
    wire  [1:0]  key_done   ;

//模块实例化
    key u_key(
        .clk      (clk)      ,
        .rst_n    (rst_n)    ,
        .key_in   (key_in)   ,

        .key_done (key_done)
);

    led u_led(
        .clk      (clk)      ,
        .rst_n    (rst_n)    ,
        .key_done (key_done) ,

        .led      (led)
);
    
endmodule

2、消抖模块

法一:检测到下降沿就开始计数

 module key (
    input                    clk      ,
    input                    rst_n    ,
    input         [1:0]      key_in   ,

    output  reg   [1:0]      key_done                
);

//定义参数
    parameter MAX_20MS = 20'd999_999 ;  //20ms最大值

//定义状态机参数
    parameter IDLE        = 4'b0001   , //空闲状态,保持高电平
              FILTER_DOWN = 4'b0010   , //按键按下抖动状态 判断是否按下
              HOLD        = 4'b0100   , //保持状态 保持低电平
              FILTER_UP   = 4'b1000   ; //按键释放抖动状态 滤除释放产生的抖动

//信号定义
    reg   [3:0]   state_c          ;   //现态
    reg   [3:0]   state_n          ;   //次态
    
    reg   [1:0]   key_r0           ;   //按键变化同步到时钟上升沿,两个按键故两位宽
    reg   [1:0]   key_r1           ;   //将key_r0延迟一个时钟周期

    wire   [1:0]   nedge           ;   //下降沿
    wire   [1:0]   pedge           ;   //上升沿

    reg   [19:0]  cnt_20ms         ;   //滤除抖动计数器 ,延时20ms
    wire          add_cnt_20ms     ;
    wire          end_cnt_20ms     ;

    wire          idle2filter_down ;
    wire          filter_down2idle ;
    wire          filter_down2hold ;
    wire          hold2filter_up   ;
    wire          filter_up2idle   ;

//状态机第一段,时序逻辑,描述状态的转移
    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)
                    state_n = FILTER_DOWN ;
                else
                    state_n = IDLE        ;
            end
            FILTER_DOWN :begin
                if(filter_down2idle)
                    state_n = IDLE        ;
                else if(filter_down2hold)
                    state_n = HOLD        ;
                else
                    state_n = FILTER_DOWN ;
            end
            HOLD        :begin
                if(hold2filter_up)
                    state_n = FILTER_UP   ;
                else 
                    state_n = HOLD        ;
            end
            FILTER_UP   :begin
                if(filter_up2idle)
                    state_n = IDLE        ;
                else
                    state_n = FILTER_UP   ;
            end 
            default:state_n = IDLE        ;
        endcase
    end

    assign idle2filter_down = state_c == IDLE        && (nedge != 0)                     ; //出现下降沿进入抖动状态
    assign filter_down2idle = state_c == FILTER_DOWN && (end_cnt_20ms && (key_r0 == 2'b11))  ; //按键出现抖动
    assign filter_down2hold = state_c == FILTER_DOWN && (end_cnt_20ms && (key_r0 != 2'b11))  ; //某个按键按下
    assign hold2filter_up   = state_c == HOLD        && (pedge != 0)                     ; //出现上升沿进入释放抖动状态
    assign filter_up2idle   = state_c == FILTER_UP   && (end_cnt_20ms)                   ; //20ms结束,返回IDLE状态

//同步打拍
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            key_r0 <= 2'b11;
            key_r1 <= 2'b11;
        end
        else begin
            key_r0 <= key_in;
            key_r1 <= key_r0;
        end
    end

//判断下降沿及上升沿
    assign nedge = (~key_r0) & (key_r1);
    assign pedge = (key_r0)  & (~key_r1); 

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

//按键输出
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            key_done <= 2'b00;
        end
        else if(filter_down2hold)begin
            key_done <= ~key_r0;
        end
        else begin
            key_done <= 2'b00;
        end
    end


    
endmodule

法二:抖动结束后计数20ms

 module key (
    input                    clk      ,
    input                    rst_n    ,
    input         [1:0]      key_in   ,

    output  reg   [1:0]      key_done                
);

//定义参数
    parameter MAX_20MS = 20'd999_999 ;  //20ms最大值

//定义状态机参数
    parameter IDLE        = 4'b0001   , //空闲状态,保持高电平
              FILTER_DOWN = 4'b0010   , //按键按下抖动状态 判断是否按下
              HOLD        = 4'b0100   , //保持状态 保持低电平
              FILTER_UP   = 4'b1000   ; //按键释放抖动状态 滤除释放产生的抖动

//信号定义
    reg   [3:0]   state_c          ;   //现态
    reg   [3:0]   state_n          ;   //次态
    
    reg   [1:0]   key_r0           ;   //按键变化同步到时钟上升沿,两个按键故两位宽
    reg   [1:0]   key_r1           ;   //将key_r0延迟一个时钟周期

    wire   [1:0]   nedge           ;   //下降沿
    wire   [1:0]   pedge           ;   //上升沿

    reg   [19:0]  cnt_20ms         ;   //滤除抖动计数器 ,延时20ms
    wire          add_cnt_20ms     ;
    wire          end_cnt_20ms     ;

    wire          idle2filter_down ;
    wire          filter_down2idle ;
    wire          filter_down2hold ;
    wire          hold2filter_up   ;
    wire          filter_up2idle   ;

//状态机第一段,时序逻辑,描述状态的转移
    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)
                    state_n = FILTER_DOWN ;
                else
                    state_n = IDLE        ;
            end
            FILTER_DOWN :begin
                if(filter_down2idle)
                    state_n = IDLE        ;
                else if(filter_down2hold)
                    state_n = HOLD        ;
                else
                    state_n = FILTER_DOWN ;
            end
            HOLD        :begin
                if(hold2filter_up)
                    state_n = FILTER_UP   ;
                else 
                    state_n = HOLD        ;
            end
            FILTER_UP   :begin
                if(filter_up2idle)
                    state_n = IDLE        ;
                else
                    state_n = FILTER_UP   ;
            end 
            default:state_n = IDLE        ;
        endcase
    end
    
    assign idle2filter_down = state_c == IDLE        && nedge != 0                         ; //出现下降沿进入抖动状态
    assign filter_down2idle = state_c == FILTER_DOWN && pedge != 0                         ; //按键出现抖动
    assign filter_down2hold = state_c == FILTER_DOWN && end_cnt_20ms                       ; //某个按键按下
    assign hold2filter_up   = state_c == HOLD        && pedge != 0                         ; //出现上升沿进入释放抖动状态
    assign filter_up2idle   = state_c == FILTER_UP   && end_cnt_20ms                       ; //20ms结束,返回IDLE状态

//同步打拍
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            key_r0 <= 2'b11;
            key_r1 <= 2'b11;
        end
        else begin
            key_r0 <= key_in;
            key_r1 <= key_r0;
        end
    end

//判断下降沿及上升沿
    assign nedge = (~key_r0) & (key_r1);
    assign pedge = (key_r0)  & (~key_r1); 

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


//按键输出
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            key_done <= 2'b00;
        end
        else if(filter_down2hold)begin
            key_done <= ~key_r0;
        end
        else begin
            key_done <= 2'b00;
        end
    end


    
endmodule

3、LED模块

module led (
    input             clk      ,
    input             rst_n    ,
    input      [1:0]  key_done ,

    output reg [3:0]  led
);
    
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            led <= 4'b1111;
        end
        else if(key_done[0])begin
            led <= ~led;
        end
        else if(key_done[1])begin
            led <= 4'b1100;
        end
    end
endmodule

三、仿真测试

1、仿真代码

`timescale 1ns/1ps
module key_tb ();
    reg         clk     ;
    reg         rst_n   ;
    reg  [1:0]  key_in  ;

    wire [3:0]  led     ;

//模块实例化
key_top u_key_top(
    .clk     (clk)          ,
    .rst_n   (rst_n)        ,
    .key_in  (key_in)       ,

    .led     (led)
);

//参数定义
    parameter  CYCLE = 20; //时钟周期20ns
    defparam   u_key_top.u_key.MAX_20MS = 20;

    always #(CYCLE/2) clk = ~clk;

    integer i,j;
    initial begin
        clk = 1'b1;
        rst_n = 1'b1;
        key_in = 2'b11;
        #(CYCLE*10);
        rst_n = 1'b0;
        #(CYCLE*10);
        rst_n = 1'b1;

        for(i = 0;i < 10;i=i+1)begin
            key_in[0] = {$random};  //0或1  ,模拟按键输入有无变化
            j = {$random}%30;       //范围0-29
            #(CYCLE*j);            //按键状态保持 J 个周期
            //如果key_in[0]为0:判断这个按键是否按下,如果j大于20,则按下,小于20,则为抖动
        end
        /*key_in = 2'b10;
        #(CYCLE*30);*/
        key_in = 2'b11;
        #(CYCLE*10);
        for(i = 0;i < 10;i=i+1)begin
            key_in[1] = {$random}%2;
            j = {$random}%25;
            #(CYCLE*j);
        end
        key_in = 2'b11;
        #(CYCLE*10);
        $stop;

    end
    
endmodule

2、仿真波形

在这里插入图片描述

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-06-01 15:23:19  更:2022-06-01 15:23:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:55:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码