一、电路模块
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秒,并开始走时。