当前位置:   article > 正文

Verilog语言fpga小脚丫数字时钟(整点报时,调时,显示秒钟等功能)_verilog实现时钟计时功能

verilog实现时钟计时功能

学弟加油!                                                                       ———来自科大焯人

最近刚好学习了数电有关知识,就做了这个项目(闹钟过于繁琐就没有做了)

希望给还在学习的大伙一点参考,完整代码在最后

在这里先附上两串代码分别是debounce(按键消抖)和divide(分频)

这两个在小脚丫的示例中都可以找到,但我还是先附在这

  1. //按键消抖
  2. module debounce (clk,rst,key,key_pulse);
  3. parameter N = 1; //要消除的按键的数量
  4. input clk;
  5. input rst;
  6. input [N-1:0] key; //输入的按键
  7. output [N-1:0] key_pulse; //按键动作产生的脉冲
  8. reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值
  9. reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值
  10. wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲
  11. //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中
  12. always @(posedge clk or negedge rst)
  13. begin
  14. if (!rst) begin
  15. key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1
  16. key_rst_pre <= {N{1'b1}};
  17. end
  18. else begin
  19. key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
  20. key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
  21. end
  22. end
  23. assign key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
  24. reg [17:0] cnt; //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器
  25. //产生20ms延时,当检测到key_edge有效是计数器清零开始计数
  26. always @(posedge clk or negedge rst)
  27. begin
  28. if(!rst)
  29. cnt <= 18'h0;
  30. else if(key_edge)
  31. cnt <= 18'h0;
  32. else
  33. cnt <= cnt + 1'h1;
  34. end
  35. reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量
  36. reg [N-1:0] key_sec;
  37. //延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效
  38. always @(posedge clk or negedge rst)
  39. begin
  40. if (!rst)
  41. key_sec <= {N{1'b1}};
  42. else if (cnt==18'h3ffff)
  43. key_sec <= key;
  44. end
  45. always @(posedge clk or negedge rst)
  46. begin
  47. if (!rst)
  48. key_sec_pre <= {N{1'b1}};
  49. else
  50. key_sec_pre <= key_sec;
  51. end
  52. assign key_pulse = key_sec_pre & (~key_sec);
  53. endmodule
  1. //分频
  2. module divide ( clk,rst_n,clkout);
  3. input clk,rst_n; //输入信号,其中clk连接到FPGA的C1脚,频率为12MHz
  4. output clkout; //输出信号,可以连接到LED观察分频的时钟
  5. //parameter是verilog里常数语句
  6. parameter WIDTH = 24; //计数器的位数,计数的最大值为 2**WIDTH-1
  7. parameter N = 12_000_000; //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出
  8. reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器
  9. reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟
  10. //上升沿触发时计数器的控制
  11. always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示信号上升沿和下降沿
  12. //当clk上升沿来临或者rst_n变低的时候执行一次always里的语句
  13. begin
  14. if(!rst_n)
  15. cnt_p<=0;
  16. else if (cnt_p==(N-1))
  17. cnt_p<=0;
  18. else cnt_p<=cnt_p+1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器
  19. end
  20. //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%
  21. always @ (posedge clk or negedge rst_n)
  22. begin
  23. if(!rst_n)
  24. clk_p<=0;
  25. else if (cnt_p<(N>>1)) //N>>1表示右移一位,相当于除以2去掉余数
  26. clk_p<=0;
  27. else
  28. clk_p<=1; //得到的分频时钟正周期比负周期多一个clk时钟
  29. end
  30. //下降沿触发时计数器的控制
  31. always @ (negedge clk or negedge rst_n)
  32. begin
  33. if(!rst_n)
  34. cnt_n<=0;
  35. else if (cnt_n==(N-1))
  36. cnt_n<=0;
  37. else cnt_n<=cnt_n+1;
  38. end
  39. //下降沿触发的分频时钟输出,和clk_p相差半个时钟
  40. always @ (negedge clk)
  41. begin
  42. if(!rst_n)
  43. clk_n<=0;
  44. else if (cnt_n<(N>>1))
  45. clk_n<=0;
  46. else
  47. clk_n<=1; //得到的分频时钟正周期比负周期多一个clk时钟
  48. end
  49. assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //条件判断表达式
  50. //当N=1时,直接输出clk
  51. //当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p
  52. //当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与
  53. endmodule

将上述两个代码文件准备好就可以开始编写我们的主体程序了。

既然是数字时钟,首先我们应当通过分频器产生1s中的信号,代码中的clk1h是我用分频器产生的1Hz信号。每经过1Hz的上升沿秒钟进一位,在秒钟个位为9且十位不为5时,下一次进位个位要变为0,十位要加1,若是59秒时则应当都置零。同时,分钟的计时和时钟的计时实现思路与秒钟计时类似,但需要额外进行判断,比如说分钟进位要在秒钟均为0时才产生进位,时钟进位要在分钟秒钟全为0时才进位。

时钟和分钟的敏感信号为clk,这是小脚丫自带的时钟信号12MHz。由于clk频率极高,在1s中内能够触发分钟和时钟计时模块多次,为实现分钟和时钟在进位时仅进位一次,我额外增加了两个判断位,分别为pan(分钟)和pan1(时钟)。在pan和pan1为0时才能分别触发分钟和时钟进位,并且触发一次进位后pan或pan1置1,直到秒钟或者分钟不全为0时pan或pan1才会重新置0。

具体代码如下

  1. //60秒计时控制
  2. always @ (posedge clk1h ) begin
  3. if(cnt_shi==5 && cnt_ge==9) begin
  4. cnt_shi <= 0;
  5. cnt_ge <= 0;
  6. end
  7. else if(cnt_shi==0 && cnt_ge==0) begin
  8. cnt_shi <= 0;
  9. cnt_ge <= 1;
  10. end
  11. else if(cnt_ge==9)begin
  12. cnt_ge <= 0;
  13. cnt_shi <= cnt_shi+1;
  14. end
  15. else
  16. cnt_ge <= cnt_ge +1;
  17. end
  18. //60分计时控制
  19. always @ (posedge clk)begin
  20. if((cnt_ge==0)&&(cnt_shi==0)&&(pan==0))begin
  21. pan<=1;
  22. if(minute_shi==5 && minute_ge==9) begin
  23. minute_shi <= 0;
  24. minute_ge <= 0;
  25. end
  26. else if(minute_ge==9)begin
  27. minute_ge <= 4'd0;
  28. minute_shi <= minute_shi+1;
  29. end
  30. else
  31. minute_ge <= minute_ge +1;
  32. end
  33. else
  34. if((cnt_ge!=0) || (cnt_shi!=0))begin
  35. pan<=0;
  36. end
  37. //24小时计时与加减法模块
  38. always @ (posedge clk ) begin
  39. if ((minute_ge==0)&&(minute_shi==0)&&(pan1==0)&&(cnt_shi==0)&&(cnt_ge==0)) begin
  40. pan1<=1;
  41. if(hour_shi==2 && hour_ge==3) begin
  42. hour_shi <= 0;
  43. hour_ge <= 0;
  44. end
  45. else if(hour_ge==9)begin
  46. hour_ge <= 4'd0;
  47. hour_shi <= hour_shi+1;
  48. end
  49. else
  50. hour_ge <= hour_ge +1;
  51. end
  52. else
  53. if((minute_ge!=0) || (minute_shi!=0))begin
  54. pan1<=0;
  55. end

至此,我们已经基本实现了数字时钟的运行,但还需要将时钟信息显示在数码管上。

我将按键触发的信号位记作change和change2。当change2为高电平且change为低电平时,数码管显示分钟;当change2为高电平且change为高电平时,数码管显示时钟;而当change2为低电平时,不论change电平,均显示秒钟。

  1. //数码管显示数字
  2. seg[0] = 7'h3f; // 0
  3. seg[1] = 7'h06; // 1
  4. seg[2] = 7'h5b; // 2
  5. seg[3] = 7'h4f; // 3
  6. seg[4] = 7'h66; // 4
  7. seg[5] = 7'h6d; // 5
  8. seg[6] = 7'h7d; // 6
  9. seg[7] = 7'h07; // 7
  10. seg[8] = 7'h7f; // 8
  11. seg[9] = 7'h6f; // 9
  12. //选择显示
  13. always @ (posedge clk)begin
  14. if((change==0)&&(change2==1))begin
  15. seg_led_1<= seg[hour_ge];
  16. seg_led_2<= seg[hour_shi];
  17. end
  18. else if((change==1)&&(change2==1))
  19. begin
  20. seg_led_1<= seg[minute_ge];
  21. seg_led_2<= seg[minute_shi];
  22. end
  23. else if(change2==0)begin
  24. seg_led_1<= seg[cnt_ge];
  25. seg_led_2<= seg[cnt_shi];
  26. end
  27. end

所有基础功能都已经实现,现在还需要进阶,也就是调时和整点报时的实现。

我们先讲调时的实现

我在实现加减法的时候均用了两个计数器add、add1和jian、jian1。

add和jian分别为按键按下次数的计数器,而add1和jian1则分别为add和jian的匹配计数器。

当add1不等于add时,将时钟进一位,并且add1+1;当jian1不等于jian时,将时钟退一位,再将jian1+1,便可以实现调时的功能。

我在写代码的时候是将调时模块整合在计时模块中的,时钟调时与分钟调时的原理是相同的。

  1. if((add1!=add)&&(change==1)) begin
  2. add1<=add1+1;
  3. if(minute_shi==5 && minute_ge==9) begin
  4. minute_shi <= 0;
  5. minute_ge <= 0;
  6. end
  7. else if(minute_ge==9)begin
  8. minute_ge <= 4'd0;
  9. minute_shi <= minute_shi+1;
  10. end
  11. else
  12. minute_ge <= minute_ge +1;
  13. end
  14. if((jian1!=jian)&&(change==1))begin
  15. jian1<=jian1+1;
  16. if(minute_shi==0 && minute_ge==0)begin
  17. minute_ge<=9;
  18. minute_shi<=5;
  19. end
  20. else if(minute_ge==0)begin
  21. minute_ge<=9;
  22. minute_shi<=minute_shi-1;
  23. end
  24. else
  25. minute_ge<=minute_ge-1;
  26. end

最后便是整点报时的实现

整点报时需要让灯光按照1Hz的频率闪烁,那么我们便可以用一个2Hz的时钟信号作为敏感信号来触发灯光闪烁。

首先只有在秒钟分钟均为0时才算作整点,在这个条件下我又用到了两个计数器cnt、cnt1与加减法的计数器功能一致,cnt1为当前整点数的两倍(因为一亮一灭需要灯光反转两次),cnt为匹配计数器,当cnt不等于cnt1时灯光反转一次,并且cnt+1。具体代码实现如下。

  1. //整点报时
  2. always @(posedge clk0)begin
  3. led=~led;
  4. if((minute_shi==0)&&(minute_ge==0)&&(cnt_shi==0)&&(cnt_ge==0))begin
  5. cnt1=2*(10*hour_shi+hour_ge);
  6. end
  7. if(cnt!=cnt1)begin
  8. rgb=~rgb;
  9. cnt<=cnt+1;
  10. end
  11. else if(cnt==cnt1)begin
  12. cnt<=0;
  13. cnt1<=0;
  14. end
  15. end

以上就是整个数字时钟的设计,最后附上数字时钟的全部代码(记得把文首的debounce和devide两个代码也装上,不然数字时钟代码无法成功编译)

  1. module counter
  2. (
  3. clk , //时钟
  4. rst , //复位
  5. plus , //加法为
  6. cut , //减法位
  7. change , //显示转化按键1
  8. change2 , //显示转化按键2
  9. seg_led_1 , //数码管1
  10. seg_led_2 , //数码管2
  11. rgb , //rgb灯光
  12. led //led
  13. );
  14. input clk,rst;
  15. input change;
  16. input change2;
  17. input plus,cut;
  18. output reg [8:0] seg_led_1,seg_led_2;
  19. output reg [7:0] led;
  20. output reg [5:0] rgb;
  21. wire clk0; //0.5秒时钟
  22. wire clk1h; //1秒钟时钟
  23. wire change_pulse; //转换按键消抖后信号
  24. wire plus_pulse; //加法按键消抖后信号
  25. wire cut_pulse; //减法按键消抖后信号
  26. reg change_flag; //转换按键标志位
  27. reg plus_flag; //加法按键标志位
  28. reg cut_flag; //减法按键标志位
  29. reg pan; //判断分钟进位
  30. reg pan1; //判断时钟进位
  31. reg add; //分钟加法按键按下计数
  32. reg add1; //分钟加法按键匹配计数
  33. reg add10; //时钟加法按键按下计数
  34. reg add11; //时钟加法按键匹配计数
  35. reg jian10; //时钟减法按键按下计数
  36. reg jian11; //时钟减法按键匹配计数
  37. reg jian; //分钟减法按键按下计数
  38. reg jian1; //分钟减法按键匹配计数
  39. reg [5:0] cnt=0; //记录当前小时数
  40. reg [5:0] cnt1=0; //小时匹配数
  41. reg [6:0] seg [9:0]; //数码管
  42. reg [3:0] cnt_ge; //秒钟个位
  43. reg [3:0] cnt_shi; //秒钟十位
  44. reg [3:0] minute_ge; //分钟个位
  45. reg [3:0] minute_shi; //分钟十位
  46. reg [3:0] hour_ge; //小时个位
  47. reg [3:0] hour_shi; //小时十位
  48. initial
  49. begin
  50. seg[0] = 7'h3f; // 0
  51. seg[1] = 7'h06; // 1
  52. seg[2] = 7'h5b; // 2
  53. seg[3] = 7'h4f; // 3
  54. seg[4] = 7'h66; // 4
  55. seg[5] = 7'h6d; // 5
  56. seg[6] = 7'h7d; // 6
  57. seg[7] = 7'h07; // 7
  58. seg[8] = 7'h7f; // 8
  59. seg[9] = 7'h6f; // 9
  60. end
  61. // 启动/暂停按键进行消抖
  62. debounce U2 (
  63. .clk(clk),
  64. .rst(rst),
  65. .key(change),
  66. .key_pulse(change_pulse)
  67. );
  68. debounce U6 (
  69. .clk(clk),
  70. .rst(rst),
  71. .key(plus),
  72. .key_pulse(plus_pulse)
  73. );
  74. debounce U7 (
  75. .clk(clk),
  76. .rst(rst),
  77. .key(cut),
  78. .key_pulse(cut_pulse)
  79. );
  80. // 用于分出一个1Hz的频率
  81. divide #(.WIDTH(32),.N(12000000)) U1 (
  82. .clk(clk),
  83. .rst_n(rst),
  84. .clkout(clk1h)
  85. );
  86. // 用于分出一个2Hz的频率
  87. divide #(.WIDTH(32),.N(6000000)) U5 (
  88. .clk(clk),
  89. .rst_n(rst),
  90. .clkout(clk0)
  91. );
  92. //按键动作标志信号产生
  93. always @ (posedge change_pulse)begin
  94. if(!rst==1)
  95. change_flag <= 0;
  96. else
  97. change_flag <= ~change_flag;
  98. end
  99. always @ (posedge plus_pulse)begin
  100. if(!rst==1)
  101. plus_flag <= 0;
  102. else
  103. plus_flag <= ~plus_flag;
  104. if(change==1)begin
  105. add<=add+1;
  106. end
  107. else if(change==0)begin
  108. add10<=add10+1;
  109. end
  110. end
  111. always @ (posedge cut_pulse)begin
  112. if(!rst==1)
  113. cut_flag <= 0;
  114. else
  115. cut_flag <= ~cut_flag;
  116. if(change==1)begin
  117. jian<=jian+1;
  118. end
  119. else if(change==0)begin
  120. jian10<=jian10+1;
  121. end
  122. end
  123. //60秒计时控制
  124. always @ (posedge clk1h ) begin
  125. if(cnt_shi==5 && cnt_ge==9) begin
  126. cnt_shi <= 0;
  127. cnt_ge <= 0;
  128. end
  129. else if(cnt_shi==0 && cnt_ge==0) begin
  130. cnt_shi <= 0;
  131. cnt_ge <= 1;
  132. end
  133. else if(cnt_ge==9)begin
  134. cnt_ge <= 0;
  135. cnt_shi <= cnt_shi+1;
  136. end
  137. else
  138. cnt_ge <= cnt_ge +1;
  139. end
  140. //60分钟计时与加减法模块
  141. always @ (posedge clk)begin
  142. if((cnt_ge==0)&&(cnt_shi==0)&&(pan==0))begin
  143. pan<=1;
  144. if(minute_shi==5 && minute_ge==9) begin
  145. minute_shi <= 0;
  146. minute_ge <= 0;
  147. end
  148. else if(minute_ge==9)begin
  149. minute_ge <= 4'd0;
  150. minute_shi <= minute_shi+1;
  151. end
  152. else
  153. minute_ge <= minute_ge +1;
  154. end
  155. else
  156. if((cnt_ge!=0) || (cnt_shi!=0))begin
  157. pan<=0;
  158. end
  159. if((add1!=add)&&(change==1)) begin
  160. add1<=add1+1;
  161. if(minute_shi==5 && minute_ge==9) begin
  162. minute_shi <= 0;
  163. minute_ge <= 0;
  164. end
  165. else if(minute_ge==9)begin
  166. minute_ge <= 4'd0;
  167. minute_shi <= minute_shi+1;
  168. end
  169. else
  170. minute_ge <= minute_ge +1;
  171. end
  172. if((jian1!=jian)&&(change==1))begin
  173. jian1<=jian1+1;
  174. if(minute_shi==0 && minute_ge==0)begin
  175. minute_ge<=9;
  176. minute_shi<=5;
  177. end
  178. else if(minute_ge==0)begin
  179. minute_ge<=9;
  180. minute_shi<=minute_shi-1;
  181. end
  182. else
  183. minute_ge<=minute_ge-1;
  184. end
  185. end
  186. //24小时计时与加减法模块
  187. always @ (posedge clk ) begin
  188. if ((minute_ge==0)&&(minute_shi==0)&&(pan1==0)&&(cnt_shi==0)&&(cnt_ge==0)) begin
  189. pan1<=1;
  190. if(hour_shi==2 && hour_ge==3) begin
  191. hour_shi <= 0;
  192. hour_ge <= 0;
  193. end
  194. else if(hour_ge==9)begin
  195. hour_ge <= 4'd0;
  196. hour_shi <= hour_shi+1;
  197. end
  198. else
  199. hour_ge <= hour_ge +1;
  200. end
  201. else
  202. if((minute_ge!=0) || (minute_shi!=0))begin
  203. pan1<=0;
  204. end
  205. if((add11!=add10)&&(change==0))begin
  206. add11<=add11+1;
  207. if(hour_shi==2 && hour_ge==3) begin
  208. hour_shi <= 0;
  209. hour_ge <= 0;
  210. end
  211. else if(hour_ge==9)begin
  212. hour_ge <= 4'd0;
  213. hour_shi <= hour_shi+1;
  214. end
  215. else
  216. hour_ge <= hour_ge +1;
  217. end
  218. if((jian11!=jian10)&&(change==0))begin
  219. jian11<=jian11+1;
  220. if(hour_shi==0 && hour_ge==0)begin
  221. hour_ge<=3;
  222. hour_shi<=2;
  223. end
  224. else if(hour_ge==0)begin
  225. hour_ge<=9;
  226. hour_shi<=hour_shi-1;
  227. end
  228. else
  229. hour_ge<=hour_ge-1;
  230. end
  231. end
  232. //选择显示
  233. always @ (posedge clk)begin
  234. if((change==0)&&(change2==1))begin
  235. seg_led_1<= seg[hour_ge];
  236. seg_led_2<= seg[hour_shi];
  237. end
  238. else if((change==1)&&(change2==1))
  239. begin
  240. seg_led_1<= seg[minute_ge];
  241. seg_led_2<= seg[minute_shi];
  242. end
  243. else if(change2==0)begin
  244. seg_led_1<= seg[cnt_ge];
  245. seg_led_2<= seg[cnt_shi];
  246. end
  247. end
  248. //整点报时
  249. always @(posedge clk0)begin
  250. led=~led;
  251. if((minute_shi==0)&&(minute_ge==0)&&(cnt_shi==0)&&(cnt_ge==0))begin
  252. cnt1=2*(10*hour_shi+hour_ge);
  253. end
  254. if(cnt!=cnt1)begin
  255. rgb=~rgb;
  256. cnt<=cnt+1;
  257. end
  258. else if(cnt==cnt1)begin
  259. cnt<=0;
  260. cnt1<=0;
  261. end
  262. end
  263. endmodule

管脚分配如下

 

有疑惑或者问题欢迎一起讨论

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号