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)