【FPGA】数码管电子时钟

一丶数码管介绍

Cyclone IV开发板上的数码管一共有6个,我们每次只能选择其中一个显示,怎么解决电子时钟时、分、秒同时显示呢?要实现电子时钟首先要了解什么是余晖效应

余晖效应一般指视觉暂留。 视觉暂留现象即视觉暂停现象(Persistence of vision,Visual staying phenomenon,duration of vision)又称“余晖效应”。只要数码管位选信号切换得足够快,数码管由亮到灭这一过程是需要一段时间的,由于时间很短,我们的眼睛是没有办法分清此时此刻数码管的状态,给人的感觉就是数码管是一直亮的。以此来达到欺骗人眼的效果,这样就可以实现同时显示时、分、秒。

在这里插入图片描述

二丶任务描述

使用数码管设计电子时钟,计数器部分有3种实现方法:

①.采用1个计数器,模为24x60x60;
②.采用3个计数器,模分别为24、60、60;
③.采用6个计数器分别计数时、分、秒的个位、十位;

方法1:计数器个数少,设计简单,但是后面数码管译码时,需要对计数值取余、取整,分离出时、分、秒的个位和十位,比较耗费组合逻辑资源;
方法2:3个计数器,后面数码管译码时,需要对时、分、秒计数值取余、取整,分离出时、分、秒的个位和十位,比较耗费组合逻辑资源;
方法3:6个计数器,相对复杂一点,但是计数值直接就是时、分、秒的个位和十位值,不需要除法器进行取余、取整操作,使用的触发器资源略多,但节省组合逻辑资源。

因为本文是作为作者stark-lin: 数码管电子时钟的拓展,原文使用了方法1,我们这里选择方法3

在这里插入图片描述

最后实现上面图片的效果,由于开发板上的数码管没有冒号(:),所以我们用小数点代替

三丶系统框图

计时器模块:我们把计时器模块细分了6个计时器,分别计时整个时间的小时的十位和个位,分钟的十位和个位,秒的十位和个位,最后将6个计时器拼接成一个dout_time输出给数码管驱动。与stark-lin的方法1不同,我们不需要对计数器做运算,只需要在数码管驱动中取出对应数码管的数字
(对应dout_time[x:y]的第x到y位,比如我要取出小时的十位,由于小时的十位最大能计时到2,换算成2进制数就是2位的,因为一天24小时嘛,所以应该对应dout_time[19:18])

数码管驱动:对dout_time数值进行译码,产生驱动数码管动态显示数字的位选信号和段选信号。
在这里插入图片描述

四丶模块调用

在这里插入图片描述

五丶模块原理图

在这里插入图片描述

六丶工程源码

1.计数器模块

counter.v:

module counter (
    input  wire         clk             ,
    input  wire         rst_n           ,
    output reg [19:0]   dout_time           //输出时间 HH:MM:SS
);
//计数器
reg  [25:0]    cnt    ;
wire           add_cnt;
wire           end_cnt; 
//S计时器

//个位 (0~9)
reg  [3:0]      cnt_s_bit;
wire            add_cnt_s_bit;
wire            end_cnt_s_bit;
//十位 (0~5)
reg  [2:0]      cnt_s_ten;
wire            add_cnt_s_ten;
wire            end_cnt_s_ten;

//M计时器

//个位 (0~9)
reg  [3:0]      cnt_m_bit;
wire            add_cnt_m_bit;
wire            end_cnt_m_bit;
//十位 (0~5)
reg  [2:0]      cnt_m_ten;
wire            add_cnt_m_ten;
wire            end_cnt_m_ten;

//H计时器

//个位 (0~9)
reg  [3:0]      cnt_h_bit;
wire            add_cnt_h_bit;
wire            end_cnt_h_bit;
//十位 (0~2)
reg  [1:0]      cnt_h_ten;
wire            add_cnt_h_ten;
wire            end_cnt_h_ten;

reg [3:0]       flag;
parameter   MAX_CNT=26'd50_000_000;


//计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt<=0;
    end
    else if (add_cnt) begin
        if (end_cnt) begin
            cnt<=0;
        end
        else
            cnt<=cnt+1;
    end
end

assign add_cnt=1'b1;    
assign end_cnt=add_cnt&&cnt==MAX_CNT-1;

//秒计时器---个位(0~9)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_s_bit<=0;
    end
    else if (add_cnt_s_bit) begin
        if (end_cnt_s_bit) begin
            cnt_s_bit<=0;
        end
        else
            cnt_s_bit<=cnt_s_bit+1;
    end

end

assign add_cnt_s_bit=end_cnt;
assign end_cnt_s_bit=add_cnt_s_bit&&cnt_s_bit==9;

//秒计时器---十位(0~5)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_s_ten<=0;
    end
    else if (add_cnt_s_ten) begin
        if (end_cnt_s_ten) begin
            cnt_s_ten<=0;
        end
        else
            cnt_s_ten<=cnt_s_ten+1;
    end

end

assign add_cnt_s_ten=end_cnt_s_bit;
assign end_cnt_s_ten=add_cnt_s_ten&&cnt_s_ten==5;

//分计时器---个位(0~9)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_m_bit<=9;
    end
    else if (add_cnt_m_bit) begin
        if (end_cnt_m_bit) begin
            cnt_m_bit<=0;
        end
        else
            cnt_m_bit<=cnt_m_bit+1;
    end

end

assign add_cnt_m_bit=end_cnt_s_ten;
assign end_cnt_m_bit=add_cnt_m_bit&&cnt_m_bit==9;

//分计时器---十位(0~5)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_m_ten<=5;
    end
    else if (add_cnt_m_ten) begin
        if (end_cnt_m_ten) begin
            cnt_m_ten<=0;
        end
        else
            cnt_m_ten<=cnt_m_ten+1;
    end

end

assign add_cnt_m_ten=end_cnt_m_bit;
assign end_cnt_m_ten=add_cnt_m_ten&&cnt_m_ten==5;

//时计时器---个位(0~9)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_h_bit<=3;
    end
    else if (add_cnt_h_bit) begin
        if (end_cnt_h_bit) begin
            cnt_h_bit<=0;
        end
        else
            cnt_h_bit<=cnt_h_bit+1;
    end

end

assign add_cnt_h_bit=end_cnt_m_ten;
assign end_cnt_h_bit=add_cnt_h_bit&&cnt_h_bit==flag;


//时计时器---十位(0~2)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_h_ten<=2;
    end
    else if (add_cnt_h_ten) begin
        if (end_cnt_h_ten) begin
            cnt_h_ten<=0;
        end
        else
            cnt_h_ten<=cnt_h_ten+1;
    end

end

assign add_cnt_h_ten=end_cnt_h_bit;
assign end_cnt_h_ten=add_cnt_h_ten&&cnt_h_ten==2;

//判断小时计时器十位是否记到 2
always @(*) begin
    if (cnt_h_ten==2) begin
        flag=4'd3;
    end
    else
        flag=4'd9;
end

//dout_time输出
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        dout_time<=20'b0;
    end
    else
        dout_time<={cnt_h_ten,cnt_h_bit,cnt_m_ten,cnt_m_bit,cnt_s_ten,cnt_s_bit};  //拼接成 HH:MM:SS
end
endmodule //counter

2.数码管驱动模块

seg_driver:

module seg_driver (
    input  wire          clk,
    input  wire          rst_n,
    input  wire [19:0]   dout_time,
    output reg  [5:0]    sel,
    output reg  [7:0]    seg
);
reg [3:0]       seg_flag;
reg             dot;  //小数点  用来显示  HH.MM.SS  这样的格式

//10ms计时器---用来切换数码管位选,以达到轮流显示时间的各位(肉眼可以看到动态的时间计数)
reg [15:0]      cnt;
wire            add_cnt;
wire            end_cnt;

parameter       MAX_CNT =50_000    ,
                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;



//计时器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt<=0;
    end
    else if(add_cnt) begin
        if (end_cnt) begin
            cnt<=0;
        end
        else
            cnt<=cnt+1;
    end
end
assign add_cnt=1'b1;
assign end_cnt=add_cnt&&cnt==MAX_CNT-1;

//切换数码管位选
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        sel<=6'b111_110;
    end
    else if(cnt==MAX_CNT-1) begin
        sel<={sel[4:0],sel[5]};
    end
end  

//切换数码管段选
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        seg_flag<=0;
    end
    else begin
        case (sel)
            6'b111_110: begin seg_flag<=dout_time[19:18]; dot<=1'b1;end  //小时 十位
            6'b111_101: begin seg_flag<=dout_time[17:14]; dot<=1'b0;end  //小时 个位
            6'b111_011: begin seg_flag<=dout_time[13:11]; dot<=1'b1;end  //分钟 十位
            6'b110_111: begin seg_flag<=dout_time[10:7];  dot<=1'b0;end  //分钟 个位
            6'b101_111: begin seg_flag<=dout_time[6:4];   dot<=1'b1;end  //秒   十位
            6'b011_111: begin seg_flag<=dout_time[3:0];   dot<=1'b1;end  //秒   个位
            default :seg_flag<=0;
        endcase
    end
end


//段选译码
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        seg<=8'b1111_1111;
    end
    else begin
        case (seg_flag)     
            0:  seg<={dot,ZERO}    ;
            1:  seg<={dot,ONE}     ;
            2:  seg<={dot,TWO}     ;
            3:  seg<={dot,THREE}   ;
            4:  seg<={dot,FOUR}    ;
            5:  seg<={dot,FIVE}    ;
            6:  seg<={dot,SIX}     ;
            7:  seg<={dot,SEVEN}   ;
            8:  seg<={dot,EIGHT}   ;
            9:  seg<={dot,NINE}    ;
            default: seg<=8'b1111_1111;
        endcase
    end
end

endmodule //seg_driver

3.顶层模块

top.v:

module top (
    input  wire       clk         ,  //系统时钟
    input  wire       rst_n       ,  //复位信号
    output wire [5:0] sel         ,  //数码管位选
    output wire [7:0] seg            //数码管段选
);
wire [19:0]     dout_time;

//例化计时模块
counter u_counter(
    .clk        (clk)       ,
    .rst_n      (rst_n)     ,
    .dout_time  (dout_time)   //输出时间 HH:MM:SS
);

//例化数码管驱动
seg_driver u_seg_driver(
    .clk            (clk)   ,
    .rst_n          (rst_n) ,
    .sel            (sel)   ,
    .seg            (seg)   ,
    .dout_time      (dout_time)
);
endmodule //top

七丶仿真测试

1.TestBench

`timescale 1ns/1ns
module tb_top();
reg              clk;
reg              rst_n;
wire  [5:0]      sel;
wire  [7:0]      seg;
top u_top(
    .clk        (clk)       ,
    .rst_n      (rst_n)     ,
    .sel        (sel)       ,
    .seg        (seg)
);
defparam u_top.u_counter.MAX_CNT=2;
defparam u_top.u_seg_driver.MAX_CNT=2;

always #10 clk=~clk;
initial begin
    clk=1;
    rst_n=1;
    #100;
    rst_n=0;
    #100;
    rst_n=1;
    #200000;
    $stop;
end

endmodule

2.仿真结果

在这里插入图片描述

八丶管脚信息

在这里插入图片描述

九丶上板验证

数码管电子时钟

十丶源码

https://github.com/xuranww/digital_clock.git

  • 15
    点赞
  • 149
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值