FPGA学习——电子时钟模拟(新)

博主在之前曾经编写过一篇电子时钟的博客(详情请见此篇博文),但曾经编写的电子时钟,未显示小数点位,同时当时的数码管模块是为了电子时钟而进行修改的,并没有对数码管驱动模块进行模块化处理。而此篇博文的数码管驱动已经进行了模块化处理,十分便于重复使用,在此篇博客之前的电子秒表模拟中,博主已经使用过该数码管驱动模块,因此后文不再赘述此模块(详情请见此篇博文

一、数码管简介

博主所用的开发板为Cyclone Ⅳ的EP4CE6F17C8,Cyclone IV开发板上的数码管一共有6个,6个数码管共用八个段选信号引脚,因此我们每次只能选择其中一个显示。

怎么解决电子时钟时、分、秒同时显示呢?要实现电子时钟首先要了解什么是余晖效应。

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

二、C4开发板数码管原理图

在这里插入图片描述

三、代码实现

本项目博主一共设计了三个模块,分别为:时钟计数器模块、数码管驱动快以及顶层模块。

时钟计数器模块:

源码分析:

  • 在时钟计数器模块中,博主一共设计了四个计数器,分别为:1s基准计数器,时钟秒位计数器,时钟分位计数器,时钟小时位计数器
  • 四个计数器通过级联的方式,依次递增,从而实现时钟的计数功能
  • 如果对计数器级联较为陌生,可以参考博主开头所说的电子秒表博文,在此不再赘述
module counter_clock(
    input       wire            clk         ,
    input       wire            rst_n       ,
    
    output      wire    [23:0]  dout        ,//输出六位数码管的值
    output      wire    [5:0]   point_out    //输出小数点 
);

//参数定义
parameter MAX1S     = 26'd5000_0000 ;//1s基准单位
parameter TIME_SEC  = 6'd60         ;//秒计数器最大值
parameter TIME_MIN  = 6'd60         ;//分计数器最大值
parameter TIME_HOUR = 5'd24         ;//小时计数器最大值

//内部信号定义
reg     [25:0]      cnt_1s          ;
wire                add_cnt_1s      ;
wire                end_cnt_1s      ;

reg     [5:0]       cnt_sec         ;
wire                add_cnt_sec     ;
wire                end_cnt_sec     ;

reg     [5:0]       cnt_min         ;
wire                add_cnt_min     ;
wire                end_cnt_min     ;

reg     [4:0]       cnt_hour        ;
wire                add_cnt_hour    ;
wire                end_cnt_hour    ;

//1s基准计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_1s <= 1'b0;
    end
    else if(add_cnt_1s)begin
        if(end_cnt_1s)begin
            cnt_1s <= 1'b1;
        end
        else begin
            cnt_1s <= cnt_1s + 1'b1;
        end
    end
    else begin
        cnt_1s <= cnt_1s;
    end
end

assign add_cnt_1s = 1'b1;
assign end_cnt_1s = add_cnt_1s && cnt_1s == MAX1S - 1'b1;



//秒计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_sec <= 1'b0;
    end
    else if(add_cnt_sec)begin
        if(end_cnt_sec)begin
            cnt_sec <= 1'b0;
        end
        else begin
            cnt_sec <= cnt_sec + 1'b1;
        end
    end
    else begin
        cnt_sec <= cnt_sec;
    end
end

assign add_cnt_sec = end_cnt_1s;
assign end_cnt_sec = add_cnt_sec && cnt_sec == TIME_SEC - 1'b1;

//分钟计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_min <= 1'b0;
    end
    else if(add_cnt_min)begin
        if(end_cnt_min)begin
            cnt_min <= 1'b0;
        end
        else begin
            cnt_min <= cnt_min + 1'b1;
        end
    end
    else begin
        cnt_min <= cnt_min;
    end
end

assign add_cnt_min = end_cnt_sec;
assign end_cnt_min = add_cnt_min && cnt_min == TIME_MIN - 1'b1;


//小时计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_hour <= 1'b0;
    end
    else if(add_cnt_hour)begin
        if(end_cnt_hour)begin
            cnt_hour <= 1'b0;
        end
        else begin
            cnt_hour <= cnt_hour + 1'b1;
        end
    end
    else begin
        cnt_hour <= cnt_hour;
    end
end

assign add_cnt_hour = end_cnt_min;
assign end_cnt_hour = add_cnt_hour && cnt_hour == TIME_HOUR - 1'b1;

//dout point_out赋值
assign dout[23:20] = cnt_sec    % 10;
assign dout[19:16] = cnt_sec    / 10;
assign dout[15:12] = cnt_min    % 10;
assign dout[11:8]  = cnt_min    / 10;
assign dout[7:4]   = cnt_hour   % 10;
assign dout[3:0]   = cnt_hour   / 10;
assign point_out   = 6'b110_101     ;

endmodule

数码管驱动模块:

/**************************************功能介绍***********************************
Date	: 2023-08-01 11:08:11
Author	: majiko
Version	: 1.0
Description: 动态数码管模块(动态扫描)
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module seg_driver( 
    input				clk		,
    input				rst_n	,
    input		[23:0]	din		,//输入6位数码管显示数据,每位数码管占4位
    input       [5:0]   point_n ,//输入小数点控制位

    output	reg	[5:0]	seg_sel	,//输出位选
    output	reg	[7:0]	seg_dig  //输出段选
);								 
//---------<参数定义>--------------------------------------------------------- 
    parameter TIME_1MS = 50_000;//1ms

    //数码管显示字符编码
    localparam NUM_0 = 7'b100_0000,//0
               NUM_1 = 7'b111_1001,//1
               NUM_2 = 7'b010_0100,//
               NUM_3 = 7'b011_0000,//
               NUM_4 = 7'b001_1001,//
               NUM_5 = 7'b001_0010,//
               NUM_6 = 7'b000_0010,//
               NUM_7 = 7'b111_1000,//
               NUM_8 = 7'b000_0000,//
               NUM_9 = 7'b001_1000,//
               A     = 7'b000_1000,//
               B     = 7'b000_0011,//b
               C     = 7'b100_0110,//
               D     = 7'b010_0001,//d
               E     = 7'b000_0110,//
               F     = 7'b000_1110;//

//---------<内部信号定义>-----------------------------------------------------
    reg			[15:0]	cnt_1ms	   	;//1ms计数器(扫描间隔计数器)
    wire				add_cnt_1ms	;
    wire				end_cnt_1ms	;

    reg         [3:0]   disp_data   ;//每一位数码管显示的数值
    reg                 point_n_r   ;//每一位数码管显示的小数点
    
//****************************************************************
//--cnt_1ms
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_1ms <= 'd0;
        end 
        else if(add_cnt_1ms)begin 
            if(end_cnt_1ms)begin 
                cnt_1ms <= 'd0;
            end
            else begin 
                cnt_1ms <= cnt_1ms + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_1ms = 1'b1;//数码管一直亮
    assign end_cnt_1ms = add_cnt_1ms && cnt_1ms == TIME_1MS - 1;
    
//****************************************************************
//--seg_sel
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            seg_sel <= 6'b111_110;//循环移位实现时,需要给位选赋初值
        end 
        else if(end_cnt_1ms)begin 
            seg_sel <= {seg_sel[4:0],seg_sel[5]};//循环左移
        end 
    end

//****************************************************************
//--disp_data
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            disp_data <= 'd0;
            point_n_r <= 1'b1;
        end 
        else begin 
            case (seg_sel)
                6'b111_110 : begin disp_data <= din[3:0]  ; point_n_r <= point_n[0]; end//第一位数码管显示的数值
                6'b111_101 : begin disp_data <= din[7:4]  ; point_n_r <= point_n[1]; end
                6'b111_011 : begin disp_data <= din[11:8] ; point_n_r <= point_n[2]; end
                6'b110_111 : begin disp_data <= din[15:12]; point_n_r <= point_n[3]; end
                6'b101_111 : begin disp_data <= din[19:16]; point_n_r <= point_n[4]; end
                6'b011_111 : begin disp_data <= din[23:20]; point_n_r <= point_n[5]; end
                default: disp_data <= 'd0;
            endcase
        end 
    end

//****************************************************************
//--seg_dig
//****************************************************************
    // always @(posedge clk or negedge rst_n)begin 
    //     if(!rst_n)begin
    //         seg_dig <= 8'hff;//数码管的段选如何赋值好?
    //     end 
    //     else begin 
    //         case (disp_data)
    //             0 :  seg_dig <= {point_n_r,NUM_0};
    //             1 :  seg_dig <= {point_n_r,NUM_1};
    //             2 :  seg_dig <= {point_n_r,NUM_2};
    //             3 :  seg_dig <= {point_n_r,NUM_3};
    //             4 :  seg_dig <= {point_n_r,NUM_4};
    //             5 :  seg_dig <= {point_n_r,NUM_5};
    //             6 :  seg_dig <= {point_n_r,NUM_6};
    //             7 :  seg_dig <= {point_n_r,NUM_7};
    //             8 :  seg_dig <= {point_n_r,NUM_8};
    //             9 :  seg_dig <= {point_n_r,NUM_9};
    //             10 : seg_dig <= {point_n_r,A    };
    //             11 : seg_dig <= {point_n_r,B    };
    //             12 : seg_dig <= {point_n_r,C    };
    //             13 : seg_dig <= {point_n_r,D    };
    //             14 : seg_dig <= {point_n_r,E    };
    //             15 : seg_dig <= {point_n_r,F    };
    //             default: seg_dig <= 8'hff;
    //         endcase
    //     end 
    // end

    always @(*)begin 
        case (disp_data)
            0 :  seg_dig <= {point_n_r,NUM_0};
            1 :  seg_dig <= {point_n_r,NUM_1};
            2 :  seg_dig <= {point_n_r,NUM_2};
            3 :  seg_dig <= {point_n_r,NUM_3};
            4 :  seg_dig <= {point_n_r,NUM_4};
            5 :  seg_dig <= {point_n_r,NUM_5};
            6 :  seg_dig <= {point_n_r,NUM_6};
            7 :  seg_dig <= {point_n_r,NUM_7};
            8 :  seg_dig <= {point_n_r,NUM_8};
            9 :  seg_dig <= {point_n_r,NUM_9};
            10 : seg_dig <= {point_n_r,A    };
            11 : seg_dig <= {point_n_r,B    };
            12 : seg_dig <= {point_n_r,C    };
            13 : seg_dig <= {point_n_r,D    };
            14 : seg_dig <= {point_n_r,E    };
            15 : seg_dig <= {point_n_r,F    };
            default: seg_dig <= 8'hff;
        endcase
    end


endmodule

顶层模块:

module top_clock (
    input       wire    clk     ,
    input       wire    rst_n   ,

    output      wire    [5:0]   sel     ,//输出位选信号
    output      wire    [7:0]   seg      //输出段选信号
);

//内部信号定义
wire    [23:0]      din         ;
wire    [5:0]       point_out   ;


//计数器例化
counter_clock u_counter_clock(
   .clk         (clk      ),
   .rst_n       (rst_n    ),
   .dout        (din      ),
   .point_out   (point_out) 
);

seg_driver u_seg_driver( 
    .clk		(clk      ),
    .rst_n	    (rst_n    ),      
    .din		(din      ),
    .point_n    (point_out),      

    .seg_sel	(sel      ),    
    .seg_dig    (seg      )    
);								
    
endmodule

四、实现效果

在这里插入图片描述

五、总结

本项目与之前的电子秒表模拟并无大异,理解了数码管驱动模块的原理并自己成功编写一次后,后续再有相关数码管的项目均可直接调用此模块,无需再次编写。
除去数码管驱动模块,该项目和电子秒表一样,实际都是在训练计数器的级联。

博主在学习FPGA时曾听过一句话:FPGA实际上就是无数个计数器和状态机。因此请大家不要忽视对计数器的练习,经过电子秒表模拟和电子时钟模拟后,博主对计数器和数码管的基础知识掌握的还不错,因此撰写了几篇博文,希望对大家能有所帮助。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
前言: 本文档介绍是一款Arrow公司最近推出的BeMicro MAX 10(MAX 10数据手册)开发板电路设计和开发应用。在之前推出的基于MAX 10 FPGA芯片设计的评估板(点击查看),我们可以看出该芯片有多强大。 功能介绍: BeMicro MAX 10开发板采用Altera的非易失性MAX 10 FPGA芯片。用户可以充分的利用MAX 10 FPGA芯片内部所有资源。例如ADC模块、温度感应二极管、flash 存储器。BeMicro Max 10开发板包含多种外设与MAX 10 FPGA组成,例如8MB SDRAM、加速度计、数模转换器(DAC)、温度传感器、热敏电阻、光敏电阻、发光二极管等。 BeMicro MAX 10开发板展示: BeMicro MAX 10开发板资源如下: CPU:MAX 10M08DAF484C8GES USB-Blaster接口(Altera的FPGA/CPLD程序下载电缆) 50MHz时钟晶振 外部接口 - 8MB SDRAM(器件:ISSI IS42S 16400)(IS42S16400数据手册) - SPI接口的三轴加速度计(器件:ADXL362)(ADXL362数据手册) - 12位、SPI接口 DAC转换器(器件:AD5681)(AD5681数据手册) - 温度传感器,I2C接口(器件:ADT7420)(ADT7420数据手册) - 热敏电阻 - 光敏电阻 通用IO口:8个LED,2个按钮 外设接口: - 2个6-pin PMOD接口 - 2个40-pin(2x20 2.54mm) 接插件接口,其中包含了MAX 10的64个数字IO接口 - 6个模拟输入接口 - 80-pin card-edge连接器 供电方式:USB供电或5V适配器供电 BeMicro MAX 10开发板结构框图: BeMicro MAX 10开发板上的电源芯片使用的是Enpirion:registered: PowerSoC产品,开发板的电源结构图如下: BeMicro MAX 10开发板售价为$30,现在就可以到Arrow官网去购买。配合BeScope的板卡(BeScope板卡资料),可以开始双通道示波器的设计。 附件内容:BeMicro MAX 10开发板原理图(pdf版本)、材料清单、用户手册、测试例程(DAC接口,ADC、温度传感器、加速度计、光感应器) 小测试:把手指放到温度传感器ADT7420上面,你会发现LED灯的亮度开始变化了。 注明:附件提供的测试例程是QAR文件,需要安装Quartus II 14.0,按照附件提供的用户手册可以打开例程。
IP(Intellectual Property)即知识产权。美国 Dataquest 咨询公司将半导体产业的 IP 定 义为“用于 ASIC 或 FPGA 中的预先设计好的电路功能模块”。简而言之,这里的 IP 即电 路功能模块。 IP 核在数字电路中常用于比较复杂的功能模块(如 FIFO、 RAM、 FIR 滤波 器、 SDRAM 控制器、 PCIE 接口等)设计成参数可修改的模块,让其他用户可以直接调用 这些模块。随着设计规模增大,复杂度提高,使用 IP 核可以提高开发效率,减少设计和调 试时间,加速开发进程,降低开发成本,是业界的发展趋势。利用 IP 核设计电子系统,引 用方便,修改基本元件的功能容易。具有复杂功能和商业价值的 IP 核一般具有知识产权, 尽管 IP 核的市场活动还不规范,但是仍有许多集成电路设计公司从事 IP 核的设计、开发 和营销工作。 IP 核有三种不同的存在形式: HDL 语言形式,网表形式、版图形式。分别对应我们常 说的三类 IP 内核:软核、固核和硬核。这种分类主要依据产品交付的方式,而这三种 IP 内核实现方法也各具特色。 PLL(Phase Locked Loop,即锁相环)是最常用的 IP 核之一,其性能强大,可以对输 入到 FPGA时钟信号进行任意分频、倍频、相位调整、占空比调整,从而输出一个期望 时钟,实际上,即使不想改变输入到 FPGA 时钟的任何参数,也常常会使用 PLL,因为经 过 PLL 后的时钟在抖动(Jitter)方面的性能更好一些。 Altera 中的 PLL 是模拟锁相环,和 数字锁相环不同的是模拟锁相环的优点是输出的稳定度高、相位连续可调、延时连续可 调;缺点是当温度过高或者电磁辐射过强时会失锁(普通环境下不考虑该问题)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值