打印
[FPGA]

数字时钟-明德扬至简设计与应用FPGA

[复制链接]
560|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
guyu_1|  楼主 | 2018-12-5 16:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

  1    项目背景  技术交流群:544453837  
       数字时钟用数字电路技术实现时、分、秒计时显示的装置。用数字同时显示时,分,秒的精确时间,并能实现准确校时。与传统表盘式机械式时钟相比,具有更高的准确性和直观性,且四轴飞行器械装置,具有更长的使用寿命。不仅如此,还具备体积小、重量轻、抗干扰能力强、对环境要求高、高精确性、容易开发等特性。本案例将详细介绍用至简设计法实现数字时钟的功能。

2    设计目标
       本工程使用6个数码管实现数字时钟功能。该功能与数字时钟相同,即从00:00:00一直到23:59:59。

       上板效果图如下图所示。


3    设计实现
3.1    顶层信号
      新建目录:D:\mdy_book\my_shizhong。在该目录中,新建一个名为my_shizong.v的文件,并用GVIM打开,开始编写代码。

      我们要实现的功能,概括起来就是控制8个数码管,让其中2个数码管常灭,其他6个数码显示不同的数字。要控制8个数码管,就需要控制位选信号,即FPGA要输出一个8位的位选信号,设为seg_sel,其中seg_sel[0]对应数码管0,seg_sel[1]对应数码管1,以此类推,seg_sel[7]对应数码管7。

       要显示不同的数字,就需要控制段选信号,不需要用到DP,一共有7根线,即FPGA要输出一个7位的段选信号,设为seg_ment,seg_ment[6]~segm_ment[0]分别对应数码管的abcdefg(注意对应顺序)。

       我们还需要时钟信号和复位信号来进行工程控制。

       综上所述,我们这个工程需要4个信号,时钟clk,复位rst_n,输出的位选信号seg_sel和输出的段选信号seg_ment。


      将module的名称定义为my_shizhong。并且我们已经知道该模块有4个信号:clk、rst_n、seg_sel和seg_ment,代码如下:


       其中clk、rst_n是1位的输入信号,seg_sel是8位的输出信号,seg_ment是7位的输出信号,根据此,补充输入输出端口定义。代码如下:


3.2   信号设计
      我们先分析要实现的功能,我们用m_g,m_s,f_g,f_s,s_g,s_s分别表示秒钟个位、秒钟十位、分钟个位、分钟十位、小时个位和小时十位所表示的数字值,m_g_s,m_s_s,f_g_s,f_s_s,s_g_s,s_s_s分别表示秒钟个位、秒钟十位、分钟个位、分钟十位、小时个位和小时十位所表示数码管段选值。

      数码管0显示的是秒个位值,则翻译成信号就是seg_sel的值为8’b1111_1110,seg_ment的值为:m_g_s。数码管1显示秒十位,也就是说seg_sel的值为8’b1111_1101,seg_ment的值为m_s_s。以此类推,数码管5显示小时十位,也就是seg_sel的值为8’b1101_1111,seg_ment的值为s_s_s。




      那么seg_ment和seg_sel多久变化一次呢?我们就需要用到数码管动态扫描原理了。

       数码管动态显示接口是应用最为广泛的一种显示方式之一,动态驱动是将所有数码管的8个显示笔划"a,b,c,d,e,f,g,dp"的同名端连在一起,另外为每个数码管的公共极COM增加位选通控制电路,位选通由各自独立的I/O线控制,当要输出字形码时,所有数码管都接收到相同的字形码,但究竟是哪个数码管会显示出字形,取决于单片机对位选通COM端电路的控制,所以我们只要将需要显示的数码管的选通控制打开,该位就显示出字形,没有选通的数码管就不会亮。通过分时轮流控制各个数码管的的COM端,就使各个数码管轮流受控显示,这就是动态驱动。在轮流显示过程中,每位数码管的点亮时间为1~2ms,由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静态显示是一样的,能够节省大量的I/O端口,而且功耗更低。

      也就是说,只要刷新时间为1~2ms,那么人眼看起来就似乎是所有数码管都在显示。因为我们把这个刷新时间定为2ms,也就是如下图。




      由波形图可知,我们需要1个计数器用来计算2毫秒的时间。本工程的工作时钟是50MHz,即周期为20ns,计数器计数到2_000_000/20=100_000个,我们就能知道2毫秒时间到了。另外,由于该计数器是不停地计数,永远不停止的,可以认为加1条件一直有效,可写成:assignadd_cnt0==1。综上所述,该计数器的代码如下。


      再次观察波形图,我们发现依次是显示的是m_g_s、m_s_s、f_g_s、f_s_s、s_g_s和s_s_s,一共6个,循环进行显示。这说明还需要另外一个计数器来表示第几个。该计数器每隔2ms加1,因此加1条件为end_cnt0。该计数器一共要数6次。所以代码为:


      有了两个计数器,我们来思考输出信号seg_sel的变化。概括起来,在第1次的时候输出值为8’hfe;在第2次的时候输出值为8’hfd;以此类推,在第6次的时候输出值为8’hdf。我们用信号cnt1来代替第几次,也就是:当cnt1==0的时候,输出值为8’hfe;在cnt1==1的时候输出值为8’hfd;以此类推,在cnt1==5的时候输出值为8’hdf。再进一步翻译成代码,就变成如下:


      或者可以简写成:


      对上面代码解释一下,第131行是指先将8’b1向左移位,再取反后的值,赋给seg_sel。假设此时cnt1等于0,那么8’b1<<0的结果是8’b0000_0001,取反的值为8’hfe;假设cnt1等于3,那么8’b1<<3的结果为8’b000_1000,取反后的结果为8’b1111_0111,即8’hf7。与第一种写法的结果都是相同的。

      我们来思考输出信号seg_ment的变化。seg_ment的值,是m_g、m_s、f_g、f_s、s_g和s_s等数值分别译码成数码管显示的信号m_g_s、m_s_s、f_g_s、f_s_s、s_g_s和s_s_s。我们一种做法,是将数值分别转成译码信号,然后再挑选出来给seg_ment。如下面的原理图:


      我们还可以是先可以选择哪一组数据,选出来后再做译码,其原理图如下:


       对比两个原理图,可以发现,实现同样的功能,第二个原理图比第一个,少了5个译码电路。这是一个简单的例子,说明实现一个功能,可以有很多种方法,方法各有优势,能用较少的资源、较快的速度实现功能,这就是设计师的能力,也是FPGA设计的魅力所在。

       我们采用第二种实现方案。设计一个信号sel_data,表示从6个数据中选择的信号,之后再做译码。波形图更新如下:


      sel_data的值有可能为0~9中的任意数字,这些数字都要转成数码管的段选信号。下表列出不同数字对应的段选信号值。


      明德扬开发板使用的是共阳数码管。根据上表,可写出下面代码。


      当然,也可以写成case的形式,结果都是一样的。


      sel_data从m_g、m_s、f_g、f_s、s_g和s_s中选取。当cnt1=0,即数码管0显示时,sel_data的值为m_g;当cnt1=1,即数码管1显示时,sel_data的值为m_s;当cnt1=2,即数码管2显示时,sel_data的值为f_g;当cnt1=3,即数码管3显示时,sel_data的值为f_s;当cnt1=4,即数码管4显示时,sel_data的值为s_g;当cnt1=5,即数码管5显示时,sel_data的值为s_s。为此,sel_data的代码为:


      接下来我们设计m_g、m_s、f_g、f_s、s_g和s_s信号,按照常识,秒个位m_g的变化规律如下:


      秒个位的数据是0、1、2~9、0这样有规律的加1变化,很明显可以看出这个m_g其实就是一个计数器,并且这个计数器每隔1秒时间才变化加1一次。1秒时间计时也需要一个计数器,设为cnt2,则cnt2是一直加1的,即可以写成assign add_cnt2 = 1。cnt2要数1秒时间,本工程的工作时钟是50MHz,即周期为20ns,计数器计数到1_000_000_000/20=50_000_000个,我们就能知道1秒时间到了,所以cnt1的周期是50_000_000个。有了cnt1,我们就知道m_g这个计数器的加1条件是1秒时间到了,即end_cnt2==1,要计数10个。综上所述,可以写出cnt2和m_g的代码如下:


     接下来思考秒十位m_s,m_s的变化情况如下:


      m_s秒十位的指示,很明显每隔10秒就会加1,很明显,m_s也是一个计数器,加1条件是10秒时间到,也就是end_m_g。该计数器周期性是数6个。所以代码如下:


       接下来思考分个位f_g,f_g的变化情况如下:



​      接下来思考分十位f_s,f_s的变化情况如下:


​      f_s分十位的指示,很明显每隔10分钟就会加1,很明显,f_s也是一个计数器,加1条件是10分钟到,也就是end_f_g。该计数器周期性是数6个。所以代码如下:


      接下来思考小时个位s_g,s_g的变化情况如下:


​      s_g小时个位的指示,很明显每隔1小时即6分钟就会加1,很明显,s_g也是一个计数器,加1条件是1小时也就是60分钟时间到,也就是end_f_g。

      接下来我们需要好好思考这个小时个位,它的周期是多少。有读者认为是10个,因为是小时个位会从0~9这样变化;有读者认为是24个,因为一共是24小时;有读者认为是4个,因为小时个位会从0~4变化。

      认为是24小时的读者,没有认识到我们只是看小时个位的变化,而不是小时个位和小时十位整体,整体才是24小时。我们盯着小时个位,会发现它是这么一个规律:0~9,0~9,0~3,0~9,0~9,0~3。因此正确的答案是,有时候周期是10,有时候是4,也就是说周期性会变的。按照明德扬的变量法,设其周期是x,则可以写出s_g的代码如下,而x是怎么来,我们先不考虑,后期再说。


     接下来思考小时十位s_s,s_s的变化情况如下:


​      s_s小时十位的指示,很明显当小时个位要清零时(注意不是每隔10小时),s_s就会加1,很明显,s_s也是一个计数器,加1条件是小时个位要清零了,也就是end_s_g。该计数器周期性是数3个。所以代码如下:


​      最后,我们再考虑x,也就是小时个位的周期性是多少,并取决于什么因素。我们可以发现,小时个位是0~9,0~9,0~3,0~9,0~9,0~3,也就是周期性是10或者是4。至于是10还是4,则取决于小时十位,即s_s。当时钟的小时十位显示为2时,小时个位只要数到3就行了,没有25点的。综上所术,发s_s为2时,x=4,否则x=10。所以x的代码如下:


​此次,主体程序已经完成。接下来是将module补充完整。



3.3   信号定义


      接下来定义信号类型。

      cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为100_000,需要用17根线表示,即位宽是17位。add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


      cnt1是用always产生的信号,因此类型为reg。cnt1计数的最大值为6,需要用3根线表示,即位宽是3位。add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:


      seg_sel是用always方式设计的,因此类型为reg,其一共有8根线,即位宽为8。因此代码如下:


​      seg_ ment是用always方式设计的,因此类型为reg,其一共有7根线,即位宽为7。因此代码如下:


​      sel_data是用always设计的,所以类型为wire。其最大值为9,所以需要4位位宽。代码如下:


      cnt2是用always产生的信号,因此类型为reg。cnt2计数的最大值为50_000_000,需要用26根线表示,即位宽是26位。add_cnt2和end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


       m_g是用always产生的信号,因此类型为reg。m_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_m_g和end_m_g都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


​      m_s是用always产生的信号,因此类型为reg。m_s计数的最大值为5,需要用3根线表示,即位宽是3位。add_m_s和end_m_s都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


​      f_g是用always产生的信号,因此类型为reg。f_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_f_g和end_f_g都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


​      f_s是用always产生的信号,因此类型为reg。f_s计数的最大值为5,需要用3根线表示,即位宽是3位。add_f_s和end_f_s都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


​      s_g是用always产生的信号,因此类型为reg。s_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_s_g和end_s_g都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


​      s_s是用always产生的信号,因此类型为reg。s_s计数的最大值为2,需要用2根线表示,即位宽是2位。add_s_s和end_s_s都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:


       x是用always产生的信号,因此类型为reg。x计数的最大值为10,需要用4根线表示,即位宽是4位。


​      至此,整个代码的设计工作已经完成。













​      下一步是新建工程和上板查看现象。

4.4     综合与上板
4.1    新建工程
       首先在d盘中创建名为“my_shizhong”的工程文件夹,将写的代码命名为“my_shizhong.v”,顶层模块名为“my_shizhong”。



      然后打开QuartusⅡ,点击File下拉列表中的New Project Wzard...新建工程选项。


​      3.再出现的界面中直接点击Next。


​      4.之后出现的是工程文件夹、工程名、顶层模块名设置界面。按照之前的命名进行填写,然后点击Next,在出现的界面选择empty project。



​      5.之后是文件添加界面。添加之前写的“my_shizhong.v”文件,点击右侧的“Add”按钮,之后文件还会出现在下方大方框里,之后点击“Next”。


​      器件型号选择界面。在上方红色箭头处选择CycloneⅣE,在中间红色箭头处选择EP4CE15F23C8,然后点击“Next”。


​      EDA工具界面。直接点击“Next”。


​      8.之后出现的界面,点击“Finish”。


4.2  综合
      1.新建工程步骤完成后,就会出现以下界面。选中要编译的文件,点击编译按钮。


      2.编译成功后会出现一下界面。


​4.3  配置管脚

​      在菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。


​     在配置窗口最下方中的location一列,参考下表中最右两列配置好FPGA管脚。


​4.4   再次综合

​      在菜单栏中,选中Processing,然后选择Start Compilation,再次对整个工程进行编译和综合。


​      出现上面的界面,就说明编译综合成功。

4.5    连接开发板
     图中,下载器接入电脑USB接口,电源接入电源,然后摁下蓝色开关。


4.6   上板
      1.双击Tasks一栏中”Program Device”。


​      2.会出现如下界面,点击add file添加.sof文件,点击“Start”,会在“Progress”出显示进度。


​3.进度条中提示成功后,即可在开发板上观察到相应的现象。

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

53

主题

61

帖子

2

粉丝