当前位置:   article > 正文

FPGA开发——数码管数字时钟的设计

FPGA开发——数码管数字时钟的设计

一、概述

        数码管数字时钟的基本原理是通过内部的计时电路(如晶振、分频器、计数器等)产生一个稳定的时钟信号,该信号经过处理后被转换为小时、分钟和秒的时间信息。这些信息随后被发送到数码管显示模块,通过控制数码管中不同LED段的亮灭来显示当前的时间。

        数码管数字时钟的显示方式通常采用“HH:MM:SS”的格式,其中“HH”表示小时,“MM”表示分钟,“SS”表示秒。有些高级的数码管时钟还会在显示时间的同时,通过额外的数码管或LED指示灯来显示其他信息,如日期、星期、温度等。

        在本次设计中我们使用6个数码管分别进行“hh.mm.ss”的格式让数码管进行显示,这篇文章我们先进行时钟的单纯显示,在下一篇文章中往里面加入按键灯进行可调的时钟的设计。

二、工程实现

涉及到复杂的代码编写时,我们就需要使用分块化的代码编写思想对不同功能的代码进行分层、分块、分文件编写,最后将各部分进行整理总和,采取这种方式进行代码可以时我们在编写代码时条件清晰,逻辑明确,如果是全部写在一个设计文件里我们的代码就会显得非常多并且就算注释写了不少也回显示杂乱无章。

1、计数器设计代码的编写

新建cnt.v文件,如下:

  1. module cnt(
  2. input clk,
  3. input rst_n,
  4. output reg[19:0] dout
  5. );
  6. parameter TIME_1s =50_000_000;
  7. reg [26:0] cnt_1s;
  8. wire add_cnt_1s;
  9. wire end_cnt_1s;
  10. /*----------------------------------------------------------------
  11. 时钟计数器
  12. ------------------------------------------------------------------*/
  13. reg [3:0] cnt_s;//秒数第一位
  14. wire add_cnt_s;
  15. wire end_cnt_s;
  16. reg [2:0] cnt_10s;//秒数第二位
  17. wire add_cnt_10s;
  18. wire end_cnt_10s;
  19. reg [3:0] cnt_m;//分数第一位
  20. wire add_cnt_m;
  21. wire end_cnt_m;
  22. reg [2:0] cnt_10m;//分数第二位
  23. wire add_cnt_10m;
  24. wire end_cnt_10m;
  25. reg [3:0] cnt_h;//小时第一位
  26. wire add_cnt_h;
  27. wire end_cnt_h;
  28. reg [1:0] cnt_10h;//小时第二位
  29. wire add_cnt_10h;
  30. wire end_cnt_10h;
  31. /*----------------------------------------------------------------
  32. 时钟
  33. -----------------------------------------------------------------*/
  34. always @(posedge clk or negedge rst_n)begin
  35. if(!rst_n)
  36. cnt_1s<=0;
  37. else if(add_cnt_1s)begin
  38. if(end_cnt_1s)
  39. cnt_1s<=0;
  40. else
  41. cnt_1s<=cnt_1s+1'b1;
  42. end
  43. end
  44. assign add_cnt_1s=1'b1;
  45. assign end_cnt_1s=add_cnt_1s && (cnt_1s==TIME_1s-1);
  46. always @(posedge clk or negedge rst_n)begin
  47. if(!rst_n)
  48. cnt_s<=0;
  49. else if(add_cnt_s)begin
  50. if(end_cnt_s)
  51. cnt_s<=0;
  52. else
  53. cnt_s<=cnt_s+1'b1;
  54. end
  55. end
  56. assign add_cnt_s=end_cnt_1s;
  57. assign end_cnt_s=add_cnt_s && (cnt_s==10-1);
  58. always @(posedge clk or negedge rst_n)begin
  59. if(!rst_n)
  60. cnt_10s<=0;
  61. else if(add_cnt_10s)begin
  62. if(end_cnt_10s)
  63. cnt_10s<=0;
  64. else
  65. cnt_10s<=cnt_10s+1'b1;
  66. end
  67. end
  68. assign add_cnt_10s=end_cnt_s;
  69. assign end_cnt_10s=add_cnt_10s && (cnt_10s==6-1);
  70. always @(posedge clk or negedge rst_n)begin
  71. if(!rst_n)
  72. cnt_m<=0;
  73. else if(add_cnt_m)begin
  74. if(end_cnt_m)
  75. cnt_m<=0;
  76. else
  77. cnt_m<=cnt_m+1'b1;
  78. end
  79. end
  80. assign add_cnt_m=end_cnt_10s;
  81. assign end_cnt_m=add_cnt_m && (cnt_m==10-1);
  82. always @(posedge clk or negedge rst_n)begin
  83. if(!rst_n)
  84. cnt_10m<=0;
  85. else if(add_cnt_10m)begin
  86. if(end_cnt_10m)
  87. cnt_10m<=0;
  88. else
  89. cnt_10m<=cnt_10m+1'b1;
  90. end
  91. end
  92. assign add_cnt_10m=end_cnt_m;
  93. assign end_cnt_10m=add_cnt_10m && (cnt_10m==6-1);
  94. always @(posedge clk or negedge rst_n)begin
  95. if(!rst_n)
  96. cnt_h<=0;
  97. else if(add_cnt_h)begin
  98. if(end_cnt_h)
  99. cnt_h<=0;
  100. else
  101. cnt_h<=cnt_h+1'b1;
  102. end
  103. end
  104. assign add_cnt_h=end_cnt_10m;
  105. assign end_cnt_h=add_cnt_h && (cnt_h==10-1);
  106. always @(posedge clk or negedge rst_n)begin
  107. if(!rst_n)
  108. cnt_10h<=0;
  109. else if(add_cnt_10h)begin
  110. if(end_cnt_10h)
  111. cnt_10h<=0;
  112. else
  113. cnt_10h<=cnt_10h+1'b1;
  114. end
  115. end
  116. assign add_cnt_10h=end_cnt_h;
  117. assign end_cnt_10h=add_cnt_10h && ((cnt_10h==2)&&(cnt_h==4));
  118. //数码管输出数据
  119. always @(posedge clk or negedge rst_n)begin
  120. if(!rst_n)
  121. dout<= 0;
  122. else
  123. dout<={cnt_10h,cnt_h,cnt_10m,cnt_m,cnt_10s,cnt_s};
  124. end
  125. endmodule

2、数码管驱动代码的编写

新建seg_driver.v文件,如下:

  1. module seg_driver(
  2. input clk ,
  3. input rst_n ,
  4. input [19:0] din ,//需要译码显示的数据
  5. output reg [5:0] seg_sel ,//数码管片选信号6个
  6. output reg [7:0] seg_dig //数码管段选信号8个
  7. );
  8. //参数定义
  9. parameter TIME_SCAM =50_000 ; //1ms,数码管轮流显示的间隔时间
  10. //显示每个数字需要亮的灯
  11. localparam ZERO = 7'b100_0000, //共阳极段码
  12. ONE = 7'b111_1001,
  13. TWO = 7'b010_0100,
  14. THREE = 7'b011_0000,
  15. FOUR = 7'b001_1001,
  16. FIVE = 7'b001_0010,
  17. SIX = 7'b000_0010,
  18. SEVEN = 7'b111_1000,
  19. EIGHT = 7'b000_0000,
  20. NINE = 7'b001_0000;
  21. //中间信号定义
  22. reg [23:0] cnt0 ;//数码管扫描1ms计数器
  23. wire add_cnt0;
  24. wire end_cnt0;
  25. reg [3:0] tmp_data;//每位数码管需要显示的数字
  26. reg dot ;//是否显示小数点的灯
  27. //1ms计数器
  28. always @(posedge clk or negedge rst_n)begin
  29. if(!rst_n)begin
  30. cnt0 <= 0;
  31. end
  32. else if(add_cnt0)begin
  33. if(end_cnt0)begin
  34. cnt0 <= 0;
  35. end
  36. else begin
  37. cnt0 <= cnt0 + 1;
  38. end
  39. end
  40. end
  41. assign add_cnt0 = 1'b1;
  42. assign end_cnt0 = add_cnt0 && cnt0 == TIME_SCAM-1;
  43. //循环亮灯 seg_sel 片选信号选择亮哪一个灯,循环过去
  44. always @(posedge clk or negedge rst_n)begin
  45. if(!rst_n)begin
  46. seg_sel <= 6'b11_1110; //首先亮最右边的灯
  47. end
  48. else if(end_cnt0)begin
  49. seg_sel <= {seg_sel[4:0],seg_sel[5]};//循环亮灯
  50. end
  51. end
  52. //tmp_data,根据片选信号去选择秒、分、时的个位、十位数字 以及是否显示小数点
  53. always @(posedge clk or negedge rst_n)begin
  54. if(!rst_n)begin
  55. tmp_data <= 0; //开始都为0
  56. dot <= 1'b1;//开始都不亮小数点的灯
  57. end
  58. else begin
  59. case (seg_sel)
  60. 6'b11_1110:begin tmp_data <= din[3:0] ;dot <= 1'b1;end
  61. 6'b11_1101:begin tmp_data <= {1'b0,din[6:4]} ;dot <= 1'b1;end//因为只占了三位,所以前面需要补0,不补也可以自动取低位
  62. 6'b11_1011:begin tmp_data <= din[10:7] ;dot <= 1'b0;end//dot <= 1'b0亮小数点的灯
  63. 6'b11_0111:begin tmp_data <= {1'b0,din[13:11]} ;dot <= 1'b1;end
  64. 6'b10_1111:begin tmp_data <= din[17:14] ;dot <= 1'b0;end
  65. 6'b01_1111:begin tmp_data <= {2'b00,din[19:18]} ;dot <= 1'b1;end
  66. default:;
  67. endcase
  68. end
  69. end
  70. //seg_dig 根据段选信号 选择对应的数字和小数点译码,根据段选信号显示数字
  71. always @(posedge clk or negedge rst_n)begin
  72. if(!rst_n)begin
  73. seg_dig <= 0;
  74. end
  75. else begin
  76. case (tmp_data)
  77. 0:seg_dig <= {dot,ZERO };
  78. 1:seg_dig <= {dot,ONE };
  79. 2:seg_dig <= {dot,TWO };
  80. 3:seg_dig <= {dot,THREE};
  81. 4:seg_dig <= {dot,FOUR };
  82. 5:seg_dig <= {dot,FIVE };
  83. 6:seg_dig <= {dot,SIX };
  84. 7:seg_dig <= {dot,SEVEN};
  85. 8:seg_dig <= {dot,EIGHT};
  86. 9:seg_dig <= {dot,NINE };
  87. default:;
  88. endcase
  89. end
  90. end
  91. endmodule

3、顶层文件的编写

新建一个top.v顶层文件,用于将前面两个设计文件行一个链接,将计数器和数码管的数据进行连接。之后实现所需要的功能。

  1. //在数码管显示计数24h24m24s 顶层模块
  2. module top(
  3. input clk ,
  4. input rst_n ,
  5. output [5:0] sel ,//片选信号,选择哪位数码管显示
  6. output [7:0] dig //段选信号,选择哪个led灯点亮
  7. );
  8. //中间信号定义
  9. wire [19:0] dout ;//在count.v中dout输出为reg,这里连接出去需要更改为wire型
  10. //模块例化
  11. cnt #() cnt_inst(
  12. .clk (clk ),
  13. .rst_n (rst_n ),
  14. .dout (dout )
  15. );
  16. seg_driver u_seg(
  17. .clk (clk ),
  18. .rst_n (rst_n ),
  19. .din (dout ),
  20. .seg_sel (sel ),
  21. .seg_dig (dig )
  22. );
  23. endmodule

三、仿真波形图

这里对于顶层文件进行仿真,所以新建一个top_tb.v文件。

图中我们可以看到在时钟秒的各级计数器计数正常并且数码管显示的值和计数器的器值相互对应,说明我们的设计没啥问题。

’ 

最后经过下板验证,我们观察到数码管从1秒开始一直计数,到这里单纯的数码管时钟设计完成。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/931674
推荐阅读
相关标签
  

闽ICP备14008679号