本章导读
电子系统中常用的显示设备有数码管、LCD液晶以及VGA显示器等。其中数码管又可分为段式显示(7段、米字型等)以及点阵显示(8*8、16*16等),LCD液晶的应用可以分为字符式液晶(1602、12864等)以及真彩液晶屏,VGA显示器一般是现在的电脑显示器。芯航线开发板对以上三种设备均提供了硬件接口。
本章将实现FPGA驱动数码管动态显示并提取出实现的电路结构,从电路结构入手编写代码,仿真对设计进行验证。最终板级调试时使用In system sources and probes editor(ISSP,系统的源和探测器工具),输入需要显示的数据,数码管则显示对应数值。本节课核心不再是代码,而是电路结构,电路结构确定后编写代码只是照图施工的过程。这也是越来越接近FPGA设计本质的硬件思维。
数码管驱动原理
其中8段数码管的结构图如图15.1所示,
图15.1 8段数码管结构图
由上图可以看出数码管有两种结构:共阴极与共阳极。这两者的区别在于,公共端是连接到地还是高电平,对于共阴数码管需要给对应段以高电平才会使其点亮,而对于共阳极数码管则需要给低电平才会点亮。AC620上板载的是共阳数码管。同时为了显示数字或字符,必须对数字或字符进行编码译码。这里先不考虑小数点也就是简化为7段数码管,其编码译码格式如表15.1所示:
表15.1 数码管编码译码表
段式数码管工作方式有两种:静态显示方式和动态显示方式。静态显示的特点是每个数码管的段选必须接一个8位数据线来保持显示的字形码。当送入一次字形码后,显示字形可一直保持,直到送入新字形码为止。这种方法由于每一个数码管均需要独立的数据线因此硬件电路比较复杂,成本较高,很少使用。
为了节约IO以及成本一般采用如图15.2所示的电路结构,这样3个数码管接在一起就比静态的少了7*2个I/O。
图15.2三位数码管等效电路图
这样就实现了另一种显示模式,动态显示。动态显示的特点是将所有位数码管的段选线并联在一起,由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选,利用发光管的余辉和人眼视觉暂留作用,使人的感觉好像各位数码管同时都在显示。
现在举例假设将扫描时间定为1S,这三个数码管分成3s,第1秒时sel数据线上为`b100,这时数码管0被选中,这时a=0,数码管0的LED0就可以点亮;第2秒时sel数据线上为`b010,这时数码管1被选中,这时b=0,数码管1的LED1就可以点亮;第3秒时sel数据线上为`b001,这时数码管2被选中,这时c=0,数码管2的LED2就可以点亮。这时的效果就会是数码管0的LED0亮一秒后数码管1的LED1亮一秒最后是数码管2的LED2亮一秒,这样再次循环。
这样如果使用1ms刷新时间的话由于数码管的余辉效应以及人的视觉暂留这样就会出现数码管0的LED0、数码管1的LED1以及数码管2的LED2 "同时"亮,并不会有闪烁感。
图15.3 7段8位的数码管原理图
三线制数码管电路设计
AC620开发板上配备的是8段8位的数码管,如果按照图15.3电路进行设计,可以看出仍需要16个IO进行驱动。下面提出另外一种三线制数码电路设计方法,其电路图如图15.4。这样的电路设计仍旧属于动态显示,但是这里通过外接了由两片8位74HC595移位寄存器级联后构成16位移位寄存器并将级联后的输出连接到位选及段选口,可以直接通过三个IO即可控制8位8段数码管。
图15.4 三线制数码管电路图
74HC595是8位串行移位寄存器,带有存储寄存器和三态寄存器,其中移位寄存器和存储寄存器分别采用不同的时钟。其内部结构图如图15.5所示,其可以把串行的信号转为并行的信号,因此常用做各种数码管以及点阵屏的驱动芯片。芯片的IO功能描述如表15.2所示。
图15.5 74HC595内部结构图
表15.2 74HC595 IO功能描述
数码管动态扫描驱动设计
模块接口设计及内部功能划分
由上面的分析可以得出图15.4的框图,其接口列表如表15.2所示:
图15.4 数码管模块框图
表15.2 模块接口列表
根据以上的分析可知,首先要有一个周期为1ms的驱动时钟,因此需要一个分频电路;在进行数码管的位选时,需要一个循环移位;在选择位后,需要选择器来选通数据输入位;要实现表15.1的功能需要一个由译码器。
数码管驱动模块逻辑电路图可以简化成如图15.5所示的,其中每一部分的作用如表15.3所示。
图15.5 数码管驱动模块逻辑电路图
表15.3 子功能块功能描述
名称 |
功能描述 |
divder |
分频产生1KHz的扫描时钟 |
Shift8 |
8位循环移位寄存器 |
MUX8 |
数据输入选择 |
MUX2 |
使能选择 |
LUT |
数据译码器 |
扫描时钟模块设计
从系统时钟50M分频得到1KHz的扫描时钟,计数器值即为25000d,这样计数器的位宽定义为15位即可。
1 reg [14:0]divider_cnt; 2 reg clk_1K; 3 4 always@(posedge Clk or negedge Rst_n) 5 if(!Rst_n) 6 divider_cnt <= 15'd0; 7 else if(!En) 8 divider_cnt <= 15'd0; 9 else if(divider_cnt == 15'd24999) 10 divider_cnt <= 15'd0; 11 else 12 divider_cnt <= divider_cnt + 1'b1; 13 14 always@(posedge Clk or negedge Rst_n) 15 if(!Rst_n) 16 clk_1K <= 1'b0; 17 else if(divider_cnt == 15'd24999) 18 clk_1K <= ~clk_1K; 19 else 20 clk_1K <= clk_1K;
数码管位选模块设计
接下来编写8位循环移位寄存器,这里利用循环移位寄存器实现0000_0001b→1000_0000b的变化,进而实现数码管的位选,即实现每个扫描时钟周期选择一个数码管。移位寄存器输出值与数码管选通的对应关系如表15.4所示,其中sel7为高位。
表15.4 移位寄存器与数码管对应关系
sel0 |
sel1 |
sel2 |
sel3 |
sel4 |
sel5 |
sel6 |
sel7 |
被选通数码管 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
数码管0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
数码管1 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
数码管2 |
…… |
||||||||
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
数码管7 |
1 reg [7:0]sel_r; 2 3 always@(posedge clk_1K or negedge Rst_n) 4 if(!Rst_n) 5 sel_r <= 8'b0000_0001; 6 else if(sel_r == 8'b1000_0000) 7 sel_r <= 8'b0000_0001; 8 else 9 sel_r <= sel_r << 1;
数码管数据显示设计
利用8选1多路器,选择端为当前扫描到的数码管也就是循环移位寄存器的输出端,利用多路器将待显示数据输送到对应到数码管上。
1 reg [3:0]data_tmp; 2 always@(*) 3 case(sel_r) 4 8'b0000_0001:data_tmp = disp_data[3:0]; 5 8'b0000_0010:data_tmp = disp_data[7:4]; 6 8'b0000_0100:data_tmp = disp_data[11:8]; 7 8'b0000_1000:data_tmp = disp_data[15:12]; 8 8'b0001_0000:data_tmp = disp_data[19:16]; 9 8'b0010_0000:data_tmp = disp_data[23:20]; 10 8'b0100_0000:data_tmp = disp_data[27:24]; 11 8'b1000_0000:data_tmp = disp_data[31:28]; 12 default:data_tmp = 4'b0000; 13 endcase
显示数据译码设计
前面所说如果要使数码管显示数字或字符,须对数字或字符进行编码译码。这里利用一个4输入查找表,来实现7位的输出显示译码。
1 always@(*) 2 case(data_tmp) 3 4'h0:seg = 7'b1000000; 4 4'h1:seg = 7'b1111001; 5 4'h2:seg = 7'b0100100; 6 4'h3:seg = 7'b0110000; 7 4'h4:seg = 7'b0011001; 8 4'h5:seg = 7'b0010010; 9 4'h6:seg = 7'b0000010; 10 4'h7:seg = 7'b1111000; 11 4'h8:seg = 7'b0000000; 12 4'h9:seg = 7'b0010000; 13 4'ha:seg = 7'b0001000; 14 4'hb:seg = 7'b0000011; 15 4'hc:seg = 7'b1000110; 16 4'hd:seg = 7'b0100001; 17 4'he:seg = 7'b0000110; 18 4'hf:seg = 7'b0001110; 19 endcase
模块使能设计
模块化的设计理念是,使得每个模块独立化,其端口设计要便于以后被调用与控制。基于这种理念,这里需要加入使能信号。关于使能子模块,直接利用一个二选一多路器即可实现。
assign sel = (En)?sel_r:8'b0000_0000; |
数码管显示模块仿真测试
以下生成了复位信号以及使能信号、待显示数据的初始化以及切换,分别在数码管上显示"87654321"以及"89abcdef"。
1 initial begin 2 Rst_n = 1'b0; 3 En = 1; 4 disp_data = 32'h12345678; 5 #(`clk_period*20); 6 Rst_n = 1; 7 #(`clk_period*20); 8 #20000000; 9 disp_data = 32'h87654321; 10 #20000000; 11 disp_data = 32'h89abcdef; 12 #20000000; 13 $stop; 14 end
设置好仿真脚本后进行功能仿真,可以看到如图15.6所示的局部波形文件,可以看出在复位信号置高之前数码管均显示0,在复位结束后数码管才开始正常显示,且当待显示数据为89ABCDEFh(MSB)后,数码管从1到8依次被选通且分别显示为FEDCBA98h(LSB)。即仿真通过。
图15.6 数码管功能仿真波形图
74HC595驱动设计
在数据手册中可以看出,不同工作温度和工作电压下的芯片工作频率值不相同,分别如表15.5与15.6所示。由于在学习板中芯片采用3.3V供电,这样在设计其工作频率时,直接使用50M晶振四分频后的时钟作为其工作时钟。
表15.5 芯片工作频率与温度对照
SYMBOL |
PARAMETER |
Vcc(V) |
MIN |
TYP |
MAX |
UNIT |
maximum clock frequency SH_CP and ST_CP |
2.0 |
9 |
30 |
- |
MHz |
|
4.5 |
30 |
91 |
- |
MHz |
||
6.0 |
35 |
108 |
- |
MHz |
||
表15.6芯片工作频率与温度对照
SYMBOL |
PARAMETER |
Vcc(V) |
MIN |
TYP |
MAX |
UNIT |
maximum clock frequency SH_CP and ST_CP |
2.0 |
4.8 |
- |
- |
MHz |
|
4.5 |
24 |
- |
- |
MHz |
||
6.0 |
28 |
- |
- |
MHz |
||
74HC595模块接口设计
从上面的分析可得出如图15.10所示的框图,其接口列表15.7如所示
图15.10 74HC595驱动模块框图
表15.7 74HC595驱动模块接口功能描述
信号名称 |
I/O |
功能描述 |
Clk |
I |
50M时钟 |
Rst_n |
I |
复位信号 |
En |
I |
数码管使能信号1使能,0关闭 |
Data[15:0] |
I |
8个数码管待显示数据,每四位组成一个BCD码 |
DS |
O |
串行数据输出 |
SH_CP |
O |
移位寄存器的时钟输出 |
ST_CP |
O |
存储寄存器的时钟输出 |
74HC595驱动模块设计
首先工作时钟产生,对50M时钟进行4分频。
1 parameter CNT_MAX = 4; //分频数 2 reg [15:0] divider_cnt;//分频计数器 3 4 always@(posedge Clk or negedge Rst_n) 5 if(!Rst_n) 6 divider_cnt <= 16'd0; 7 else if(divider_cnt == CNT_MAX) 8 divider_cnt <= 16'd0; 9 else 10 divider_cnt <= divider_cnt + 1'b1; 11 12 wire sck_pluse; 13 assign sck_pluse = (divider_cnt == CNT_MAX);
对sck_pluse进行计数,用于查找表实现数据的串行输入以及移位时钟SH_CP与存储时钟ST_CP的产生。
1 reg [4:0]SHCP_EDGE_CNT;//SH_CP EDGE counter 2 3 always@(posedge Clk or negedge Rst_n) 4 if(!Rst_n) 5 SHCP_EDGE_CNT <= 5'd0; 6 else if(sck_pluse)begin 7 if(SHCP_EDGE_CNT == 5'd31) 8 SHCP_EDGE_CNT <= 5'd0; 9 else 10 SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'd1; 11 end 12 else 13 SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
查找表实现状态输出。
1 always@(posedge Clk or negedge Rst_n) 2 if(!Rst_n)begin 3 SH_CP <= 1'b0; 4 ST_CP <= 1'b0; 5 DS <= 1'b0; 6 end 7 else begin 8 case(SHCP_EDGE_CNT) 9 5'd0:begin SH_CP <= 1'b0; ST_CP <= 1'b1; DS <= r_data[15]; end 10 5'd1:begin SH_CP <= 1'b1; ST_CP <= 1'b0;end 11 5'd2:begin SH_CP <= 1'b0; DS <= r_data[14];end 12 5'd3:begin SH_CP <= 1'b1; end 13 5'd4:begin SH_CP <= 1'b0; DS <= r_data[13];end 14 5'd5:begin SH_CP <= 1'b1; end 15 5'd6:begin SH_CP <= 1'b0; DS <= r_data[12];end 16 5'd7:begin SH_CP <= 1'b1; end 17 5'd8:begin SH_CP <= 1'b0; DS <= r_data[11];end 18 5'd9:begin SH_CP <= 1'b1; end 19 5'd10:begin SH_CP <= 1'b0; DS <= r_data[10];end 20 5'd11:begin SH_CP <= 1'b1; end 21 5'd12:begin SH_CP <= 1'b0; DS <= r_data[9];end 22 5'd13:begin SH_CP <= 1'b1; end 23 5'd14:begin SH_CP <= 1'b0; DS <= r_data[8];end 24 5'd15:begin SH_CP <= 1'b1; end 25 5'd16:begin SH_CP <= 1'b0; DS <= r_data[7];end 26 5'd17:begin SH_CP <= 1'b1; end 27 5'd18:begin SH_CP <= 1'b0; DS <= r_data[6];end 28 5'd19:begin SH_CP <= 1'b1; end 29 5'd20:begin SH_CP <= 1'b0; DS <= r_data[5];end 30 5'd21:begin SH_CP <= 1'b1; end 31 5'd22:begin SH_CP <= 1'b0; DS <= r_data[4];end 32 5'd23:begin SH_CP <= 1'b1; end 33 5'd24:begin SH_CP <= 1'b0; DS <= r_data[3];end 34 5'd25:begin SH_CP <= 1'b1; end 35 5'd26:begin SH_CP <= 1'b0; DS <= r_data[2];end 36 5'd27:begin SH_CP <= 1'b1; end 37 5'd28:begin SH_CP <= 1'b0; DS <= r_data[1];end 38 5'd29:begin SH_CP <= 1'b1; end 39 5'd30:begin SH_CP <= 1'b0; DS <= r_data[0];end 40 5'd31:begin SH_CP <= 1'b1; end 41 endcase 42 end
74HC595驱动模块仿真测试
此处仿真与前面类似,不再重复。部分波形如图15.11所示,可以看出在模块复位结束后每在移位时钟SH_CP上升沿时串行数据依次输出且高位在前。移位结束后ST_CP产生一个高电平更新显示数据,因此各信号工作正常。
图15.11 74HC595驱动仿真
ISSP生成及使用
为了更便捷地进行板级调试,这里介绍Qusrtus Prime自带的的In system sources and probes editor(ISSP)调试工具,测试数码管可以只用其提供的源,而至于其探针功能的使用会在后面的RAM章节再做详细介绍。这样测试整体模块框图就可以简化为如图15.12所示。
图15.12 整体测试框图
这里ISSP是以IP核的形式提供的,因此第一步单击Tools来启动IP Catalog来新建一个定制IP核。这里需要指出的是这里与Quartus II不同,在Quartus Prime中与qsys相关的IP核均集中到Qsys中。在Quartus II的使用方法在本节末也有相应讲解,可以对比参考。
在弹出的图15.13 IP目录中,找到Simulation;Debug and Verification下的Altera In-System Source & Probes,并将输出目录确定为工程文件夹下的ip文件夹,并以hex_data保存,单击Next。
图15.13 新建ISSP
图15.14设置ISSP名称及文件位置
在弹出的图15.15配置界面中将源位宽定义为32,探针位宽定义为0,然后单击Next并将语言类型选择为Verilog HDL。
图15.15 ISSP配置界面
图15.16 语言选择成Verilog HDL并启动转换
在图15.16点击Generate后软件会将ISSP先保存为Qsys系统。在提示系统保存成功后点击Close会自动启动转换。弹出如图15.18界面,标志完成。
图15.17 Qsys系统保存完成
图15.18 转换完成
单击Close,然后在主界面点击Finish。此时会弹出如图15.19所示的汇总信息,确认无误后单击Close即可。
图15.19 汇总信息
这时会弹出图15.20提示需要手动将Qsys系统添加到工程中。这样直接在Files右键选择Add/Remove Files in Project,找到对应的文件即可。
图15.20 添加qsys到工程
图15.21 手动添加qsys到工程
ISSP依旧属于IP核,因此在使用前需要将其在工程文件中进行例化。ISSP在生成的文件中含有例化模板可以直接打开进行复制修改。可以打开生成的IP目录下的hex_data_inst.v文件。
图15.22 例化文件所在文件夹位置
图15.23 例化模板
板级调试与验证
新建顶层文件HEX_top.v,并将编写好的数码管驱动模块以及ISSP例化到顶层文件中。
1 module HXE_top(Clk,Rst_n,SH_CP,ST_CP,DS); 2 3 input Clk; //50M 4 input Rst_n; 5 6 output SH_CP; //shift clock 7 output ST_CP; //latch data clock 8 output DS; //shift serial data 9 10 wire[31:0]disp_data; 11 wire [7:0] sel;//数码管位选(选择当前要显示的数码管) 12 wire [6:0] seg;//数码管段选(当前要显示的内容) 13 14 hex_data hex_data( 15 .probe(), 16 .source(disp_data) 17 ); 18 19 20 HXE8 HXE8( 21 .Clk(Clk), 22 .Rst_n(Rst_n), 23 .En(1'b1), 24 .disp_data(disp_data), 25 .sel(sel), 26 .seg(seg) 27 ); 28 29 30 HC595_Driver HC595_Driver( 31 .Clk(Clk), 32 .Rst_n(Rst_n), 33 .Data({1'b1,seg,sel}), 34 .S_EN(1'b1), 35 .SH_CP(SH_CP), 36 .ST_CP(ST_CP), 37 .DS(DS) 38 ); 39 40 endmodule
分配引脚并全编译无误后下载到开发板中。这时可以看到数码管全显示0,与设计一致。在Quartus Prime中点击Tools→In-System Source and Probes Editor启动ISSP,启动后的界面如图15.24所示。这里需手动选择下载器,并将数据格式改为设计中的hex格式。
图15.24 启动ISSP
图15.25 ISSP操作界面
图15.26 切换数据格式
这样ISSP界面的Data中输入相应的数据即可在数码管上看到与之对应的显示,如图15.27所示。至此完成了数码管的动态显示。
图15.27 数码管显示数据
本章中介绍了数码管的两种驱动方式,静态扫描与动态扫描。最终实现了三线制数码管的驱动显示。得益于模块化设计的优势,如果需要实现FPGA直接驱动数码管,只需直接调用HEX8文件即可。在板级验证中使用了In system sources and probes editor(ISSP)调试工具。
在本章的基础上,可以结合第14章的内容,设计一款具有清零及暂停功能的秒表。
Quartus II中ISSP使用方法
在Quartus II中使用方式如下所示:
这里ISSP是以IP核的形式提供的,因此第一步单击Tools→Mega Wizard Plug-In Manager来启动Mega Wizard插件管理器,并新建一个定制IP核;
图15.28 启动Mega Wizard插件管理器
在弹出的图15.29 Mega Wizard插件管理器的参数设置界面中,找到JTAG-accessible Extensions下选择In-System Source and Probes,并将输出目录确定为工程文件夹下的ip文件夹,并以hex_data保存,单击Next。
在弹出的图15.30配置界面中将源位宽定义为32,探针位宽定义为0,然后单击Next即可。
图15.29 Mega Wizard插件管理器参数设置
图15.30.1 ISSP配置界面
图15.30.2 ISSP配置界面
图15. 30.3 ISSP配置界面
在调用时打开生成后的hex_data.v复制端口并进行相应修改即可。剩余使用方式就与Quartus Prime一致了。
1 hex_data hex_data( 2 3 .probe(), 4 5 .source(disp_data) 6 7 );