FPGA实战——动态数码管

  • rtl文件
    • 模块设计:
      • 计数模块,产生数码管的数据
      • 数码管显示驱动模块
        • 时钟分频
        • 数字转码(二进制—>BCD码)
        • 位选信号切换
      • 调试warning:
    • ucf文件

**任务:**使用开发板上的 6 位数码管以动态方式从 0 开始计数,每 100ms 计数值加1,计数值从 0 到 999999 循环计数。
硬件设计:与上节相同。
共阳极的6个数码管

rtl文件

此次使用动态模式,相比静态模式,动态扫描就是利用人眼的余辉效果,先只打开第一个数码管,让第一个数码管显示一个数字,比如1ms,然后马上关掉第一个,再让第二个显示另一个数字,持续1ms,再关掉第二个,然后重复循环刷新,这样两个位就显示了不一样的数字。

  • 对于要显示的数字的拆解,比如234,三位分别是
    2 = 234/100 ;
    3 = 234 /10 % 10;
    4 = 234%10;
    6位数据则以此类推。

模块设计:

首先需要一个数码管动态显示模块,能够依次点亮 6 个数码管,并将对应的数据输出至数码管,也就是需要分别控制段选和位选信号;同时还需要一个计数模块,能够将 0—999999 依次输出至数码管动态显示模块。

  • data:计数模块将计数值通过 data 端口传递给数码管动态显示模块,
  • en:使能信号 en 使能数码管显示数据,
  • point:小数点显示信号 point 控制小数点的显示,
  • sign:符号信号 sign 可以让数码管显示负号。

计数模块(count):显示的数字每 100ms 加“1”。
数码管动态显示模块(seg_dig):数码管动态显示模块在数码管上以动态方式显示数值。每1ms动态刷新一次。

也就是100ms显示的数值加1,而每1ms位选加1,最多6ms刷新完一周期,这样一个数值就可以显示多次,人眼就可以看到数字。这个1ms。如果再大(也就是变慢)可能会让我们看到闪烁,而如果再快,那就会浪费资源。

计数模块,产生数码管的数据

module count(input             sys_clk,input             sys_rst_n,output reg [19:0] data,   //显示的数据 0-999999 ,20位output reg  [5:0] point,   //小数点output reg        sign,   //负号output reg        en      //使能显示);reg [22:0]  cnt;   //100ms计数器
reg         flag;    //100ms标志
parameter   MAX_CNT = 5_000_000;//计数器100ms
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begincnt  <= 28'd0;flag <= 1'b0;endelse if(cnt < MAX_CNT - 1'b1)begincnt  <= cnt + 1'b1;flag <= 1'b0;endelse begincnt  <= 28'd0;flag <= 1'b1;end
end//数码管显示值data
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begindata  <= 20'd0;point <= 6'b000000;     //为1标志着要显示小数点(只是一个标志,不涉及共阴极共阳极)sign  <= 1'b0;en    <= 1'b0;end   else beginpoint <= 6'b000000; sign  <= 1'b0;en    <= 1'b1;         //打开使能if(flag) begin if(data < 20'd999999)data <= data + 1'b1;else data <= 20'd0;     //这时已经到了最大值了,所以该清零了。endendendendmodule

数码管显示驱动模块

时钟分频

对于数码管的驱动时钟,我们采用分频得到。当然我们也可以直接定义一个计数器来得到时钟,但是因为我们看到上面的计数器,100ms的计数器占了23位位宽,位宽大很浪费资源。所以1ms的计时器这里我们就采取分频的方法得到。
      我们采用对系统晶振时钟分频的方法。定义dri_clk作为驱动时钟,然后一个clk_cnt作为分频计数器。sys_clk是50MHz,这里我们选择10分频,得到5MHz的dri_clk。10分频的意思就是,sys_clk每走十个完整的周期,dri_clk就走一个完整的周期,那么dri_clk在sys_clk的第5个时钟周期就需要翻转。所以N(偶数)分频,只需计数到N/2-1即可。详情可以见代码。

这样,我们看到,之前如果是50MHz,那么一个时钟周期是20ns,现在变成了200ns,所以相应的,我们产生一个1ms的时钟的时候,原来是50000(16位),现在只需要计数器到5000,(13位),这就是资源的节省。

数字转码(二进制—>BCD码)

我们在第一个计数模块用了data作为输出,输出了0-999999这个20位的二进制数。然后在数码管显示驱动模块一开始就用了前面讲的方法把data的各位拆解开,变成了data0—data5,那是不是这样就行了呢。不是的,我们显示数据,还要判断他是几位,有没有小数点,有没有负号,所以我们在显示的时候,要利用之前拆解开的data0-5,重新定义一个num,作为我们要显示的数据。data0-5都是4位二进制数表示的十进制数,把他们以及小数点point、负号sign赋给num的对应位,就实现了一个把20位的2进制数据变成8421BCD码的过程。(比如,把data4-data0依次赋给num的19-0位,而23-20位为负号sign)
如果理解不了这个编码过程,就可以理解成,前面的data输入之后拆解为data0-5,然后我们利用data0-5和小数点以及负号位来判断这个数需要几位数码管显示(因为我们呢后面是需要刷新数码管的位的)

位选信号切换

按照惯用的套路,我们分频之后,首先要定义一节计数器,来得到1ms 的一个计数器,然后通过这个计数器,我们定义一个位选计数器,每计数一个周期,位选计数器的值加1 ,这样就实现了位选信号的切换。这两个always语句块其实很简单,但是刚开始分析起来可能一看好多always就会头大。

数码管显示驱动模块
module seg_led(input            sys_clk,input            sys_rst_n,input     [19:0] data,     //显示的数据 0-999999 ,20位input      [5:0] point,    //小数点input            sign,     //负号input            en,       //使能显示output reg [5:0] seg_sel,  //位选output reg [7:0] seg_dig   //段选);//数字拆解变量data0-5
wire  [3:0]  data0;
wire  [3:0]  data1;
wire  [3:0]  data2;
wire  [3:0]  data3;
wire  [3:0]  data4;
wire  [3:0]  data5;//数码管驱动时钟以及计数器
reg          dri_clk;
reg   [3:0]  cnt_clk;//时钟计数器
parameter DIV_CLK = 10;  //时钟分频系数
parameter MAX_CNT = 5000;
reg   [12:0] cnt_1;//数码管1ms位选计数器
reg          flag;   //数码管位选计数器标志//数码管显示
reg   [23:0] num;    //24位BCD码寄存器
reg   [2 :0] cnt_sel;//数码管位选状态寄存器
reg   [3 :0] num_disp;//数码管对应位要显示的数
reg          dot_disp;//数码管对应位要显示的小数点//拆解数码管显示的data的数据的各位的十进制
assign data0 = data % 4'd10;                  //个位数
assign data1 = data / 4'd10      % 4'd10;     //十位数
assign data2 = data / 7'd100     % 4'd10;     //百位数
assign data3 = data / 10'd1000   % 4'd10;     //千位数
assign data4 = data / 14'd10000  % 4'd10;     //万位数
assign data5 = data / 17'd100000        ;     //十万位数//数码管驱动时钟——系统时钟分频
always @(posedge sys_clk or negedge sys_rst_n)beginif(!sys_rst_n)begincnt_clk  <= 4'b0;dri_clk  <= 1'b1;endelse if(cnt_clk == DIV_CLK/2 - 1'b1)begincnt_clk  <= 4'b0;dri_clk  <= ~dri_clk;endelse begincnt_clk  <= cnt_clk + 1'b1;dri_clk  <= dri_clk;end
end//20位二进制data转码为8421BCD码num
always @(posedge dri_clk or negedge sys_rst_n)beginif(!sys_rst_n)num  <= 24'd0;else beginif(data5 || point[5])begin  //说明第六个数码管有数据num[23:20] <= data5;num[19:16] <= data4;num[15:12] <= data3;num[11: 8] <= data2;num[7 : 4] <= data1;num[3 : 0] <= data0;endelse beginif(data4 || point[4])beginnum[19:0] <= {data4,data3,data2,data1,data0};if(sign)num[23:20] <= 4'd11;    //符号else num[23:20] <= 4'd10;    //不显示endelse beginnum[23:20] <= 4'd10;if(data3 || point[3])beginnum[15:0] <= {data3,data2,data1,data0};if(sign)num[19:16] <= 4'd11;    //符号else num[19:16] <= 4'd10;    //不显示endelse beginnum[23:16] <= {2{4'd10}};if(data2 || point[2])beginnum[11:0] <= {data2,data1,data0};if(sign)num[15:12] <= 4'd11;    //符号else num[15:12] <= 4'd10;    //不显示endelse beginnum[23:12] <= {3{4'd10}};if(data1 || point[1])beginnum[7:0] <= {data1,data0};if(sign)num[11:8] <= 4'd11;    //符号else num[11:8] <= 4'd10;    //不显示endelse beginnum[23:8] <= {4{4'd10}};//if(data0 || point[0])beginnum[3:0] <= data0;if(sign)num[11:8] <= 4'd11;    //符号else num[11:8] <= 4'd10;    //不显示//endendendendend           endend
end     //1ms数码管位选计数器
always @ (posedge dri_clk or negedge sys_rst_n)beginif(!sys_rst_n)begincnt_1  <= 13'd0;flag   <= 1'b0;endelse if(cnt_1 < MAX_CNT - 1'b1)begincnt_1  <= cnt_1 + 1'b1;flag   <= 1'b0;endelse begincnt_1  <= 13'd0;flag   <= 1'b1;end
end//位选状态寄寄存器
always @ (posedge dri_clk or negedge sys_rst_n)beginif(!sys_rst_n)cnt_sel <= 3'd0;else beginif(flag)beginif(cnt_sel < 3'd5)cnt_sel <= cnt_sel + 1'b1;elsecnt_sel <= 3'd0;endelsecnt_sel <= cnt_sel;end
end//数码管位选驱动,使六个数码管轮流显示
always @ (posedge dri_clk or negedge sys_rst_n)beginif(!sys_rst_n)beginseg_sel   <= 6'b111111;      //6位均不显示num_disp  <= 4'd0;dot_disp  <= 4'd1;           //默认不显示小数点endelse beginif(en)begincase(cnt_sel)3'd0  : beginseg_sel   <= 6'b111110;num_disp  <= num[3 : 0];dot_disp  <= ~point[0];  //对于point这个reg来说,我们规定1为显示小数点,而硬件上0为显示,所以取反即可。end3'd1  : beginseg_sel   <= 6'b111101;num_disp  <= num[7 : 4];dot_disp  <= ~point[1];  end3'd2  : beginseg_sel   <= 6'b111011;num_disp  <= num[11: 8];dot_disp  <= ~point[2];  end3'd3  : beginseg_sel   <= 6'b110111;num_disp  <= num[15:12];dot_disp  <= ~point[3];  end3'd4  : beginseg_sel   <= 6'b101111;num_disp  <= num[19:16];dot_disp  <= ~point[4];  end3'd5  : beginseg_sel   <= 6'b011111;num_disp  <= num[23:20];dot_disp  <= ~point[5];  endendcaseendelse begin   seg_sel   <= 6'b111111;num_disp  <= 4'd0;dot_disp  <= 4'd1;endend
end//数码管段选驱动,显示字符
always @ (posedge dri_clk or negedge sys_rst_n)beginif(!sys_rst_n)seg_dig  <= 8'b0000_0000;else begincase(num_disp)4'd0 : seg_dig <= {dot_disp,7'b100_0000};     //小数点位为最高位4'd1 : seg_dig <= {dot_disp,7'b111_1001}; 4'd2 : seg_dig <= {dot_disp,7'b010_0100}; 4'd3 : seg_dig <= {dot_disp,7'b011_0000}; 4'd4 : seg_dig <= {dot_disp,7'b001_1001}; 4'd5 : seg_dig <= {dot_disp,7'b001_0010}; 4'd6 : seg_dig <= {dot_disp,7'b000_0010}; 4'd7 : seg_dig <= {dot_disp,7'b111_1000}; 4'd8 : seg_dig <= {dot_disp,7'b000_0000}; 4'd9 : seg_dig <= {dot_disp,7'b001_0000}; 4'd10: seg_dig <= 8'b1111_1111;                //都不显示4'd11: seg_dig <= 8'b1011_1111;             //显示符号default: seg_dig <= {dot_disp,7'b100_0000};endcaseend
end                                     endmodule
顶层例化模块
module top_seg_led(input            sys_clk,input            sys_rst_n,output  [5:0] seg_sel,  //位选output  [7:0] seg_dig   //段选);wire [19:0] data;
wire  [5:0] point;
wire        sign;
wire        en;   seg_led seg_led_u(.sys_clk    (sys_clk  ),.sys_rst_n  (sys_rst_n),.data       (data     ),   .point      (point    ),   .sign       (sign     ),   .en         (en       ),     .seg_sel    (seg_sel  ),  .seg_dig    (seg_dig  ));   count count_u(.sys_clk         (sys_clk  ),.sys_rst_n       (sys_rst_n),.data            (data     ),   .point           (point    ),  .sign            (sign     ),   .en              (en       )  );
endmodule

调试warning:

程序编译完成之后,有不少warning,抱着试试的心态先下载了bit流文件,发现只有最低位亮,于是排查warning。
1.WARNING:Xst:2404 - FFs/Latches <flag<0:0>> (without init value) have a constant value of 0 in block <seg_led>.
这是说在seg_led模块的flag恒为常量0。显然程序中出现了一个低级的赋值语句(就是复制之后忘了修改值。)

2.Xst:1710 - FF/Latch <cnt_sel_0> (without init value) has a constant value of 0 in block <seg_led_u>. This FF/Latch will be trimmed during the optimization process.
这句话是指seg_led_utranslater ctrl模块中的cnt_sel_0信号是个固定值0,在优化过程中将被优化掉。这个warning其实是由于上面那个warning引发的连锁反应,解决。

3.Node <dot_disp_1> of sequential type is unconnected in block <seg_led>.
序列类型的节点<dot_disp_1>在block<seg_led>中未连接。
这里其实是

实际上是位数不匹配,当时对变量的使用不明确,就导致了这个错误。

ucf文件

NET sys_clk            TNM_NET = sys_clk_pin;
TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 20ns HIGH 50%;NET sys_clk     LOC = T8 | IOSTANDARD = LVCMOS33;
NET sys_rst_n   LOC = L3 | IOSTANDARD = LVCMOS33;##################################################################################
#seg pin define
##################################################################################
NET seg_dig<0>             LOC = C7  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_dig<1>             LOC = E6  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_dig<2>             LOC = C5  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_dig<3>             LOC = F7  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_dig<4>             LOC = D6  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_dig<5>             LOC = E7  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_dig<6>             LOC = D5  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_dig<7>             LOC = C6  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;   ## 小数点段为最高段
NET seg_sel<5>             LOC = D9  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_sel<4>             LOC = E10 | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_sel<3>             LOC = F10 | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_sel<2>             LOC = F9  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_sel<1>             LOC = E8  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;
NET seg_sel<0>             LOC = D8  | IOSTANDARD = "LVCMOS33"  | SLEW=FAST;

FPGA实战篇——【6】动态数码管相关推荐

  1. 单片机零基础入门(8-4)实战:单片机动态数码管消影---附源代码

    单片机零基础入门(8-4)实战:单片机动态数码管消影 一.回顾 二.问题及原因 三.解决办法: 四.解决后的源代码: 五.补充知识:数码管驱动方式 1.单片机直接扫描: 2.专用驱动芯片: 一.回顾 ...

  2. FPGA实战篇——【3】按键控制蜂鸣器

    FPGA实战--按键控制蜂鸣器 目录 FPGA实战--按键控制蜂鸣器 实验任务: 蜂鸣器 硬件设计 程序设计 rtl文件 按键消抖 ucf文件 编译 RTL图 补充--例化模块 的软件操作: 下载及d ...

  3. 动态数码管verilog模块功能分析

    学习正点原子FPGA开发板关于动态数码管章节: 实验任务是使用FPGA 开发板上的 6 位数码管以动态方式从 0 开始计数,每 100 ms 计数值增加 一,当计数值从 0 增加到 999999 后重 ...

  4. 第三篇:动态 8位数码管显示---亚龙236电路

    第三篇:动态8位数码管显示 -亚龙236电路 上一篇中已经实现了数码管的静态显示,如果按照上一篇的思路89s52芯片最多可以驱动4位数码管.这一节我们来看看其它显示更多位数的方案. 目前最常用的是 5 ...

  5. 开发板实战篇2 6位数码管静态显示

    与"开发板实战篇1 LED点灯(开篇)" 的区别,增加模式切换  总结: 模块例化思想: 定时器模块 + 点灯模块 根据自己思路编写代码,调试仿真代码,同时熟悉环境.加深细节理解 ...

  6. FPGA 动态数码管显示实验

    参考:正点原子开拓者 FPGA 开发指南 一.数码管动态显示简介 由于一般的静态驱动操作虽然方便,但占用的I/0口较多,例如要驱动6位8段数码管,以静态驱动方式让数码管各个位显示不同的数值,如&quo ...

  7. Vue实战篇二十六:创建动态仪表盘

    系列文章目录 Vue基础篇一:编写第一个Vue程序 Vue基础篇二:Vue组件的核心概念 Vue基础篇三:Vue的计算属性与侦听器 Vue基础篇四:Vue的生命周期(秒杀案例实战) Vue基础篇五:V ...

  8. 单片机之动态数码管篇

    思来想去还是决定要出这期教程,因为我看很多同学在这个数码管的问题上都还是有点迷惑,特别是这个动态数码管的工作原理,以及这个相应代码该如何编写,那么教程来了~ 首先看一下一位数码管内部原理,以及各个引脚 ...

  9. MySQL的进阶实战篇

    关联文章: MySQL的初次见面礼基础实战篇 MySQL的进阶实战篇 本篇上一篇博文MySQL的初次见面礼基础实战篇的延续,是mysql的进阶内容的记录,本篇主要知识点如下: 进阶实战篇 进阶实战篇 ...

最新文章

  1. c语言基础 验证ascii 码表
  2. Java菜鸟教程math类_Java Number Math 类
  3. 跪求解,oc内存回收问题
  4. java zip文件夹_如何使用java压缩文件夹成为zip包
  5. java integer 值传递_在java中String,对象,Integer(包装类型的)关于引用传递仍是值传递...
  6. FL Studio常见问题之通道窗口和步进音序器的设置
  7. sklearn模型支持输入list吗?
  8. python不会怎么办_怕你还不会Python函数,我特意为你整理了一篇博客
  9. 【ElasticSearch】大数据搜索选开源还是商业软件?ElasticSearch 对比 Splunk
  10. 【Java】Java StreamCorruptedException: invalid stream header: EFBFBDEF
  11. SQL PLUS编辑器的一些常用设置
  12. python linux 下开发环境搭建
  13. java引导类加载器_Java类加载器层次结构(一)
  14. 6. JavaScript String 对象
  15. Angr安装与使用之使用篇(四)
  16. RestClient查询文档
  17. PHP为什么是最好的编程语言?
  18. 64位windows在安装winsdk过程中遇到的问题及解决方案
  19. 带你认识有源晶振的分类和英文缩写
  20. python面试题搜集

热门文章

  1. 大数据前景如何,G20告诉你答案
  2. [美味菜谱]蒸鸡蛋膏——要点详尽
  3. c#将字符串写入Sream中
  4. MatlabR2016b安装及弹出“弹出dVd1 并插入dVd2”解决方法
  5. 区块链共识机制与账本存储
  6. ArcMap已停止工作
  7. mac安装brew失败443
  8. JZOJ 6316.djq的朋友圈【状压】
  9. 股票图形的智能识别及源码自动生成
  10. 使用百度统计功能快速统计网站的访问情况