基于EP4CE6F17C8的FPGA数码管动态显示实例
一、电路模块
1、数码管
开发板板载了6个数码管,全部为共阳型,原理图如下图所示,段码端引脚为DIG[0]~DIG[7]共8位(包含小数点),位选端引脚为SEL[0]~SEL[5]共6位。端口均为低电平有效。
其实物图如下所示。
数码管引脚分配见下表。
2、时钟晶振
开发板板载了一个50MHz的有源晶振,为系统提供时钟。
其实物图如下所示。
时钟输出引脚分配见下表。
3、按键
开发板板载了4个独立按键,其中有3个用户按键(KEY1~KEY3),1个功能按键(RESET)。按键按下为低电平(0),释放为高电平(1),4个按键的原理图如下图所示。本例中只使用了RESET键。
其实物图如下所示。
按键的引脚分配见下表。
二、实验代码
本例实现6个数码管显示123456,代码使用Verilog编写,采用例化的形式,共有两个文件。
先编写数码管实现字形的程序,模块名称为seg,文件名称为seg.v,代码如下。
module seg( input[3:0] data, //显示的字形,可显示0~F十六个字形,所以需要4位 output reg[7:0] seg //字形编码,包含小数点,共8位 ); always@(*) //敏感信号为所有输入量 begin case(data) 4'd0:seg <= 8'b1100_0000; //字形0的编码 4'd1:seg <= 8'b1111_1001; //字形1的编码 4'd2:seg <= 8'b1010_0100; //字形2的编码 4'd3:seg <= 8'b1011_0000; //字形3的编码 4'd4:seg <= 8'b1001_1001; //字形4的编码 4'd5:seg <= 8'b1001_0010; //字形5的编码 4'd6:seg <= 8'b1000_0010; //字形6的编码 4'd7:seg <= 8'b1111_1000; //字形7的编码 4'd8:seg <= 8'b1000_0000; //字形8的编码 4'd9:seg <= 8'b1001_0000; //字形9的编码 4'ha:seg <= 8'b1000_1000; //字形A的编码 4'hb:seg <= 8'b1000_0011; //字形B的编码 4'hc:seg <= 8'b1100_0110; //字形C的编码 4'hd:seg <= 8'b1010_0001; //字形D的编码 4'he:seg <= 8'b1000_0110; //字形E的编码 4'hf:seg <= 8'b1000_1110; //字形F的编码 default:seg <= 7'b111_1111;//默认不显示 endcase end endmodule
接下来编写数码管显示的程序,并设置为顶层模块,模块名称为seg_show,文件名称为seg_show.v,代码如下。
module seg_show( input clk, //板载50HMz系统时钟 input rst, //复位按键 output reg[7:0] seg7, //段码端口 output reg[5:0] bit //位选端口 ); reg[19:0] cnt; //定义20位时钟计数器 //下面定义6个存放显示字形的变量 wire[7:0] seg_data0,seg_data1,seg_data2,seg_data3,seg_data4,seg_data5; //下面定义6个要显示的数字 wire[3:0] data0 = 4'd1; //data0赋值1 wire[3:0] data1 = 4'd2; //data1赋值2 wire[3:0] data2 = 4'd3; //data2赋值3 wire[3:0] data3 = 4'd4; //data3赋值4 wire[3:0] data4 = 4'd5; //data4赋值5 wire[3:0] data5 = 4'd6; //data5赋值6 //以下例化了6个数码管seg0~seg5,把各自的显示数值与字形编码联系起来 seg seg0(.data(data0), .seg(seg_data0)); seg seg1(.data(data1), .seg(seg_data1)); seg seg2(.data(data2), .seg(seg_data2)); seg seg3(.data(data3), .seg(seg_data3)); seg seg4(.data(data4), .seg(seg_data4)); seg seg5(.data(data5), .seg(seg_data5)); //20毫秒循环计数 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(!rst) //低电平复位 cnt <= 20'd0; //复位时时钟计数器清零 else if(cnt == 20'd999_999) //时钟计数器到达20毫秒时 cnt <= 20'd0; //时钟计数器清零 else cnt <= cnt + 20'd1; //否则时钟计数器加1,即来一次时钟脉冲加一次 end //数码管扫描显示 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(!rst) //低电平复位时数码管全灭 begin bit <= 6'b111111; seg7 <= 8'hff; end else if(cnt == 20'd166_666) //时钟到3毫秒时,数码管0显示1 begin bit <= 6'b111110; seg7 <= seg_data0; end else if(cnt == 20'd333_333) //时钟到6毫秒时,数码管1显示2 begin bit <= 6'b111101; seg7 <= seg_data1; end else if(cnt == 20'd499_999) //时钟到10毫秒时,数码管2显示3 begin bit <= 6'b111011; seg7 <= seg_data2; end else if(cnt == 20'd666_666) //时钟到13毫秒时,数码管3显示4 begin bit <= 6'b110111; seg7 <= seg_data3; end else if(cnt == 20'd833_333) //时钟到16毫秒时,数码管4显示5 begin bit <= 6'b101111; seg7 <= seg_data4; end else if(cnt == 20'd999_999) //时钟到20毫秒时,数码管5显示6 begin bit <= 6'b011111; seg7 <= seg_data5; end end endmodule
seg_show.v的代码还可以写成如下形式。
module seg_show( input clk, //板载50HMz系统时钟 input rst, //复位按键 output reg[7:0] seg7, //段码端口 output reg[5:0] bit //位选端口 ); reg[17:0] cnt; //定义20位时钟计数器 reg[2:0] scan_sel; //定义扫描位置计数器 //下面定义6个存放显示字形的变量 wire[7:0] seg_data0,seg_data1,seg_data2,seg_data3,seg_data4,seg_data5; //下面定义6个要显示的数字 wire[3:0] data0 = 4'd1; //data0赋值1 wire[3:0] data1 = 4'd2; //data1赋值2 wire[3:0] data2 = 4'd3; //data2赋值3 wire[3:0] data3 = 4'd4; //data3赋值4 wire[3:0] data4 = 4'd5; //data4赋值5 wire[3:0] data5 = 4'd6; //data5赋值6 //以下例化了6个数码管seg0~seg5,把各自的显示数值与字形编码联系起来 seg seg0(.data(data0), .seg(seg_data0)); seg seg1(.data(data1), .seg(seg_data1)); seg seg2(.data(data2), .seg(seg_data2)); seg seg3(.data(data3), .seg(seg_data3)); seg seg4(.data(data4), .seg(seg_data4)); seg seg5(.data(data5), .seg(seg_data5)); //3.3毫秒循环计数 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(rst == 1'b0) //低电平复位时计数器全部清零 begin cnt <= 18'd0; scan_sel <= 3'd0; end else if(cnt >= 18'd166_666) //时钟计数器到达3.3毫秒时 begin cnt <= 18'd0; //时钟计数器清零 if(scan_sel == 3'd5) //如果扫描位置计数器已经到5则恢复0 scan_sel <= 3'd0; else scan_sel <= scan_sel + 3'd1; //否则扫描位置计数器加1,即每3.3ms加一次 end else begin cnt <= cnt + 18'd1; //否则时钟计数器加1,即来一次时钟脉冲加一次 end end //数码管扫描显示 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(!rst) //低电平复位时数码管全灭 begin bit <= 6'b111111; seg7 <= 8'hff; end else case(scan_sel) 4'd0: //数码管0显示1 begin bit <= 6'b111110; seg7 <= seg_data0; end 4'd1: //数码管0显示2 begin bit <= 6'b111101; seg7 <= seg_data1; end 4'd2: //数码管0显示3 begin bit <= 6'b111011; seg7 <= seg_data2; end 4'd3: //数码管0显示4 begin bit <= 6'b110111; seg7 <= seg_data3; end 4'd4: //数码管0显示5 begin bit <= 6'b101111; seg7 <= seg_data4; end 4'd5: //数码管0显示6 begin bit <= 6'b011111; seg7 <= seg_data5; end default: //数码管全部熄灭 begin bit <= 6'b11_1111; seg7 <= 8'hff; end endcase end endmodule
三、代码说明
1、第一种方式中使用了计数器来分频时钟,总计约20ms的循环,并在此间分配了6个数码管进行显示,每个数码管大约分得3.3ms左右的显示时间。
2、时钟计数器选取了20位,对50MHz时钟进行分频,最大能达到20.9ms,刚好大于所需要的20ms,所以取20位进行计数比较合适,太多了浪费硬件资源。
3、第二种方式只用18位计数器来定时3.3ms,然后控制扫描位置计数器增长,并依靠扫描位置计数器的值来选择要点亮的数码管。
4、时钟分频还可以采用PLL的方式,待频率降下来了之后,再使用少量的计数器来得到所需要的时间,可进一步节约资源,但会消耗一个PLL。
5、程序中使用了数码管例化的形式,共例化出了6个数码管。例化的数码管负责解码,即把要显示的数值解码成显示的字形编码,再输出给数码管的段码端口。
6、在定义的变量中,data0~data5分别给了固定值1~6,seg_data0~seg_data5分别接收解码后的字形编码,并在下面的过程语句中,分别送给相应数码管的段端口进行显示。以0号数码管为例,它要显示字符1,先把1赋值给变量data0,然后通过例化seg seg0(.clk(clk), .data(data0), .seg(seg_data0));把data0的值传给元件内部变量data(即文件seg.v中的data),而后得到字形编码,输出给变量seg_data0,然后当时钟计数cnt达到20'd166_666时,把seg_data0的数据送给seg7端口,同时打开0号数码管的位选通端口(bit <= 6'b111110;),此时该数码管显示出字形1。其他数码管的显示原理以此类推。
7、由上可以看出,元件例化有点类似于C语句中的函数调用及返回,但元件的每一个例化都是要产生出实际的电路的(要消耗逻辑器件),可查看生成的RTL电路图。
四、实验步骤
FPGA开发的详细步骤请参见“基于EP4CE6F17C8的FPGA开发流程(以半加器为例)”一文,本例只对不同之处进行说明。
本例工程放在D:\EDA_FPGA\Exam_3文件夹下,工程名称为Exam_3。有两个模块文件,一个名称为seg_show.v,设置为顶层实体,另一个名称为seg.v,用于提供例化。其余步骤与“基于EP4CE6F17C8的FPGA开发流程”中的一样。
接下来看管脚约束,本例中6个数码管一共有16个引脚,再加上时钟晶振和复位按钮,一共18个。具体的端口分配如下图所示。
对于未用到的引脚设置为三态输入方式,多用用途引脚全部做为普通I/O端口,电压设置为3.3-V LVTTL(与”基于EP4CE6F17C8的FPGA开发流程“中的一样)。需要注意,程序中的每个端口都必须为其分配管脚,如果系统中存在未分配的I/O,软件可能会进行随机分配,这将造成不可预料的后果,存在烧坏FPGA芯片的风险。
接下来对工程进行编译,编译完成后,可查看一下逻辑器件的消耗情况,第一种代码的消耗如下图所示。
第二种代码的消耗如下图所示。
可以看到,第二种方式消耗的器件更少。另外,还可以点击菜单Tools->Netlist Viewers->RTL Viewer,查看一下生成的RTL电路图。
最后进行程序下载,并查看结果。下图为6个数码管的动态显示效果。
当按下复位键后,所有数码管均熄灭,如下图所示。