至简设计系列_闹钟
--作者:小黑同学
本文为明德扬原创及录用文章,转载请注明出处!
1.1 总体设计
1.1.1 概述
数字时钟是采用数字电路技术实现时、分、秒计时显示的装置,可以用数字同时显示时,分,秒
的精确时间并实现准确校时,具备体积小、重量轻、抗干扰能力强、对环境要求高、高精确性、容易
开发等特性,在工业控制系统、智能化仪器表、办公自动化系统等诸多领域取得了极为广泛的应用,
诸如自动报警、按时自动打铃、时间程序自动控制、定时广播、自定启闭路灯、定时开关烘箱、通断
动力设备、甚至各种定时电器的自动启用等。与传统表盘式机械时钟相比,数字时钟具有更高的准确
性和直观性,由于没有机械装置,其使用寿命更长。
1.1.2 设计目标
设计一款具有闹钟功能的数字时钟,具体要求如下
1、 用 8 个数码管实现,四个一组,每组有分钟和秒。左边一组是时间显示,右边一组用来
做闹钟时间。
2、 当左边时间等于右边时,蜂鸣器响 5 秒。
3、 闹钟时间和显示时间均可通过 3 个按键设置。设置方法:按下按键 1,时钟暂停,跳到
设置时间状态,再按下按键 1,回到正常状态。通过按键 2,选择要设置的位置,初始
设置秒个位,按一下,设置秒十位,再按下,设置分个位,以此类推,循环设置。通过
按键 3,设置数值,按一下数值加 1,如果溢出则重新变为 0。
1.1.3 系统结构框图
系统结构框图如下所示:
结构图共分两个,如果使用的开发板上是矩阵键盘的时候,对应的结构图是图一。如果使用的开
发板上是普通按键的时候,对应的结构图是图二。
1.1.4 模块功能
➢ 按键检测模块实现功能
1、将外来异步信号打两拍处理,将异步信号同步化。
2、实现 20ms 按键消抖功能,并输出有效按键信号。
➢ 矩阵键盘模块实现功能
1、将外来异步信号打两拍处理,将异步信号同步化。
2、实现 20ms 按键消抖功能。
3、实现矩阵键盘的按键检测功能,并输出有效按键信号。
➢ 时间产生模块实现功能
1、 产生显示时间数据。
2、 产生闹钟时间数据,
3、根据接收到的不同的按键信号,产生暂停、开启、设置时间的功能。
➢ 数码管显示模块实现功能
1、 对接收到的时间数据进行译码。
➢ 蜂鸣器模块实现功能
1、 将接受到的显示时间数据与闹钟时间数据进行比较,控制蜂鸣器的开启。
1.1.5 顶层信号
1.1.6 参考代码
下面是使用普通按键的顶层代码:
1. module alarm_clock( 2. clk , 3. rst_n , 4. key , 5. segment , 6. seg_sel , 7. beep 8. ); 9. input clk ; 10. input rst_n ; 11. input [2:0 ] key ; 12. output [7:0 ] segment ; 13. output [7:0 ] seg_sel ; 14. output beep ; 15. 16. wire [7:0 ] segment ; 17. wire [7:0 ] seg_sel ; 18. wire beep ; 19. wire [3:0 ] xs_sec_low ; 20. wire [3:0 ] xs_sec_high ; 21. wire [3:0 ] xs_min_low ; 22. wire [3:0 ] xs_min_high ; 23. wire [3:0 ] sec_low ; 24. wire [3:0 ] sec_high ; 25. wire [3:0 ] min_low ; 26. wire [3:0 ] min_high ; 27. wire [25:0] counter ; 28. wire [3:0 ] key_vld ; 29. wire flag_set ; 30. 31. key_module u0( 32. .clk (clk ), 33. .rst_n (rst_n ), 34. .key_in (key ), 35. .key_vld (key_vld ) 36. ); 37. time_data u1( 38. .clk (clk ), 39. .rst_n (rst_n ), 40. .key_vld (key_vld ), 41. .flag_set (flag_set ), 42. .counter (counter ), 43. .sec_low (sec_low ), 44. .sec_high (sec_high ), 45. .min_low (min_low ), 46. .min_high (min_high ), 47. .xs_sec_low (xs_sec_low ), 48. .xs_sec_high (xs_sec_high ), 49. .xs_min_low (xs_min_low ), 50. .xs_min_high (xs_min_high ) 51. ); 52. beep u2( 53. .clk (clk ), 54. .rst_n (rst_n ), 55. .flag_set (flag_set ), 56. .counter (counter ), 57. .beep (beep ), 58. .sec_low (sec_low ), 59. .sec_high (sec_high ), 60. .min_low (min_low ), 61. .min_high (min_high ), 62. .xs_sec_low (xs_sec_low ), 63. .xs_sec_high (xs_sec_high ), 64. .xs_min_low (xs_min_low ), 65. .xs_min_high (xs_min_high ) 66. ); 67. seg_disp u3( 68. .clk (clk ), 69. .rst_n (rst_n ), 70. .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}), 71. .segment (segment ), 72. .seg_sel (seg_sel ) 73. ); 74. 75. 76. endmodule
下面是使用矩阵键盘的顶层代码:
1. module alarm_clock_jvzhen( 2. clk , 3. rst_n , 4. key_col , 5. key_row , 6. segment , 7. seg_sel , 8. beep 9. ); 10. input clk ; 11. input rst_n ; 12. input [3:0 ] key_col ; 13. output [3:0 ] key_row ; 14. output [7:0 ] segment ; 15. output [7:0 ] seg_sel ; 16. output beep ; 17. 18. wire [7:0 ] segment ; 19. wire [7:0 ] seg_sel ; 20. wire beep ; 21. wire [3:0 ] xs_sec_low ; 22. wire [3:0 ] xs_sec_high ; 23. wire [3:0 ] xs_min_low ; 24. wire [3:0 ] xs_min_high ; 25. wire [3:0 ] sec_low ; 26. wire [3:0 ] sec_high ; 27. wire [3:0 ] min_low ; 28. wire [3:0 ] min_high ; 29. wire [25:0] counter ; 30. wire [3:0 ] key_vld ; 31. wire flag_set ; 32. wire [15:0] key_out ; 33. 34. key_scan u0( 35. .clk (clk ), 36. .rst_n (rst_n ), 37. .key_col (key_col ), 38. .key_row (key_row ), 39. .key_en (key_vld ) 40. ); 41. time_data u1( 42. .clk (clk ), 43. .rst_n (rst_n ), 44. .key_vld (key_vld ), 45. .flag_set (flag_set ), 46. .counter (counter ), 47. .sec_low (sec_low ), 48. .sec_high (sec_high ), 49. .min_low (min_low ), 50. .min_high (min_high ), 51. .xs_sec_low (xs_sec_low ), 52. .xs_sec_high (xs_sec_high ), 53. .xs_min_low (xs_min_low ), 54. .xs_min_high (xs_min_high ) 55. ); 56. beep u2( 57. .clk (clk ), 58. .rst_n (rst_n ), 59. .flag_set (flag_set ), 60. .counter (counter ), 61. .beep (beep ), 62. .sec_low (sec_low ), 63. .sec_high (sec_high ), 64. .min_low (min_low ), 65. .min_high (min_high ), 66. .xs_sec_low (xs_sec_low ), 67. .xs_sec_high (xs_sec_high ), 68. .xs_min_low (xs_min_low ), 69. .xs_min_high (xs_min_high ) 70. ); 71. seg_disp u3( 72. .clk (clk ), 73. .rst_n (rst_n ), 74. .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}), 75. .segment (segment ), 76. .seg_sel (seg_sel ) 77. ); 78. 79. 80. endmodule
1.2 按键检测模块设计
1.2.1 接口信号
1.2.2 设计思路
在前面的案例中已经有按键检测的介绍,所以这里不在过多介绍,详细介绍请看下方链接:
【每周 FPGA 案例】至简设计系列_按键控制数字时钟
1.2.3 参考代码
使用明德扬的计数器模板,可以很快速很熟练地写出按键消抖模块。
1. module alarm_clock_jvzhen( 2. clk , 3. rst_n , 4. key_col , 5. key_row , 6. segment , 7. seg_sel , 8. beep 9. ); 10. input clk ; 11. input rst_n ; 12. input [3:0 ] key_col ; 13. output [3:0 ] key_row ; 14. output [7:0 ] segment ; 15. output [7:0 ] seg_sel ; 16. output beep ; 17. 18. wire [7:0 ] segment ; 19. wire [7:0 ] seg_sel ; 20. wire beep ; 21. wire [3:0 ] xs_sec_low ; 22. wire [3:0 ] xs_sec_high ; 23. wire [3:0 ] xs_min_low ; 24. wire [3:0 ] xs_min_high ; 25. wire [3:0 ] sec_low ; 26. wire [3:0 ] sec_high ; 27. wire [3:0 ] min_low ; 28. wire [3:0 ] min_high ; 29. wire [25:0] counter ; 30. wire [3:0 ] key_vld ; 31. wire flag_set ; 32. wire [15:0] key_out ; 33. 34. key_scan u0( 35. .clk (clk ), 36. .rst_n (rst_n ), 37. .key_col (key_col ), 38. .key_row (key_row ), 39. .key_en (key_vld ) 40. ); 41. time_data u1( 42. .clk (clk ), 43. .rst_n (rst_n ), 44. .key_vld (key_vld ), 45. .flag_set (flag_set ), 46. .counter (counter ), 47. .sec_low (sec_low ), 48. .sec_high (sec_high ), 49. .min_low (min_low ), 50. .min_high (min_high ), 51. .xs_sec_low (xs_sec_low ), 52. .xs_sec_high (xs_sec_high ), 53. .xs_min_low (xs_min_low ), 54. .xs_min_high (xs_min_high ) 55. ); 56. beep u2( 57. .clk (clk ), 58. .rst_n (rst_n ), 59. .flag_set (flag_set ), 60. .counter (counter ), 61. .beep (beep ), 62. .sec_low (sec_low ), 63. .sec_high (sec_high ), 64. .min_low (min_low ), 65. .min_high (min_high ), 66. .xs_sec_low (xs_sec_low ), 67. .xs_sec_high (xs_sec_high ), 68. .xs_min_low (xs_min_low ), 69. .xs_min_high (xs_min_high ) 70. ); 71. seg_disp u3( 72. .clk (clk ), 73. .rst_n (rst_n ), 74. .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}), 75. .segment (segment ), 76. .seg_sel (seg_sel ) 77. ); 78. 79. 80. endmodule
1.3 矩阵键盘模块设计
1.3.1 接口信号
1.3.2 设计思路
在前面的案例中已经有矩阵键盘的介绍,所以这里不在过多介绍,详细介绍请看下方链接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310
1.3.3 参考代码
1. always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1'b0)begin 3. key_col_ff0 <= 4'b1111; 4. key_col_ff1 <= 4'b1111; 5. end 6. else begin 7. key_col_ff0 <= key_col ; 8. key_col_ff1 <= key_col_ff0; 9. end 10. end 11. 12. 13. always @(posedge clk or negedge rst_n) begin 14. if (rst_n==0) begin 15. shake_cnt <= 0; 16. end 17. else if(add_shake_cnt) begin 18. if(end_shake_cnt) 19. shake_cnt <= 0; 20. else 21. shake_cnt <= shake_cnt+1 ; 22. end 23. end 24. assign add_shake_cnt = key_col_ff1!=4'hf; 25. assign end_shake_cnt = add_shake_cnt && shake_cnt == TIME_20MS-1 ; 26. 27. 28. always @(posedge clk or negedge rst_n)begin 29. if(rst_n==1'b0)begin 30. state_c <= CHK_COL; 31. end 32. else begin 33. state_c <= state_n; 34. end 35. end 36. 37. always @(*)begin 38. case(state_c) 39. CHK_COL: begin 40. if(col2row_start )begin 41. state_n = CHK_ROW; 42. end 43. else begin 44. state_n = CHK_COL; 45. end 46. end 47. CHK_ROW: begin 48. if(row2del_start)begin 49. state_n = DELAY; 50. end 51. else begin 52. state_n = CHK_ROW; 53. end 54. end 55. DELAY : begin 56. if(del2wait_start)begin 57. state_n = WAIT_END; 58. end 59. else begin 60. state_n = DELAY; 61. end 62. end 63. WAIT_END: begin 64. if(wait2col_start)begin 65. state_n = CHK_COL; 66. end 67. else begin 68. state_n = WAIT_END; 69. end 70. end 71. default: state_n = CHK_COL; 72. endcase 73. end 74. assign col2row_start = state_c==CHK_COL && end_shake_cnt; 75. assign row2del_start = state_c==CHK_ROW && row_index==3 && end_row_cnt; 76. assign del2wait_start= state_c==DELAY && end_row_cnt; 77. assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf; 78. 79. always @(posedge clk or negedge rst_n)begin 80. if(rst_n==1'b0)begin 81. key_row <= 4'b0; 82. end 83. else if(state_c==CHK_ROW)begin 84. key_row <= ~(1'b1 << row_index); 85. end 86. else begin 87. key_row <= 4'b0; 88. end 89. end 90. 91. 92. always @(posedge clk or negedge rst_n) begin 93. if (rst_n==0) begin 94. row_index <= 0; 95. end 96. else if(add_row_index) begin 97. if(end_row_index) 98. row_index <= 0; 99. else 100. row_index <= row_index+1 ; 101. end 102. else if(state_c!=CHK_ROW)begin 103. row_index <= 0; 104. end 105. end 106. assign add_row_index = state_c==CHK_ROW && end_row_cnt; 107. assign end_row_index = add_row_index && row_index == 4-1 ; 108. 109. 110. always @(posedge clk or negedge rst_n) begin 111. if (rst_n==0) begin 112. row_cnt <= 0; 113. end 114. else if(add_row_cnt) begin 115. if(end_row_cnt) 116. row_cnt <= 0; 117. else 118. row_cnt <= row_cnt+1 ; 119. end 120. end 121. assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY; 122. assign end_row_cnt = add_row_cnt && row_cnt == 16-1 ; 123. 124. 125. always @(posedge clk or negedge rst_n)begin 126. if(rst_n==1'b0)begin 127. key_col_get <= 0; 128. end 129. else if(state_c==CHK_COL && end_shake_cnt ) begin 130. if(key_col_ff1==4'b1110) 131. key_col_get <= 0; 132. else if(key_col_ff1==4'b1101) 133. key_col_get <= 1; 134. else if(key_col_ff1==4'b1011) 135. key_col_get <= 2; 136. else 137. key_col_get <= 3; 138. end 139. end 140. 141. 142. always @(posedge clk or negedge rst_n)begin 143. if(rst_n==1'b0)begin 144. key_out <= 0; 145. end 146. else if(state_c==CHK_ROW && end_row_cnt)begin 147. key_out <= {row_index,key_col_get}; 148. end 149. else begin 150. key_out <= 0; 151. end 152. end 153. 154. always @(posedge clk or negedge rst_n)begin 155. if(rst_n==1'b0)begin 156. key_vld <= 1'b0; 157. end 158. else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin 159. key_vld <= 1'b1; 160. end 161. else begin 162. key_vld <= 1'b0; 163. end 164. end 165. 166. 167. always @(*)begin 168. if(rst_n==1'b0)begin 169. key_en = 0; 170. end 171. else if(key_vld && key_out==0)begin 172. key_en = 4'b0001; 173. end 174. else if(key_vld && key_out==1)begin 175. key_en = 4'b0010; 176. end 177. else if(key_vld && key_out==2)begin 178. key_en = 4'b0100; 179. end 180. else begin 181. key_en = 0; 182. end 183. end
1.4 时间产生模块设计
1.4.1 接口信号
1.4.2 设计思路
根据题目功能要求可知,要显示的时间就是在完整的数字时钟的基础上,减少了时的高位和低位
的显示,再介绍架构之前,先了解一下本模块其他几个信号的作用。
设置状态指示信号 flag_set:该信号初始状态为低电平,表示模块处于正常工作状态,当按下按
键 key1 时,设置状态指示信号进行翻转,变为高电平,表示进入到设置状态。
设置位计数器 sel_cnt:该计数器表示要设置的位,初始状态为 0,表示可以设置闹钟的秒低位,
当其为 1 时表示可以设置闹钟的秒的高位,按照这样的顺序依次类推,当其为 7 的时候,表示可以设
置显示时间的分高位。加一条件为 key_vld[1]==1'b1,表示按下按键 key2 的时候加一;结束条件为 8,
显示时间的四个数码管加上闹钟的四个数码管共 8 个,所以数 8 个就清零。
由此可提出 5 个计数器的架构,如下图所示:
该架构由 5 个计数器组成:时钟计数器 counter、秒低位计数器 xs_sec_low、秒高位计数器 xs
_sec_high、分低位计数器 xs_min_low、分高位计数器 xs_min_high。
时钟计数器 counter:用于计算 1 秒的时钟个数,加一条件为 flag_set==1'b0,表示刚上电时开
始计数,key1 按下之后,进入设置模式,停止计数,再按下又重新开始计数;结束条件为 5000000
0,表示数到 1 秒就清零。
秒低位计数器 xs_sec_low:用于对 1 秒进行计数,加一条件为(sel_cnt==5-1 && set_en) || e
nd_counter,表示在设置状态下可通过按键 key3 来控制加一,或者在正常状态时数到 1 秒就加 1;
结束条件为 10,表示数到 10 秒就清零。
秒高位计数器 xs_sec_high:用于对 10 秒进行计数,加一条件为(sel_cnt==6-1 && set_en) ||
end_xs_sec_low,表示在设置状态下可通过按键 key3 来控制加一,或者在正常状态时数到 10 秒就
加 1;结束条件为 6,表示数到 60 秒就清零。
分低位计数器 xs_min_low:用于对 1 分进行计数,加一条件为(sel_cnt==7-1 && set_en) || e
nd_xs_sec_high,表示在设置状态下可通过按键 key3 来控制加一,或者在正常状态时数到 1 分就加
1;结束条件为 10,表示数到 10 分就清零。
分高位计数器 xs_min_high:用于对 10 分进行计数,加一条件为(sel_cnt==8-1 && set_en) ||
end_xs_min_low,表示在设置状态下可通过按键 key3 来控制加一,或者在正常状态时数到 10 分就
加 1;结束条件为 6,表示数到 60 分就清零。
上面介绍了显示时间的计数器架构,下面我们来思考一下闹钟部分的架构。
我们都知道闹钟的工作原理,它本身不会自动计数,需要我们手动设置。根据本设计的功能要求,
有四个数码管来显示设置的闹钟秒的高低位和分的高低位,因此我们提出四个计数器组成的架构,这
四个计数器相互独立,互不干涉,结构图如下:
该架构由 4 个计数器组成:秒低位计数器 sec_low、秒高位计数器 sec_high、分低位计数器 mi
n_low、分高位计数器 min_high。
秒低位计数器 sec_low:用于对闹钟秒的低位进行计数,加一条件为 sel_cnt==1-1 && set_en,
表示在设置状态下通过按键 key3 来控制加一 ;结束条件为 10,表示最大能设置为 9,超过之后便
清零。
秒高位计数器 sec_high:用于对闹钟秒的高位进行计数,加一条件为 sel_cnt==2-1 && set_en,
表示在设置状态下可通过按键 key3 来控制加一;结束条件为 6,表示最大能设置为 5,超过之后便
清零。
分低位计数器 min_low:用于对闹钟分的低位进行计数,加一条件为 sel_cnt==3-1 && set_en,
表示在设置状态下可通过按键 key3 来控制加一;结束条件为 10,表示最大能设置为 9,超过之后便
清零。
分高位计数器 min_high:用于对闹钟分高位进行计数,加一条件为 sel_cnt==4-1 && set_en,
表示在设置状态下可通过按键 key3 来控制加一;结束条件为 6,表示最大能设置为 5,超过之后便
清零。
1.4.3 参考代码
使用明德扬的计数器模板,可以很快速很熟练地写出时间产生模块。
always @(posedge clk or negedge rst_n)begin 2. if(rst_n==1'b0)begin 3. flag_set<=1'b0; 4. end 5. else if(key_vld[0]==1'b1)begin 6. flag_set<=~flag_set; 7. end 8. else begin 9. flag_set<=flag_set; 10. end 11. end 12. 13. 14. always @(posedge clk or negedge rst_n) begin 15. if (rst_n==0) begin 16. sel_cnt <= 0; 17. end 18. else if(add_sel_cnt) begin 19. if(end_sel_cnt) 20. sel_cnt <= 0; 21. else 22. sel_cnt <= sel_cnt+1 ; 23. end 24. end 25. assign add_sel_cnt = key_vld[1]==1'b1; 26. assign end_sel_cnt = add_sel_cnt && sel_cnt == 8-1 ; 27. 28. 29. always @(posedge clk or negedge rst_n)begin 30. if(rst_n==1'b0)begin 31. set_en<=1'b0; 32. end 33. else if(flag_set==1'b1 && key_vld[2]==1'b1)begin 34. set_en<=1'b1; 35. end 36. else begin 37. set_en<=1'b0; 38. end 39. end 40. 41. 42. always @(posedge clk or negedge rst_n) begin 43. if (rst_n==0) begin 44. counter <= 0; 45. end 46. else if(add_counter) begin 47. if(end_counter) 48. counter <= 0; 49. else 50. counter <= counter+1 ; 51. end 52. end 53. assign add_counter = flag_set==1'b0; 54. assign end_counter = add_counter && counter == 26'd5000_0000-1; 55. 56. 57. always @(posedge clk or negedge rst_n) begin 58. if (rst_n==0) begin 59. sec_low <= 0; 60. end 61. else if(add_sec_low) begin 62. if(end_sec_low) 63. sec_low <= 0; 64. else 65. sec_low <= sec_low+1 ; 66. end 67. end 68. assign add_sec_low = sel_cnt==1-1 && set_en; 69. assign end_sec_low = add_sec_low && sec_low == 10-1 ; 70. 71. 72. always @(posedge clk or negedge rst_n) begin 73. if (rst_n==0) begin 74. sec_high <= 0; 75. end 76. else if(add_sec_high) begin 77. if(end_sec_high) 78. sec_high <= 0; 79. else 80. sec_high <= sec_high+1 ; 81. end 82. end 83. assign add_sec_high = sel_cnt==2-1 && set_en; 84. assign end_sec_high = add_sec_high && sec_high == 6-1 ; 85. 86. 87. always @(posedge clk or negedge rst_n) begin 88. if (rst_n==0) begin 89. min_low <= 0; 90. end 91. else if(add_min_low) begin 92. if(end_min_low) 93. min_low <= 0; 94. else 95. min_low <= min_low+1 ; 96. end 97. end 98. assign add_min_low = sel_cnt==3-1 && set_en; 99. assign end_min_low = add_min_low && min_low == 10-1 ; 100. 101. always @(posedge clk or negedge rst_n) begin 102. if (rst_n==0) begin 103. min_high <= 0; 104. end 105. else if(add_min_high) begin 106. if(end_min_high) 107. min_high <= 0; 108. else 109. min_high <= min_high+1 ; 110. end 111. end 112. assign add_min_high = sel_cnt==4-1 && set_en; 113. assign end_min_high = add_min_high && min_high == 6-1 ; 114. 115. 116. always @(posedge clk or negedge rst_n) begin 117. if (rst_n==0) begin 118. xs_sec_low <= 0; 119. end 120. else if(add_xs_sec_low) begin 121. if(end_xs_sec_low) 122. xs_sec_low <= 0; 123. else 124. xs_sec_low <= xs_sec_low+1 ; 125. end 126. end 127. assign add_xs_sec_low = (sel_cnt==5-1 && set_en) || end_counter; 128. assign end_xs_sec_low = add_xs_sec_low && xs_sec_low == 10-1 ; 129. 130. 131. always @(posedge clk or negedge rst_n) begin 132. if (rst_n==0) begin 133. xs_sec_high <= 0; 134. end 135. else if(add_xs_sec_high) begin 136. if(end_xs_sec_high) 137. xs_sec_high <= 0; 138. else 139. xs_sec_high <= xs_sec_high+1 ; 140. end 141. end 142. assign add_xs_sec_high = (sel_cnt==6-1 && set_en) || end_xs_sec_low; 143. assign end_xs_sec_high = add_xs_sec_high && xs_sec_high == 6-1 ; 144. 145. 146. always @(posedge clk or negedge rst_n) begin 147. if (rst_n==0) begin 148. xs_min_low <= 0; 149. end 150. else if(add_xs_min_low) begin 151. if(end_xs_min_low) 152. xs_min_low <= 0; 153. else 154. xs_min_low <= xs_min_low+1 ; 155. end 156. end 157. assign add_xs_min_low = (sel_cnt==7-1 && set_en) || end_xs_sec_high; 158. assign end_xs_min_low = add_xs_min_low && xs_min_low == 10-1 ; 159. 160. 161. always @(posedge clk or negedge rst_n) begin 162. if (rst_n==0) begin 163. xs_min_high <= 0; 164. end 165. else if(add_xs_min_high) begin 166. if(end_xs_min_high) 167. xs_min_high <= 0; 168. else 169. xs_min_high <= xs_min_high+1 ; 170. end 171. end 172. assign add_xs_min_high = (sel_cnt==8-1 && set_en) || end_xs_min_low; 173. assign end_xs_min_high = add_xs_min_high && xs_min_high == 6-1 ;
1.5 数码管显示模块设计
1.5.1 接口信号
1.5.2 设计思路
在前面的案例中已经有数码管显示的介绍,所以这里不在过多介绍,详细介绍请看下方链接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=399
1.5.3 参考代码
174. always @(posedge clk or negedge rst_n) begin 175. if (rst_n==0) begin 176. delay <= 0; 177. end 178. else if(add_delay) begin 179. if(end_delay) 180. delay <= 0; 181. else 182. delay <= delay+1 ; 183. end 184. end 185. assign add_delay = 1; 186. assign end_delay = add_delay && delay == 2000-1 ; 187. 188. 189. 190. 191. always @(posedge clk or negedge rst_n) begin 192. if (rst_n==0) begin 193. delay_time <= 0; 194. end 195. else if(add_delay_time) begin 196. if(end_delay_time) 197. delay_time <= 0; 198. else 199. delay_time <= delay_time+1 ; 200. end 201. end 202. assign add_delay_time = end_delay; 203. assign end_delay_time = add_delay_time && delay_time == 8-1 ; 204. 205. 206. assign segment_tmp = segment_data[(1+delay_time)*4-1 -:4]; 207. always @(posedge clk or negedge rst_n)begin 208. if(rst_n==1'b0)begin 209. segment <= ZERO; 210. end 211. else begin 212. case(segment_tmp) 213. 4'd0:segment <= ZERO; 214. 4'd1:segment <= ONE ; 215. 4'd2:segment <= TWO ; 216. 4'd3:segment <= THREE; 217. 4'd4:segment <= FOUR ; 218. 4'd5:segment <= FIVE ; 219. 4'd6:segment <= SIX ; 220. 4'd7:segment <= SEVEN; 221. 4'd8:segment <= EIGHT; 222. 4'd9:segment <= NINE ; 223. default:begin 224. segment <= segment; 225. end 226. endcase 227. end 228. end 229. 230. 231. always @(posedge clk or negedge rst_n)begin 232. if(rst_n==1'b0)begin 233. seg_sel <= 8'b1111_1111; 234. end 235. else begin 236. seg_sel <= ~(8'b1<<delay_time); 237. end 238. end
1.6 蜂鸣器模块设计
1.6.1 接口信号
1.6.2 设计思路
本模块主要通过将显示时间与设置的闹钟时间进行比较,如果相同的话,就控制 beep 拉低,持
续时间为 5 秒。由此提出一个计数器的架构,如下图所示。
该架构由蜂鸣器控制信号 beep、秒计数器 miao 和闹钟触发指示信号 flag_add 组成。
秒计数器秒:用于对 5 秒的时间进行计数,加一条件为 flag_add && end_counter,表示当闹
钟被触发,并且经过 1 秒的时间就加一;结束条件为 5,表示数完 5 秒就清零。
闹钟触发指示信号 flag:当其为高电平时表示闹钟被触发,低电平表示没有被触发。初始状态为
低电平,从低变高的条件为 sec_low==xs_sec_low&&sec_high==xs_sec_high&&min_low==xs_min
_low&&min_high==xs_min_high&&init&&!key1_func,表示当显示时间的秒高低位、分高低位和闹钟
设置的秒高低位、分高低位相等,同时不处于刚上电的初始状态和设置状态时,闹钟被触发;从高变
低的条件为 end_miao,表示当 5 秒数完之后,就拉低。
蜂鸣器控制信号 beep:当其为低电平时,控制蜂鸣器响,为高电平时不响。初始状态为高电平,
从高变低的条件为 flag_add,表示计数器开始计数之后便将其拉低,当检测到 flag_add=0 的时候,
便将其拉高。
1.6.3 参考代码
239. always @(posedge clk or negedge rst_n)begin 240. if(rst_n==1'b0)begin 241. flag_add <= 0; 242. end 243. else if(sec_low==xs_sec_low&&sec_high==xs_sec_high&&min_low==xs_min_low&&min_high==xs_min_high&&init&&flag_set==0)begin 244. flag_add <= 1; 245. end 246. else if(end_miao)begin 247. flag_add <= 0; 248. end 249. end 250. 251. 252. always@(*)begin 253. if(!sec_low&&!sec_high&&!min_low&&!min_high)begin 254. init=0; 255. end 256. else begin 257. init=1; 258. end 259. end 260. 261. 262. always @(posedge clk or negedge rst_n) begin 263. if (rst_n==0) begin 264. miao <= 0; 265. end 266. else if(add_miao) begin 267. if(end_miao) 268. miao <= 0; 269. else 270. miao <= miao+1 ; 271. end 272. end 273. assign add_miao = flag_add && end_counter; 274. assign end_miao = add_miao && miao == 5-1 ; 275. 276. 277. always@(posedge clk or negedge rst_n)begin 278. if(rst_n==1'b0)begin 279. beep<=1'b1; 280. end 281. else if(flag_add)begin 282. beep<=1'b0; 283. end 284. else 285. beep<=1'b1; 286. end
1.7 效果和总结
➢ 下图是该工程在 mp801 开发板上的现象
其中按键 s4 控制数字时钟的暂停与开始,按键 s3 来选择需要设置的位,按键 s2 设置数值。左
边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。
➢ 下图是该工程在 db603 开发板上的现象
其中按键 s1 控制数字时钟的暂停与开始,按键 s2 来选择需要设置的位,按键 s3 设置数值。左
边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。
➢ 下图是该工程在 ms980 试验箱上的现象
其中按键 s1 控制数字时钟的暂停与开始,按键 s2 来选择需要设置的位,按键 s3 设置数值。左
边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。
由于该项目的上板现象是动态的,开始、暂停、时间设置等现象无法通过图片表现出来,想观看
完整现象的朋友可以看一下现象演示的视频。
感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行 FPGA 相关工程设计学习,
也可以看一下我们往期的文章:
《基于 FPGA 的密码锁设计》
《波形相位频率可调 DDS 信号发生器》
《基于 FPGA 的曼彻斯特编码解码设计》
《基于 FPGA 的出租车计费系统》
《数电基础与 Verilog 设计》
《基于 FPGA 的频率、电压测量》
《基于 FPGA 的汉明码编码解码设计》
《关于锁存器问题的讨论》
《阻塞赋值与非阻塞赋值》
《参数例化时自动计算位宽的解决办法》
明德扬是一家专注于 FPGA 领域的专业性公司,公司主要业务包括开发板、教育培训、项目承
接、人才服务等多个方向。
点拨开发板——学习 FPGA 的入门之选。
MP801 开发板——千兆网、ADDA、大容量 SDRAM 等,学习和项目需求一步到位。
网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习 FPGA。
周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。
就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。
专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO 架构
设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。
项目承接——承接企业 FPGA 研发项目。
人才服务——提供人才推荐、人才代培、人才派遣等服务。