差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

后一修订版
前一修订版
8._计时控制 [2017/03/23 00:46]
zhijun 创建
8._计时控制 [2021/10/03 01:29]
gongyu
行 1: 行 1:
-=====计时控制=====+## 计时控制
 在之前的实验中我们掌握了如何进行时钟分频、如何进行数码管显示与按键消抖的处理,那么在本节实验之中,我们将会实现一个篮球赛场上常见的24秒计时器。 在之前的实验中我们掌握了如何进行时钟分频、如何进行数码管显示与按键消抖的处理,那么在本节实验之中,我们将会实现一个篮球赛场上常见的24秒计时器。
 \\ \\
-====硬件说明==== 
-------- 
-按键是一种常用的电子开关,电子设计中不可缺少的输入设备。当按下时使开关导通,松开时则开关断开,内部结构是靠金属弹片来实现通断。 
-\\  ​ 
-  ​ 
-**按键抖动的原理** 
-\\ 
-{{ :​抖动原理.png |抖动原理}} 
  
-  - 抖动的产生 :通常的按键所用的开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。 +### 1. 硬说明
-  - 消除抖动的措施:一般我们采用软方法消抖。即检测到按键按下动作之后进行10ms~20ms左右的延时,当前沿的抖动消失之后再一次检测按键的状态。如果仍然是按下的电平状态,则认为这是一次真正的按键按下;同样检测到按键释放,也要做10ms~20ms延时,检测到后沿抖动消失后认为是一个完整的按键弹起过程。 +
-  - 消抖的好处:执行按键消抖有两个好处, +
-     - 消除误触发:我们想通过按键来翻转信号(例如按下一次led亮,在按一次led灭),如果没有进行消抖,则会产生很多误触发造成不必要的翻转。 +
-     - 记录按键次数:执行按键消抖可以让我们记录按键动作的次数,在很多应用里这非常有用。+
  
-\\   +之前的实验中我们为读者详细介绍过小脚丫MXO2按键、数码管、LED等硬件外设,在此不再赘述。本节将实现由数码显示模块,按键作为控制信号的输入(包含复位信号和暂停信号)Lattice MXO2 4000HC作控制核心的篮球读秒系统,实现框图如下:
-[[1. 点亮LED|点亮LED]]实验中我们知道了小脚丫板上按键当按键未被按下时,连接到FPGA脚认是高电平;当按键被按下时连接到FPGA管脚认是低电平。+
  
-\\ +{{ ::计时器框图.png?​800 |}}
-要消除按键的抖动,我们需要去扫描按键,也就是不断的去采集按键的状态。软件消抖我们一般只考虑按键按下时的抖动,而放弃对释放时抖动的消除。用系统时钟(频率较高)去采集按键状态,当检测到按下时用计数延时20ms,再去检测按键状态,如果这时仍为按下状态,确认是一次按下动作,否侧的话认为无按键按下。如何检测按键状态变化就需要用到脉冲边沿检测的方法。+
  
 \\ \\
-**脉冲边沿检测** 
  
-\\ +### 2. Verilog代码
-检测按键按下时要用到脉冲边沿检测的方法,捕捉信号的突变、捕捉时钟的上升下降沿等经常会用到这种方法。简单地说就是用一个频率更高的时钟去触发要检测的信号,用两个寄存器去储存相邻两个时钟采集到的值,然后进行异或运算,如果不为零,代表发生了上升沿或者下降沿。 +
- +
-\\ +
-在按键消抖的过程中,同样运用了脉冲边沿检测。用两个寄存器储存相邻时钟采集的值(例如data_pre,​data),​然后将data取反与前一个值相与(state=data_pre&​(~data)),​如果为1,则判断有下降沿既按键按下由高到低;否则无变化。 +
-\\ +
-将一个信号由连续时钟采集,相邻两个钟触发的值存入两个寄存器。理解verilog实现这个过程要充分了解其中的[[非阻塞赋值]]。 +
- +
-\\ +
-====Verilog代码==== +
-------+
  
 <code verilog> <code verilog>
行 43: 行 18:
 // >>>>>>>>>>>>>>>>>>>>>>>>>​ COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<​ // >>>>>>>>>>>>>>>>>>>>>>>>>​ COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<​
 // ******************************************************************** // ********************************************************************
-// File name    : debounce.v +// File name    : counter.v 
-// Module name  : debounce+// Module name  : counter
 // Author ​      : STEP // Author ​      : STEP
 // Description ​ :  // Description ​ : 
行 55: 行 30:
 // V1.0     ​|2017/​03/​02 ​  ​|Initial ver // V1.0     ​|2017/​03/​02 ​  ​|Initial ver
 // -------------------------------------------------------------------- // --------------------------------------------------------------------
-// Module Function:​按键消抖+// Module Function:24秒倒计时计数器 
 +  
 +module counter 
 +
 + clk , ​   //时钟 
 + rst , ​   //复位 
 + hold , ​   //​启动暂停按键 
 + seg_led_1 , ​   //​数码管1 
 + seg_led_2 , ​   //​数码管2 
 + led                  //led 
 +); 
 +  
 + input clk,rst; 
 + input hold;​ 
 +  
 + output [8:​0] seg_led_1,​seg_led_2;​ 
 + output reg [7:​0] led;​ 
 +  
 + wire clk1h; ​       //​1Hz时钟 
 + wire hold_pulse; ​  //按键消抖后信号 
 + reg hold_flag; ​   //​按键标志位 
 + reg back_to_zero_flag ;​ //​计时完成信号 
 + reg   ​ [6:​0] ​  ​seg [9:​0]; ​  
 + reg [3:​0] cnt_ge; ​     //个位 
 + reg [3:​0] cnt_shi; ​    //​十位 
 +  
 + initial  
 + begin 
 + seg[0] = 7'​h3f;​  ​  // ​ 0 
 + seg[1] = 7'​h06;​  ​  // ​ 1 
 + seg[2] = 7'​h5b;​  ​  // ​ 2 
 + seg[3] = 7'​h4f;​  ​  // ​ 3 
 + seg[4] = 7'​h66;​  ​  // ​ 4 
 + seg[5] = 7'​h6d;​  ​  // ​ 5 
 + seg[6] = 7'​h7d;​  ​  // ​ 6 
 + seg[7] = 7'​h07;​  ​  // ​ 7 
 + seg[8] = 7'​h7f;​  ​  // ​ 8 
 + seg[9] = 7'​h6f;​  ​  // ​ 9 
 +/​*若需要显示A-F,​解除此段注释即可 
 + seg[10]= 7'​hf7;​  ​  // ​ A 
 + seg[11]= 7'​h7c;​  ​  // ​ b 
 + seg[12]= 7'​h39; ​   //  C 
 + seg[13]= 7'​h5e; ​   //  d 
 + seg[14]= 7'​h79; ​   //  E 
 + seg[15]= 7'​h71; ​   //  F*/ 
 + end 
 +
  
-module ​debounce (clk,​rst,​key,​key_pulse);​+  
 + // 启动/​暂停按键进行消抖 
 + debounce ​ ​U2 ​( 
 + .clk(clk), 
 + .rst(rst), 
 + .key(hold), 
 + .key_pulse(hold_pulse) 
 + ); 
 + // 用于分出一个1Hz的频率  
 + divide #​(.WIDTH(32),​.N(12000000)) U1 (  
 + .clk(clk),​ 
 + .rst_n(rst), ​      
 + .clkout(clk1h) 
 + ); 
 +    //​按键动作标志信号产生 
 + always @ (posedge hold_pulse) 
 + if(!rst==1) 
 + hold_flag <= 0; 
 + else 
 + hold_flag <= ~hold_flag;​ 
 + //​计时完成标志信号产生 
 + always @ (*) 
 + if(!rst == 1) 
 + back_to_zero_flag <= 0; 
 + else if(cnt_shi==0 && cnt_ge==0) 
 + back_to_zero_flag <= 1; 
 + else 
 + back_to_zero_flag <= 0; 
 +    //​24秒倒计时控制 
 + always @ (posedge clk1h or negedge rst) begin 
 + if (!rst == 1) begin 
 + cnt_ge <= 4'​d4;​ 
 + cnt_shi <= 4'd2;  
 + end 
 + else if(hold_flag == 1)begin 
 + cnt_ge <= cnt_ge; 
 + cnt_shi <= cnt_shi; 
 + end  
 + else if(cnt_shi==0 && cnt_ge==0) begin 
 + cnt_shi <= cnt_shi; 
 + cnt_ge <= cnt_ge; 
 + end 
 + else if(cnt_ge==0)begin 
 + cnt_ge <= 4'​d9;​ 
 + cnt_shi <= cnt_shi-1;​end 
 + else 
 + cnt_ge <= cnt_ge -1; 
 + end 
 + //​计时完成点亮led 
 + always @ ( back_to_zero_flag)begin 
 + if (back_to_zero_flag==1) 
 + led = 8'​b0;​ 
 + else 
 + led = 8'​b11111111; 
 + end
  
-        parameter ​      ​N  ​ 1                     //​要消除的按键的数量+ assign seg_led_1[8:​0] ​{2'​b00,​seg[cnt_ge]};​ 
 +  
 + assign seg_led_2[8:​0] = {2'​b00,​seg[cnt_shi]};
  
- input ​            ​clk;​ +  
-        input             ​rst;​ +endmodule
-        input [N-1:​0] ​  ​key; ​                       //​输入的按键  +
- output ​ [N-1:​0] ​  ​key_pulse; ​                 //​按键动作产生的脉冲  +
-         +
-        reg     ​[N-1:​0] ​  ​key_rst_pre; ​               //​定义一个寄存器型变量存储上一个触发时的按键值 +
-        reg     ​[N-1:​0] ​  ​key_rst; ​                   //​定义一个寄存器变量储存储当前时刻触发的按键值 +
-         +
-        wire    [N-1:​0] ​  ​key_edge; ​                  //​检测到按键由高到低变化是产生一个高脉冲 +
-         +
-        //​利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中 +
-        always @(posedge clk  or  negedge rst) +
-          begin +
-             if (!rst) begin +
-                 ​key_rst <= {N{1'​b1}}; ​               //​初始化时给key_rst赋值全为1,{}中表示N个1 +
-                 ​key_rst_pre <= {N{1'​b1}};​ +
-             end +
-             else begin +
-                 ​key_rst <= key;                     //​第一个时钟上升沿触发之后key的值赋给key_rst,​同时key_rst的值赋给key_rst_pre +
-                 ​key_rst_pre <= key_rst; ​            //​非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值 +
-             ​end ​    +
-           end +
-            +
-        assign ​ key_edge = key_rst_pre & (~key_rst);//​脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平 +
-         +
-        reg [17:​0]  ​ cnt;                       //​产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器 ​     +
-         +
-        //​产生20ms延时,当检测到key_edge有效是计数器清零开始计数 +
-        always @(posedge clk or negedge rst) +
-           ​begin +
-             ​if(!rst) +
-                cnt <= 18'​h0;​ +
-             else if(key_edge) +
-                cnt <= 18'​h0;​ +
-             ​else +
-                cnt <= cnt + 1'​h1;​ +
-             ​end ​  +
-         +
-        reg     ​[N-1:​0] ​  ​key_sec_pre; ​               //​延时后检测电平寄存器变量 +
-        reg     ​[N-1:​0] ​  ​key_sec; ​                    +
-         +
-                      +
-        //​延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效 +
-        always @(posedge clk  or  negedge rst) +
-          begin +
-             if (!rst)  +
-                 ​key_sec <= {N{1'​b1}}; ​                +
-             else if (cnt==18'​h3ffff) +
-                 ​key_sec <= key;   +
-          end +
-       ​always @(posedge clk  or  negedge rst) +
-          begin +
-             if (!rst) +
-                 ​key_sec_pre <= {N{1'​b1}};​ +
-             ​else ​                   +
-                 ​key_sec_pre <= key_sec; ​             +
-             ​end ​    +
-           ​end ​      +
-       ​assign ​ key_pulse = key_sec_pre & (~key_sec); ​     +
-        +
-endmodule ​+
  
 </​code>​ </​code>​
行 127: 行 145:
 \\ \\
  
-以上就是一个N位按键的消抖程序,如果有按键按下会输出一个时钟周期的高脉冲。下面我们可以试试用这个按键消抖的输出来触发LED的显示,既按键一次LED翻转。你也可以不加按键消抖试试用按键来控制LED(按一次变亮,再按一次灭掉)。+### 3. 引脚分配
  
-\\ 
-下面的程序是例化调用debounce模块来控制LED 
-\\ 
-<code verilog> 
-// ******************************************************************** 
-// >>>>>>>>>>>>>>>>>>>>>>>>>​ COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<​ 
-// ******************************************************************** 
-// File name    : top.v 
-// Module name  : top 
-// Author ​      : STEP 
-// Description ​ :  
-// Web          : www.stepfpga.com 
-//  
-// -------------------------------------------------------------------- 
-// Code Revision History :  
-// -------------------------------------------------------------------- 
-// Version: |Mod. Date:   ​|Changes Made: 
-// V1.0     ​|2017/​03/​02 ​  ​|Initial ver 
-// -------------------------------------------------------------------- 
-// Module Function:​进过按键消抖后控制led显示翻转 
- 
-module top (clk,​rst,​key,​led);​ 
- 
-        input             clk; 
-        input             rst; 
-        input           key;                       
- output ​  ​reg ​     led;        ​ 
- 
-        wire              key_pulse; 
-        ​ 
-        //​当按键按下时产生一个高脉冲,翻转一次led 
-        always @(posedge clk  or  negedge rst) 
-           begin 
-             if (!rst) ​ 
- led <= 1'b1; 
-      else if (key_pulse) 
- led <= ~led; 
-      else 
-                led <= led; 
-    ​end ​   ​ 
-         //​例化消抖module,这里没有传递参数N,采用了默认的N=1 ​     
-         ​debounce ​ u1 (                                
-                       .clk (clk), 
-                       .rst (rst), 
-                       .key (key), 
-                       ​.key_pulse (key_pulse) 
-                       ); 
- ​endmodule 
-</​code>​ 
- 
-\\ 
-====引脚分配==== 
-------- 
 设置好复位键可消抖的按键,编译完成后下载,通过按键就可以翻转LED。你也可以定义多个按键控制多个LED,还可以比较不加按键消抖情况下实际的效果对比如何。 设置好复位键可消抖的按键,编译完成后下载,通过按键就可以翻转LED。你也可以定义多个按键控制多个LED,还可以比较不加按键消抖情况下实际的效果对比如何。
 \\ \\
行 187: 行 152:
 |clk             ​|C1 ​              ^ |clk             ​|C1 ​              ^
 |rst             ​|L14 ​             ^ |rst             ​|L14 ​             ^
-|key             |N14             +|hold            ​|M13              ​
-|led             |N13              ^+|seg_led_1[0] ​   ​|C12              ^ 
 +|seg_led_1[1] ​   |B14              ^ 
 +|seg_led_1[2] ​   |J1              ^ 
 +|seg_led_1[3] ​   |H1              ^ 
 +|seg_led_1[4] ​   |H2              ^ 
 +|seg_led_1[5] ​   |B12              ^ 
 +|seg_led_1[6] ​   |A11              ^ 
 +|seg_led_1[7] ​   |K1              ^ 
 +|seg_led_1[8] ​   |A12              ^ 
 +|seg_led_2[0] ​   |A10              ^ 
 +|seg_led_2[1] ​   |C11              ^ 
 +|seg_led_2[2] ​   |F2               ^ 
 +|seg_led_2[3] ​   |E1               ^ 
 +|seg_led_2[4] ​   |E2               ^ 
 +|seg_led_2[5] ​   |A9               ^ 
 +|seg_led_2[6] ​   |B9               ^ 
 +|seg_led_2[7] ​   |F1               ^ 
 +|seg_led_2[8] ​   |C9               ^ 
 + 
 +### 4. 小结
  
-====小结==== +本实验主要介绍计时器的实现方式,并且包含了复位与暂停功能,读者可自修改程序内部时钟参数来调节计时时间下一节将介绍PWM调制技术的应用[[9呼吸灯|呼吸灯]]。
------- +
-本实验学习如何进按键消抖在很多应用情况下我们必须采取消抖才能更好地控制逻辑。在下一个实验[[8计时控制|计时控制]]中我们将学习计时的显示和控制,在这里我们要用到按键的消抖以及数码管,我们甚至可以用小脚丫做一个计时器甚至电子表+