FPGA课程设计:简单计时器闹钟

Posted zstar-_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FPGA课程设计:简单计时器闹钟相关的知识,希望对你有一定的参考价值。

本文是EDA实验的课程设计
完整源码和实验报告可从此处下载:https://gitee.com/zstar1003/xdu-homework/tree/master/EDA%E5%AE%9E%E9%AA%8C

一、实验目的

设计一个电子闹钟。要求电路上电后自动计时,到达预置的闹响时刻后,由蜂鸣器发出音乐报警。闹响时刻可利用按键设置,设置范围0~999999。
此次实验除了满足上述基本功能外,额外添置了流水灯功能,当到达预置的闹响时刻后,不仅蜂鸣器会发出音乐报警,并且LED会形成流水灯。

二、实验环境

2.1 硬件环境

本实验采用的开发板是正点原子的开拓者FPGA开发板。

2.2 软件环境

使用软件:Quartus Ⅱ (18.1) 、ModelSim(10.5)
操作系统:Windows 10(64位)

三、方案设计及理论计算

3.1 原理框图

输入:时钟信号,重置信号,按键信号。
输出:数码管位选信号、数码管段选信号、流水灯控制信号、蜂鸣器控制信号。

3.2 分频器

输入:时钟信号,重置信号。
输出:分频时钟信号。
功能:对50MHz分频。
板子上电之后需每隔1s进行计数,板子的时钟频率为50MHz,为满足这一功能,需要设计一个分频器,对板子的50MHz频率进行分频,从而输出一个1Hz的时钟信号。
分频器模值、系统时钟和期望输出时钟频率关系为

所以,把50MHz时钟分频,输出1Hz的时钟,分频器的模值为

为了保证分频器正常工作,计数器寄存器所能表示的最大值必须大于分频器的模值。这里,设置把计数器寄存器的位数设定为26位。
流程图如图 1所示。

数码管的动态显示和流水灯也同样用到分频器,原理一样。其中,数码管对系统时钟频率进行了10分频,流水灯对系统时钟频率进行了5分频。

3.3 按键消抖

输入:时钟信号,重置信号,按键信号。
输出:按键数据有效信号,按键消抖后数据。
功能:消除按键抖动。
由于每次按下按键时存在抖动,容易引起按键的多次触发,因此按键消抖模块就可以解决这个问题。
按键消抖主要通过延时来实现,即当按键的一个状态保持20ms以上,即锁存按键的状态,这样既保证按键消抖的稳定又保证了一定的灵敏性。原理图如图2所示。

3.4 状态机

实验中需要通过按键实现时间显示和时间设定两个模式的切换,本实验采用了一位寄存器作为状态机,当按键按下时,状态机可以从0、1两个状态之间来回切换。原理如图3所示:

为了防止时间设定模式时对蜂鸣器和流水灯的干扰,本实验单独设置了一个寄存器信号,用于隔离两种模式。同时,显示时间和设定时间采用不同的寄存器存储,这样方便比较并且当时间设定时,时间显示会处于暂停的状态。

3.5 数码管动态显示

输入:重置信号,时钟信号,计数时间,设定时间,数码管使能信号、模式信号。
输出:数码管位选信号,数码管段选信号。
功能:将计数时间/设定时间在数码管上进行显示。
在数码管动态显示模块中,首先需要根据模式信号来判断需要显示的时间,即时间显示模式显示计数时间,时间设定模式显示设定时间。之后,通过整除去尾、取模取尾的计算方式,将时间每个位数上的数据提取出来。例如,提取十位数的数据公式为:

将每一位上的数据提取出来后,将其转换成8421BCD码,与每一个数码管进行绑定。通过对系统时钟进行10分频,得到的频率为5MHz的数码管驱动时钟,用来控制数码管的位选信号,使每一个数码管以1ms的时间周期轮流显示。
当数码管需要显示时,通过段选信号将每一位数码管绑定的数据进行转换,从而显示出正确的数值。本实验的开发板采用的是共阳极数码管,段选真值表如图4所示。

3.6 引脚分配

根据开发手册配置相关引脚如图 5所示。

四、波形仿真

4.1 仿真参数

sys_clk:时钟信号
sys_rst_n:复位信号
key0:按键信号,用于切换模式
key1:按键信号,在设定模式下,按一次设定时间加一秒
key2:按键信号,在设定模式下,按一次设定时间减一秒
key3:按键信号,在设定模式下,按一次设定时间加十秒
beep:蜂鸣器信号
seg_sel:数码管位选信号
seg_led:数码管段选信号
led:流水灯信号

4.2 仿真波形图

可以看到上电之后,程序开始正常计时,首先按下模式切换按键key0,切换到设定时间模式,之后按下key1,设定时间加一,再按下key2,设定时间减一,再按下key3,设定时间加十,共设计十秒时间。最后再按一次key0,重新切换到计时模式。当计时时间到达设定时间时,蜂鸣器的beep信号变为高电平,流水灯开始工作。按下复位信号后,蜂鸣器的beep信号变为低电平,停止工作,流水灯也回复到初始状态。

五、实验结果

将程序通过驱动下载到开发板上后,开发板开始自动计数,按下key0进入设定模式,通过其它三个按键设定好闹钟时间。再按key0,返回计数模式。当时间到达设定时间时,蜂鸣器播放音乐,流水灯开始工作,达到预期效果。
实验效果图如图所示。

六、完整代码

顶层模块:

1.	module top_alarm(  
2.	    //global clock  
3.	    input            sys_clk  ,       // 全局时钟信号  
4.	    input            sys_rst_n,       // 复位信号(低有效)  
5.	    input           key0,           //按键信号  
6.	    input           key1,  
7.	    input           key2,  
8.	    input           key3,  
9.	    //seg_led interface  
10.	    output    [5:0]  seg_sel  ,       // 数码管位选信号  
11.	    output    [7:0]  seg_led  ,        // 数码管段选信号  
12.	    output   [3:0]  led ,       //4个LED灯  
13.	    output beep  
14.	);  
15.	  
16.	//wire define  
17.	wire    [19:0]  data;                 // 数码管正常计数显示的数值  
18.	wire    [19:0]  set_data;         // 数码管设定闹钟显示的数值  
19.	wire   en;                                // 数码管显示使能信号  
20.	wire    en_beep;                                // 蜂鸣器使能信号  
21.	wire   key_value0;  
22.	wire   key_value1;  
23.	wire   key_value2;  
24.	wire   key_value3;  
25.	wire   key_flag0;  
26.	wire   key_flag1;  
27.	wire   key_flag2;  
28.	wire   key_flag3;  
29.	  
30.	// 对四个按键分别进行消抖  
31.	key_debounce k0 (  
32.	    .sys_clk(sys_clk),          //外部50M时钟  
33.	    .sys_rst_n(sys_rst_n),        //外部复位信号,低有效  
34.	    .key(key0),              //外部按键输入  
35.	    .key_flag(key_flag0),         //按键数据有效信号  
36.	    .key_value(key_value0)       //按键消抖后的数据    
37.	    );  
38.	    
39.	key_debounce k1(  
40.	    .sys_clk(sys_clk),          //外部50M时钟  
41.	    .sys_rst_n(sys_rst_n),        //外部复位信号,低有效  
42.	    .key(key1),              //外部按键输入  
43.	    .key_flag(key_flag1),         //按键数据有效信号  
44.	    .key_value(key_value1)       //按键消抖后的数据    
45.	);  
46.	      
47.	 key_debounce k2(  
48.	    .sys_clk(sys_clk),          //外部50M时钟  
49.	    .sys_rst_n(sys_rst_n),        //外部复位信号,低有效  
50.	    .key(key2),              //外部按键输入  
51.	    .key_flag(key_flag2),         //按键数据有效信号  
52.	    .key_value(key_value2)       //按键消抖后的数据    
53.	 );  
54.	      
55.	 key_debounce k3(  
56.	    .sys_clk(sys_clk),          //外部50M时钟  
57.	    .sys_rst_n(sys_rst_n),        //外部复位信号,低有效  
58.	    .key(key3),              //外部按键输入  
59.	    .key_flag(key_flag3),         //按键数据有效信号  
60.	    .key_value(key_value3)       //按键消抖后的数据    
61.	 );  
62.	      
63.	//计数器模块,产生数码管需要显示的数据  
64.	count u_count(  
65.	    .clk           (sys_clk  ),       // 时钟信号  
66.	    .rst_n         (sys_rst_n),       // 复位信号  
67.	    .key_flag0   (key_flag0) ,    //按键有效信号  
68.	    .key_value0 (key_value0),   //消抖后的按键信号    
69.	    .key_flag1   (key_flag1) ,    //按键有效信号  
70.	    .key_value1 (key_value1),   //消抖后的按键信号    
71.	    .key_flag2   (key_flag2) ,    //按键有效信号  
72.	    .key_value2 (key_value2),   //消抖后的按键信号    
73.	    .key_flag3   (key_flag3) ,    //按键有效信号  
74.	    .key_value3 (key_value3),   //消抖后的按键信号    
75.	    .set_data(set_data) ,      // 设定0~999999时间  
76.	    .data          (data),       // 6位数码管要显示的数值  
77.	    .en            (en),       // 数码管使能信号  
78.	    .en_beep (en_beep ),  
79.	    .mode   (mode)  
80.	);  
81.	  
82.	  
83.	    
84.	//数码管动态显示模块  
85.	seg_led u_seg_led(  
86.	    .clk           (sys_clk  ),       // 时钟信号  
87.	    .rst_n         (sys_rst_n),       // 复位信号  
88.	      
89.	    .original_data           (data),       // 显示的数值  
90.	    .set_data(set_data),  
91.	    .en            (en       ),       // 数码管使能信号  
92.	    .mode   (mode),  
93.	      
94.	    .seg_sel       (seg_sel  ),       // 位选  
95.	    .seg_led       (seg_led  )        // 段选  
96.	);  
97.	  
98.	// 蜂鸣器音乐模块  
99.	alarm_music alarm(  
100.	    .clk           (sys_clk  ),           // 时钟信号  
101.	    .rst_n         (sys_rst_n),       // 复位信号  
102.	    .data          (data) ,              // 实际的时间  
103.	    .set_data (set_data ),      // 设定的时间  
104.	    .en_beep (en_beep ),  
105.	    .beep(beep)  
106.	);  
107.	  
108.	flow_light light(  
109.	    .sys_clk  (sys_clk) ,  //系统时钟  
110.	    .sys_rst_n (sys_rst_n) ,  //系统复位,低电平有效  
111.	    .data          (data) ,              // 实际的时间  
112.	    .set_data (set_data ),      // 设定的时间  
113.	    .en_beep (en_beep ),  
114.	    .led (led)         //4个LED灯  
115.	    );  
116.	endmodule  

按键消抖模块

1.	module key_debounce(  
2.	    input            sys_clk,          //外部50M时钟      
3.	    input            sys_rst_n,        //外部复位信号,低有效  
4.	      
5.	    input            key,              //外部按键输入  
6.	    output reg       key_flag,         //按键数据有效信号  
7.	    output reg       key_value         //按键消抖后的数据    
8.	    );  
9.	  
10.	//reg define      
11.	reg [31:0] delay_cnt;  
12.	reg        key_reg;  
13.	  
14.	  
15.	always @(posedge sys_clk or negedge sys_rst_n) begin   
16.	    if (!sys_rst_n) begin   
17.	        key_reg   <= 1'b1;  
18.	        delay_cnt <= 32'd0;  
19.	    end  
20.	    else begin  
21.	        key_reg <= key;  
22.	        if(key_reg != key)             //一旦检测到按键状态发生变化(有按键被按下或释放)  
23.	            delay_cnt <= 32'd1000000;  //给延时计数器重新装载初始值(计数时间为20ms)  
24.	        else if(key_reg == key) begin  //在按键状态稳定时,计数器递减,开始20ms倒计时  
25.	                 if(delay_cnt > 32'd0)  
26.	                     delay_cnt <= delay_cnt - 1'b1;  
27.	                 else  
28.	                     delay_cnt <= delay_cnt;  
29.	             end             
30.	    end     
31.	end  
32.	  
33.	always @(posedge sys_clk or negedge sys_rst_n) begin   
34.	    if (!sys_rst_n) begin   
35.	        key_flag  <= 1'b0;  
36.	        key_value <= 1'b1;            
37.	    end  
38.	    else begin  
39.	        if(delay_cnt == 32'd1) begin   //当计数器递减到1时,说明按键稳定状态维持了20ms  
40.	            key_flag  <= 1'b1;         //此时消抖过程结束,给出一个时钟周期的标志信号  
41.	            key_value <= key;          //并寄存此时按键的值  
42.	        end  
43.	        else begin  
44.	            key_flag  <= 1'b0;  
45.	            key_value <= key_value;   
46.	        end    
47.	    end     
48.	end  
49.	      
50.	endmodule   

计数模块

1.	module count(  
2.	    //mudule clock  
3.	    input                   clk  ,      // 时钟信号  
4.	    input                   rst_n,      // 复位信号  
5.	    input        key_flag0,    //按键有效信号  
6.	    input        key_value0,    //消抖后的按键信号  
7.	    input        key_flag1,    //按键有效信号  
8.	    input        key_value1,    //消抖后的按键信号    
9.	    input        key_flag2,    //按键有效信号  
10.	    input        key_value2,    //消抖后的按键信号  
11.	    input        key_flag3,    //按键有效信号  
12.	    input        key_value3,    //消抖后的按键信号  
13.	      
14.	    //user interface  
15.	    output   reg [19:0]     data ,      // 6个数码管要显示的数值  
16.	    output   reg [19:0]     set_data ,      // 设定0~999999时间  
17.	    output   reg     en  ,     // 数码管使能信号  
18.	    output   reg     en_beep  ,     // 蜂鸣器使能信号  
19.	    output   reg     mode                //为了控制不同状态的显示,同时需输出当前模式  
20.	    );  
21.	  
22.	//parameter define  
23.	parameter  MAX_NUM = 26'd5000_0000;      // 计数器计数的最大值 1s/20ns  
24.	  
25.	//reg define  
26.	reg    [25:0]   cnt ;                   // 计数器,用于计时1s  
27.	reg             flag;                   // 标志信号  
28.	reg             flag_key;                   // 标志信号  
29.	  
30.	  
31.	//模式选择  
32.	always @ (posedge clk or negedge rst_n) begin  
33.	    if(!rst_n)  
34.	         mode <= 1'd0;  
35.	    else if(key_flag0&& (~key_value0)) begin //判断按键是否有效按下  
36.	         mode <= mode + 1'b1;   // 一位状态机,0、1循环  
37.	         end  
38.	    else begin  
39.	            mode <= mode;  
40.	     end     
41.	 end  
42.	   
43.	//计数器对系统时钟计数达1s时,输出一个时钟周期的脉冲信号  
44.	always @ (posedge clk or negedge rst_n) begin  
45.	    if (!rst_n) begin  
46.	        cnt <= 26'b0;  
47.	        flag<= 1'b0;  
48.	    end  
49.	    else if (cnt < MAX_NUM - 1'b1) begin  
50.	        cnt <= cnt + 1'b1;  
51.	        flag<= 1'b0;  
52.	    end  
53.	    else begin  
54.	        cnt <= 26'b0;  
55.	        flag <= 1'b1;  
56.	    end  
57.	end   
58.	  
59.	  
60.	//数码管需要显示的数据,从0累加到999999  
61.	always @ (posedge clk or negedge rst_n) begin  
62.	    if (!rst_n)begin  
63.	        data  <= 20'b0;  
64.	        set_data <=20'b0;  
65.	        en    <= 1'b0;  
66.	    end   
67.	    // 按键0切换状态,mode=0时正常计数  
68.	    else if ( mode == 1'b0) begin  
69.	        en    <= 1'b1;                  //打开数码管使能信号  
70.	        en_beep    <= 1'b1;               
71.	        if (flag) begin                 //显示数值每隔1s累加一次  
72.	            if(data < 20'd999999)   
73.	                data <= data +1'b1;       
74.	            else  
75.	                data <= 20'b0;  
76.	        end   
77.	    else   
78.	           data <= data;  
79.	    end   
80.	    // 按键0切换状态,mode=1时进入设定模式  
81.	    else if (mode == 1'b1) begin  
82.	        en    <= 1'b1;     
83.	        en_beep    <= 1'b0;     
84.	        //按下按键1设定数+1  
85.	        if (key_flag1&& (~key_value1))  begin  
86.	                if(set_data < 20'd999999)   
87.	                        set_data <= set_data + 1'b1;  
88.	                 else  
89.	                        set_data <= 20'b0;  
90.	                 end  
91.	         //按下按键3设定数+10  
92.	        else if (key_flag3&& (~key_value3))  begin  
93.	                if(set_data < 20'd999999)   
94.	                        set_data <= set_data + 4'd10;  
95.	                 else  
96.	                        set_data <= 20'b0;  
97.	                 end  
98.	        //按下按键2设定数-1  
99.	        else if (key_flag2&& (~key_value2))  begin  
100.	                if(set_data > 20'd0)   
101.	                        set_data <= set_data - 1'b1;  
102.	                 else  
103.	                        set_data <= 20'b0;  
104.	                 end  
105.	         //不按键的时设定数维持不变  
106.	        else   
107.	                 set_data <=  set_data;  
108.	        end   
109.	end   
110.	  
111.	endmodule   

数码管显示模块

1.	module seg_led(  
2.	    input                   clk    ,        // 时钟信号  
3.	    input                   rst_n  ,        // 复位信号  
4.	  
5.	    input         [19:0]    original_data   ,        // 6位数码管要显示的计数时间数值  
6.	    input         [19:0]    set_data  ,        // 6位数码管要显示的设定时间数值  
7.	    input                   en     ,        // 数码管使能信号  
8.	    input         [1:0] mode ,  
9.	  
10.	    output   reg  [5:0]     seg_sel,        // 数码管位选,最左侧数码管为最高位  
11.	    output   reg  [7:0]     seg_led         // 数码管段选  
12.	    );  
13.	  
14.	//parameter define  
15.	localparam  CLK_DIVIDE = 4'd10     ;        // 时钟分频系数  
16.	localparam  MAX_NUM    = 13'd5000  ;        // 对数码管驱动时钟(5MHz)计数1ms所需的计数值  
17.	  
18.	//reg define  
19.	reg    [ 3:0]             clk_cnt  ;        // 时钟分频计数器  
20.	reg                       dri_clk  ;        // 数码管的驱动时钟,5MHz  
21.	reg    [23:0]             num      ;        // 24位bcd码寄存器  
22.	reg    [12:0]             cnt0     ;        // 数码管驱动时钟计数器  
23.	reg                       flag     ;        // 标志信号(标志着cnt0计数达1ms)  
24.	reg    [2:0]              cnt_sel  ;        // 数码管位选计数器  
25.	reg    [3:0]              num_disp ;        // 当前数码管显示的数据  
26.	  
27.	  
28.	  
29.	//wire define  
30.	wire   [19:0]              data    ;        // 需要生成的data  
31.	wire   [3:0]              data0    ;        // 个位数  
32.	wire   [3:0]              data1    ;        // 十位数  
33.	wire   [3:以上是关于FPGA课程设计:简单计时器闹钟的主要内容,如果未能解决你的问题,请参考以下文章

[SystemVerilog] 基于 FPGA 的数字钟设计

[FPGA]Verilog 60s秒表计时器(最大可计时间长达9min)

FPGA教程案例9基于vivado核的时钟管理器设计与实现

FPGA 开发基础------------奇数分频,占空比50%

FPGA教程案例8基于verilog的分频器设计与实现

数字信号处理的FPGA实现——混频器(Mixer)