• 基于 FPGA 实现数字时钟详细原理讲解及验证结果


    本文内容:基于 FPGA 实现数字时钟,如果后续有时间可以添加一些额外的功能,比如设置时间、闹钟等等
    中间的基础篇和进阶篇主要训练数码管的灵活应用,如果熟悉了并完全掌握的话,可以更加熟练的实现数字时钟

    一、数码管原理

    • 我使用的开发板型号为 EP4CE6F17C8,它的数码管有六位,原理图如下:
      在这里插入图片描述
    • 主要是由 DIGSEL 这两个信号控制 6 位数码管显示,高电平灭,低电平亮,下面主要介绍如何控制

    SEL 信号

    • SEL 信号主要用来控制数码管的每一位,共有 6 位,SEL 位宽也就是 6 位,如下图所示:
      在这里插入图片描述
      举个例子,在代码中位宽的表示是低位在右,高位在左,所以在代码中写成 SEL = 6’b111_110 时,实际上就是第 0 位数码管亮,其余五位灭,如下图所示:
      在这里插入图片描述
      SEL = 6’b101_110 时,实际上就是第 0 位第 4 位数码管亮,如下图所示:
      在这里插入图片描述
      但是只有一个 SEL 信号不足以完全控制每一位数码管中的每一段

    DIG 信号

    • DIG 信号主要控制每一位数码管中的 8 个段的数码管亮灭,如下图所示:
      在这里插入图片描述
    • DIG = 8’b1010_0100 时,一位数码管就显示数字 2,如下图所示:
      在这里插入图片描述
      其它每个数字所对应的 DIG 值就不一一列举出来了
    • 这两个信号时需要相互配合才可以在数码管上呈现我们想要的效果
    • 比如说当 SEL = 6’b111_101DIG = 8’b1010_0100 时,前面 SEL 表示第 1 位(从 0 开始的)数码管亮,配合 DIG 显示数字 2,那么在开发板上就可以呈现第 1 位数码管显示数字 2 其余数码管灭,代码如下:
    module display(
        input                   clk         ,
        input                   rst_n       ,
    
        output      [7:0]       DIG         ,
        output      [5:0]       SEL
    );
    
        assign SEL = 6'b111_101;
        assign DIG = 8'b1010_0100;
        
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 效果图如下:
      在这里插入图片描述

    二、基础篇

    2.1 原理及代码

    • 如何让数码管依次显示 123456 呢?首先要知道 SEL 和 DIG 如何配合控制数码管显示的
    • 原理图:
      在这里插入图片描述
      (1) 首先设置 SEL 显示第 0 位,也就是 SEL = 6’b111_110,再设置 DIG 显示 1 的比特值,这样的话,数码管就第 0 位显示数字 1 了,但是其它位是
      (2) 然后设置 SEL 显示第 1 位,也就是 SEL = 6’b111_101,再设置 DIG 显示 2 的比特值,这样的话,数码管就第 1 位显示数字 2 了,但是其它位是
      (3) 按照上面的套路,依次让每一位显示相应数字,其它位灭,当 SEL 的值改变的速度不断的增加,那么就可以连续显示 123456 了,这主要应用到了视觉残留的机制
    • 仔细好好的理一下逻辑
    • 代码也是十分简单的,当然可以不用 SEL_num 来做选择,直接对 SEL 使用拼接运算符也可以

    方法一:普通版

    module display #(parameter MS_1 = 17'd100_000)(
        input                   clk         ,
        input                   rst_n       ,
    
        output  reg [7:0]       DIG         ,
        output  reg [5:0]       SEL
    );
    
        // 参数定义
        parameter SEL_MAX   = 3'd6          ;   // 数码管位数 
    
    // 信号定义
        reg     [ 2:0]          SEL_num     ;   // SEL序号选择      
    
        reg     [16:0]          cnt_flicker ;   // 数码管闪烁频率计数器
    
    // 逻辑实现
        // 闪烁频率计数器
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                cnt_flicker <= 17'd0;
            end
            else if (cnt_flicker >= MS_1 - 17'd1) begin
                cnt_flicker <= 17'd0;
            end
            else begin
                cnt_flicker <= cnt_flicker + 17'd1; 
            end
        end
    
        // SEL序号选择
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                SEL_num <= 3'd0;
            end
            else if (cnt_flicker >= MS_1 - 17'd1) begin
                if (SEL_num >= SEL_MAX - 3'd1) begin
                    SEL_num <= 3'd0;
                end
                else begin
                    SEL_num <= SEL_num + 3'd1;
                end
            end
            else begin
                SEL_num <= SEL_num;
            end
        end
    
        // SEL信号输出
        always @(*) begin
            case (SEL_num)
                3'd0 : SEL = 6'b111_110;
                3'd1 : SEL = 6'b111_101;
                3'd2 : SEL = 6'b111_011;
                3'd3 : SEL = 6'b110_111;
                3'd4 : SEL = 6'b101_111;
                3'd5 : SEL = 6'b011_111;
                default: SEL = 6'b111_111;
            endcase
        end
    
        // DIG信号输出
        always @(*) begin
            case (SEL_num)
                3'd0 : DIG = 8'b1111_1001;
                3'd1 : DIG = 8'b0010_0100;
                3'd2 : DIG = 8'b1011_0000;
                3'd3 : DIG = 8'b0001_1001;
                3'd4 : DIG = 8'b1001_0010;
                3'd5 : DIG = 8'b1000_0010;
                default: DIG = 8'b1111_1111; 
            endcase
        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

    方法二:拼接运算版

    module display #(parameter MS_1 = 17'd100_000)(
        input                               clk         ,
        input                               rst_n       ,
    
        output  reg     [7:0]               DIG         ,
        output  reg     [5:0]               SEL
    );
    
    // 信号定义    
        reg             [16:0]              cnt_flicker ;   // 数码管闪烁频率计数器
        wire            [ 0:0]              SEL_change  ;
    
    // 逻辑实现
        // 闪烁频率计数器
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                cnt_flicker <= 17'd0;
            end
            else if (SEL_change) begin
                cnt_flicker <= 17'd0;
            end
            else begin
                cnt_flicker <= cnt_flicker + 17'd1; 
            end
        end
    
        assign SEL_change = cnt_flicker >= MS_1 - 17'd1 ? 1'b1 : 1'b0;
    
        // SEL信号输出
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                SEL <= 6'b111_110;
            end
            else if (SEL_change) begin
                SEL <= {SEL[4:0], SEL[5]};
            end
            else begin
                SEL <= SEL;
            end
        end
    
        // DIG信号输出
        always @(*) begin
            case (SEL)
                6'b111_110 : DIG = 8'b1111_1001;
                6'b111_101 : DIG = 8'b0010_0100;
                6'b111_011 : DIG = 8'b1011_0000;
                6'b110_111 : DIG = 8'b0001_1001;
                6'b101_111 : DIG = 8'b1001_0010;
                6'b011_111 : DIG = 8'b1000_0010;
                default: DIG = 8'b1111_1111; 
            endcase
        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

    2.2 验证结果

    • 效果图如下:
      在这里插入图片描述

    三、进阶篇

    3.1 原理及代码

    • 该部分主要进一步探究数码管 SEL 与 DIG 之间配合显示

    实现目标:让 6 位数码管从右往左滑动显示 0 - 9,当显示 9 时,后面连续显示 “-”,直到 9 消失,又从 0 开始滑动

    • 可以自己画图分析如何实现,这里我将数码管显示分为 16 个状态,每经过 0.5 s 的时间就向左滑动一下,也就是从一个状态跳转到下一个状态
    • 将一些特殊的情况罗列出来,可以发现用状态序号+位选号来确定每一位所对应的 DIG 信号
    • 进一步分析,随着状态的转移,它们之间的和就大于 9 了,那么就需要分情况讨论
    • 和 <= 9
      直接输出对应的 DIG 二进制
      15>= 和 >= 10
      输出 “-” 对应的 DIG 二进制
      和 >= 16
      输出 和 - 16 对应的 DIG 二进制
      在这里插入图片描述
    • 现在思路清晰了,实现代码如下:
    module dig_demo #(parameter MS_1    = 17'd100_000,
                                MS_200  = 25'd2500_0000)(
        input                               clk                     ,   // 50MHz时钟
        input                               rst_n                   ,   // 复位信号
    
        output  reg     [ 7:0]              DIG                     ,   // 输出DIG
        output  reg     [ 5:0]              SEL                         // 输出SEL
    );
    
    // 信号定义    
        reg             [16:0]              cnt_flicker             ;   // SEL刷新频率计数器
        wire            [ 0:0]              end_cnt_flicker         ;   // cnt_flicker停止计数信号
        
        reg             [24:0]              cnt_200ms               ;   // 200ms计数器
        wire            [ 0:0]              end_cnt_200ms           ;   // cnt_200ms停止计数使能信号
    
        reg             [ 3:0]              cnt_16state             ;   // 16 个状态计数器
        wire            [ 0:0]              end_cnt_16state         ;   // 结束计时
    
        reg             [ 2:0]              SEL_now                 ;   // SEL现态
        wire            [ 4:0]              cnt_16state_and_SEL_now ;   // cnt_16state + SEL_now
        reg             [ 1:0]              DIG_now_status          ;   // DIG现态所处的情况
        reg             [ 3:0]              DIG_now                 ;   // DIG现态
    
    // 逻辑实现
        // SEL刷新频率计数器
            /*
            每过10_0000个时钟周期刷新SEL值
            */
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                cnt_flicker <= 17'd0;
            end
            else if (end_cnt_flicker) begin
                cnt_flicker <= 17'd0;
            end
            else begin
                cnt_flicker <= cnt_flicker + 17'd1; 
            end
        end
    
        assign end_cnt_flicker = cnt_flicker >= MS_1 - 17'd1 ? 1'b1 : 1'b0;
    
        // SEL现态
            /*
            当cnt_flicker计数到最大值10_0000个时钟周期后
            SEL就从第n位跳到第n+1位
            当跳到第5位后,就回到第0位
            */
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                SEL_now <= 'd0;
            end
            else if (end_cnt_flicker) begin
                if (SEL_now >= 'd5) begin
                    SEL_now <= 'd0;
                end
                else begin
                    SEL_now <= SEL_now + 'd1;
                end
            end
            else begin
                SEL_now <= SEL_now;
            end
        end
    
        // SEL信号输出
            /*
            根据SEL_now选择SEL输出二进制
            */
        always @(*) begin
            case (SEL_now)
              3'd0 : SEL = 6'b111_110;
              3'd1 : SEL = 6'b111_101;
              3'd2 : SEL = 6'b111_011;
              3'd3 : SEL = 6'b110_111;
              3'd4 : SEL = 6'b101_111;
              3'd5 : SEL = 6'b011_111;
              default: SEL = 6'b111_111;
            endcase
        end
    
        // cnt_200ms
            /*
            计数200ms
            每过200ms,数码管就向左滑动一下
            */
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                cnt_200ms <= 'd0;
            end
            else if (end_cnt_200ms) begin
                cnt_200ms <= 'd0;
            end
            else begin
                cnt_200ms <= cnt_200ms + 'd1;
            end
        end
    
        assign end_cnt_200ms = cnt_200ms >= MS_200 - 'd1 ? 1'b1 : 1'b0;
    
        // cnt_16state
            /*
            数码管滑动分为16个状态
            每个状态持续200ms
            */
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                cnt_16state <= 'd0;
            end
            else if (end_cnt_200ms) begin
                if (end_cnt_16state) begin
                    cnt_16state <= 'd0;
                end
                else begin
                    cnt_16state <= cnt_16state + 'd1;
                end
            end
            else begin
                cnt_16state <= cnt_16state;
            end
        end
    
        assign end_cnt_16state = cnt_16state >= 4'd15 ? 1'b1 : 1'b0;
    
        // 计算当前16个状态中的一个状态值与SEL现在位数的和
        assign cnt_16state_and_SEL_now = cnt_16state + SEL_now;
    
        // DIG现态值判断
            /*
            根据上面计算出的和
            判断当前数码管处于3个状态中的哪一个状态
                0:数码管每一位显示数字
                1:数码管前面显示数字,后面显示"-"
                2:数码管前面显示"-",后面显示数字
            */
        always @(*) begin
            if (cnt_16state_and_SEL_now > 9 && cnt_16state_and_SEL_now < 16) begin
                DIG_now_status <= 'd1;
            end
            else if (cnt_16state_and_SEL_now >= 16) begin
                DIG_now_status <= 'd2;
            end
            else begin
                DIG_now_status <= 'd0;
            end
        end
    
        // DIG现态
            /*
            根据前面状态的判断计算出当前SEL所对应的DIG的值
            */
        always @(*) begin
            if (DIG_now_status == 'd0) begin
                DIG_now = cnt_16state_and_SEL_now;
            end
            else if (DIG_now_status == 'd1) begin
                DIG_now = 'd10;
            end
            else if (DIG_now_status == 'd2) begin
                DIG_now = cnt_16state_and_SEL_now - 'd16;
            end
            else begin
                DIG_now = DIG_now;
            end
        end
    
        // DIG对应数字输出
        always @(*) begin
            case (DIG_now)
                4'd0  : DIG = 8'b1100_0000;
                4'd1  : DIG = 8'b1111_1001;
                4'd2  : DIG = 8'b1010_0100;
                4'd3  : DIG = 8'b1011_0000;
                4'd4  : DIG = 8'b1001_1001;
                4'd5  : DIG = 8'b1001_0010;
                4'd6  : DIG = 8'b1000_0010;
                4'd7  : DIG = 8'b1111_1000;
                4'd8  : DIG = 8'b1000_0000;
                4'd9  : DIG = 8'b1001_0000;
                4'd10 : DIG = 8'b1011_1111;
                default : DIG = 8'b1111_1111;
            endcase
        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
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187

    3.2 验证结果

    数码管滑动显示

    四、数字时钟

    4.1 原理及代码

    需求分析:

    1. 数码管显示拥有多个界面
      (1)时分秒:显示界面、设置界面
      (2)年月日:显示界面、设置界面
      (3)闹钟:状态界面(开启/关闭)、设置界面
    2. 年月日:
      (1)默认主界面为时分秒显示界面,通过按键可调出年月日界面,并显示三秒钟后自动跳转回主界面(时分秒界面)
      (2)进入到年月日界面后,可通过设置按键设置年月日的值,设置完成后自动保存
    3. 时分秒:
      (1)默认主界面为时分秒显示界面,设置按键可设置当前显示界面时分秒的值,设置完成后自动保存
    4. 闹钟:
      (1)闹钟设置为闹钟显示界面,如果开启闹钟,则显示设置的闹钟时间,如果关闭闹钟,则显示连续的 “-”,表示关闭了闹钟
      (2)当时间达到闹钟设的值时,蜂鸣器播放歌曲,播放歌曲期间按键任意按键即可关闭闹钟

    原理讲解:

    • 说实话,这原理其实真没啥好讲的,根据上面写出来的需求一步一步添加相应的条件或者使能信号就实现出来了
    • 上面两部分倒是有些原理可讲,如果能熟练写出上面的代码,那么对于一些信号的灵活应用以及代码编写的能力也能提高很多,而这时钟是真不好讲,所以只说说模块划分吧
      在这里插入图片描述
    • 数字时钟,无非就是用按键设置时钟的值,这里空口白话的说也理不清楚里面的逻辑关系,涉及到很多使能信号之间的条件关系
    • 可以仿真出来看看,就很清楚明白了
    • 工程文件链接:https://pan.baidu.com/s/1NR6dzZ2G6TEWzoaGswhYww?pwd=30gh——提取码:30gh
    • 简单说一下工程内的文件
      在这里插入图片描述
    • 源码主要是在 rtl 下面的 .v 文件
      在这里插入图片描述
    • 看看上面那张系统设计的图,就可以知道每个文件之间的依赖关系

    4.2 验证结果

    • 最后来看看在开发板上的效果吧

    FPGA多功能数字时钟

  • 相关阅读:
    593. 有效的正方形 : 简单几何运用题
    C语言的5个内存段你了解吗?( 代码段/数据段/栈/堆)
    面试官:Java的重写和重载有什么区别?
    一览「数字身份」市场结构:我们将在元宇宙中成为谁?
    看看Vue
    【HMS Core】华为分析服务通过REST方式上报用户行为,控制台为何无法查询到相关数据?
    node开发时避免重复重启
    【目标跟踪】|模板更新 时间上下文信息(UpdateNet)《Learning the Model Update for Siamese Trackers》
    HP魔法觉醒自动抢协作和自动社团答题的半成品
    OPC UA客户端工具Softing OPC Client使用图文教程
  • 原文地址:https://blog.csdn.net/ssj925319/article/details/126009499