明德扬周老师 发表于 2020-5-28 09:53:50

【FPGA至简设计原理与应用】书籍连载14第三篇FPGA至简设计项目 第五章数码管动态扫描

温馨提示:明德扬2023推出了全新课程——逻辑设计基本功修炼课,降低学习FPGA门槛的同时,增加了学习的趣味性,并组织了考试赢积分活动(点击→了解课程详情)http://www.mdy-edu.com/ffkc/415.html,感兴趣请联系易老师:13112063618(微信同步)


本案例的编号为:001600000111,如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)
本文为明德扬原创及录用文章,转载请注明出处

本案例讲数码管可以通过驱动电路来驱动内部的各个段码,从而显示出需要的数字。发光二极管单元按照连接方式可分为共阳极数码管和共阴极数码管。其中,将所有发光二极管的阳极连接到一起形成公共阳极(COM)的数码管为共阳极数码管。在应用时应将共阳极数码管的公共极 COM 接到+5V,当某一字段发光二极管的阴极为低电平时,该字段点亮,当某一字段的阴极为高电平时,该字段不亮。例如,当共阳极数码管的 abcdefg 值分别是 1001111 时,即 b、c 字段亮,其他字段不亮时,数码管显示数字“1”。反之,将所有发光二极管的阴极连接到一起形成公共阴极(COM)的数码管为共阴极数码管。在应用时应将共阴极数码管的公共极 COM 接到地线 GND 上,当某一字段发光二极管的阳极为高电平时,该字段点亮,当某一字段的阳极为低电平时,该字段不亮。


大家好,近期我们会连载《FPGA至简设计原理与应用》一书,有兴趣的同学可以学习,也希望大家可以对我们的书提出宝贵的意见和建议。《FPGA至简设计原理与应用》书籍连载索引目录
http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=989


读过的朋友可积极在贴后留言,书籍正式出版时,我们会从留言者中挑选20位幸运读者,幸运读者可获潘老师亲笔签名书籍一本。

注:手机浏览可能格式会乱,建议用电脑端进行浏览。



第三篇 FPGA至简设计项目实践   

    第五章 数码管动态扫描


本文档编号:001600000017
需要看对应的视频,请点击视频编号:001700000426
1.至简原理与应用配套的案例文档
2.实现开发板上数码管显示的功能,使8个数码管有规律的按照时间进行显示。复位后,数码管0显示数字“0”;1秒后,数码管1显示数字"1";再1秒后,数码管2显示数字"2";以此类推,每隔疫苗变化一次,最后当数码管7显示数字"7"后再次回到数码管0显示"0";按这样的规律循环。
3. ALTERA入门学习案例文档


第1节 项目背景


led数码管(LED Segment Displays)是由多个发光二极管封装在一起的器件,这些二极管组成“8”字型,在内部完成引线连接,只引出它们的各个笔划和公共电极。一般来说,led数码管常用段数为7段,如下图中所示的a、b、c、d、e、f、g,有的数码管还会添加一个小数点,如图中的h所示。                              图3.5-18位数码管
数码管可以通过驱动电路来驱动内部的各个段码,从而显示出需要的数字。发光二极管单元按照连接方式可分为共阳极数码管和共阴极数码管。其中,将所有发光二极管的阳极连接到一起形成公共阳极(COM)的数码管为共阳极数码管。在应用时应将共阳极数码管的公共极COM接到+5V,当某一字段发光二极管的阴极为低电平时,该字段点亮,当某一字段的阴极为高电平时,该字段不亮。例如,当共阳极数码管的abcdefg值分别是1001111时,即b、c字段亮,其他字段不亮时,数码管显示数字“1”。反之,将所有发光二极管的阴极连接到一起形成公共阴极(COM)的数码管为共阴极数码管。在应用时应将共阴极数码管的公共极COM接到地线GND上,当某一字段发光二极管的阳极为高电平时,该字段点亮,当某一字段的阳极为低电平时,该字段不亮。
根据共阳极、共阴极数码管的工作原理可得,数码管显示数字0到9对应的abcdefg值如下表所示。
表3.5- 1 数码管显示数字与字段值的对应关系
显示数字共阳abcdefg2进制共阳abcdefg16进制共阴abcdefg2进制共阴abcdefg16进制
07’b00000017’h017’b 11111107’h7e
17’b 10011117’h4f7’b 01100007’h30
27’b 00100107’h127’b 11011017’h6d
37’b 00001107’h067’b 11110017’h79
47’b 10011007’h4c7’b 01100117’h33
57’b 01001007’h247’b 10110117’h5b
67’b 01000007’h207’b 10111117’h3f
77’b 00011117’h0f7’b 11100007’h70
87’b 00000007’h007’b 11111117’h7f
97’b 00001007’h047’b 11110117’h7b

LED数码管的正常显示需要驱动电路来驱动数码管的各个码段,根据数码管驱动方式的不同,可以将其分为静态式和动态式两类。
静态驱动是指每个数码管的每一个段码都通过一个单片机的I/O端口进行驱动,或使用如BCD码二-十进制译码器译码进行驱动,也称直流驱动。静态驱动编程简单,显示亮度高,但占用的I/O端口多:如果想要驱动5个数码管静态显示则需要5×8=40根I/O端口来完成驱动,而一个89S51单片机可用的I/O端口才32个。如此一来,在实际应用中则必须增加译码驱动器进行驱动,从而增加了硬件电路的复杂性。
由于静态驱动的这一缺点,LED数码管动态显示接口应用更广。动态驱动是将所有数码管的8个显示字段"a、b、c、d、e、f、g、h"的同名端连接在一起,此外每个数码管的公共极COM需增加由各自独立I/O线控制的位选通控制电路。当要输出某一字形码时,所有数码管都会接收到相同的字形码,但究竟是哪个数码管会显示出字形取决于单片机对位选通COM端电路的控制。只需将显示数码管的选通控制打开,该位就会显示出字形,而没有选通的数码管并不会点亮。综上所述,动态驱动是通过分时轮流控制各数码管的COM端,使各个数码管轮流受控显示。在这一过程中,每位数码管的点亮时间为1~2ms,由于人的视觉暂留现象及发光二极管的余辉效应,尽管各位数码管并非同时点亮,但只要扫描速度足够快,人们观察到的就是一组稳定的显示数据,而不会产生闪烁感。在显示效果上,动态显示和静态显示相同的,但动态显示不仅能够节省大量的I/O端口,而且功耗更低。
至简设计法开发板上一共有2组4位的共阳数码管,也就是说一共有8个共阳数码管。数码管的配置电路如下。 图3.5-2教学板数码管原理图
图中的SEG_A、SEG_B~SEG_DP是段选信号,8个数码管共用。
DIG1~DIG8是位选信号,与8个数码管分别对应。当位选信号为0时,段选信号的值将赋给相应数码管。例如DIG3信号为0则表示将段选信号SEG_A~SEG_DP的值赋给数码管3。信号SEG_A~SEG_DP,DIG1~DIG8,都与电阻进行连接,如下图所示。 图3.5-3教学板数码管连接电阻
由此可见,SEG_A~SEG_DP由SEG0~SEG7产生,DIG1~DIG8由DIG_EN1~DIG_EN8产生。而SEG0~SEG7和DIG_EN1~DIG_EN8都直接与FPGA的IO相连,如下图所示。 图3.5-4FPGA与数码管信号连接图
这些信号与FPGA管脚的对应关系如下表。FPGA通过控制相应的管脚,从而实现对数码管显示的控制。
表3.5- 2 信号线与管脚的关系
信号线信号线FPGA管脚
SEG_ESEG0Y6
SEG_DPSEG1W6
SEG_GSEG2Y7
SEG_FSEG3W7
SEG_DSEG4P3
SEG_CSEG5P4
SEG_BSEG6R5
SEG_ASEG7T3
DIG1DIG_EN1T4
DIG2DIG_EN2V4
DIG3DIG_EN3V3
DIG4DIG_EN4Y3
DIG5DIG_EN5Y8
DIG6DIG_EN6W8
DIG7DIG_EN7W10
DIG8DIG_EN8Y10


第2节 设计目标

按照至简设计法的设计特色,在开始一个新的设计之前应明确设计目标。设计目标是整个设计的核心灵魂,后续的每个步骤与操作都是围绕设计目标进行展开。至简设计法的原理很简单,即每一个步骤和思路都力求简单快捷,明确设计目标就是为了确保后续的每个阶段都有意义,而不去进行不必要的工程。这一做法可以少走很多弯路,尤其对于初学者来说,好习惯的养成可以为以后的工程师生涯带来无限的方便。所以再次强调,在开始设计前一定要将设计目标分析透彻,认真思考本次设计最终想要实现什么目的,达到什么效果,然后再投入到设计中去。
本次设计需要实现开发板上数码管显示的功能,使8 个数码管有规律的按照时间进行显示。复位后,数码管 0 显示数字“0”;1 秒后,数码管 1 显示数字“1”;再1 秒后,数码管 2 显示数字“2”;以此类推,每隔 1 秒变化一次,最后当数码管 7 显示数字“7”后再次回到数码管0显示“0”,按照这样的显示规律循环往复。
本设计的上板效果图如下图所示,也可以登陆至简设计法官方网站观看上板演示视频效果:www.mdy-edu.com/xxxx。      图3.5-5数码管动态扫描效果图第3节 设计实现

在设计的实现阶段本书会按照步骤和原理分析进行案例实现的分享,考虑到初学者的需要,此部分的内容会比较详细。如果基础知识掌握得比较牢靠,只想学习此设计的步骤,可以跳过此部分,直接进入后面章节中的简化版步骤分享。对于初学者来说,建议不要选择捷径,一定按照详细分析的内容进行学习,只有掌握好基础知识,才可以从容的独立完成项目设计。
3.1 顶层信号

新建目录:D:\mdy_book\my_seg,并在此目录中新建一个名为my_seg.v的文件。用GVIM打开文件后开始编写代码。这里再次强调,建议初学者按照书中提供的文件路径以及文件名进行设置,避免后续出现未知错误。
分析设计目标可知,本设计需要控制8个数码管,让其显示不同的数字。通过前文分析的LED数码管显示原理可以得知:如果想要控制8个数码管,则需控制位选信号,即FPGA要输出一个8位的位选信号,将其设为seg_sel。其中seg_sel对应数码管0,seg_sel对应数码管1,以此类推,seg_sel对应数码管7。
控制数码管的不同数字显示需要通过控制每个数码管上的7个字段,即通过控制段选信号可以实现数码管不同数字的显示,此处由于只需显示数字,无需用到“h”子段,因此一共有7个子段,即FPGA要输出一个7位的段选信号,将其设为seg_ment。其中seg_ment~segm_ment分别对应数码管的abcdefg字段(注意对应顺序)。当然,除位选信号和段选信号外,设计中进行工程控制的时钟信号和复位信号也同样必不可少。
综上所述,本设计一共需要4个信号:时钟信号clk,复位信号rst_n,输出的位选信号seg_sel和输出的段选信号seg_ment。信号和硬件管脚的对应关系如下表所示。
表3.5 - 3信号和管脚关系
器件信号线信号线FPGA管脚内部信号
U6,U7SEG_ESEG0Y6seg_ment
SEG_DPSEG1W6未用到
SEG_GSEG2Y7seg_ment
SEG_FSEG3W7seg_ment
SEG_DSEG4P3seg_ment
SEG_CSEG5P4seg_ment
SEG_BSEG6R5seg_ment
SEG_ASEG7T3seg_ment
DIG1DIG_EN1T4seg_sel
DIG2DIG_EN2V4seg_sel
DIG3DIG_EN3V3seg_sel
DIG4DIG_EN4Y3seg_sel
DIG5DIG_EN5Y8seg_sel
DIG6DIG_EN6W8seg_sel
DIG7DIG_EN7W10seg_sel
DIG8DIG_EN8Y10seg_sel
X1 SYS_CLKG1clk
K1 SYS_RSTAB12rst_n

将module的名称定义为my_seg,已知该模块有4个信号:clk、rst_n、seg_sel和seg_ment。在顶层信号代码中需要将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接,其具体代码如下:
123456modulemy_seg(rst_n,clk,seg_sel,               segment                       );

随后对信号输入输出属性进行声明,指出这一信号对于FPGA来说属于输入信号还是输出信号,若为输入信号则声明其为input,若为输出信号则声明其为output。在本设计中,由于clk是外部晶振输送给FPGA的,因此在FPGA中clk为1位的输入信号input;同样地,rst_n是外部按键给FPGA的,因此在FPGA中rst_n也为1位的输入信号input;seg_sel是FPGA控制数码管亮灭的信号,因此seg_sel是8位的输出信号output;seg_ment是FPGA控制数码管显示数字内容的信号,因此seg_ment是7位的输出信号output,综上所述,其具体代码如下:
1234input                  clk         ;input                  rst_n         ;output[ 7:0]            seg_sel;output[ 6:0]            segment       ;
3.2 信号设计

在设计信号之前,先按照至简设计法的思路来进行架构设计。分析需要实现的功能为:第1秒时数码管0显示数字“0”,其余数码管不显示任何数字;第2秒时数码管1显示数字“1”,其余数码管同样不显示任何数字;第3秒时数码管3显示数字“3”,其余数码管不显示数字;以此类推,第八秒数码管7显示数字“7”,循环往复。
将现象翻译成信号表示如下:第1秒,数码管0显示数字“0”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b000_0001;第2秒,数码管1显示数字“1”,即seg_sel的值为8’b1111_1101,seg_ment的值为7’b100_1111;第3秒,数码管2显示数字“2”,即seg_sel的值为8’b1111_1011,seg_ment的值为7’b001_0010;第4秒,数码管3显示数字“3”,即seg_sel的值为8’b1111_0111,seg_ment的值为7’b000_0110;第5秒,数码管4显示数字“4”,即seg_sel的值为8’b1110_1111,seg_ment的值为7’b100_1100;第6秒,数码管5显示数字“5”,即seg_sel的值为8’b1101_1111,seg_ment的值为7’b010_0100;第7秒,数码管6显示数字“6”,即seg_sel的值为8’b1011_1111,seg_ment的值为7’b010_0000;第8秒,数码管7显示数字“7”,即seg_sel的值为8’b0111_1111,seg_ment的值为7’b000_1111;第九秒,回到数码管0显示数字“0”,以此进行循环。
总结发现,数码管每隔1秒进行变化,且8个数码管轮流显示。通过分析可以画出信号波形示意图如下图所示。 图3.5-6数码管动态扫描实现架构
通过seg_sel和seg_seg信号的变化波形图可以看出:显示第1个数字时,seg_sel=8’hfe,seg_ment=7’h01,该状态会持续1秒;显示第2个数字时,seg_sel=8’hfd,seg_ment=7’h4f,同样持续1秒;以此类推,到第8个数字时,持续1秒seg_sel=8’h7f,seg_ment=7’h0f。随后进入新一轮循环,再次重复seg_sel=8’hfe,seg_ment=7’h01的1秒持续。
根据波形图分析可以得到本设计的计数器架构:本设计一共需要两个计数器,一个计数器用来计算1秒的时间,另一个计数器用来计算1秒的次数,即这是第几个1秒。
来思考一下:既然设计目标为1秒改变一次状态,为什么除了1秒的计数器之外,还要设计数第几个的计数器,这样岂不是更麻烦吗?实际上增加计数器的操作正是运用了至简设计法的道理,让信号代码更加有条理并便于设计师确定位置。
举个生活中常见的例子,如下图所示,将8个数码管一次显示循环看做楼层,把每1秒改变一次的状态记作门牌号,即第1秒、第2秒、第3秒在一层楼一对应1号2号3号,以此类推。如果只用一个计数器的话,那么一楼门牌号为1、2、3、4、5、6、7、8,二楼门牌号则为9、10、11、12、13、14、15、16、17、18,三楼以此类推。这种只有用一种计数单位的方法,开始确实没有太大问题,但是随着楼层的变高,这种计数方式的弊端就会显露出来。比如在这种情况下想要找76号,就可能需要很久才能找到。 图3.5-7单一门牌号计数模式
如果在一个计数单位的基础上再加一个计数单位,即采用两种技术模式,一个记楼层,一个记门牌号,如下图所示。在同样的门牌号计数中,可以记为一楼的1、2、3、4、5、6、7、8号,二楼的1、2、3、4、5、6、7、8、9、10号,以此类推,每一层都有对应的房间号。在这种计数模式下,如果想要找到七层6号房间,不需要多做思考就可以一下就定位到正确位置。
图3.5-8两种门牌号复合计数模式
通过案例可以确定两种计数器复合计数才是最简单的计数模式,因此本设计使用一个计数1秒的计数器cnt0,一个表示第几个1秒的计数器cnt1。这样如果后续遇到问题,也可以快速的定位到相应的位置,避免了很多麻烦。
此外,在以上门牌号计数案例中,如果想要寻找每一层的固定位置房间,按照两种计数单位复合的模式,用cnt0来表示房间号,其范围是0-9,用cnt1来表示楼层号,其范围是0-1。通过这种方法可以通过cnt0和cnt1来找到任何一个房间。如果想找同一个位置的房间,也可以直接用cnt0来表示。例如cnt0==4可以统一表示每层楼的四号房间。但如果只有房间号这一计数模式而没有楼层的话,想表示每层楼的四号房间,则表示方式为“cnt0==4”、“cnt0=12”,更高楼层以此类推。两种表现形式的难易程度显而易见。
可见两个计数器复合计数的方式并不是多此一举,反而是最适合本设计的计数器方案。不论是简单设计还是复杂设计,至简设计法都会全面的考虑设计需求,在每一个环节都会采用最适合的设计方案,尽量为后续的步骤减少不必要的麻烦。确定了两个计数器后,来依次讨论每个计数器的实现。至简设计法的设计规则中有讲过,计数器的设计只考虑两个因素:加1条件和计数数量。只要确定相应逻辑,就能完成计数器代码设计。
首先讨论计数器cnt0的加1条件:由于该计数器在不停地计数,永不停止,因此可以认为其加1条件一直有效,即assign add_cnt0==1。此处可能有同学存在疑惑加1条件的概念是什么?这里以停车位来进行比喻,一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,其可以继续进行编号,即assign add_cnt0 = “没有石头”。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加1条件是一直有效的。
接着讨论计数器cnt0的计数数量:本工程的工作时钟是50MHz,即周期为20ns,当计数器计数到第1_000_000_000/20=50_000_000个时,就代表1秒时间到了。因此因此cnt0的计数数量为50_000_000。
确定好加1条件和计数数量后开始进行代码编写。相信各位往常都是一行行输入代码,但是至简设计法有一个小技巧,可以为同学们编写代码省去不少时间,并且一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分做成了模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。
打开GVIM工具,在命令模式下输入“:Mdyjsq”后点击回车,就调出了对应模板,如下图所示。之后再将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。 图3.5-9至简设计法调用计数器代码模板
补充完整后得到计数器cnt0的代码如下。
1234567891011121314always@(posedge clk or negedge rst_n)begin    if(!rst_n)begin      cnt0 <= 0;    end    else if(add_cnt0)begin      if(end_cnt0)            cnt0 <= 0;      else            cnt0 <= cnt0 + 1;    endend assignadd_cnt0 = 1 ;assignend_cnt0 = add_cnt0 && cnt0==50_000_000-1 ;
下面来设计记录1秒次数的计数器cnt1。该计数器表示数到第几个1秒,即每完成1秒计数后就加1,因此其加1条件为end_cnt0。该计数器一共要数8次进入下一个循环,即计数数量为8。继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车后调出对应模板,将“add_cnt1”和“end_cnt1”补充完整,得到该计数器的代码如下:
1234567891011121314always@(posedge clk or negedge rst_n)begin     if(!rst_n)begin      cnt1 <= 0;    end    else if(add_cnt1)begin      if(end_cnt1)            cnt1 <= 0;      else            cnt1 <= cnt1 + 1;    endend assignadd_cnt1 = end_cnt0;assignend_cnt1 = add_cnt1 && cnt1==8-1 ;

确定完两个计数器后来思考输出信号seg_sel的变化。根据信号波形图得知,第1秒时FPGA输出值为8’hfe;第2秒时输出值为8’hfd;以此类推,在第8秒时输出值为8’h7f。设计中使用cnt1来表示第几个1秒,即cnt1==0的时候,输出值为8’hfe;在cnt1==1的时候输出值为8’hfd;以此类推,在cnt1==7时输出值为8’h7f。将其翻译成代码,调用至简设计法模板,在编辑模式下输入“Shixu2”,然后补充完整,得到信号seg_sel代码如下:
1234567891011121314151617181920212223242526272829always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginseg_sel<= 8'hfe;    end    else if(cnt1==0) beginseg_sel<= 8'hfe;    end    else if(cnt1==1) beginseg_sel<= 8'hfd;    end    else if(cnt1==2) beginseg_sel<= 8'hfb;    end    else if(cnt1==3) beginseg_sel<=8'hf7;    end    else if(cnt1==4) beginseg_sel<=8'hef;    end    else if(cnt1==5) beginseg_sel<=8'hdf;    end    else if(cnt1==6) beginseg_sel<=8'hbf;    end    else if(cnt1==7) beginseg_sel<=8'h7f;    endend

观察补充完整的代码可以发现:代码表示与文字描述基本上是一模一样的,这其实是体现了verilog“硬件描述语言”的特点。上文所列代码可以正确实现seg_sel功能,且从实现角度和资源角度来说都足以完成设计,但依然可以将其进行进一步的概括从而得到简化版的代码。
根据设计目标可知,第1个数码管显示“0”时,seg_sel输出为b8’b1111_1110;第2个数码管显示“1”时,seg_sel输出为b8’b1111_1101;第3个数码管显示“2”时,seg_sel输出为b8’b1111_1011;第4个数码显示“3”时,seg_sel输出为b8’b1111_0111;第5个数码管显示“4”时,seg_sel输出为b8’b1110_1111;第6个数码管显示“5”时,seg_sel输出为b8’b1101_1111;第7个数码管显示“6”时,seg_sel输出为b8’b1011_1111;第8个数码管显示“7”时,seg_sel输出为b8’b0111_1111;根据规律可以发现每个控制数码管的闪亮信号,其对应数字“0”都依次向左移动1位。在设计中可以统一设为将8’b1向左移位,将其取反后的值再赋给seg_sel,可以观察出每次左移的位数和cnt1的值相等。因此可得以下代码,其中的第6行即表示先将8’b1向左移位,再取反,将该值赋给seg_sel。
也可以带入数值计算一下,假如此刻cnt1等于0,则8’b1<<0的结果是8’b0000_0001,取反的值为8’hfe即b8’b1111_1110;假如此刻cnt1等于3,则8’b1<<3的结果为8’b000_1000,取反后的结果为8’hf7即8’b1111_0111。两段代码的对比结果相同,但是这种方法下代码经过简化,更加清晰。这里同样可以在编辑模式下输入“Shixu2”,调出至简设计法模板,补充完整后得到代码如下:
12345678always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginseg_sel<=8'hfe ;    end    else beginseg_sel<=~(8'b1<<cnt1) ;    endend

最后来思考输出信号seg_ment的变化。分析波形图可知,在第1次显示时,其输出值为7’h01;在第2次显示时,其输出值为7’h4f;以此类推,在第8次显示时,其输出值为7’h0f。用计数器cnt1表示第几次显示,也就是说:当cnt1==0时seg_ment输出值为7’h01;在cnt1==1时输出值为7’h4f;以此类推,在cnt1==7时输出值为7’h0f。将其翻译成代码,在编辑模式下“Shixu2”,调出至简设计法模板,补充完整后的代码如下:
1234567891011121314151617181920212223242526272829always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginseg_ment<=7'h01;    end    else if(cnt1==0) beginseg_ment<=7'h01;    end    else if(cnt1==1) beginseg_ment<=7'h4f;    end    else if(cnt1==2) beginseg_ment<=7'h12;    end    else if(cnt1==3) beginseg_ment<=7'h06;    end    else if(cnt1==4) beginseg_ment<=7'h4c;    end    else if(cnt1==5) beginseg_ment<=7'h24;    end    else if(cnt1==6) beginseg_ment<=7'h20;    end    else if(cnt1==7) beginseg_ment<=7'h0f;    endend

上面的代码可以正确实现seg_ment的功能,对于本设计来说已经足够。但进一步思考可以发现:该代码实现了类似译码的功能,将数字设成数码管显示的值,代码中只对0~7进行译码。为了方便日后设计,在这里设计一个通用的译码模块,将0~9都进行译码。这样的话今后遇到类似的工程,就可以随时调用这一代码,译码模块代码如下所示:
1234567891011121314151617181920212223242526272829303132333435always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginseg_ment<=7'h01;    end    else if(data==0) beginseg_ment<=7'h01;    end    else if(data==1) beginseg_ment<=7'h4f;    end    else if(data==2) beginseg_ment<=7'h12;    end    else if(data==3) beginseg_ment<=7'h06;    end    else if(data==4) beginseg_ment<=7'h4c;    end    else if(data==5) beginseg_ment<=7'h24;    end    else if(data==6) beginseg_ment<=7'h20;    end    else if(data==7) beginseg_ment<=7'h0f;    end    else if(data==8) beginseg_ment<=7'h00;    end    else if(data==9) beginseg_ment<=7'h04;    endend

在本设计中只要控制data信号,即可实现在数码管显示相应数字。前文可知:当cnt1=0,则数码管会显示0。当cnt1=1,则数码管会显示1。因此data信号代码如下:
1assigndata = cnt1 ;
在代码的最后一行写下endmodule
1endmodule
至此,主体程序已经完成。
3.3 信号定义

接下来要将module补充完整,首先来定义信号类型。再次强调,在进行reg和wire的判断时,总会有多余的联想,比如认为reg是寄存器,wire是线;或者认为reg的会综合成寄存器,wire不会综合成寄存器。但是这些其实和reg型还是wire型都并无关系,在信号类型的判断时不需要做任何的联想,只要记住一个规则“用always实现的是reg型,其他都是wire型”就可以了。
进行类型定义时,需要设定信号的位宽。至简设计法在这里们分享一个非常实用的位宽获取技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽,如下图所示。 图3.5-10通过计算器获取信号位宽
cnt0是用always产生的信号,因此类型为reg。其计数的最大值为50_000_000,通过计算器可以得知需要用26根线表示,即位宽是26位。add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。其值是0或1,用1个线表示即可。打开GVIM,在编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下:
123reg                cnt0      ;wire                     add_cnt0    ;wire                     end_cnt0    ;

同理,cnt1是用always产生的信号,因此类型为reg。其计数的最大值为7,需要用3根线表示,即位宽是3位。编辑模式下输入“Reg3”调用至简设计法模板并补充完整;add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下:
123reg    [ 2:0]            cnt1      ;wire                     add_cnt1    ;wire                     end_cnt1    ;

seg_sel是用always方式设计的,因此类型为reg,其需要用8根线表示,即位宽为8。编辑模式下输入“Reg8”调用至简设计法模板,补充完整后得到代码表示如下:
1reg    [ 7:0]            seg_sel;

seg_ment是用always方式设计的,因此类型为reg,其需要用7根线表示,即位宽为7,代码表示如下:
1reg    [ 6:0]            segment       ;

如果使用译码电路则会用到data信号。该信号是用assign设计的,所以类型为wire,其最大值为9,位宽为4。编辑模式下输入“Wire4”调用至简设计法模板,补充完整后得到代码表示如下:
1wire    data   ;

至此,整个代码的设计工作已经完成。完整版的工程代码如下:
1modulemy_seg(clk,rst_n,seg_sel,seg_ment); input         clk   ;input         rst_n;output   seg_sel   ;output   seg_ment ; reg    cnt0   ;wire          add_cnt0 ;wire          end_cnt0 ;reg   cnt1   ;wire          add_cnt1 ;wire          end_cnt1 ;reg[ 7:0]    seg_sel;reg[ 6:0]    seg_ment ; always@(posedge clk or negedge rst_n)begin    if(!rst_n)begin      cnt0 <= 0;    end    else if(add_cnt0)begin      if(end_cnt0)            cnt0 <= 0;      else            cnt0 <= cnt0 + 1;    endend assignadd_cnt0 = 1;assignend_cnt0 = add_cnt0 && cnt0==50_000 -1 ; always@(posedge clk or negedge rst_n)begin     if(!rst_n)begin      cnt1 <= 0;    end    else if(add_cnt1)begin      if(end_cnt1)            cnt1 <= 0;      else            cnt1 <= cnt1 + 1;    endend assignadd_cnt1 = end_cnt0;assignend_cnt1 = add_cnt1 && cnt1==8 -1 ; always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginseg_sel<=8'hfe;    end    else if(cnt1==0)beginseg_sel<=8'hfe;    end    else if(cnt1==1)beginseg_sel<=8'hfd;    end    else if(cnt1==2)beginseg_sel<=8'hfb;    end    else if(cnt1==3)beginseg_sel<=8'hf7;    end    else if(cnt1==4)beginseg_sel<=8'hef;    end    else if(cnt1==5)beginseg_sel<=8'hdf;    end    else if(cnt1==6)beginseg_sel<=8'hbf;    end   else if(cnt1==7)beginseg_sel<=8'h7f;    endend always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginseg_ment<=7'h01;    end    else if(cnt1==0)beginseg_ment<=7'h01;    end    else if(cnt1==1)beginseg_ment<=7'h4f;    end    else if(cnt1==2)beginseg_ment<=7'h12;    end    else if(cnt1==3)beginseg_ment<=7'h06;    end    else if(cnt1==4)beginseg_ment<=7'h4c;    end    else if(cnt1==5)beginseg_ment<=7'h24;    end    else if(cnt1==6)beginseg_ment<=7'h20;    end    else if(cnt1==7)beginseg_ment<=7'h0f;    endend endmodule
第4节 综合与上板

4.1 新建工程

打开软件Quartus Ⅱ,点击“File”下拉列表中的New ProjectWzard...新建工程选项,如下图所示。 图3.5-11Quartus新建工程
随后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。 图3.5-12Quartus新建工程介绍
此时会出现工程文件夹、工程名、顶层模块名设置界面,如图3.5-13。设置目录为:D:/mdy_book/my_seg,工程名和顶层名为my_seg。再次强调,为了避免初学者在后续操作中出现报错情况,强烈建议设置的文件目录和工程名称与本书保持一致。设置完成后点击“Next”。 图3.5-13QUARTUS新建工程设置名称
新建工程类型设置选择选择“Empty project”,如下图所示,然后点击“Next”。 图3.5-14QUARTUS新建工程类型文件添加界面如图3.5- 15所示,点击右侧的“Add”按钮,添加之前写好的“my_seg.v”文件,可以看到界面下方会显示出文件,随后点击“Next”。 图3.5-15QUARTUS添加文件芯片型号选择界面如图3.5- 16所示,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,点击“Next”。 图3.5-16QUARTUS选择芯片型号图3.5- 17为QUARTUS设置工具界面,不必做任何修改,直接点击“Next”。 图3.5-17QUARTUS设置工具界面新建工程的汇总情况如下图所示,点击“Finish”后完成新建工程。 图3.5-18QUARTUS新建工程汇总界面 4.2 综合

新建工程步骤完成后,就会出现如下所示的QUARTUS界面。 图3.5-19QUARTUS新建工程后界面点击编译按钮,可以对整个工程进行编译。编译成功的界面如图3.5- 20所示。 图3.5-20QUARTUS编译后界面 4.3 配置管脚

下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后会弹出配置管脚的窗口。 图3.5-21QUARTUS配置管脚选项
在配置窗口最下方中的“location”一列,参考信号和管脚关系,按照表3.5- 3中最右两列配置好FPGA管脚。配置管理来源参见管脚配置环节,最终配置结果如图3.5- 22。配置完成后,关闭“PinPlanner”,软件自动会保存管脚配置信息。 表3.5 - 3信号和管脚关系
器件信号线信号线FPGA管脚内部信号
U6,U7SEG_ESEG0Y6seg_ment
SEG_DPSEG1W6未用到
SEG_GSEG2Y7seg_ment
SEG_FSEG3W7seg_ment
SEG_DSEG4P3seg_ment
SEG_CSEG5P4seg_ment
SEG_BSEG6R5seg_ment
SEG_ASEG7T3seg_ment
DIG1DIG_EN1T4seg_sel
DIG2DIG_EN2V4seg_sel
DIG3DIG_EN3V3seg_sel
DIG4DIG_EN4Y3seg_sel
DIG5DIG_EN5Y8seg_sel
DIG6DIG_EN6W8seg_sel
DIG7DIG_EN7W10seg_sel
DIG8DIG_EN8Y10seg_sel
X1 SYS_CLKG1clk
K1 SYS_RSTAB12rst_n

图3.5-22QUARTUS配置管脚 4.4 再次综合

再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“StartCompilation”,再次对整个工程进行编译和综合,如下图所示。 图3.5-23QUARTUS编译选项
当出现如下所示的编译成功标志时则说明编译综合成功。 图3.5-24QUARTUS编译成功标志4.5 连接开发板

完成编译后开始进行上板调试操作,按照下图的方式,将下载器接入电脑USB接口,接上开发板电源后按下开发板下方蓝色开关,硬件连接完毕。 图3.5-25开发板连接图4.6 上板

打开QUARTUS界面,单击界面中的“ ”会弹出配置界面。在界面中点击“addfile”添加“.sof”文件后点击“Start”,会在“Progress”出现显示进度。 图3.5-26QUARTUS界面当进度条到100%时提示成功,如下图所示,此时即可在开发板上观察相应的现象。 图3.5-27QUARTUS下载程序界面如果操作没有错误,此时可以观察到开发板上数码管的数字每一秒增加一个,并且按照从左到右数码管的排列依次显示。如果显示方向或者时间错误,就需要从头开始进行错误排查。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定可以达到想要的效果。
第5节 简化版步骤分享

这里依旧会分享简化版的步骤,方便掌握基础原理后进行反复操作复习。
5.1 设计实现

5.1.1顶层信号


新建目录:D:\mdy_book\my_seg。在该目录中,新建一个名为my_seg.v的文件,用GVIM打开后开始编写代码。确定顶层信号。工程信号和管脚关系如下表所示:表3.5 - 3信号和管脚关系
器件信号线信号线FPGA管脚内部信号
U6,U7SEG_ESEG0Y6seg_ment
SEG_DPSEG1W6未用到
SEG_GSEG2Y7seg_ment
SEG_FSEG3W7seg_ment
SEG_DSEG4P3seg_ment
SEG_CSEG5P4seg_ment
SEG_BSEG6R5seg_ment
SEG_ASEG7T3seg_ment
DIG1DIG_EN1T4seg_sel
DIG2DIG_EN2V4seg_sel
DIG3DIG_EN3V3seg_sel
DIG4DIG_EN4Y3seg_sel
DIG5DIG_EN5Y8seg_sel
DIG6DIG_EN6W8seg_sel
DIG7DIG_EN7W10seg_sel
DIG8DIG_EN8Y10seg_sel
X1 SYS_CLKG1clk
K1 SYS_RSTAB12rst_n
写出顶层信号代码:
123456modulemy_seg(rst_n,clk,seg_sel,               segment                       );
声明输入输出属性:
1234input                  clk         ;input                  rst_n         ;output [ 7:0]            seg_sel;output [ 6:0]            segment       ;
5.1.2信号设计

首先进行架构设计,分析设计目标得出波形示意图如下所示: 图3.5-6数码管动态扫描实现架构设计计数器架构,表示1秒计数器cnt0的代码如下:
1234567891011121314always @(posedge clk or negedgerst_n)begin    if(!rst_n)begin      cnt0 <= 0;    end    else if(add_cnt0)begin      if(end_cnt0)            cnt0 <= 0;      else            cnt0 <= cnt0 + 1;    endend assign add_cnt0 = 1 ;assign end_cnt0 =add_cnt0 && cnt0==50_000_000-1 ;
表示第几个1秒的计数器cnt1代码如下:
1234567891011121314always @(posedge clk or negedgerst_n)begin     if(!rst_n)begin      cnt1 <= 0;    end    else if(add_cnt1)begin      if(end_cnt1)            cnt1 <= 0;      else            cnt1 <= cnt1 + 1;    endend assign add_cnt1 =end_cnt0;assign end_cnt1 =add_cnt1 && cnt1==8-1 ;
设计输出信号seg_sel:
12345678always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginseg_sel<= 8'hfe ;    end    else beginseg_sel<=~(8'b1<<cnt1) ;    endend
设计输出信号seg_ment:
1234567891011121314151617181920212223242526272829always@(posedge clk or negedge rst_n)begin    if(rst_n==1'b0)beginseg_ment<= 7'h01;    end    else if(cnt1==0) beginseg_ment<= 7'h01;    end    else if(cnt1==1) beginseg_ment<= 7'h4f;    end    else if(cnt1==2) beginseg_ment<= 7'h12;    end    else if(cnt1==3) beginseg_ment<= 7'h06;    end    else if(cnt1==4) beginseg_ment<= 7'h4c;    end    else if(cnt1==5) beginseg_ment<= 7'h24;    end    else if(cnt1==6) beginseg_ment<= 7'h20;    end    else if(cnt1==7) beginseg_ment<= 7'h0f;    endend
data代码表示如下:
1assign data = cnt1 ;
在代码的最后一行写下endmodule:
1endmodule
主体程序完成后,下面将module补充完整。
5.1.3信号定义

定义信号类型。cnt0、add_cnt0 和 end_cnt0的信号定义如下:
123reg                cnt0      ;wire                     add_cnt0    ;wire                     end_cnt0    ;
cnt1、add_cnt1和end_cnt1的信号定义如下:
123reg    [ 2:0]            cnt1      ;wire                     add_cnt1    ;wire                     end_cnt1    ;
seg_sel的信号定义如下:
1reg    [ 7:0]            seg_sel;
seg_ ment的信号定义如下:
1reg    [ 6:0]            segment       ;
data的信号定义如下:
1wire    data   ;
至此,整个工程的代码的设计工作已经完成,后续应对代码进行编译综合以及上板查看现象。
5.2 综合与上板

5.2.1新建工程

打开软件Quartus Ⅱ,点击“File”下拉列表中的New ProjectWzard...新建工程选项。 图3.5-11Quartus新建工程直接点击“Next”。 图3.5-12Quartus新建工程介绍
此时会出现是工程文件夹、工程名、顶层模块名设置界面,设置目录为:D:/mdy_book/my_seg,工程名和顶层名为my_seg,完成设置后点击“Next”。 图3.5-13QUARTUS新建工程设置名称选择“Empty project”后点击“Next”。 图3.5-14QUARTUS新建工程类型 点击右侧的“Add”按钮,选择“my_seg.v”文件,添加文件后点击“Next”。 图3.5-15QUARTUS添加文件
对芯片型号进行选择:“Devicefamily”选项下选择“Cyclone ⅣE”,“Available devices”选项下选择“EP4CE15F23C8”,随后点击“Next”。 图3.5-16QUARTUS选择芯片型号
直接点击“Next”。 图3.5-17QUARTUS设置工具界面 点击“Finish”,完成新建工程。 图3.5-18QUARTUS新建工程汇总界面 5.2.2综合

新建工程后界面如下图所示,点击“编译”。 图3.5-19QUARTUS新建工程后界面 编译成功界面如下图。 图3.5-20QUARTUS编译后界面 5.2.3配置管脚

在菜单栏点击“Assignments”后点击“Pin Planner”,此时会弹出配置管脚的窗口。 图3.5-21QUARTUS配置管脚选项在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。 图3.5-22QUARTUS配置管脚 5.2.4再次综合

打开“QUARTUS”软件,在菜单栏中选择“Processing”,点击“StartCompilation”再次进行综合。 图3.5-23QUARTUS编译选项 出现 QUARTUS 编译成功标志则表示此次编译成功。 图3.5-24QUARTUS编译成功标志 5.2.5连接开发板

下载器接入电脑 USB 接口,开发板接上电源后按下蓝色开关。 图3.5-25开发板连接图 5.2.6上板

打开 QUARTUS 界面,单击“ ”图标: 图3.5-26QUARTUS界面
点击“add file”,添加.sof文件,随后点击“Start”。在“Progress”中会显示进度,当进度条显示“100%”表示成功,可在开发板上观察现象。 图3.5-27QUARTUS下载程序界面 第6节 扩展练习

至此,数码管动态扫描工程已经分享完毕,相信同学们已经可以完全掌握这一工程。在掌握工程的基础上可以多做一些思考,在工程原理不变的基础上进行一定的数据调整,例如尝试改变停留时间或改变数码管的显示顺序,这样可以更深刻的掌握案例。也欢迎有更好思路和想法的同学前往至简设计法论坛进行讨论。




页: [1]
查看完整版本: 【FPGA至简设计原理与应用】书籍连载14第三篇FPGA至简设计项目 第五章数码管动态扫描