基于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个按键的原理图如下图所示。
其实物图如下所示。
按键的引脚分配见下表。
二、实验代码
本例使用6个数码管显示时钟的时、分、秒,时与分之间及分与秒之间通过小数点来分隔,按键reset为复位,key1为调校,key2为增加,key3为减少,代码使用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
再编写一个看带小数点显示的字形解码程序,模块名称为seg_decode_dot,文件名称为seg_decode_dot.v,代码如下。
module seg_decode_dot( input[3:0] data, //显示的字形,可显示0~9十个字形,所以需要4位 output reg[7:0] seg //字形编码,包含小数点,共8位 ); always@(*) //敏感信号为所有输入量 begin case(data) 4'd0:seg <= 8'b0100_0000; //字形0的编码(带小数点) 4'd1:seg <= 8'b0111_1001; //字形1的编码(带小数点) 4'd2:seg <= 8'b0010_0100; //字形2的编码(带小数点) 4'd3:seg <= 8'b0011_0000; //字形3的编码(带小数点) 4'd4:seg <= 8'b0001_1001; //字形4的编码(带小数点) 4'd5:seg <= 8'b0001_0010; //字形5的编码(带小数点) 4'd6:seg <= 8'b0000_0010; //字形6的编码(带小数点) 4'd7:seg <= 8'b0111_1000; //字形7的编码(带小数点) 4'd8:seg <= 8'b0000_0000; //字形8的编码(带小数点) 4'd9:seg <= 8'b0001_0000; //字形9的编码(带小数点) default:seg <= 7'b111_1111; //默认不显示 endcase end endmodule
接下来编写模10和模6的两个带增减调校的计数模块,名称分别为count_m10和count_m6。先看count_m10的模块,文件名称为count_m10.v,代码如下。
module count_m10( input clk, //板载50HMz系统时钟 input rst_n, //复位按键 input set, //计数增加 input clr, //计数减少 input en, //计数使能位 output reg[3:0]data_out, //计数值,从0~9共10位,所以用4位 output reg t, //进位位 output reg c //借位位 ); always@(posedge clk or negedge rst_n) //敏感信号为时钟上沿或复位下沿 begin if(rst_n==0) //低电平复位 begin data_out <= 4'd0; //复位时计数值及进位位清零 t <= 1'd0; c <= 1'd0; end else if(en || set) //如果计数使能或增加位有效,则执行增计数 begin if(data_out==4'd9) //如果计数到达9时 begin t<= 1'b1; //进位位置1 data_out <= 4'd0; //计数值清零 end else begin t <= 1'b0; //否则进位位清零,计数值加1 data_out <= data_out + 4'd1; end end else if(clr) //如果减少位有效,则执行减计数 begin if(data_out != 4'd0) //如果没有减到0,则执行减1 data_out <= data_out - 1'd1; else begin c <= 1'b1; //否则借位位置1 data_out <= 4'b1001; //计数值回到9 end end else //如果计数不使能,进、借位位置0 begin t <= 1'b0; c <= 1'b0; end end endmodule
再来看count_m6的模块,文件名称为count_m6.v,代码如下。
module count_m6( input clk, //板载50HMz系统时钟 input rst_n, //复位按键 input clr, //计数减少 input en, //计数使能位 output reg[3:0]data_out, //计数值,从0~5共6位 output reg t //进位位 ); always@(posedge clk or negedge rst_n) //敏感信号为时钟上沿或复位下沿 begin if(rst_n==0) //低电平复位 begin data_out <= 4'd0; //复位时计数值及进位位清零 t <= 1'd0; end else if(en) //如果计数使能,则执行增计数 begin if(data_out==4'd5) //如果计数到达5时 begin t<= 1'b1; //进位位置1 data_out <= 4'd0; //计数值清零 end else begin t <= 1'b0; //否则进位位清零,计数值加1 data_out <= data_out + 4'd1; end end else if(clr) //如果减少位有效,则执行减计数 begin if(data_out != 4'd0) data_out <= data_out - 1'd1; //如果没有减到0,则执行减1 else begin data_out <= 4'b0101; //否则计数值回到5 end end else //如果计数不使能,进位位置0 t <= 1'b0; end endmodule
接下来编写一个模60的带增减调校的计数模块,但它是通过例化前模10和模6模块来实现的,模块名称为count_m60,文件名称为count_m60.v,代码如下。
module count_m60( input clk, //板载50HMz系统时钟 input rst, //复位按键 input set, //计数增加 input clr, //计数减少 input en, //计数使能位 output [7:0] data, //计数值从00~59共,使用BCD方式 output t //进位位 ); wire [3:0] count_data0,count_data1; //定义计数值的个位和十位 wire t0,t1,c0; //定义个位和十位的进位位及个位的借位 //下面例化个位计数单元(十进制) count_m10 u0(.clk(clk), .rst_n(rst), .set(set), .clr(clr), .en(en), .data_out(count_data0), .t(t0), .c(c0)); //下面例化十位计数单元(六进制) count_m6 u1(.clk(clk), .rst_n(rst), .clr(c0), .en(t0), .data_out(count_data1), .t(t1)); assign t = t1; //连接输出高位进位位 assign data = {count_data1, count_data0}; //并位连接输出60进制计数值 endmodule
接下来是模24的带增减调校的计数模块,模块名称为count_m24,文件名称为count_m24.v,代码如下。
module count_m24( input clk, //板载50HMz系统时钟 input rst_n, //复位按键 input set, //计数增加 input clr, //计数减少 input en, //计数使能位 output reg[7:0]data_out //计数值从00~23共,使用BCD方式 ); always@(posedge clk or negedge rst_n) //敏感信号为时钟上沿或复位下沿 begin if(rst_n==0) //低电平复位 begin data_out <= 8'd0; //复位时计数值清零 end else if(en || set) //如果计数使能或增加位有效,则执行增计数 begin if(data_out == 8'b00100011) //如果计数值BCD码为23,则计数值清零 begin data_out <= 8'd0; end else if(data_out[3:0] == 4'b1001) //如果计数值低位等于9,则低位清零,高位加1 begin data_out[3:0] <= 4'd0; data_out[7:4] <= data_out[7:4] + 1'd1; end else begin data_out <= data_out + 1'd1; //否则计数值加1 end end else if(clr) //如果减少位有效,则执行减计数 begin if(data_out[3:0] != 4'd0) //如果个位没有减到0,则个位减1 data_out[3:0] <= data_out[3:0] - 1'd1; else if(data_out[7:4] != 4'd0) //如果十位没减到0,则十位减1,个位回到9 begin data_out[3:0] <= 4'b1001; data_out[7:4] <= data_out[7:4] - 1'd1; end else begin data_out <= 8'b00100011; //个位十位都减到0,再来一个时钟回到23 end end end endmodule
接下来编写调校按键模块,模块名称为key,文件名称为key.v,代码如下。
module key( input clk, //板载50HMz系统时钟 input rst, //复位按键 input [2:0] key_h, //按键输入 output [2:0] key_press //按键键值 ); wire key = key_h[0] & key_h[1] & key_h[2]; //定义三个按键相与 reg[3:0] keyr; //定义按键存储变量 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(!rst) //低电平复位 keyr <= 4'b1111; //复位时按键值为全1 else keyr <= {keyr[2:0], key}; //相当于每个时钟之后用key值向左填充keyr end wire key_neg = ~keyr[2] & keyr[3]; //按键按下时钟打三拍之后判定有下降沿 wire key_pos = keyr[2] & ~keyr[3]; //按键释放时钟打三拍之后判定有上升沿 //定时计数20ms时间,用于对按键的消抖判断 reg[19:0] cnt; always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(!rst) //低电平复位时计数值清零 cnt <= 20'd0; else if(key_pos || key_neg) //如果有上升沿或下降沿发生,计数值清零 cnt <= 20'd0; else if(cnt < 20'd999_999) //如果未计到20ms,则继续加1计数 cnt <= cnt + 20'd1; else cnt <= 20'd0; //到20ms,计数值清零 end //定时采集按键值 reg[2:0] key_halue[1:0]; //定义两个健值存储变量 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(!rst) //低电平复位时健值变量全部置1 begin key_halue[0] <= 3'b111; key_halue[1] <= 3'b111; end else begin key_halue[1] <= key_halue[0]; //两次键值相差一个时钟节拍,用于在不相同时产生一个变化脉冲 if(cnt == 20'd999_999) key_halue[0] <= key_h; //到20ms后,获取外部按键值 end end assign key_press = key_halue[1] & ~key_halue[0]; //按键值按下时产生一个变化脉冲 //wire[3:0] key_press = ~key_halue[1] & key_halue[0]; //按键值释放时产生一个变化脉冲 endmodule
最后编写时钟显示模块,并设置为顶层模块,模块名称为seg_clock,文件名称为seg_clock.v,代码如下。
module seg_clock( input clk, //板载50HMz系统时钟 input rst, //复位按键 input [2:0] key_n, //三个按键 output reg[7:0] seg7, //段码端口 output reg[5:0] bit //位选端口 ); wire t0,t1,t2; //定义进位信号 reg [25:0] cnt; //定义26位时钟计数器 reg sec; //定义秒信号 reg start_sec; //定义开始走时信号 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(rst == 0) //低电平复位 begin sec <= 1'b0; //秒计数清零 end else if(start_sec) begin 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 end //下面定义6个数码管显示数值的存储变量 wire [3:0] count_data0,count_data1,count_data2,count_data3,count_data4,count_data5; //下面定义时、分、秒的存储变量 wire [7:0] count_data_0,count_data_1,count_data_2; //下面定义6个数码管的字形码存储变量 wire [7:0] seg_0,seg_1,seg_2,seg_3,seg_4,seg_5; //下面例化秒的计数单元(六十进制) count_m60 u1(.clk(clk), .rst(rst), .set(set0), .clr(clr0), .en(sec), .data(count_data_0), .t(t0)); //下面例化分的计数单元(六十进制) count_m60 u2(.clk(clk), .rst(rst), .set(set1), .clr(clr1), .en(t0), .data(count_data_1), .t(t1)); //下面例化时的计数单元(二十四进制) count_m24 u3(.clk(clk), .rst_n(rst), .set(set2), .clr(clr2), .en(t1), .data_out(count_data_2)); //下面分别取出6个数码管的显示值 assign count_data0 = count_data_0[3:0]; assign count_data1 = count_data_0[7:4]; assign count_data2 = count_data_1[3:0]; assign count_data3 = count_data_1[7:4]; assign count_data4 = count_data_2[3:0]; assign count_data5 = count_data_2[7:4]; //下面例化秒的个位字形解码单元 seg_decode seg0(.data(count_data0), .seg(seg_0)); //下面例化秒的十位字形解码单元 seg_decode seg1(.data(count_data1), .seg(seg_1)); //下面例化分的个位字形解码单元 seg_decode_dot seg2(.data(count_data2), .seg(seg_2)); //下面例化分的十位字形解码单元 seg_decode seg3(.data(count_data3), .seg(seg_3)); //下面例化时的个位字形解码单元 seg_decode_dot seg4(.data(count_data4), .seg(seg_4)); //下面例化时的十位字形解码单元 seg_decode seg5(.data(count_data5), .seg(seg_5)); 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'd5) //如果扫描位置计数器已经到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显示秒的个位 if(flash_sec == 1'b0) //如果秒闪烁标志为0,则正常显示 begin bit <= 6'b111110; seg7 <= seg_0; end else //否则该位显示黑屏 begin bit <= 6'b111110; seg7 <= 8'hff; end 4'd1: //数码管1显示秒的十位 if(flash_sec == 1'b0) //如果秒闪烁标志为0,则正常显示 begin bit <= 6'b111101; seg7 <= seg_1; end else //否则该位显示黑屏 begin bit <= 6'b111101; seg7 <= 8'hff; end 4'd2: //数码管2显示分的个位 if(flash_min == 1'b0) //如果分闪烁标志为0,则正常显示 begin bit <= 6'b111011; seg7 <= seg_2; end else //否则该位显示黑屏 begin bit <= 6'b111011; seg7 <= 8'hff; end 4'd3: //数码管3显示分的十位 if(flash_min == 1'b0) //如果分闪烁标志为0,则正常显示 begin bit <= 6'b110111; seg7 <= seg_3; end else //否则该位显示黑屏 begin bit <= 6'b110111; seg7 <= 8'hff; end 4'd4: //数码管4显示时的个位 if(flash_hour == 1'b0) //如果时闪烁标志为0,则正常显示 begin bit <= 6'b101111; seg7 <= seg_4; end else //否则该位显示黑屏 begin bit <= 6'b101111; seg7 <= 8'hff; end 4'd5: //数码管5显示时的十位 if(flash_hour == 1'b0) //如果时闪烁标志为0,则正常显示 begin if (count_data5 == 4'd0) begin bit <= 6'b011111;//如果十位为0则不显示 seg7 <= 8'hff; end else //否则正常显示 begin bit <= 6'b011111; seg7 <= seg_5; end end else //否则该位显示黑屏 begin if (count_data5 == 4'd0) begin bit <= 6'b011111;//如果十位为0则不显示 seg7 <= 8'hff; end else begin bit <= 6'b011111; //否则正常显示 seg7 <= 8'hff; end end default: //数码管全部熄灭 begin bit <= 6'b111111; seg7 <= 8'hff; end endcase end wire [2:0] key_pressed; //定义健值存储变量 //下面例化按键扫描单元 key u4(.clk(clk), .rst(rst), .key_h(key_n), .key_press(key_pressed)); reg[2:0] flash; //定义时分秒显示闪烁标志 always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(!rst) //低电平复位 begin flash <= 3'd0; //闪烁标号清零 start_sec <= 1'b1; //走时使能 end else if(key_pressed[0]) //如果key1按下 begin flash <= flash + 3'd1; //闪烁标志加1 start_sec <= 1'b0; //走时禁止 end else if(flash == 3'd4) //如果闪烁标志值加到4 begin flash <= 3'd0; //闪烁标志清零 start_sec <= 1'b1; //走时使能 end end reg [22:0] flash_cnt; //定义23位闪烁计数器 reg flash_sec, flash_min, flash_hour; always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(rst == 0) //低电平复位全部计数器清零 begin flash_sec <= 1'b0; flash_min <= 1'b0; flash_hour <= 1'b0; flash_cnt <= 23'd0; end else if(flash_cnt == 23'd7_999_999) //时钟计数器到达0.16秒时(用于控制闪烁频率) begin flash_cnt <= 23'd0; //闪烁计数器清零 if(flash == 3'd1) //如果闪烁标志值为1,表示秒闪烁 flash_sec <= ~flash_sec; //秒闪烁标志取反,产生闪烁 else flash_sec <= 1'b0; //否则秒不闪烁,正常显示 if(flash == 3'd2) //如果闪烁标志值为2,表示分闪烁 flash_min <= ~flash_min; //分闪烁标志取反,产生闪烁 else flash_min <= 1'b0; //否则分不闪烁,正常显示 if(flash == 3'd3) //如果闪烁标志值为3,表示时闪烁 flash_hour <= ~flash_hour;//时闪烁标志取反,产生闪烁 else flash_hour <= 1'b0; //否则时不闪烁,正常显示 end else begin flash_cnt <= flash_cnt + 23'd1; //闪烁计数器加1,即来一次时钟脉冲加一次 end end reg set0,set1,set2; //定义秒、分、时的计数增加信号变量 always@(posedge clk) //敏感量为时钟上升沿 begin case(flash) //根据闪烁标志的值,区分秒、分、时的增加 3'd0: //值为0时,都不增加 begin set0 <= 1'b0; set1 <= 1'b0; set2 <= 1'b0; end 3'd1: //值为1时,key1按下秒增加 set0 <= key_pressed[1]; 3'd2: //值为2时,key1按下分增加 set1 <= key_pressed[1]; 3'd3: //值为3时,key1按下时增加 set2 <= key_pressed[1]; default: //默认都不增加 begin set0 <= 1'b0; set1 <= 1'b0; set2 <= 1'b0; end endcase end reg clr0,clr1,clr2; //定义秒、分、时的计数减少信号变量 always@(posedge clk) //敏感量为时钟上升沿 begin case(flash) //根据闪烁标志的值,区分秒、分、时的减少 3'd0: //值为0时,都不减少 begin clr0 <= 1'b0; clr1 <= 1'b0; clr2 <= 1'b0; end 3'd1: //值为1时,key2按下秒减少 clr0 <= key_pressed[2]; 3'd2: //值为2时,key2按下分减少 clr1 <= key_pressed[2]; 3'd3: //值为3时,key2按下时减少 clr2 <= key_pressed[2]; default: //默认都不减少 begin clr0 <= 1'b0; clr1 <= 1'b0; clr2 <= 1'b0; end endcase end endmodule
第二种方式,共有六个文件,它没有使用模10和模6的模块,而是直接写了一个模60的模块,模块名称为count_m60,文件名称仍为count_m60.v,代码如下。
module count_m60( input clk, //板载50HMz系统时钟 input rst, //复位按键 input set, //计数增加 input clr, //计数减少 input en, //计数使能位 output reg[7:0]data, //计数值从00~59共,使用BCD方式 output reg t //进位位 ); always@(posedge clk or negedge rst) //敏感信号为时钟上沿或复位下沿 begin if(rst==0) //低电平复位 begin data <= 8'd0; //复位时计数值及进位位清零 t <= 1'd0; end else if(set) //如果增加位有效,则执行增计数 begin if(data == 8'b01011001) //如果计数值BCD码为59,则计数值清零,进位位置1 begin data <= 8'd0; end else if(data[3:0] == 4'b1001) //如果计数值低位等于9,则低位清零,高位加1 begin data[3:0] <= 4'd0; data[7:4] <= data[7:4] + 1'd1; end else begin data <= data + 1'd1; //否则计数值加1,进位位清零 end end else if(clr) //如果减少位有效,则执行减计数 begin if(data[3:0] != 4'd0) //如果个位没有减到0,则个位执行减1 data[3:0] <= data[3:0] - 1'd1; else if(data[7:4] != 4'd0) //如果十位没减到0,则十位减1,个位回到9 begin data[3:0] <= 4'b1001; data[7:4] <= data[7:4] - 1'd1; end else begin data <= 8'b01011001; //个位十位都减到0,再来一个时钟回到59 end end else if(en) //如果计数使能,则执行计数,否则保持上一次的值不变 begin if(data == 8'b01011001) //如果计数值BCD码为59,则计数值清零,进位位置1 begin data <= 8'd0; t<= 1'b1; end else if(data[3:0] == 4'b1001) //如果计数值低位等于9,则低位清零,高位加1 begin data[3:0] <= 4'd0; data[7:4] <= data[7:4] + 1'd1; end else begin data <= data + 1'd1; //否则计数值加1,进位位清零 t<= 1'b0; end end else //如果计数不使能,进位位置0 t<= 1'b0; end endmodule
从上面的代码中可以看到,该模块与第一种方式中的不一样。其余五个文件(seg_clock.v、seg_decode.v、seg_decode_dot.v、count_m24.v、key.v)与第一种方式中的一样。
三、代码说明
1、本例把前面的数码管扫描、时钟走时和按键消抖等部分结合起来,形成了一个具备调校功能的数码管时钟。其中的详细内容请参考“基于EP4CE6F17C8的FPGA数码管动态显示实例”、“基于EP4CE6F17C8的FPGA数码管时钟显示实例”及“基于EP4CE6F17C8的FPGA键控灯实例”等章节。
2、这里主要讨论如何通过实例化的模块进行计数的增减控制。
3、在第二种方式中,由于模60的模块和模24的模块的计数都是自己实现的(没有例化其他模块),所以对于这两个计数模块,可通过传入增(set)或减(clr)信号来实现模块内部计数值的更新,所以只需要对以前的计数模块稍加修改即可。
4、对于第一种方式,由于模60的模块是由模10和模6两个模块例化来实现的,所以修改起来相对麻烦,必须把相关信号分层传入。在模60的模块中,通过例化传入增(set)或减(clr)信号,在此模块中,再通过例化模10的模块,把增(set)或减(clr)信号传入其中,同时还要考虑当该模块计数值减到0时的借位输出,以此信号提供给例化的模6的模块进行减计数。而增计数就借由模块原来的进位信号来提供。也就是说,模6的模块不用再传入增信号set,而是依靠模10来的进位信号就行了。同理,模6的模块也不用再传入减信号clr,而是依靠模10来的借位信号就行了。这种方式虽然简化了操作,但缺点是在调校到溢出时也会产生进位信号,所以还具备进一步改进的空间。
5、调校时的闪烁控制由时、分、秒各自的flash变量来承担,为节约器件且实现较好的闪烁效果,代码中使用了23位计数器,通过取反产生出约0.16秒的闪烁间隔,实践观察效果还不错。在显示控制中,当时、分、秒各自的闪烁变量(flash_sec、flash_min、flash_hour)为0时,正常显示,当为1时,显示为黑屏(即不显示)。通过每间隔0.16秒对相应的flash变量取反一次,就实现了闪烁效果。
6、对于设置按键key1,第一次按下,对应秒闪烁,第二次按下,对应分闪烁,第三次按下,对应时闪烁,第四次按下,恢复到走时状态(不闪烁)。只有在闪烁状态时,增加键key2和减少键key3才有效。当处于调校状态时,走时停止,调校结束后才开始走时。
四、实验步骤
FPGA开发的详细步骤请参见“基于EP4CE6F17C8的FPGA开发流程(以半加器为例)”一文,本例只对不同之处进行说明。
本例工程放在D:\EDA_FPGA\Exam_8文件夹下,工程名称为Exam_8。有八个模块文件,一个名称为seg_clock.v,设置为顶层实体,另外七个名称分别为seg_decode.v、seg_decode_dot.v、count_m10.v、count_m6.v、count_m60.v、count_m24.v和key.v,用于提供例化。其余步骤与“基于EP4CE6F17C8的FPGA开发流程”中的一样。
接下来看管脚约束,本例中6个数码管一共有14个引脚,再加上时钟晶振和复位以及其他三个按钮,一共19个。具体的端口分配如下图所示。
对于未用到的引脚设置为三态输入方式,多用用途引脚全部做为普通I/O端口,电压设置为3.3-V LVTTL(与”基于EP4CE6F17C8的FPGA开发流程“中的一样)。需要注意,程序中的每个端口都必须为其分配管脚,如果系统中存在未分配的I/O,软件可能会进行随机分配,这将造成不可预料的后果,存在烧坏FPGA芯片的风险。
接下来对工程进行编译,编译完成后,可查看一下逻辑器件的消耗情况,第一种方式的器件消耗量如下图所示。
第二种方式的器件消耗量如下图所示。
可以看到,第二种方式消耗的器件更多一些,但它解决了调校过程中的进位问题,因此还是推荐使用第二种方式。另外,还可以点击菜单Tools->Netlist Viewers->RTL Viewer,查看一下生成的RTL电路图。
最后进行程序下载,并查看结果。下图为时钟正常走时的图片。
下图为按下一次key1键对秒进行调校时的图片,秒显示部分闪烁。
下图为按下key2键,秒计数值增加。
下图为按下key3键,秒计数值减小。
以上为秒调校的情况,秒调校好后,再按一次key1键,分显示部分闪烁,可进行加减调校,完毕后再接一次key1键,调校时部分,全部完成后再按一次key1键,调校结束,进入到走时状态。在走时状态,按key2和key3键均不起作用。当按下reset键时,显示熄灭,释放reset键时,显示0时0分0秒,并开始走时。