硬件平台:征战Pro开发板
软件平台:Vivado2018.3
仿真软件:Modelsim10.6d
文本编译器:Notepad++
征战Pro开发板资料
链接:https://pan.baidu.com/s/1AIcnaGBpNLgFT8GG1yC-cA?pwd=x3u8
提取码:x3u8
1 知识背景
1.1 数码管简介
数码管是一种半导体发光器件,其基本单元是发光二极管。数码管按段数一般分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管(多一个小数点显示)。当然也还有一些其他类型的数码管如“N”形管、“米”字管以及工业科研领域用的 16 段管、24 段管等,在此就不详细介绍。下面将为大家详细介绍本次实验使用的八段数码管。
图6-3- 1八段数码管结构图
由图 6-3-1 可以看出,八段数码管是一个8字型数码管,分为八段: a、 b、 c、 d、 e、f、 g、 dp,其中 dp 为小数点,每一段即为一个发光二极管,这样的八段我们称之为段选信号。数码管常用的有 10 根管脚,每一段有一根管脚,另外两根管脚为一个数码管的公共端,两根互相连接。
数码管分为共阳极数码管和共阴极数码管。共阳极数码管就是把发光二极管的正极连接在一起作为一个引脚,负极分开。相反的,共阴极数码管就是把发光二极管的阴极连接在一起作为一个引脚,正极分开。这两者的区别在于,公共端是连接到地还是高电平,对于共阳极数码管需要给对应段低电平才会使其点亮,而对于共阴极数码管则需要给其高电平才会点亮。本次实验使用的是共阳极数码管,也就是说给对应段低电平才会被点亮。给不同的段点亮可显示 0~f 的值,假如需要显示“0”,如下图所示:
图6-3- 2 数码管段选示意图
从上图可以看出,我们需要点亮abcdef这6个段的LED,就可以显示“0”,由于我们选用的是共阳数码管,所以a,b,c,d,e,f等于0,其余段等于1。按此规则我们对数码管进行编码。如表6-3-1 所示。
表6-3- 1数码管编码表
我们只要点亮相应的段码,就能显示我们需要显示的内容。
段式数码管工作方式有两种:静态显示和动态显示。静态显示的特点是每个数码管的段选必须接一个 8 位数据线来显示字符,显示字符可一直保持,直到送入新段码为止。我们开发板上面是 8 位数码管,那么如果点亮 8 位数码管是不是需要 48 位数据线去分别控制每一个码管的段选?理论上这种方式也是可以的,但是其占用的 I/O 口较多,占用了宝贵的IO资源,成本较高,很少使用。
那么如何节约IO资源呢?以我们本实验使用的数码管为例,如图 6-3-3 所示:
图6-3- 3 四位数码管等效电路图
征战 Pro 开发板上的 8 位数码管是由两个 4 位数码管拼接而成的,由上图可以看到,我们将8个数码管的段选信号连接在一起,而位选(sel)独立控制,这样8个数码管接在一起就少了8×7个 I/O 口。这里对位选信号特别说明一下:由上图可以看到每一个数码管都有一个位选信号,而这个位选信号就控制着数码管的亮灭。这样我们就可以通过位选信号去控制数码管亮,而在同一时刻,位选选通的数码管上显示的字形是一样的,因为我们将 8 个数码管相对应的段选连在了一起,数码管的显示自然就相同了,数码管的这种显示方式即为静态显示。而如果要让每个数码管显示的值不同,我们要用到另外一种显示方式,即动态显示,将在下一章节给大家介绍。本章节先讲述 8 位共阳极数码管的静态显示,为下个章节的动态显示做准备。
由图 6-3-3 可以看到,即使这样我们控制数码管仍然需要占用 16 个 I/O 口资源( 8 个段选, 8 个位选)。如果我们想节约 FPGA 芯片的更多 I/O 口去做其它设计,那能不能使用更少的 I/O 口去驱动数码管显示呢?这当然是可以的,我们可通过 74HC595 芯片(串并转换芯片) 进行实现。通过FPGA去控制74HC595芯片,再让74HC595去驱动数码管。
1.2 串并转换芯片74HC595 简介
74HC595 是一个 8 位串行输入、并行输出的位移缓存器。其内部具有 8 位移位寄存器和一个存储器,具有三态输出功能。我们先跟据该芯片的引脚图来为大家讲解其功能,如图 6-3-4 所示。
图6-3- 4芯片引脚图
表6-3- 2引脚功能简介
如表6-3-2 所示,该芯片有个并行的数据输出,同时芯片的输入是串行数据,也就是说我们使用一个串行输入口就可以并行输出八个输入的串行数据。从上一小节数码管的讲解我们知道8位(开发板上由两个4位数码管组成)八端数码管需要 16 个 I/O(也就是 16bit)去驱动数码管,而使用该芯片后,我们就可以将输出的数码管信号以串行(1bit)的方式输入该芯片,然后该芯片会将我们输入的数码管信号以并行的方式输出,加上SRCLK,RCLK两个信号,总共只需要3个管脚就可以控制8位数码管,这样大幅节约了FPGA管脚。但是可以看到一片芯片只能并行输出 8 位数据,而我们的8位八段数码管是需要 16 位数据驱动的,这该怎么办呢?这里我们可以使用两片 74HC595 芯片进行输出,事实上这正是 74HC595 的一大特点,可以进行级联。74HC595 芯片有个 QH’ 引脚,该引脚的功能为串行数据输出,我们将这个输出接入下一片74HC595 芯片的串行数据输入端,这样后面的数据就会在下一片 74HC595 芯片输出了。应用好这个联级功能,我们最少只需占用三个 I/O 口就可以控制很多片 74HC595,如果不考虑扫描时间,理论上可以控制N多个数码管。这颗芯片在实际工作中非常有用,可以极大的节约宝贵的FPGA管脚。
我们知道了数据的输入输出,那我们该如何控制这些数据输入和输出呢?我们通过74HC595 芯片内部结构图来为大家讲解其控制过程,如图 6-3-5 所示。
图6-3- 574HC595 内部结构图
如图 6-3-5 所示,SRCLR是复位管脚,低电平有效, 低电平时将移位寄存器的数据清零,在原理图中我们将其接到 VCC 上以防止数据清零。 SRCLK 为移位寄存器时钟输入,上升沿时将输入的串行数据(SER 端输入)移入移位寄存器中。需要注要的是它是一个移位寄存器,也就是说当下一个脉冲(时钟上升沿)到来时,上一个脉冲移入的数据就会往下移动一位。如果我们串行输入 8bit 数据,8bit 数据输入完之后,那么第一位输入的数据将会移动到最后面。若我们一次输入的数据超过 8bit,那么数据就会通过 Q7S 端口输出,此时我们可以将该接口接到另一片74HC595 芯片的串行输入端(级联),这样数据就会随着脉冲依次移位到另一片 74HC595芯片上。
当串行数据移入到 74HC595 芯片的移位寄存器之后, 74HC595 内部有一个 8 位存储寄存器,该寄存器由 RCLK(存储寄存器时钟)控制, RCLK上升沿时移位寄存器的数据会进入数据存储寄存器中,通过让OE(输出使能输入)为低,在原理图中,我们将OE接GND,让其输出使用一直有效,即可让存储寄存器中的数据进行输出。
数据手册中的功能映射表如下:
表6-3- 3功能映射表
可以看到每个管脚的状态决定了芯片的功能,大家仔细看一下。
芯片时序图如下:
图6-3- 6芯片时序图
同时我们还需要关注该芯片所支持的时钟频率,因为如果时钟频率超出了芯片支持范围,芯片也无法正常的响应,通过查看数据手册,我们发现时钟频率(SRCLK)不能超过25MHZ,如下图所示:
图6-3- 7 时钟频率
我们在做项目时,看芯片手册是非常重要的,我们要知道如何控制芯片,一定要将数据手册了解清楚,上图便是手册中的控制时序,我们在写FPGA程序时,就要将该时序做出来,这样才能让芯片按我们的想法进行工作。
接下来我们看一下串行输入和并行输出的关系,如下图所示:
图6-3- 8 串并输出关系
如上图所示最先输入的数据会被移位到最后位进行输出。
74HC595芯片简介就到此结束,如需更详细的了解该芯片,大家可自行查看数据手册。
2 实验任务
控制 8 位数码管让其以 00000000、11111111、 22222222 一直到 FFFFFFFF 循环显示。每个字符显示 0.5s 后变化。
3硬件设计
3.1 原理图分析
图6-3- 9数码管原理图
图6-3- 10 74HC595 原理图
图 6-3-9所示,由两个4位数码管组成一个8位数码管,数码管为共阳数码管,即段选为低电平点亮。对应的数码管点亮原理图为图 6-3-3 所示。从图中可以看到,其位选为高电平时才能点亮对应数码管, DIG1(sel[7])对应的是开发板上最左侧数码管,依次类推, DIG8(sel[0])对应的就是开发板上最右侧数码管。
如图 6-3-10 所示开发板上搭载了两片 74HC595 芯片用于输出数码管驱动信号,其中SCLR_N我们接到了 VCC 防止数据清零,输出使能(G_N)我们接到了GND,让输出一直有效, 所以这两片 74HC595 芯片我们只用三个 IO 口控制即可。我们将两片 74HC595 进行级联,一片的 QH_O 输出端接到另一片的数据输入端 SER,这样我们输入的 16 位串行输入数码管信号的前8位就会在第二片74HC595输出。(注意:因为是移位寄存器,如果一次共输入 16 位数据,那么第一位输入的串行数据会在第二片 74HC595 芯片的 QH 输出)。
3.2 管脚映射表
表6-3- 4 管脚映射表
3.3编写XDC约束文件
4 程序设计
为了让我们的代码能更好的重复利用,我们尽可能将代码按功能模块来进行划分,比如我们现在这个实验,可以划分成三个部份。
1. 顶层模块(smg_static_top),用于例化各个功能模块
2. 段选位选生成模块(smg_static),用于生成00000000~FFFFFFFF数据
3. 74HC595驱动模块(hc595_driver),用于生成74HC595的驱动时序(该模块我们就可以直接应用于其它数码管的显示实验,不用再去编写,直接调用即可)
4.1 顶层模块
4.1.1 模块框图
图6-3- 11 顶层模块框图
从图中可以看出,例化了smg_static和hc595_driver两个模块。smg_static输出数码管的段选(seg)和位选(sel)信号给hc595_driver模块。hc595_driver模块则输出hc595的控制信号给芯片管脚。
4.1.2 设计思路
在本实验中,顶层模块只用于例化各个功能模块,比较简单,此处不做讲解。
4.1.3 代码编写
限于篇幅,仅贴出部份代码(详见 Source 文件夹下 smg_static_top.v 文件)
模块端口定义
4.2段选位选生成模块(smg_static)
4.2.1模块框图
图6-3- 12 段选位选生成模块框图
输入端口:
clk:时钟信号,50Mhz,来源于晶振
rst_n:复位信号,低电平复位,来源于按键
输出端口:
seg:数码管段选信号,传给hc595_driver模块
sel:数码管位选信号,传给hc595_driver模块
4.2.2 设计思路
通过原理图,我们可以看到HC595是两片级联,分别生成两个8bit的并行数据,一个8bit表示段选,一个8bit表示位选。所以在该模块我们就要生成这两个8bit数据,交给下一个hc595_driver模块。
段选用来指示数码管显示的数字编码,位选用于点亮对应的数码管。通过前面介绍, 我们知道显示”0“,段选应该为8’hc0,由于我们选用的是共阳数码管,全部点亮 8 位,那位选我们需要全部置为高电平。
任务里面需要每0.5秒刷新一次,那么我们还需要有一个0.5秒的计数器。有了这些大体思路,我们就可以开始编写代码了。该思路只做参考,并非唯一方法,读者也可利用所学知识,按照自己思路进行设计。
4.2.3 代码编写
限于篇幅,仅贴出部份源码,详见Source文件夹smg_static.v文件
模块端口定义,代码如下:
定义了一个time_cnt计数器,第 42 行,每 0.5秒进行一次清零操作(在代码中是TIME_0S5 -1 ),为什么要减 1 呢?是因为我们计数是从0开始,0~24999999,刚好是25000000。第45行,每来一个时钟(clk)上升沿就进行加 1,第 40 行,当复位等于 0 时,time_cnt 清 0。代码如下所示:
定义了一个 0.5 秒标志信号寄存器,pulse_0s5,代码第51行,当time_cnt == TIME_0S5-1时,pulse_0s5等于1,否则等于0 ,这样就每隔 0.5 秒会产生一个高脉冲。代码如下所示:
定义了一个计数器num,从4’h0到4’hf循环,第60行,每来一个pulse_0s5脉冲,num进行加 1,由于num的位宽是 4 位,所以当加到4’hf时会自动又从4’h0 开始。代码如下所示:
输出段码,用case语句,将num与段码做一个映射关系(就是上文提到的编码表),代码如下所示:
数码管位选我们直接用assign语句,将其赋值为8’b1111_1111,如下所示:
注意:sel我们直接是output,但要seg我们需要加一个reg,如下所示:
这是因为sel是组合逻辑,通过assign赋值,seg是时序逻辑,需要在always块语句进行赋值。通过这我们可以得出一个结论,当我们用assign赋值的寄存器,我们需要定义成wire型(默认为wire);当在always块语句中赋值的寄存器,需要定义成reg型。
4.3 74HC595驱动模块(hc595_driver)
4.3.1 模块框图
图6-3- 13 74hc595驱动模块
输入端口:
clk:时钟信号,50Mhz,来源于晶振
rst_n:复位信号,低电平复位,来源于按键
seg:数码管段选信号,来源smg_static模块
sel:数码管位选信号,来源smg_static模块
输出端口:
rclk:移位寄存器的时钟
ser_dat:串行数据
srclk: 数据存储寄时钟
4.3.2 设计思路
该模块我们需要产生 srclk、 rclk、 ser_dat三个信号对74HC595 进行控制。其中 ds(串行数据)就是输入的数码管位选信号和段选信号; srclk(移位寄存器时钟),这个是 ser_dat 数据进入移位寄存器的时钟。在上面提到 srclk 最大不超过25Mhz,本实验我们使用系统时钟(50MHz)四分频得到的 srclk 时钟(12.5MHz)去进行驱动,而 rclk 时钟是在我们串行输入 16 位数码管段选和位选数据之后拉高的,其频率远远小于 srclk,所以这里我们只要确定 srclk 的频率即可。
4.3.3 代码编写
限于篇幅,仅贴出部份代码,详见Source文件夹hc595_driver.v文件
模块端口定义,代码如下所示:
seg(数码管段选)和 sel(数码管位选)由数码管驱动模块传来。 seg 数据和 sel 数据就是我们需要串并转换的数据,这两个信号的位宽加起来为 16 位,即我们每传输 16 位数据后需并行输出。通过assign语句将sel和seg组成一个16位的数据,如下所示:
cnt_div4:分频计数器。 这里我们让计数器在 0 和 3 之间循环计数,这样一个循环生成一个时钟即为四分频时钟。由于cnt_div4的位宽是2位,所以当计数到3时,会自动从0开始中,代码如下所示:
cnt_bit:传输位数计数器。我们知道我们需要传输 16bit 的数据, 故我们需要一个计数器对传输的位数进行计数,这样我们对传输完成 16 位数据就可以用这个计数器进行判别了。当 cnt_div4 等于 3 时让 cnt_bit 计数器加 1,由于cnt_bit位宽是4位,当计数到15又会自动从0开始循环计数,每个数值代表传输一位数据。代码如下所示:
srclk:移位寄存器时钟,上升沿时将数据写入移位寄存器中。 我们在 ser_dat 数据的中间状态拉高产生上升沿,避免采到数据的跳变沿,这样可以使 srclk 采得的 ser_dat 数据更加稳定。我们在 cnt_div4 >= 2 时拉高, cnt_div4 < 2 时拉低,即可产生该时钟, 其频率即为系统时钟四分频(12.5MHz)。代码如下所示:
rclk:存储寄存器时钟。 当我们 16 位数码管的位选和段选信号传输完之后我们需要拉高一个rclk 时钟来将信号存入存储寄存器之中。最后一个数据是在 cnt_bit = 15 且 cnt_div4 = 2 时传输的,所以我们就在下一个时钟(cnt_bit = 15 且 cnt = 3 时) 将 rclk 拉高一个时钟产生上升沿即可。代码如下所示:
ser_dat:串行数据输出,每当cnt_div4 = 0时,将data_buf中的数据按位输出给ser_dat,即实现了并转串的操作。代码如下所示:
5 仿真验证
5.1 顶层模块(smg_static_top)仿真验证
由于顶层文件例化了其它功能模块,我们只用给顶层文件的输入信号灌入激励信号,其它功能模块也都有了激励信号,在本实验中我们仅用写顶层激励文件即可。
5.1.1 仿真激励代码编写
限于篇幅,仅贴出部份代码,详见Sim文件夹tb_smg_static_top.v文件。
5.1.2批处理仿真
使用批处理仿真,在该章节我们可以不用再更改modelsim.bat文件,直接拷贝前面章节的即可。sim.do 文件也仅仅只用修改一处地方,如下图所示:
此处改为我们当前的仿真代码模块名:tb_smg_static_top,改好以后,保存。
双击 modelsim.bat,我们就不管了,先喝茶,等软件自已跑(具体步骤参考前一章节)
5.1.3仿真波形分析
我们将smg_static模块的信号加入波形窗口显示,如下图所示:
图6-3- 14 仿真波形1
可以看到sel等于8’b11111111,num的值每来一个pulse_0s5脉冲进行加 1 操作,seg 按数码管编码进行赋值,我们将波形放大,观察time_cnt,为了方便我们观察,可将time_cnt 的显示格式改为无符号型(unsigned),设置方式如下图所示:
波形放大后如下图所示:
图6-3- 15 仿真波形2
可以看到当 time_cnt = 9时,下一个clk上升沿到来时time_cnt变为0,与设计吻合。大家注意,为了节约仿真时间,此处TIME_0S5在仿真激励文件中重新赋值了等于10,所以仿真时就是(10-1),而不是(25000000-1)。我们再来看hc595_driver模块仿真
图6-3- 16 仿真波形3
可以看到ser_dat生成了一串16’b11000000_11111111的数据,刚好是data_buf(16’hff03 = 16’b11111111_00000011)从低位开始的数据,即并转串正确;rclk在16位传输完成后,生成了一个高脉冲信号;srclk是clk的4分频。说明我们的设计正确。
仿真验证没问题后,我们就可以新建Vivado工程,综合编译下载了。
6 综合编译
6.1 新建Vivado工程
在前面,我们已经把源码(.v)和管脚约束文件(.xdc)通过notepad++ 编辑好,这时候我们只用把源码和约束文件加入工程,直接生成bit文件即可。
步骤1:File --> Project --> New,然后点击Next
图6-3- 17 新建Vivado工程步骤1
步骤2:更改工程名及工程保存路径(注意:路径不能有中文字符),修改好以后,点击Next
图6-3- 18新建Vivado工程步骤2
步骤3: 默认选择RTL Project即可,点击Next
图6-3- 19新建Vivado工程步骤3
步骤4:添加源文件(.v),点击Add Files,然后找到源文件存放的路径,将 .v 文件全部选中,点击OK
图6-3- 20新建Vivado工程步骤4-1
图6-3- 21新建Vivado工程步骤4-2
步骤5:可以看到我们加入到工程的 .v 文件,点击Next
图6-3- 22新建Vivado工程步骤5
步骤6:添加约束文件(.xdc),点击Add Files
图6-3- 23新建Vivado工程步骤6
步骤7: 找到我们的管脚约束文件(pin.xdc),点击Next
图6-3- 24新建Vivado工程步骤7
步骤8: 选择器件xc7a35tfgg484-2,点击Next
图6-3- 25新建Vivado工程步骤8
步骤9:可以看到我们新建工程的总结,点击Finish即可
图6-3- 26新建Vivado工程步骤9
步骤10:可以看到新建工程的结构,包括了3个源码,1个约束文件,器件型号,如下图所示:
步骤11:这时对于初学者可以打开RTL视图,看一下我们的整体程序架构,各个模块之间的连线是否正确。点击Schematic,即可生成RTL视图。
图6-3- 28新建Vivado工程步骤11
从上图可以看到包括两个模块,各个模块之间连线也正确。
6.2 生成下载文件bit
接下来,我们直接生成bit文件,点击Generate Bitstream
图6-3- 29 生成bit文件
如果编译有错,我们就根据错误信息对我们的代码或约束文件进行修改,如果编译成功,如下图所示:
图6-3- 30 编译成功
为了能更快的进行程序下载,我们选择View Reports,如下图所示:
图6-3- 31 选择View Reports
7 下载验证
程序下载进去后,可以看到数码管的00000000~FFFFFFFF之间循环显示,如下图所示:
图6-3- 32 实验现象