文章目录
第五章 数码管动态扫描
第 1 节 项目背景
led
数码管(LED Segment Displays
)是由多个发光二极管封装在一起的器件,这些二极管组成“8”字型,在内部完成引线连接,只引出它们的各个笔划和公共电极。一般来说,led
> 数码管常用段数为 7 段,如下图中所示的 a、b、c、d、e、f、g,有的数码管还会添加一个小数点,如图中的 h所示。
数码管可以通过驱动电路来驱动内部的各个段码,从而显示出需要的数字。发光二极管单元按照连接方式可分为共阳极数码管和共阴极数码管。其中,将所有发光二极管的阳极连接到一起形成公共阳极(COM)的数码管为共阳极数码管。在应用时应将共阳极数码管的公共极COM 接到+5V,当某一字段发光二极管的阴极为低电平时,该字段点亮,当某一字段的阴极为高电平时,该字段不亮。例如,当共阳极数码管的 abcdefg 值分别是 1001111 时,即 b、c字段亮,其他字段不亮时,数码管显示数字“1”。反之,将所有发光二极管的阴极连接到一起形成公共阴极(COM)的数码管为共阴极数码管。在应用时应将共阴极数码管的公共极COM 接到地线 GND上,当某一字段发光二极管的阳极为高电平时,该字段点亮,当某一字段的阳极为低电平时,该字段不亮。根据共阳极、共阴极数码管的工作原理可得,数码管显示数字 0 到 9 对应的 abcdefg 值如下表所示。
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 个共阳数码管。数码管 的配置电路如下。
图中的
SEG_A
、SEG_B~SEG_DP
是段选信号,8 个数码管共用。DIG1~DIG8
是位选信号,与 8 个数码管分别对应。当位选信号为 0 时,段选信号的值将赋给相 应数码管。例如DIG3
信号为 0 则表示将段选信号SEG_A~SEG_DP
的值赋给数码管 3。 信号SEG_A~SEG_DP,DIG1~DIG8
,都与电阻进行连接,如下图所示。
由此可见,
SEG_A~SEG_DP
由SEG0~SEG7
产生,DIG1~DIG8
由DIG_EN1~DIG_EN8
产生。而SEG0~SEG7
和DIG_EN1~DIG_EN8
都直接与FPGA
的IO
相连,如下图所示。
这些信号与 FPGA 管脚的对应关系如下表。FPGA 通过控制相应的管脚,从而实现对数码管显示的控制。
第 2 节 设计目标
按照至简设计法的设计特色,在开始一个新的设计之前应明确设计目标。设计目标是整个设计的核心灵魂,后续的每个步骤与操作都是围绕设计目标进行展开。至简设计法的原理很简单,即每一个步骤和思路都力求简单快捷,明确设计目标就是为了确保后续的每个阶段都有意义,而不去进行不必要的工程。这一做法可以少走很多弯路,尤其对于初学者来说,好习惯的养成可以为以后的工程师生涯带来无限的方便。所以再次强调,在开始设计前一定要将设计目标分析透彻,认真思考本次设计最终想要实现什么目的,达到什么效果,然后再投入到设计中去。本次设计需要实现开发板上数码管显示的功能,使 8 个数码管有规律的按照时间进行显示。复位后,数码管 0 显示数字“0”;1 秒后,数码管 1 显示数字“1”;再 1 秒后,数码管 2 显示数字“2”;以此类推,每隔 1 秒变化一次,最后当数码管 7 显示数字“7”后再次回到数码管 0 显示“0”,按照这样的显示规律循环往复。本设计的上板效果图如下图所示,也可以登陆至简设计法官方网站观看上板演示视频效果:
www.mdy-edu.com/xxxx
。
第 3 节 设计实现
在设计的实现阶段本书会按照步骤和原理分析进行案例实现的分享,考虑到初学者的需要,此部分的内容会比较详细。如果基础知识掌握得比较牢靠,只想学习此设计的步骤,可以跳过此部分,直接进入后面章节中的简化版步骤分享。对于初学者来说,建议不要选择捷径,一定按照详细分析的内容进行学习,只有掌握好基础知识,才可以从容的独立完成项目设计。
3.1 顶层信号
新建目录:
D:\mdy_book\mdyBookMySeg
,并在此目录中新建一个名为mdyBookMySeg.v
的文件。用GVIM
打开文件后开始编写代码。这里再次强调,建议初学者按照书中提供的文件路径以及文件名进行设置,避免后续出现未知错误。
分析设计目标可知,本设计需要控制 8 个数码管,让其显示不同的数字。通过前文分析的 LED数码管显示原理可以得知:如果想要控制 8 个数码管,则需控制位选信号,即 FPGA 要输出一个 8位的位选信号,将其设为
seg_sel
。其中seg_sel[0]
对应数码管 0,seg_sel[1]
对应数码管 1,以此类推,seg_sel[7]
对应数码管 7。
控制数码管的不同数字显示需要通过控制每个数码管上的 7 个字段,即通过控制段选信号可以实现数码管不同数字的显示,此处由于只需显示数字,无需用到“h”子段,因此一共有 7 个子段,即 FPGA 要输出一个 7 位的段选信号,将其设为
seg_ment
。其中seg_ment[6]~segm_ment[0]
分别对应数码管的 abcdefg 字段(注意对应顺序)。当然,除位选信号和段选信号外,设计中进行工程控制的时钟信号和复位信号也同样必不可少。
综上所述,本设计一共需要 4 个信号:时钟信号
clk
,复位信号rst_n
,输出的位选信号seg_sel
和输出的段选信号seg_ment
。信号和硬件管脚的对应关系如下表所示。
将
module
的名称定义为my_seg
,已知该模块有 4 个信号:clk、rst_n、seg_sel
和seg_ment
。在顶层信号代码中需要将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接,其具体代码如下:
随后对信号输入输出属性进行声明,指出这一信号对于 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,综上所述,其具体代码如下:
3.2 信号设计
在设计信号之前,先按照至简设计法的思路来进行架构设计。分析需要实现的功能为:第 1 秒时数码管 0 显示数字“0”,其余数码管不显示任何数字;第 2 秒时数码管 1 显示数字“1”,其余数码管同样不显示任何数字;第 3 秒时数码管 3 显示数字“3”,其余数码管不显示数字;以此类推,第八秒数码管 7 显示数字“7”,循环往复。
将现象翻译成信号表示如下:
第 1 秒,数码管 0 显示数字“0”,即 seg_sel 的值为 8’b1111_1110,seg_ment 的值为 7’b00_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 个数码管轮流显示。通过分析可以画出信号波形示意图如下图所示。
通过
seg_sel
和seg_ment
信号的变化波形图可以看出:显示第 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 号,
就可能需要很久才能找到。
如果在一个计数单位的基础上再加一个计数单位,即采用两种技术模式,一个记楼层,一个记门牌号,如下图所示。在同样的门牌号计数中,可以记为一楼的 1、2、3、4、5、6、7、8 号,二楼的1、2、3、4、5、6、7、8、9、10 号,以此类推,每一层都有对应的房间号。在这种计数模式下,如果想要找到七层 6 号房间,不需要多做思考就可以一下就定位到正确位置。
通过案例可以确定两种计数器复合计数才是最简单的计数模式,因此本设计使用一个计数 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
”后点击回车,就调出了对应模板,如下图所示。之后再将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。
补充完整后得到计数器 cnt0的代码如下:
下面来设计记录 1 秒次数的计数器 cnt1。该计数器表示数到第几个 1 秒,即每完成 1 秒计数后就加 1,因此其加 1 条件为
end_cnt0
。该计数器一共要数 8 次进入下一个循环,即计数数量为 8。继续调用至简设计法模板,在命令模式下输入“:Mdyjsq
”,点击回车后调出对应模板,将“add_cnt1
”和“end_cnt1
”补充完整,得到该计数器的代码如下:
确定完两个计数器后来思考输出信号
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
代码如下:
观察补充完整的代码可以发现:代码表示与文字描述基本上是一模一样的,这其实是体现了 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
”,调出至简设计法模板,补充完整后得到代码如下:
最后来思考输出信号
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
”,调出至简设计法模板,补充完整后的代码如下:
上面的代码可以正确实现
seg_ment
的功能,对于本设计来说已经足够。但进一步思考可以发现:该代码实现了类似译码的功能,将数字设成数码管显示的值,代码中只对 0~7 进行译码。为了方便日后设计,在这里设计一个通用的译码模块,将0~9
都进行译码。这样的话今后遇到类似的工程,就可以随时调用这一代码,译码模块代码如下所示:
在本设计中只要控制 data 信号,即可实现在数码管显示相应数字。前文可知:当 cnt1=0,则数码管会显示 0。当 cnt1=1,则数码管会显示 1。因此 data 信号代码如下:
在代码的最后一行写下 endmodule
至此,主体程序已经完成。
3.3 信号定义
接下来要将 module 补充完整,首先来定义信号类型。再次强调,在进行 reg 和 wire 的判断时,总会有多余的联想,比如认为 reg 是寄存器,wire 是线;或者认为 reg 的会综合成寄存器,wire 不会综合成寄存器。但是这些其实和 reg 型还是 wire 型都并无关系,在信号类型的判断时不需要做任何的联想,只要记住一个规则“用 always 实现的是 reg 型,其他都是 wire 型”就可以了。进行类型定义时,需要设定信号的位宽。至简设计法在这里们分享一个非常实用的位宽获取技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽,如下图所示。
cnt0 是用 always 产生的信号,因此类型为 reg。其计数的最大值为 50_000_000,通过计算器可以得知需要用 26 根线表示,即位宽是 26 位。add_cnt0 和 end_cnt0 都是用 assign 方式设计的,因此类型为 wire。其值是 0 或 1,用 1 个线表示即可。打开 GVIM,在编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下:
同理,cnt1 是用 always 产生的信号,因此类型为 reg。其计数的最大值为 7,需要用 3 根线表示,即位宽是 3 位。编辑模式下输入“Reg3”调用至简设计法模板并补充完整;add_cnt1 和 end_cnt1 都是用 assign 方式设计的,因此类型为 wire。其值是 0 或者 1,用 1 根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下:
seg_sel 是用 always 方式设计的,因此类型为 reg,其需要用 8 根线表示,即位宽为 8。编辑模式下输入“Reg8”调用至简设计法模板,补充完整后得到代码表示如下:
seg_ ment 是用 always 方式设计的,因此类型为 reg,其需要用 7 根线表示,即位宽为 7,代
码表示如下:
如果使用译码电路则会用到 data 信号。该信号是用 assign 设计的,所以类型为 wire,其最大值为 9,位宽为 4。编辑模式下输入“Wire4”调用至简设计法模板,补充完整后得到代码表示如下:
至此,整个代码的设计工作已经完成。完整版的工程代码如下:
第四节 综合和上板
4.1 新建工程
打开软件 Quartus Ⅱ,点击“File”下拉列表中的 New Project Wzard…新建工程选项,如下图所示。
随后会出现 Quartus 新建工程介绍,如下图所示,直接点击“Next”。
此时会出现工程文件夹、工程名、顶层模块名设置界面,如图 3.5-13。设置目录为:D:/mdy_book/mdyBookMySeg,工程名和顶层名为 mdyBookMySeg。再次强调,为了避免初学者在后续操作中出现报错情况,强烈建议设置的文件目录和工程名称与本书保持一致。设置完成后点击“Next”。
新建工程类型设置选择选择“Empty project”,如下图所示,然后点击“Next”。
文件添加界面如图 3.5- 15 所示,点击右侧的“Add”按钮,添加之前写好
的“mdyBookMySeg.v”文件,可以看到界面下方会显示出文件,随后点击“Next”。
芯片型号选择界面如图 3.5- 16 所示,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,点击“Next”。
图 3.5- 17 为 QUARTUS 设置工具界面,不必做任何修改,直接点击“Next”。
新建工程的汇总情况如下图所示,点击“Finish”后完成新建工程。
4.2 综合
新建工程步骤完成后,就会出现如下所示的 QUARTUS 界面
点击编译按钮,可以对整个工程进行编译。编译成功的界面如图 3.5-20所示:
4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后会弹出配置管脚的窗口。
,按照表 3.5- 3 中最右两列配置好 FPGA 管脚。配置管理来源参见管脚配置环节,最终配置结果如图 3.5- 22。配置完成后,关闭“Pin Planner”,软件自动会保存管脚配置信息。
4.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如下图所示。
当出现图 3.2-19QUARTUS 编译成功标志时则说明编译综合成功。
4.5 连接开发板
完成编译后开始进行上板调试操作,按照下图的方式,将下载器接入电脑 USB 接口,接上开发板电源后按下开发板下方蓝色开关,硬件连接完毕。
4.6 上板
打开 QUARTUS 界面,单击界面中的下图会弹出配置界面。在界面中点击“add file”添加“.sof”文件后点击“Start”,会在“Progress”出现显示进度。
当进度条到 100%时提示成功,如下图所示,此时即可在开发板上观察相应的现象。
如果操作没有错误,此时可以观察到开发板上数码管的数字每一秒增加一个,并且按照从左到右数码管的排列依次显示。如果显示方向或者时间错误,就需要从头开始进行错误排查。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定可以达到想要的效果。