当前位置:   article > 正文

【代码】Xilinx + Vivado + 数字时钟(时分秒) + LED指示_fpga数码管显示小时和分钟,led 闪烁表示秒

fpga数码管显示小时和分钟,led 闪烁表示秒

【代码】【Xilinx + Vivado + 数字时钟(时分秒) + LED指示】实践项目

最终效果(没图)

  • 每1秒sec计数累加1次,59次之后清零;
  • 每1分钟min计数累加1次,59次之后清零;
  • 每1小时hour计数累加1次,23次之后清零;
  • LED1交替翻转,1秒翻转1次,所以1亮1灭是2秒;
  • LED2交替翻转,1分钟翻转1次;

相关截图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

过程记录

首先要申明一点,目前入门FPGA的阶段属于非常非常,所有工程代码、工程逻辑都可能存在错误,希望各位同学老师能够指出,同时也参考了大量的互联网上优质内容,在这里一并感谢,基于这个原因也决定将核心逻辑进行共享。请勿做一切商业用途。

ok,let’s go

首先是搞清楚思路,我们设计一个时钟,是在我们这个现实世界里边的时钟,滴答滴答1秒就是1秒,而不是FPGA规定的1秒。如何将FPGA里边的1秒,对应上现实世界滴答滴答1秒,关键在于配合FPGA的时钟和计数值二者。假如我们现在FPGA的时钟是50MHz(6次方),那么FPGA计数一次就是20ns,我们需要计算50_000_000次,才能够达到现实世界滴答滴答1秒的效果。注意,到这里两个1秒就同步上了,因为现实世界、FPGA都消耗了1秒,不管你是从哪一帧开始的,从这一帧开始匹配上之后,往后的每个1秒(只要FPGA不要断电 and 世界不要毁灭)理论上都能一直匹配上,这就是数字时钟能够显示现实世界时间的原理。当然FPGA并不知道当前北京时间是多少(可以加上时间校正功能),也并不知道1小时有60分钟,1分钟有60秒,这些逻辑都是依靠我们自己去实现的。

设计模块1:分频部分,配合FPGA的时钟和计数值,实现1秒的计时;
设计模块2:秒钟部分,实现秒钟的累加,数值达到59自动清零;
设计模块3:分钟部分,实现分钟的累加,数值达到59自动清零;
设计模块4:时钟部分,实现时钟的累计,数值达到23自动清零;

由于我们定义secminhour都是全局的,所以我们不需要额外的标志位,再去判断是否技术满足1分钟,或者计数满足1小时。只需要判断是否等于59即可,因为下个时钟沿才会下一段判断,而刚好下个时钟沿不就正好60了吗(上一刻赋值,下一刻判断,注意使用非阻塞赋值)。所以我们要做的就是维护这些计数值达到59/23的时候自动清零即可。

在每次秒钟累加的时候,翻转LED1,而在累加59秒钟,自动清零那一块的代码里边,翻转LED2。具体不展开,可以结合代码边调边看。

// 秒钟部分
always @ ( posedge clk_div  or posedge rst)begin
       if(rst)begin
           sec <= 0;
           led_out1 <= 1'b0;
           led_out2 <= 1'b0;
       end
      else begin
        if ( sec == 59 )begin  
             sec <= 0 ;
             led_out2 <= ~led_out2;
            end
        else begin
            led_out1 <= ~led_out1;
            sec <= sec + 1 ;
        end   
    end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

还需要讲一下这个分频部分,这一块比较重要。

变量含义
clk_out1经过PLL分频得到的50MHz时钟信号
cntFPGA自加计数的值
clk_divFPGA累计满1秒给的信号(上升沿)
sec秒钟计数值(59后清零)
min分钟计数值(59后清零)
hour时钟计数值(23后清零)
parameter TIME_2HZ = 25_000_000 ; 
parameter TIME_1HZ= 50_000_000  ; 

//分频部分  50MHz - 1Hz/2Hz
always @ ( posedge clk_out1 or posedge rst)begin
       if(rst)begin
           cnt <= 0;
           clk_div <= 0;
        end
       else begin
        if ( cnt < TIME_2HZ  - 1 )
           begin
               clk_div <= 0;
               cnt <= cnt + 1;
           end
        else if ( cnt < TIME_1HZ - 1 )
           begin
               clk_div <= 1;
               cnt <= cnt + 1 ;
           end
        else
           cnt <=0 ;
       end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

另外由于板载自带的晶振源是200MHz,所以我们需要PLL分频把晶振源降低到50MHz,这一部分参考之前的那篇博客。如果你的晶振已经是50MHz,那么直接把clk_out1换成sys_clk(50MHz)即可。

上述博客地址:【已解决】FPGA系列Xilinx Artix7找不到SYS_CLK在哪里,如何输出50MHZ频率方波

逻辑是这样的,系统时钟现在是50MHz,计数一次就是20ns,我们可以计数累计50_000_000就翻转一次clk_div的状态;而这里用的方法稍微有些不同,相当于是一个500ms高电平,一个500ms低电平,当两个高电平之间相隔还是1秒整。但如果我们1秒翻转1次翻转一次clk_div的状态,那么两次上升沿的间隔就是2秒(1秒高电平1秒低电平)而不是1秒。

在这里插入图片描述

集合图像来看,假设我涂阴影的部分是1秒的自然时间,那么方案1就是维持500ms的低电平,维持500ms的高电平。而方案2就是维持1_000ms也就是1秒的高电平,再维持1_000ms的低电平。上述两种方案,哪种更加贴近我们的需求呢?显然是第一种方案,因为它能够维持每隔1秒,传递一个有效上升沿clk_div ,而方案2需要每隔2秒才能传递一个有效的高电平。

另外需要注意的一点是,每个1秒产生一个clk_div信号,并且在秒钟、分钟、时钟的计时处理函数中,都将clk_div的上升沿作为触发条件。看似没有什么问题,但其实也有更好的写法。理论上应该使用系统时钟(50MHz)作为触发条件,而不是自己分频的时钟,并且在系统时钟触发之后,再去判断clk_div上升沿完成一系列操作。虽然目前看工程暂时没有什么问题,这里先mark一下看看有没有其他大佬指点一下。

搞定这几个主要逻辑之后,读后边的代码,应该就比较顺畅了。具体还有问题的话可以评论区留言一下。

代码工程

首先要申明一点,目前入门FPGA的阶段属于非常非常,所有工程代码、工程逻辑都可能存在错误,希望各位同学老师能够指出,同时也参考了大量的互联网上优质内容,在这里一并感谢,基于这个原因也决定将核心逻辑进行共享。请勿做一切商业用途。

另外本次工程项目也继续产用了层次化的设计,具体什么是层次化(仅个人理解),代码复用带来什么好处请看我另一篇博客,这里不再赘述。以下代码包含三个部分,第一部分是核心clock module的实现。第二部分是Top.v,也就是最后下载到板子上的逻辑,这部分调用了clock module并且申明了一些端口的输出。第三部分是Testbench,同样是调用了clock module,并且申明了相应端口的输出,不同的是这里也申明了secminhour三个端口的输出,毕竟我们要看的就是这三个呀!?!

我的另一篇博客:FPGA基于Vivado开发,设计顶层文件Top.v

完整的clock module代码,主要实现功能是在50MHz的晶振源下,通过时钟+计数值的配合,每隔1秒输出一个有效的上升沿clk_div,并且进入具体逻辑的判断,包含计数值累加、计数值清零等操作。具体可以结合代码+仿真再看看。

`timescale 1ns / 1ns

//数字时钟模块
module clock1(
    sec,
    min,
    hour,
    rst,
    sys_clk_p,
    sys_clk_n,
    clk_out1,
    led_out1,
    led_out2
    );
       
input      rst;
input      sys_clk_p;            // Differential input clock 200Mhz
input      sys_clk_n;            // Differential input clock 200Mhz
output     clk_out1;  
output [7:0]  sec,min;
output [7:0]  hour;
output    led_out1;
output    led_out2;
reg    [7:0]  sec=0;
reg    [7:0]  min=0;
reg    [7:0]  hour=0;
reg    [32:0] cnt;
reg    clk_div;
reg    led_out1;
reg    led_out2;

//parameter TIME_2HZ = 25 ;  // 测试仿真使用
//parameter TIME_1HZ= 50  ; 
parameter TIME_2HZ = 25_000_000 ; 
parameter TIME_1HZ= 50_000_000  ; 


//分频部分  50MHz - 1Hz
always @ ( posedge clk_out1 or posedge rst)begin
       if(rst)begin
           cnt <= 0;
           clk_div <= 0;
        end
       else begin
        if ( cnt < TIME_2HZ  - 1 )
           begin
               clk_div <= 0;
               cnt <= cnt + 1;
           end
        else if ( cnt < TIME_1HZ - 1 )
           begin
               clk_div <= 1;
               cnt <= cnt + 1 ;
           end
        else
           cnt <=0 ;
       end
end

// 秒钟部分
always @ ( posedge clk_div  or posedge rst)begin
       if(rst)begin
           sec <= 0;
           led_out1 <= 1'b0;
           led_out2 <= 1'b0;
       end
      else begin
        if ( sec == 59 )begin  
             sec <= 0 ;
             led_out2 <= ~led_out2;
            end
        else begin
            led_out1 <= ~led_out1;
            sec <= sec + 1 ;
        end   
    end
end

//分钟部分
always @ ( posedge clk_div or posedge rst)begin
   if(rst)begin
        min <= 0;
   end 
   else begin
     if ( sec == 59 )begin
         if (min ==59 )
            min <= 0;
         else
            min <= min + 1 ;
     end
   end
end

// 时钟部分
always @ ( posedge clk_div or posedge rst)
    begin
       if(rst)begin
           hour <= 0;
       end
       else begin
            if ( sec == 59 && min ==59 )begin
                if (hour == 23)
                    hour <= 0 ;
                else
                    hour <= hour + 1;
            end
       end
end

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110

完整的顶层Top.v文件代码,常规的clock module初始化,PLL分频是因为我的板子是200MHz的,通过PLL分频为50MHz(之前的博客写了)。

`timescale 1ns / 1ps


module TOP(
    input rst,
    input sys_clk_p,
    input sys_clk_n,
    output clk_out1,
    output led_out1,
    output led_out2
    );

//***********差分时钟50MHz *****************************************
wire   sys_clk_p;
wire   sys_clk_n;
wire   rst;
wire   clk_out1; 

//***********数字时钟****************************************
wire   led_out1;
wire   led_out2;


//时钟模块初始化
clock1 clock(
    .led_out1(led_out1),
    .led_out2(led_out2),
    .rst(rst),
    .sys_clk_p(sys_clk_p        ),
    .sys_clk_n(sys_clk_n        ),
    .clk_out1(clk_out1        )
    );

//PLL分频的代码
clk_wiz_0 clk_wiz_0
(
// Clock out ports
.clk_out1(clk_out1),     // output clk_out1
// Status and control signals
.reset(rst), 
//.locked(locked),       // output locked
// Clock in ports
.clk_in1_p(sys_clk_p),   
.clk_in1_n(sys_clk_n));    // input clk_in1_n

endmodule

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

然后就是仿真文件,也都比较常规,注意的是这里还申明了sec、min、hour三个输出,方便我们看他的波形。另外值得注意的点是,这里的数字时钟计数方式是十六进制,也就是说09、0a、0b、0c ……这样的计数方式,具体原因我也还没搞清楚。后续我将这个clock制作成为数字时钟并且显示的时候,另外做了一些处理,最终也能够正常显示成09、10、11、12……这种形式,感兴趣的话可以翻翻那一篇博客。

`timescale 1ns / 1ns


module clock1_tb();

//***********??????*****************************************
reg   sys_clk_p;
wire  sys_clk_n;
reg     rst;
wire    clk_out1; 
//***********??????*****************************************
wire [7:0]   sec;
wire [7:0]   min;
wire [7:0]   hour;
wire   led_out1;
wire   led_out2;


//初始化系统时钟
initial begin
    sys_clk_p = 1'b0;
    rst <= 1'b0; 
    #2000
    rst <= 1'b1;
    #20000
    rst <= 1'b0;  
    #2000000
    rst <= 1'b1;
    #20000
    rst <= 1'b0;  
end


parameter T = 5; //200MHz时钟周期为5ns
always #(T/2) sys_clk_p = ~ sys_clk_p;   
assign sys_clk_n = ~ sys_clk_p;


//数字时钟初始化
clock1 clock(
    .led_out1(led_out1),
    .led_out2(led_out2),
    .sec(sec),
    .min(min),
    .hour(hour),
    .rst(rst),
    .sys_clk_p          (sys_clk_p        ),
    .sys_clk_n          (sys_clk_n        ),
    .clk_out1         (clk_out1        )
    );    

//PLL时钟分频
clk_wiz_0 clk_wiz_0
(
// Clock out ports
.clk_out1(clk_out1),     // output clk_out1
// Status and control signals
.reset(rst), 
//.locked(locked),       // output locked
// Clock in ports
.clk_in1_p(sys_clk_p),   
.clk_in1_n(sys_clk_n));    // input clk_in1_n

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

在这里插入图片描述

后续可以烧录板子具体看一看现象,引脚图上边已经给出了,但我们的引脚不会是一样的。另外因为我的时钟200MHz,所以会有sys_clk_p、sys_clk_n(二者差分)、clk_out1(最终的50MHz,其实可以不输出)。板子的复位是高电平有效,这一点也和传统复位可能有些区别,这一点大家注意一下。如果你是低电平复位建议写成rst_n,我是高电平复位所以是rst

总结

  • 通过时钟+计数值的配合,在FPGA环境下产生周期为1秒的上升沿clk_div
  • 在此上升沿的基础之上,实现了秒钟、分钟、时钟的计时逻辑;
  • 最终展示效果是LED1每隔1秒翻转一次(2秒完成一次闪烁),LED2每隔1分钟翻转一次。

参考

以上。

如果你觉得这篇文章对你有帮助,请为我点个赞谢谢

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