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

avatar
作者
筋斗云
阅读量:0

一、概述

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

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

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

二、工程实现

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

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

新建cnt.v文件,如下:

module cnt(   input  clk,   input  rst_n,   output reg[19:0] dout );  parameter   TIME_1s =50_000_000;  reg [26:0] cnt_1s; wire       add_cnt_1s; wire       end_cnt_1s;   /*---------------------------------------------------------------- 时钟计数器 ------------------------------------------------------------------*/  reg [3:0]  cnt_s;//秒数第一位 wire       add_cnt_s; wire       end_cnt_s;   reg [2:0] cnt_10s;//秒数第二位 wire       add_cnt_10s; wire       end_cnt_10s;   reg [3:0]  cnt_m;//分数第一位 wire       add_cnt_m; wire       end_cnt_m;   reg [2:0] cnt_10m;//分数第二位 wire       add_cnt_10m; wire       end_cnt_10m;   reg [3:0]  cnt_h;//小时第一位 wire       add_cnt_h; wire       end_cnt_h;   reg  [1:0]      cnt_10h;//小时第二位 wire       add_cnt_10h; wire       end_cnt_10h;    /*---------------------------------------------------------------- 时钟 -----------------------------------------------------------------*/  always @(posedge clk or negedge rst_n)begin     if(!rst_n)       cnt_1s<=0;     else if(add_cnt_1s)begin       if(end_cnt_1s)         cnt_1s<=0;       else         cnt_1s<=cnt_1s+1'b1;     end end assign add_cnt_1s=1'b1; assign end_cnt_1s=add_cnt_1s && (cnt_1s==TIME_1s-1);  always @(posedge clk or negedge rst_n)begin     if(!rst_n)       cnt_s<=0;     else if(add_cnt_s)begin       if(end_cnt_s)         cnt_s<=0;       else         cnt_s<=cnt_s+1'b1;     end end assign add_cnt_s=end_cnt_1s; assign end_cnt_s=add_cnt_s && (cnt_s==10-1);  always @(posedge clk or negedge rst_n)begin     if(!rst_n)       cnt_10s<=0;     else if(add_cnt_10s)begin       if(end_cnt_10s)         cnt_10s<=0;       else         cnt_10s<=cnt_10s+1'b1;     end end assign add_cnt_10s=end_cnt_s; assign end_cnt_10s=add_cnt_10s && (cnt_10s==6-1);  always @(posedge clk or negedge rst_n)begin     if(!rst_n)       cnt_m<=0;     else if(add_cnt_m)begin       if(end_cnt_m)         cnt_m<=0;       else         cnt_m<=cnt_m+1'b1;     end end assign add_cnt_m=end_cnt_10s; assign end_cnt_m=add_cnt_m && (cnt_m==10-1);  always @(posedge clk or negedge rst_n)begin     if(!rst_n)       cnt_10m<=0;     else if(add_cnt_10m)begin       if(end_cnt_10m)         cnt_10m<=0;       else         cnt_10m<=cnt_10m+1'b1;     end end assign add_cnt_10m=end_cnt_m; assign end_cnt_10m=add_cnt_10m && (cnt_10m==6-1);  always @(posedge clk or negedge rst_n)begin     if(!rst_n)       cnt_h<=0;     else if(add_cnt_h)begin       if(end_cnt_h)         cnt_h<=0;       else         cnt_h<=cnt_h+1'b1;     end end assign add_cnt_h=end_cnt_10m; assign end_cnt_h=add_cnt_h && (cnt_h==10-1);  always @(posedge clk or negedge rst_n)begin     if(!rst_n)       cnt_10h<=0;     else if(add_cnt_10h)begin       if(end_cnt_10h)         cnt_10h<=0;       else         cnt_10h<=cnt_10h+1'b1;     end end assign add_cnt_10h=end_cnt_h; assign end_cnt_10h=add_cnt_10h && ((cnt_10h==2)&&(cnt_h==4));   //数码管输出数据 always @(posedge clk or negedge rst_n)begin   if(!rst_n)     dout<= 0;   else      dout<={cnt_10h,cnt_h,cnt_10m,cnt_m,cnt_10s,cnt_s}; end  endmodule

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

新建seg_driver.v文件,如下:

module seg_driver(      input				clk		    ,     input				rst_n	    ,     input       [19:0]	din		    ,//需要译码显示的数据          output	reg	[5:0]	seg_sel	    ,//数码管片选信号6个     output	reg	[7:0]	seg_dig	     //数码管段选信号8个 );								      //参数定义			      parameter TIME_SCAM =50_000 ; //1ms,数码管轮流显示的间隔时间      //显示每个数字需要亮的灯     localparam  ZERO  = 7'b100_0000,   //共阳极段码                 ONE   = 7'b111_1001,                 TWO   = 7'b010_0100,                 THREE = 7'b011_0000,                 FOUR  = 7'b001_1001,                 FIVE  = 7'b001_0010,                 SIX   = 7'b000_0010,                 SEVEN = 7'b111_1000,                 EIGHT = 7'b000_0000,                 NINE  = 7'b001_0000;      //中间信号定义		      reg		[23:0]	cnt0    ;//数码管扫描1ms计数器     wire		    add_cnt0;     wire		    end_cnt0;      reg     [3:0]   tmp_data;//每位数码管需要显示的数字     reg             dot     ;//是否显示小数点的灯      //1ms计数器     always @(posedge clk or negedge rst_n)begin          if(!rst_n)begin             cnt0 <= 0;         end          else if(add_cnt0)begin              if(end_cnt0)begin                  cnt0 <= 0;             end             else begin                  cnt0 <= cnt0 + 1;             end          end     end     assign add_cnt0 = 1'b1;     assign end_cnt0 = add_cnt0 && cnt0 == TIME_SCAM-1;      //循环亮灯  seg_sel 片选信号选择亮哪一个灯,循环过去     always @(posedge clk or negedge rst_n)begin          if(!rst_n)begin             seg_sel <= 6'b11_1110; //首先亮最右边的灯         end          else if(end_cnt0)begin              seg_sel <= {seg_sel[4:0],seg_sel[5]};//循环亮灯         end      end      //tmp_data,根据片选信号去选择秒、分、时的个位、十位数字 以及是否显示小数点     always @(posedge clk or negedge rst_n)begin          if(!rst_n)begin             tmp_data <= 0;   //开始都为0               dot      <= 1'b1;//开始都不亮小数点的灯         end         else begin             case (seg_sel)                 6'b11_1110:begin tmp_data <= din[3:0]           ;dot <= 1'b1;end                 6'b11_1101:begin tmp_data <= {1'b0,din[6:4]}    ;dot <= 1'b1;end//因为只占了三位,所以前面需要补0,不补也可以自动取低位                 6'b11_1011:begin tmp_data <= din[10:7]          ;dot <= 1'b0;end//dot <= 1'b0亮小数点的灯                  6'b11_0111:begin tmp_data <= {1'b0,din[13:11]}  ;dot <= 1'b1;end                 6'b10_1111:begin tmp_data <= din[17:14]         ;dot <= 1'b0;end                 6'b01_1111:begin tmp_data <= {2'b00,din[19:18]} ;dot <= 1'b1;end                 default:;             endcase         end     end      //seg_dig 根据段选信号  选择对应的数字和小数点译码,根据段选信号显示数字     always @(posedge clk or negedge rst_n)begin          if(!rst_n)begin             seg_dig <= 0;         end          else begin              case (tmp_data)                 0:seg_dig <= {dot,ZERO };                 1:seg_dig <= {dot,ONE  };                 2:seg_dig <= {dot,TWO  };                 3:seg_dig <= {dot,THREE};                 4:seg_dig <= {dot,FOUR };                 5:seg_dig <= {dot,FIVE };                 6:seg_dig <= {dot,SIX  };                 7:seg_dig <= {dot,SEVEN};                 8:seg_dig <= {dot,EIGHT};                 9:seg_dig <= {dot,NINE };                 default:;             endcase               end      end      endmodule 

3、顶层文件的编写

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

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

三、仿真波形图

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

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

’ 

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

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!