基于EP4CE6F17C8的FPGA双数码管六十进制秒计数实例
一、电路模块
本例的电路模块与“基于EP4CE6F17C8的FPGA数码管动态显示实例”中的完全一样,此处就不再给出了。
二、实验代码
本例实现2个数码管循环显示00~59,显示间隔为1秒,代码使用Verilog编写,采用例化的形式,共有三个文件。
先编写数码管实现显示字形解码的程序,模块名称为seg_decode,文件名称为seg_decode.v,代码如下。
module seg_decode( input[3:0] data, //显示的字形,可显示0~9十个字形,所以需要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的编码 default:seg <= 7'b111_1111; //默认不显示 endcase end endmodule
接下来编写秒计数程序,共有两个模块,名称分别为count_m10和count_m6。先看count_m10的模块,文件名称为count_m10.v,代码如下。
module count_m10( input clk, //板载50HMz系统时钟 input rst_n, //复位按键 input en, //计数使能位 output reg[3:0]data, //计数值,从0~9共10位,所以用4位 output reg t //进位位 ); always@(posedge clk or negedge rst_n) //敏感信号为时钟上沿或复位下沿 begin if(rst_n==0) //低电平复位 begin data <= 4'd0; //复位时计数值及进位位清零 t <= 1'd0; end else if(en) //如果计数使能,则执行计数,否则保持上一次的值不变 begin if(data==4'd9) //如果计数到达9时 begin t<= 1'b1; //进位位置1 data <= 4'd0; //计数值清零 end else begin t <= 1'b0; //否则进位位清零,计数值加1 data <= data + 4'd1; end end else //如果计数不使能,进位位置0 t <= 1'b0; end endmodule
再来看count_m6的模块,文件名称为count_m6.v,代码如下。
module count_m6( input clk, //板载50HMz系统时钟 input rst_n, //复位按键 input en, //计数使能位 output reg[3:0]data, //计数值,从0~9共10位,所以用4位 output reg t //进位位 ); always@(posedge clk or negedge rst_n) //敏感信号为时钟上沿或复位下沿 begin if(rst_n==0) //低电平复位 begin data <= 4'd0; //复位时计数值及进位位清零 t <= 1'd0; end else if(en) //如果计数使能,则执行计数,否则保持上一次的值不变 begin if(data==4'd5) //如果计数到达5时 begin t<= 1'b1; //进位位置1 data <= 4'd0; //计数值清零 end else begin t <= 1'b0; //否则进位位清零,计数值加1 data <= data + 4'd1; end end else //如果计数不使能,进位位置0 t <= 1'b0; end endmodule
最后编写数码管显示程序,并设置为顶层模块,模块名称为seg_count,文件名称为seg_count.v,代码如下。
module seg_count( input clk, //板载50HMz系统时钟 input rst, //复位按键 output reg[7:0] seg7, //段码端口 output reg[1:0] bit //位选端口 ); reg [25:0] cnt; //定义26位时钟计数器 reg sec; //定义秒信号 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(rst == 0) //低电平复位时秒计数清零 begin sec <= 1'b0; end else if(cnt == 26'd49_999_999) //时钟计数器到达1秒时 begin cnt <= 26'd0; //时钟计数器清零 sec <= 1'b1; //产生秒信号 end else begin sec <= 1'b0; //否则秒信号清零 cnt <= cnt + 26'd1; //时钟计数器加1,即来一次时钟脉冲加一次 end end wire t0; //定义个位的进位位 wire [3:0] count_data0,count_data1; //定义显示的值(个位、十位共两个) wire [7:0] seg_0,seg_1; //定义显示字形码的值(个位、十位共两个) //下面例化秒的个位计数单元(十进制) count_m10 u0(.clk(clk), .rst_n(rst), .en(sec), .data(count_data0), .t(t0)); //下面例化秒的十位计数单元(六进制) count_m6 u1(.clk(clk), .rst_n(rst), .en(t0), .data(count_data1), .t()); //下面例化秒的个位字形解码单元 seg_decode seg0(.data(count_data0), .seg(seg_0)); //下面例化秒的十位字形解码单元 seg_decode seg1(.data(count_data1), .seg(seg_1)); reg[17:0] time_cnt; //定义20位时钟计数器 reg[3:0] scan_sel; //定义扫描位置计数器 //3.3毫秒循环计数 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(rst == 1'b0) //低电平复位时计数器全部清零 begin time_cnt <= 18'd0; scan_sel <= 4'd0; end else if(time_cnt >= 18'd166_666) //时钟计数器到达3.3毫秒时 begin time_cnt <= 18'd0; //时钟计数器清零 if(scan_sel == 4'd3) //如果扫描位置计数器已经到1则恢复0 scan_sel <= 4'd0; else scan_sel <= scan_sel + 4'd1; //否则扫描位置计数器加1,即每3.3ms加一次 end else begin time_cnt <= time_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显示秒的个位 begin bit <= 6'b111110; seg7 <= seg_0; end 4'd1: //数码管1显示秒的十位 begin bit <= 6'b111101; seg7 <= seg_1; end default: //数码管全部熄灭 begin bit <= 6'b111111; seg7 <= 8'hff; end endcase end endmodule
三、代码说明
1、要实现60进制的秒计数,可以拆分为个位上的10进制计数和十位上的6进制计数。个位通过计秒信号,直到9后,再计一次数清零,同时产生一个进位信号,十位通过计个位的进位信号,直到5后,再计一次数清零。如此就可实现从00~59的计数了。
2、count_m10模块主要负责10进制计数,在系统时钟的同步下,来一个en脉冲计一次数,计满后回零并产生进位信号t。计数的快慢取决于en端口输入的脉冲周期,产生的计数值则通过data端口向外输出,产生的进位信号通过t端口向外输出。
3、count_m6模块主要负责6进制计数,在系统时钟的同步下,来一个进位信号t计一次数,计满后回零并产生分的进位信号t。计数的快慢取决于个位进位信号t的周期,产生的计数值则通过data端口向外输出,产生的进位信号通过t端口向外输出。
4、seg_count模块为顶层模块,负责产生秒时钟信号(即en脉冲),通过例化10进制计数单元,把秒信号en传入,之后获得按秒变化的计数值count_data0。同时通过例化数码管字形解码单元,把计数值count_data0传入,之后获得个位上的字形编码seg7。同时,通过例化6进制计数单元,把个位上来的进位信号t传入,之后获得按十秒变化的计数值count_data1。同时通过例化数码管字形解码单元,把计数值count_data1传入,之后获得十位上的字形编码seg7。
5、数码管的扫描方式请参考“基于EP4CE6F17C8的FPGA数码管动态显示实例”一文,这里就不再赘述了。
四、实验步骤
FPGA开发的详细步骤请参见“基于EP4CE6F17C8的FPGA开发流程(以半加器为例)”一文,本例只对不同之处进行说明。
本例工程放在D:\EDA_FPGA\Exam_5文件夹下,工程名称为Exam_5。有四个模块文件,一个名称为seg_count.v,设置为顶层实体,另外三个名称分别为seg_decode.v、count_m10.v和count_m6.v,用于提供例化。其余步骤与“基于EP4CE6F17C8的FPGA开发流程”中的一样。
接下来看管脚约束,本例中只用到了两个数码管,一共有10个引脚,再加上时钟晶振和复位按钮,一共12个。具体的端口分配如下图所示。
对于未用到的引脚设置为三态输入方式,多用用途引脚全部做为普通I/O端口,电压设置为3.3-V LVTTL(与”基于EP4CE6F17C8的FPGA开发流程“中的一样)。需要注意,程序中的每个端口都必须为其分配管脚,如果系统中存在未分配的I/O,软件可能会进行随机分配,这将造成不可预料的后果,存在烧坏FPGA芯片的风险。
接下来对工程进行编译,编译完成后,可查看一下逻辑器件的消耗情况,如下图所示。
另外,还可以点击菜单Tools->Netlist Viewers->RTL Viewer,查看一下生成的RTL电路图,如下图所示。
最后进行程序下载,并查看结果。下面是两位数码管动态显示秒计数图片的其中几张。
当按下复位键后,所有数码管熄灭,如下图所示。