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.进度条中提示成功后,即可在开发板上观察到相应的现象。
此内容由EEWORLD论坛网友guyu_1原创,如需转载或用于商业用途需征得作者同意并注明出处