FPGA学习—通过数码管实现电子秒表模拟

这篇具有很好参考价值的文章主要介绍了FPGA学习—通过数码管实现电子秒表模拟。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、数码管简介

请参阅博主以前写过的一篇电子时钟模拟,在此不再赘述。

https://blog.csdn.net/qq_54347584/article/details/130402287

二、项目分析

  • 项目说明:本次项目是为了通过数码管实现秒表模拟。其中,六位数码管分别显示秒表的分位,秒位,毫秒位(由于毫秒有三位,在此只取百位和十位),其中分位和秒位,秒位和毫秒位之间用小数点隔开
  • 本次项目拟设置四个模块,分别为:按键消抖模块,计数模块,数码管驱动模块,以及顶层模块
  • 按键消抖模块要求:传出两个按键的脉冲信号,一个用来暂停/开始秒表的计数,一个用来清空秒表的计数
  • 计数模块要求:能传出秒表的各位值以及小数点位置
  • 数码管驱动模块要求:能正常显示秒表各位值

三、项目源码及分析

数码管驱动模块:

代码分析:

  • 由于本开发板有六位数码管,每位数码管设置显示0-F,因此在此模块中,博主定义了一个24位的din信号(六位每位能显示十六个字符(因此每位需要四位位宽))以此来给每位数码管赋值
  • 同时博主将八段式数码管拆分为七段加一段小数点,因此设计了一个六位的point_n以此来控制小数点的显示
  • 其余代码与博主以前写的数码管动态显示0-F类似,在此不再赘述(如果是FPGA初学者,没有数码管开发经验,请仔细阅读博主动态显示电子时钟两篇博客!!)
/**************************************功能介绍***********************************
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_1110,//E替换为F
               F     = 7'b011_1111,//F替换为--
               DIV   = 7'b011_1111;

//---------<内部信号定义>-----------------------------------------------------
    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    };
            16 : seg_dig <= {point_n_r,DIV  };
            default: seg_dig <= 8'b1101_1111;
        endcase
    end


endmodule

计数器模块:

代码分析:

  • 由项目分析可以得出,由于数码管资源有限,毫秒位只能实现百位和十位的显示,因此我们不妨设置一个基准单位为10ms,每当计数到10ms时,毫秒计数器才进行加一(这样毫秒计数器只需加到100次即可)
  • 除此之外,秒位计数器和分位计数器的技术条件分别为毫秒计数器计满和秒位计数器计满
  • 由于本次项目需要引入按键信号进行秒表的暂停、继续以及清空,因此博主引入了两位按键信号。
  • 一位按键控制秒表的暂停与继续。由上述分析可知,如果想要暂停秒表的计数,我们只需暂停基准单位10ms的计数即可(其余三个计数器,均要在10ms计数器工作的前提下才能逐级工作),因此我们只需要引入一个中间信号flag,用flag作为其计数的条件即可(博主将flag初值设为0不计数,按键按下后flag反转为1开始计数,再次按下再次反转,停止计数)
  • 剩余一位按键用于四个计数器的清零,一旦四个计数器全部清零,传出的数码管数据自然为0
  • 在代码的最后博主将毫秒,秒,分计数器的值赋给dout,再将其传入数码管驱动模块即可(之所以是这个顺序是因为博主前面数码管驱动模块对位选信号赋值好像写反了,不过影响不大)
module counter (
    input   wire            clk         ,
    input   wire            rst_n       ,
    input   wire    [1:0]   key_in      ,//按键信号输入

    output  wire    [23:0]  dout        ,//数码管各位值输出
    output  wire    [5:0]   point_out    //小数点输出
);

//内部参数定义
parameter TIME_10ms  = 19'd500_000;//10ms计数器,计满秒表毫秒位加1
parameter TIME_990ms = 7'd100     ;//计数器毫秒位,以10ms为单位
parameter TIME_1s    = 6'd60      ;//计数器秒位,计满清零
parameter TIME_1min  = 6'd60      ;//计数器分位,计满清零

//内部信号定义
reg     [18:0]  cnt_10ms    ;//10毫秒计数器寄存器
reg     [6:0]   cnt_99ms    ;//毫秒位计数寄存器
reg     [5:0]   cnt_1s      ;//秒位计数器寄存器
reg     [5:0]   cnt_1min    ;//分位计数器寄存器

wire    add_cnt_10ms        ;
wire    end_cnt_10ms        ;

wire    add_cnt_990ms       ;
wire    end_cnt_990ms       ;

wire    add_cnt_1s          ;
wire    end_cnt_1s          ;

wire    add_cnt_1min        ;
wire    end_cnt_1min        ;

reg     flag                ;//运行/暂停标志信号寄存器
reg     flag_0              ;//清零信号标志寄存器

//10毫秒计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_10ms <= 1'b0;
    end
    else if(add_cnt_10ms)begin
        if(end_cnt_10ms)begin
            cnt_10ms <= 1'b0;
        end
        else begin
            cnt_10ms <= cnt_10ms + 1'b1;
        end
    end
    else begin
        cnt_10ms <= cnt_10ms;
    end
end

assign add_cnt_10ms = 1'b1 && flag;//运行标志位有效
assign end_cnt_10ms = (add_cnt_10ms && cnt_10ms == TIME_10ms - 1'b1) || key_in[1];//按下清零按键也会清零

//毫秒位计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_99ms <= 1'b0;
    end
    else if(add_cnt_990ms)begin
        if(end_cnt_990ms)begin
            cnt_99ms <= 1'b0;
        end
        else begin
            cnt_99ms <= cnt_99ms + 1'b1;
        end
    end
    else begin
        cnt_99ms <= cnt_99ms;
    end
end

assign add_cnt_990ms = end_cnt_10ms;
assign end_cnt_990ms = (add_cnt_990ms && cnt_99ms == TIME_990ms - 1'b1) || key_in[1];

//秒位计数器
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'b0;
        end
        else begin
            cnt_1s <= cnt_1s + 1'b1;
        end
    end
    else begin
        cnt_1s <= cnt_1s;
    end
end

assign add_cnt_1s = end_cnt_990ms;
assign end_cnt_1s = (add_cnt_1s && cnt_1s == TIME_1s - 1'b1) || key_in[1];

//分位计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_1min <= 1'b0;
    end
    else if(add_cnt_1min)begin
        if(end_cnt_1min)begin
            cnt_1min <= 1'b0;
        end
        else begin
            cnt_1min <= cnt_1min + 1'b1;
        end
    end
    else begin
        cnt_1min <= cnt_1min;
    end
end

assign add_cnt_1min = end_cnt_1s;
assign end_cnt_1min = (add_cnt_1min && cnt_1min == TIME_1min - 1'b1) || key_in[1];

//flag信号控制秒表运行/暂停
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        flag <= 1'b0;
    end
    else if(key_in[0])begin
        flag <= ~flag;
    end
    else begin
        flag <= flag;
    end
end

//输出值赋值
assign dout[23:20] = cnt_99ms % 10;
assign dout[19:16] = cnt_99ms / 10;
assign dout[15:12] = cnt_1s   % 10;
assign dout[11:8]  = cnt_1s   / 10;
assign dout[7:4]   = cnt_1min % 10;
assign dout[3:0]   = cnt_1min / 10;
assign point_out   = 6'b110_101   ;


    
endmodule

按键消抖模块:

代码分析:请详细参阅博主所写的按键消抖模块博文,在此不再赘述

module key_filter#(parameter WIDTH = 2)//参数化按键位宽
(
    input       wire            clk     ,
    input       wire            rst_n   ,
    input       wire  [WIDTH - 1:0]     key_in  ,//按键输入信号

    output      reg   [WIDTH - 1:0]     key_out  //输出稳定的脉冲信号
);

parameter MAX = 20'd1_000_000;

reg     [19:0]                  cnt_delay       ; //20ms延时计数寄存器
wire                            add_cnt_delay   ; //开始计数的标志
wire                            end_cnt_delay   ; //结束计数的标志

reg     [WIDTH - 1:0]           key_r0          ; //同步
reg     [WIDTH - 1:0]           key_r1          ; //打一拍
reg     [WIDTH - 1:0]           key_r2          ; //打两拍

wire    [WIDTH - 1:0]           nedge           ; //下降沿寄存器


//同步打拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        key_r0 <= {WIDTH{1'b1}};
        key_r1 <= {WIDTH{1'b1}};
        key_r2 <= {WIDTH{1'b1}};
    end
    else begin
        key_r0 <= key_in; //同步
        key_r1 <= key_r0; //寄存一拍
        key_r2 <= key_r1; //寄存两拍
    end
end

//20ms计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_delay <= 1'b0;
    end
    else if(add_cnt_delay )begin
        if(nedge)begin //检测到下降沿从0开始计数
            cnt_delay <= 1'b0;
        end
        else if(cnt_delay == MAX - 1'b1)begin
            cnt_delay <= cnt_delay; //计数计满结束后保持,避免产生多个输出脉冲
        end
        else begin
            cnt_delay <= cnt_delay + 1'b1;
        end
    end
    else begin
        cnt_delay <= 1'b0;
    end
end

assign nedge = ~key_r1 & key_r2; //下降沿检测
assign add_cnt_delay = 1'b1; 
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX - 1'b1;


//key_out脉冲信号赋值
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_out <= 'd0;
    end
    else if(cnt_delay == MAX - 2'd2)begin //计数计满前一个脉冲时产生按键脉冲
        key_out <= ~key_in;
    end
    else begin
        key_out <= 'd0;
    end
end

endmodule

顶层模块:

/****
项目说明:本次项目是为了通过数码管实现秒表模拟。其中,六位数码管分别显示秒表的分位,秒位,毫秒位(百位和十位)。

本次项目拟设置四个模块,分别为:按键消抖模块,计数模块,数码管驱动模块,以及顶层模块。

本项目按键消抖模块要求传出两个按键的脉冲信号,一个用来暂停/开始秒表的计数,一个用来清空秒表的计数。

本项目计数模块要求能传出秒表的各位值以及小数点位置

本项目数码管驱动模块要求能正常显示秒表各位值
****/
module top(
    input   wire            clk     ,
    input   wire            rst_n   ,
    input   wire    [1:0]   key_in  ,

    output  wire    [5:0]   sel     ,
    output  wire    [7:0]   seg    
);

wire       [1:0]    key_out ;
wire	   [23:0]	din		;
wire       [5:0]    point_n ;

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

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


key_filter u_key_filter(
    .clk     (clk    ),
    .rst_n   (rst_n  ),
    .key_in  (key_in ),

    .key_out (key_out)
);

counter u_counter(
   .clk         (clk         ),
   .rst_n       (rst_n       ),
   .key_in      (key_out     ),

   .dout        (din         ),
   .point_out   (point_n     ) 
);

endmodule

四、实现效果

FPGA学习—通过数码管实现电子秒表模拟,fpga开发,学习

五、总结

本项目主要锻炼了FPGA的数码管开发和计数器编写,实际仍未FPGA学习基础,博主学习时常常被数码管位选信号的段选信号的赋值绕晕,解决方法也只有自己亲自编写几遍,否则仍然无法理解余晖效应和动态扫面是如何让数码管同时显示不同字符的。后续对于基础部分的学习博主应该还会写几篇关于蜂鸣器的博文,至此算是基础语法学习结束。

再往后博主学习到IP核HLS,通讯协议时,也许会继续编写博客,但也要看博主是否有足够的精力,以及能否自己理解并讲解清楚,否则话的还是请各位自行上网寻找视频教学资料,不管是野火还是正点原子,都有针对的FPGA学习视频。

学海无涯,大家有缘再见。文章来源地址https://www.toymoban.com/news/detail-625240.html

到了这里,关于FPGA学习—通过数码管实现电子秒表模拟的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 单片机AT89C51六位数码管秒表

    详细代码讨论加我QQ:1271370903   本设计的数字电子秒表系统采用AT89C51单片机为中心器件,利用其定时器/计数器定时和记数的原理,结合显示电路、LED数码管以及外部中断电路来设计计时器。将软、硬件有机地结合起来,使得系统能够实现LED显示,显示时间为0~9999.99秒,计时

    2024年02月03日
    浏览(125)
  • [FPGA 学习记录] 数码管动态显示

    数码管动态显示 在上一小节当中,我们对数码管的静态显示做了一个详细的讲解;但是如果单单只掌握数码管的静态显示这种显示方式是远远不够的,因为数码管的静态显示当中,被选中的数码位它们显示的内容都是相同的,这种显示方式在我们的实际应用当中显然是不合适

    2024年02月04日
    浏览(52)
  • [FPGA 学习记录] 数码管静态显示

    数码管静态显示 在我们的许多项目设计当中,我们通常需要一些显示设备来显示我们需要的信息。我们可以选择的显示设备有很多,而我们的数码管就是使用最多、最简单的显示设备之一。数码管它是一种半导体发光器件,它具有响应时间短、体积小、重量轻、寿命长的优点

    2024年02月03日
    浏览(53)
  • FPGA学习汇总(六)----数码管显示(1)

    目录 概念 单个数码管显示单个数字  操作  代码  现象 分析 四个数码管定时单个显示数字 分析 代码 四个数码管同时显示 分析 代码 现象

    2024年02月01日
    浏览(54)
  • 【FPGA零基础学习之旅#11】数码管动态扫描

    🎉欢迎来到FPGA专栏~数码管动态扫描 ☆* o(≧▽≦)o *☆ 嗨 ~我是 小夏与酒 🍹 ✨ 博客主页: 小夏与酒的博客 🎈该系列 文章专栏: FPGA学习之旅 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏 📜 欢迎大家关注! ❤️ 🥝 Spirit_V2开发板按键控制数码管:

    2024年02月11日
    浏览(40)
  • 四位数码管3641AS的FPGA实现

             四位数码管3641AS为一款 共阴极 的四位八段数码管,其具体的每一段为单个二极管,可通过压降实现点亮,通过控制单位多段二极管的点亮实现数字或字母等字符。         共阴极:八段发光二极管的阴极端连接在一起,阳极端分开控制,使用时候公共端接地

    2024年02月05日
    浏览(52)
  • 74HC595工作原理及FPGA实现数码管驱动方法

     74HC595,移位寄存器,串行输入,8位并行输出,一般用于数码管电路以减少使用的IO口数量。 管脚介绍: Q0~Q7 :八位并行输出位 Q7\\\'       :  级联位,若输入位数大于8位,先进入的位会从此口连续输出,用于多片之间的级联 VCC GND 不多介绍 Ds        :  串行数据输入位

    2023年04月08日
    浏览(45)
  • 数码管电子时钟

            本人用的FPGA板子用的是Cyclone IV ,这个板子的数码管是共阳极的,即当给的信号为0时,才会点亮它,而且数码管的段选信号是六个位置共用的,意味着它不能在同一时间两个位置显示不同的内容,而要想达到同时看到时分秒,只能不断将每个位置的数码管赋予不同

    2024年02月05日
    浏览(40)
  • FPGA课程设计——数字电子时钟VERILOG(基于正点原子新起点开发板,支持8位或6位共阳极数码管显示时分秒毫秒,可校时,可设闹钟,闹钟开关,led指示)

    2019   级    电子科学与技术   专业FPGA课程设计 报   告 2022  年 5 月 20 日 多功能数字电子钟的设计 摘要 电子设计自动化(EDA)是一种实现电子系统或电子产品自动化设计的技术,使用EDA技术设计的结果既可以用FPGA / CPLD来实施验证,也可以直接做成专用集成电路(ASIC)。

    2024年02月03日
    浏览(44)
  • FPGA学习日志——74hc595驱动的数码管静态显示seg_595_static

    数码管是一种半导体发光器件,其基本单元是发光二极管。数码管按段数一般分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管(多一个小数点显示)。 八段数码管是一个八字型数码管,分为八段:a、b、c、d、e、f、g、dp,其中dp为小数点,每一段即

    2024年02月08日
    浏览(125)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包