第五章 数码管动态扫描
第 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”补充完整,得到该计数器的代码如下: