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

news2024/12/23 8:59:17

文章目录

  • 一、数码管简介
  • 二、C4开发板数码管原理图
  • 三、代码实现
  • 四、实现效果
  • 五、总结

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

一、数码管简介

博主所用的开发板为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实际上就是无数个计数器和状态机。因此请大家不要忽视对计数器的练习,经过电子秒表模拟和电子时钟模拟后,博主对计数器和数码管的基础知识掌握的还不错,因此撰写了几篇博文,希望对大家能有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/828072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

增量式PID算法及其MATLAB实现

增量式PID算法是一种常用的控制算法,用于控制系统中的反馈控制。它通过对系统的误差进行递推式的计算,实现对系统输出的调节,使得系统的输出逐渐趋向于设定值。 delta u(k)=u(k)-u(k-1)=Kp*(e(k)-e(k-1))+Ki*e(k)+Kd*(e(k)-2*e(k-1)+e(k-2)) PID算法由三个部分组成:比例(…

如何通过Navicat连接Oracle数据库

本文介绍如何通过Navicat 连接Oracle数据库。以往总是使用Oracle客户端来连接Oracle数据库&#xff0c;但是Oracle客户端一般有几百M的大小&#xff0c;而且安装繁琐配置麻烦。如果可以通过Navicat直接连接Oracle则会非常轻松方便。 1、下载Instant Client Base 用使用Navicat…

【contenteditable属性将元素改为可编辑状态】

元素添加contenteditable属性之后点击即可进入编辑状态 像这种只修改一条属性不必再打开弹框进行编辑&#xff0c;使用contenteditable会很方便 添加失焦、回车、获焦事件 如 <p :contenteditable"item.contenteditable || false"keydown.enter"key($event…

HarmonyOS/OpenHarmony-ArkTS基于API9元服务开发快速入门

一、创建项目 二、创建卡片 三、添加资源 四、具体代码 Entry Component struct WidgetNewCard {/** The title.*/readonly TITLE: string harmonyOs;readonly CONTEXT: string 技术构建万物智联;/** The action type.*/readonly ACTION_TYPE: string router;/** The…

fastadmin列表页 修改 正序排列 倒序排列 desc asc

打开控制器对应的 js文件 &#xff0c;文件目录为 public/assets/js/backend/xxx.js

接口自动化报告,生成本地服务并自动打开时失败

错误原因&#xff1a; 端口号被占用 首先可以在cmd中调出命令窗口然后执行命令netstat -ano就可以查看所有活动的链接&#xff0c;找到被占用的端口号 1、通过命令taskkill /f /t /im "进程名称" &#xff0c;根据进程的名称杀掉所有的进程。或者taskkill /f /t /p…

《码上行动:零基础学会Python编程》书籍分享

Python是一种高级的、面向对象的编程语言&#xff0c;由Guido van Rossum于1991年开发。它具有简洁、易读和可维护的语法&#xff0c;被广泛用于科学计算、Web开发、数据分析、人工智能等领域。 以下是Python的一些特点和优势&#xff1a; 简洁易读&#xff1a;Python采用简洁…

svn工具使用

svn 介绍 解决之道&#xff1a; SCM&#xff1a;软件配置管理 所谓的软件配置管理实际就是对软件源代码进行控制与管理 CVS&#xff1a;元老级产品 VSS&#xff1a;入门级产品 ClearCase&#xff1a;IBM 公司提供技术支持 SVN&#xff1a;主流产品 什么是SVN&#xff…

ATFX汇市:惠誉下调美债评级,白宫债务无序扩张下,美元国际信用受损

环球汇市行情摘要—— 昨日&#xff0c;美元指数上涨0.06%&#xff0c;收盘在101.94点&#xff0c; 欧元贬值0.12%&#xff0c;收盘价1.0984点&#xff1b; 日元贬值0.75%&#xff0c;收盘价143.33点&#xff1b; 英镑贬值0.46%&#xff0c;收盘价1.2776点&#xff1b; 瑞…

【WiFi】Wi-Fi HaLow技术

目录 1.10大Wi-Fi物联网芯片 2.面向物联网的无线通信技术对比 3.Wi-Fi HaLow值得期待 4.选择IoT Wi-Fi芯片和模块时需要考虑哪些因素&#xff1f; 5.Wi-Fi 6 IoT芯片 5.1.乐鑫科技ESP32-C6 5.2.高通Immersive Home 318平台 6.WiFi BLE IoT芯片 6.1.博流智能 BL602/BL…

SQL-事务

set autocommit 0; select * from acount where name 嘉宝 && acount.money > 1000; update acount set money money - 1000 where name 嘉宝; update acount set money money 1000 where name 煎包; commit ; 脏读;当有两个事务使用同一数据库时&#xff0c…

使用 github 同步谷歌浏览器书签

想必使用谷歌浏览器Chrome的用户一定非常头疼的一件事就是&#xff1a;账户不能登录&#xff0c;书签收藏夹不能同步&#xff0c;换一台电脑书签收藏夹没有了&#xff01; 下面教大家一招亲测有效适用的方法解决书签同步问题&#xff0c;在任何电脑都可以同步了 1、去下载谷歌…

Delphi 中High DPI开发注意事项

目录 前言&#xff1a; 什么是High DPI? 一、表现不一致的现象 二、当前的解决方案 三、重点 前言&#xff1a; 什么是High DPI? High DPI&#xff08;高分辨率显示&#xff09;是指显示设备具有高像素密度的特征。它意味着在相同的显示区域内&#xff0c;显示设备能够…

第7章 动态创建标记

一些传统方法 document.write document对象的write()方法可以方便快捷地把字符串(支持html元素)插入到文档里。 document.write("<h1>hello world</h1>")document.write的最大缺点是它违背了“行为应该与表现分离”的原则。JavaScript和HTML代码混杂在…

eNSP interface g0/0/0 报错解决办法

文章目录 1 报错截图2 解决办法2.1 排查设备是否有 GM 接口2.2 更换适合的路由器&#xff0c;并验证 1 报错截图 2 解决办法 2.1 排查设备是否有 GM 接口 查看下设备是否支持 GM 接口&#xff08;GigabitEthernet&#xff09; 方式一&#xff1a;右键路由器设备 - 设置 - 查看…

【C++】类和对象-继承

0.前言 1.基本语法 继承的用处就是极大的减少代码的重复性&#xff0c;如果没有用继承&#xff0c;看看以下代码&#xff0c;你知道了。。。。 基本实现代码&#xff1a; #include <iostream> using namespace std; /******************************************/void …

8.5周六|Move Dev Meetup厦门站不见不散

Move Dev Meetup是由MoveFuns DAO发起的线下研讨会&#xff0c;旨在为参与的开发者提供深入了解Move语言机会。截至目前为止&#xff0c;Meetup已在成都、深圳、杭州等多个城市举办。Move Dev Meetup厦门站将在8月5日&#xff08;本周六&#xff09;举办&#xff0c;Sui将作为支…

观察级水下机器人使用系列之五三维激光扫描仪

本文主要讲观察级水下机器人Valor配套的三维激光扫描仪&#xff0c;它是近年来ROV的主力光学设备。三维激光扫描仪是Voyis&#xff08;原公司为2G Robotics&#xff09;公司生产的&#xff0c;型号为ULS-500 Micro。 ​编辑​ ULS-500 Micro的技术参数如下图。ULS-500 Pro扫描…

关于docker的一些深入了解

本文将深入介绍一下docker方面的知识&#xff0c;不尽完全&#xff0c;慢慢完善。 进程 进程的概念 在介绍docker的相关知识前&#xff0c;先了解一下相关概念。进程就是系统中正在运行的程序&#xff0c;进程是操作系统的概念&#xff0c;每当我们执行一个程序时&#xff0…

关于 Ubuntu 长按 shift 无效, 按 Esc 直接进入 grub 改密码的解决方法

本次长按shift没有反应&#xff0c;直接进入了系统界面&#xff0c;所以改用长按Esc键&#xff0c;步骤如下&#xff1a; 1. 长按esc&#xff0c;进入grub>提示 2.输入grub>normal &#xff0c;回车 3.上一步回车后&#xff0c;继续敲击Esc &#xff0c;出现grub界面 …