目录

  • 1 软件安装
    • 1.1 实验课程需要安装的软件
    • 1.2 实验软件平台安装方法
    • 1.3 第一次课视频-相关软件安装方法
    • 1.4 课程实验板照片及视频
    • 1.5 实验板原理图
  • 2 在线课堂回放视频
    • 2.1 第一次课视频-相关软件安装方法
    • 2.2 第二次课ModelSim仿真与Verilog编程
    • 2.3 第三次课Efinity操作
  • 3 软件基本操作——以流水灯为例
    • 3.1 Efinity设计输入演示视频
      • 3.1.1 流水灯Verilog源代码
    • 3.2 Efinity引脚分配及锁定视频
    • 3.3 Efinity设计JTAG下载视频
    • 3.4 Efinity设计调试-GTKWave使用
    • 3.5 Efinity设计Flash编程
    • 3.6 Efinity加入SoC(RISC-V)处理器
    • 3.7 SoC的Interface_Designer引脚创建及锁定
    • 3.8 启动Eclipse软件调试RISC-V处理器
    • 3.9 修改C语言代码实现流水灯功能.
  • 4 本实验课程课件
    • 4.1 数字逻辑与微处理器实验板-逻辑设计
    • 4.2 数字逻辑与微处理器实验板-微处理与编程
  • 5 软件操作视频-课后学习
    • 5.1 使用Efinity建立和编译工程
    • 5.2 使用Interface Designer进行管脚锁定
    • 5.3 使用Programmer下载FPGA
    • 5.4 使用Debugger进行在线调试
    • 5.5 Debugger手动例化和VIO使用
    • 5.6 RubySOC Demo 硬件工程
    • 5.7 RubySOC Demo 软件工程
    • 5.8 RubySOC Demo 环境搭建
    • 5.9 RubySOC Demo 示例演示
  • 6 实验老师课程回放
    • 6.1 Modelsim仿真1-MUX2-1设计及仿真
    • 6.2 Modelsim仿真2-全加器设计及仿真
    • 6.3 Modelsim仿真3-计数器设计及仿真
    • 6.4 Efinity的计数器下载
  • 7 综合设计报告参考
    • 7.1 综合设计报告模板
    • 7.2 综合设计参考-T20实验板程序说明
    • 7.3 相关设计源代码参考
      • 7.3.1 XD-T20F256-实验板顶层文件
  • 8 VHDL数字系统设计课件
    • 8.1 HDL编程-Lecture1
    • 8.2 HDL编程-Lecture2
    • 8.3 HDL编程-Lecture3
    • 8.4 HDL编程-Lecture4
    • 8.5 HDL编程-Lecture5
    • 8.6 HDL编程-Lecture6
    • 8.7 HDL编程-Lecture7
  • 9 QuartusII EDA软件应用
    • 9.1 FPGA技术及软件基础
    • 9.2 DDS综合设计实例操作
    • 9.3 实验部分-Lab1
    • 9.4 实验部分-Lab2
    • 9.5 实验部分-Lab3
    • 9.6 实验部分-Lab4
    • 9.7 FPGA设计与调试视频讲解1
    • 9.8 FPGA设计与调试视频讲解2
    • 9.9 FPGA设计与调试视频讲解3
    • 9.10 DE0开发板资料
    • 9.11 DE1-SoC开发板资料
  • 10 综合设计教学视频
    • 10.1 基于DDS的信号产生实验视频
    • 10.2 基于FPGA嵌入式软核处理器的流水灯设计实验视频
    • 10.3 基于FPGA的精简指令集CPU设计视频
    • 10.4 FFT算法的FPGA硬件实现视频
  • 11 FPGA设计实例参考代码
    • 11.1 实例参考声明
    • 11.2 FPGA对LED显示器的控制
    • 11.3 FPGA对LCD显示器的控制
    • 11.4 ADC0809接口电路及程序设计
    • 11.5 TLC5510接口电路及程序设计
    • 11.6 DAC0832接口电路及程序设计
    • 11.7 TLC7524接口电路及程序设计
    • 11.8 FPGA通用异步收发器(UART)
    • 11.9 二进制振幅键控(ASK)调制器与解调器设
    • 11.10 二进制频移键控(FSK)
    • 11.11 二进制相位键控(PSK)调制器与解调器设计
    • 11.12 多进制数字振幅调制(MASK)系统
    • 11.13 多进制数字频率调制(MFSK)系
    • 11.14 多进制数字相位调制(MPSK)系统
    • 11.15 数字基带信号传输码型发生器设计
    • 11.16 采用测频原理的数字频率计
    • 11.17 采用等精度测频原理的频率计
    • 11.18 电子琴设计
    • 11.19 自动升降电梯控制器设计
    • 11.20 电子时钟设计
    • 11.21 自动售货机控制系统设计
    • 11.22 出租车自动计价器设计
    • 11.23 多功能波形发生器的系统设计
    • 11.24 步进电机定位控制
  • 12 EDA综合设计实例-参考
    • 12.1 正弦波发生器-6通道
    • 12.2 ARM9架构CORE实现
    • 12.3 高速8051-IP-Core实现
    • 12.4 数字日历-VerilogHDL实现
    • 12.5 数字滤波器-VerilogHDL实现
    • 12.6 H.264的OpenCore-VHDL实现
    • 12.7 电梯控制器-VHDL设计
    • 12.8 半整数及整数分频器参数化设计
    • 12.9 家用防盗报警器设计
    • 12.10 实现ALU-16种运算设计
    • 12.11 万年历-八音自动播放电子琴设计
    • 12.12 多功能电子钟设计
    • 12.13 播放刘德华-月老-歌曲设计
    • 12.14 闹钟设计
  • 13 数字信号处理的FPGA实现
    • 13.1 Computer Arithmetic设计
    • 13.2 FIR数字滤波器设计
    • 13.3 IIR数字滤波器设计
    • 13.4 多速率信号处理设计
    • 13.5 傅里叶变换设计
    • 13.6 Advanced Topics-NTT和LFSR设计
    • 13.7 自适应滤波器设计
    • 13.8 微处理器设计
    • 13.9 数字信号处理的FPGA实现
  • 14 常见实际问题解决方案
    • 14.1 按键消抖
按键消抖

【说明】按键消抖常用于机械按键的抖动消除,由于我们平时用的按键(实验板上的按键)都是弹簧结构,在按下后弹簧会来回抖动几次,这样如果用按键信号作为时钟触发的话就会导致多次触发(可以试一下用按键信号作为一个计数器的时钟试一试),因此实际使用时是需要按键消抖控制的。


按键消抖的Verilog程序和Modelsim仿真:

【摘录:https://mp.weixin.qq.com/s/bsevwum0t86IQzb_4GS77Q, 原创:张俊涛,人工智能科学与技术】

在数字电路中,开关用于用于产生高、低电平,按键用于产生单次脉冲。由于开关和按键为机械部件,每次按下或者释放时,由于簧片的弹性会产生短暂的抖动,然后才能稳定接通或者断开。抖动现象会导致按键电路的输出产生毛刺,如图1所示,从而可能导致系统产生误动作。开关和按键的抖动时间一般在20ms以内。为了防止因按键抖动引起的系统误动作,必须对按键电路进行消抖,只在按键闭合或者断开稳定后才允许输出。

                      

图1 按键抖动现象

按键消抖有软件消抖和硬件消抖两类方法。软件消抖是在嵌入式系统中,检测到按键按下时,应用软件延时20ms后再次检测按键的状态,如果两次状态相同,则确认按键已经按下。这种处理方式虽然简单,但是会浪费CPU资源。

硬件消抖有多种方法。第一种方法是应用施密特电路的回差特性配合积分电路实现按键消抖,应用电路如图2所示。

            

图2 应用积分电路实现按键消

第二种方法是应用锁存器的保持功能实现开关消抖,应用电路如图3所示。

            

 图3 应用锁存器实现开关消抖

除了上述两种按键消抖方法外,在基于FPGA的数字系统设计中,也可以应用状态机设计按键消抖电路,在FPGA内部实现。

基于状态机设计按键消抖电路时,需要将按键的一次动作分解为:按下前、按下时、稳定期和释放时4个状态,如图1中所示,分别用KEY_IDLE、KEY_PRESSED、KEY_ACTIVE和KEY_RELEASE表示。设按键输入用key_in表示,低电平有效,设计消抖时间为20ms,则按键消抖状态机的状态转换关系如图4所示。

图4 按键消抖状态机

根据上述状态转换关系,描述按键消抖模块的Verilog HDL代码参考如下:

-----------------------------------------------

module KEY_debounce #(parameter DEBOUNCE_TIME = 1000_000 )( 

  // 50MHz时钟时,对应消抖时间为20ms

input clk_50,                         // 50MHz时钟,周期为20ns

      input rst_n,                          // 复位信号

      input key_in,                         // 按键输入

      output reg key_out  // 消抖后输出

      );

      // 内部状态定义,循环编码方式

      localparam KEY_IDLE    = 2'b00,     // 按下前

                   KEY_PRESSED = 2'b01,    // 按下时

                   KEY_ACTIVE  = 2'b11,    // 稳定期

                   KEY_RELEASE = 2'b10;    // 释放时

      // 内部变量定义          

      reg [19:0] debounce_cnt;                // 消抖计数变量

      reg [1:0]  current_state,next_state;  // 现态和次态

      reg [0:1]  keytmp;                        // 同步寄存器

      // 内部线网定义 

      wire cnt_en,cnt_end;                     // 计数允许和停止计数标志

      wire cnt_flag;                            // 消抖计数标志

      wire release_flag;                       // 按键释放标志

      // 允许消抖计数逻辑:按键按下时或者释放时,cnt_en有效。

assign cnt_en = current_state == KEY_PRESSED 

|| current_state == KEY_RELEASE;

      // 停止计数标志:cnt_en有效并且debounce_cnt达到最大值,则cnt_end有效。

       assign cnt_end = cnt_en && ( debounce_cnt == DEBOUNCE_TIME - 1 );

      // 正在计数标志:cnt_en有效并且debounce_cnt未达到最大值,则cnt_flag有效。

      assign cnt_flag = cnt_en && ( debounce_cnt < DEBOUNCE_TIME );

      // 按键已释放标志: cnt_end有效,并且keytmp[1]为高电平,则release_flag有效。

      assign release_flag = cnt_end && keytmp[1];

      // 按键输入两级同步寄存过程,以消除亚稳态。

      always @(posedge clk_50 or negedge rst_n)

         if ( !rst_n ) 

            keytmp <= 2'b00;                         // 清零

         else 

            keytmp[0:1] <= {key_in,keytmp[0]};   // 右移  

      // 时序逻辑过程,描述状态转换

      always @(posedge clk_50 or negedge rst_n) 

         if ( !rst_n )

             current_state <= KEY_IDLE;

         else 

             current_state <= next_state;

      // 组合逻辑过程,定义次态 

      always @(*)  begin

         case ( current_state )

             KEY_IDLE: if ( !keytmp[1] ) // 按键按下时,进入KEY_PRESSED

                            next_state = KEY_PRESSED;

                          else                // 否则,保持KEY_IDLE

                            next_state = current_state; 

             KEY_PRESSED: if ( cnt_end && !keytmp[1] ) 

// 消抖时间到且keytmp[1]为0,确认按下有效

                                 next_state = KEY_ACTIVE;

                             else if ( cnt_flag && keytmp[1] ) 

// 正在计数,但keytmp[1]为1,则为抖动

                                       next_state = KEY_IDLE;

else  // 否则状态保持

                                       next_state = current_state; 

             KEY_ACTIVE: if ( keytmp[1] ) // keytmp[1]跳变为1则进入释放状态

                               next_state = KEY_RELEASE;

                            else 

                               next_state = current_state; // 否则状态保持

             KEY_RELEASE: if ( release_flag ) // 按键已释放,返回

                                next_state = KEY_IDLE;  

                             else if ( cnt_flag && !keytmp[1] )

                                      // 正在计数,但keytmp[1]为0,则为抖动

                                      next_state = KEY_ACTIVE;  

                                   else  // 否则状态保持

                                      next_state = current_state;

                 default:  next_state = KEY_IDLE;

         endcase

      end

      // 时序逻辑过程,消抖计时

      always @( posedge clk_50 or negedge rst_n ) 

         if ( !rst_n )

             debounce_cnt <= 20'b0;

         else if ( cnt_en )       // 计数允许信号有效

                  if ( cnt_end )    // 消抖时间到

                     debounce_cnt <= 20'b0;

                  else             // 消抖时间未到

                     debounce_cnt <= debounce_cnt + 1'b1;

                else                // 计数允许信号无效

                   debounce_cnt <= 20'b0;

// 时序逻辑过程,按键消抖后输出

      always @( posedge clk_50 or negedge rst_n ) 

         if ( !rst_n )

             key_out <= 1'b1;   

         else 

             case ( current_state )

                KEY_IDLE   :     key_out <= 1'b1;

KEY_PRESSED:   key_out <= 1'b1;

KEY_ACTIVE :    key_out <= 1'b0; 

KEY_RELEASE:   key_out <= 1'b0;

default:  key_out <= 1'b1;

             endcase

endmodule

---------------------------------------------------

对上述代码进行仿真验证时,需要建立testbench文件,应用系统函数$random产生随机数,以模拟不规则的抖动脉冲间隔。

应用系统函数$random产生随机整数的语法格式为

num = $random%b

其中b为十进制整数,num为-(b-1) ~ (b-1)范围内的随机整数。

应用系统函数$random产生随机正整数的语法格式为

num ={$random}%b

其中b为十进制整数,num为0 ~ (b-1)范围内的随机整数。

测试按键消抖模块功能的testbench代码参考如下:

--------------------------------------------------------------

`timescale 1ns/1ps

module KEY_debounce_vlg_tst();

     reg  clk;

     reg  rst_n;

     reg  key_in;

     wire key_out;

     // 模块参数重定义,减少计数容量,以缩短仿真时间

     defparam KEY_debounce.DEBOUNCE_TIME = 50000;

// 内部变量定义

     reg [15:0] rand_num;

     // 仿真参数定义

     parameter RESET_TIME = 2, STEP = 5;

     // 按键消抖模块例化 

     KEY_debounce i1

        ( .clk      (clk),

          .rst_n    (rst_n),

          .key_in   (key_in),

          .key_out (key_out));

     // 设置复位信号波形 

   initial begin

        rst_n = 1;

        #1;

        rst_n = 0;

        #(STEP * RESET_TIME);

        rst_n = 1;

     end

     // 设置按键输入 

     initial begin

        #1; key_in = 1;                  //按下前

        #(STEP * 10); press_key;       // 第1次按键过程

        #10_000;       press_key;       // 第2次按键过程

     end

     // 设置时钟信号  

     initial clk_50 = 0;

     always  #(STEP/2) clk_50 = ~clk_50;

     // 监测任务

         initial                             

        $monitor($time,"clk_50=%b rst_n=%b key_in=%b key_out=%b",

clk_50,rst_n,key_in,key_out); 

     // 按键任务定义 

     task press_key;

        begin

          repeat (20) begin // 模拟前沿抖动过程

            rand_num = {$random}%5000;

            #rand_num key_in = ~key_in;

            end

          key_in = 0;

          #300_000;

          repeat (20) begin  // 模拟后沿抖动过程

            rand_num = {$random}%5000;

            #rand_num key_in = ~key_in;

            end

          key_in = 1;

          #300_000;

       end

     endtask

 endmodule

--------------------------------------------------------------

上述代码中使用了defparam语句用于对KEY_debounce模块中的DEBOUNCE_TIME参数进行重定义,在确保功能验证的前提下缩短消抖时间。启动modelsim进行仿真,结果如图5所示。

5 按键消抖模块仿真波形

从波形图中可以看出,消抖电路对按键按下和释放产生的4次抖动都能实现有效消抖,因此验证基于状态机设计的按键消抖模块功能正确。