目录
0.写在最前
一、课程设计要求:
三、名词说明解释
四、Vivado代码实现部分
五、仿真测试程序
六、约束文件
七、开发板结果展示
八、关于改进/扩展
① 增加秒与 0.1s 之间的分隔符“.”号的点亮:
② 取消 0.1s,0.01s 显示,增加小时形成“时分.秒”的显示方式
③ 其它改进/扩展方式,在分和秒之间再加一个小数点:
九、写在最后
0.写在最前
本课程设计项目是我数电课上的课程设计,也听说是一些同学数电实验的自学项目,因为自己专业学习期间通过CSDN获得到很多帮助,因此在做完之后也希望能发到CSDN上,供同学们一起学习进步。在完成本课程设计项目的过程中也借鉴到CSDN其他博主的一些内容,特此感谢博主:初升的太阳LX 的帮助,此处附上原文链接:https://blog.csdn.net/weixin_43977564/article/details/96343437。
那份博客已经是两年前的作品,不很完整,所以我这里也算是将其补全,致敬学长。大佬勿喷~
一、课程设计要求:
(1)计时范围:00.00.00到59.59.99
(2)能够完成复位、启动、暂停功能
(6)用6位七段数码管显示读数
注意事项:
(1)时钟分频:秒表精度为0.01秒,我们使用的FPGA提供50MHZ信号,因此要用到一个500000HZ的分频器。
(2)实现小数点显示:
首先我们知道数码管里有七个led灯,用7bit,但是我们要用八位,在实现小数点的数码管中,将最高位置为1即可。
三、名词说明解释
信号 |
说明 |
CLR_L |
清零/启动 |
start_stop |
暂停/继续 |
clk_50M |
50MHz时钟信号 |
clk_s |
100Hz信号 |
disp_data_right0 |
分(十位) |
disp_data_right1 |
分(个位) |
disp_data_right2 |
秒(十位) |
disp_data_right3 |
秒(个位) |
disp_data_right4 |
0.1 秒 |
disp_data_right5 |
0.01 秒 |
seg |
数码管七段码 |
dig |
数码管共阴极位控码 |
四、Vivado代码实现部分
顶层模块final_top:
module final_top(
input clk_50M,
input CLR_L,
input start_stop,
output [7:0] seg,
output [5:0] dig
);
//分频部分
wire clk_s; //100HZ
clk_div u1(.clk_in(clk_50M),.clk_out(clk_s));
//计数器部分
wire[3:0] Q_0,Q_1,Q_2,Q_3,Q_4,Q_5;// 计数器的输出
wire cy_0,cy_1,cy_2,cy_3,cy_4,cy_5; //进位信号
modu10_counter u2(.clk(clk_s),.clr(CLR_L),.EN(start_stop),.cy(cy_0),.Q(Q_0));
modu10_counter u3(.clk(clk_s),.clr(CLR_L),.EN(cy_0),.cy(cy_1),.Q(Q_1));
modu10_counter u4(.clk(clk_s),.clr(CLR_L),.EN(cy_1),.cy(cy_2),.Q(Q_2));
modu6_counter u5(.clk(clk_s),.clr(CLR_L),.EN(cy_2),.cy(cy_3),.Q(Q_3));
modu10_counter u6(.clk(clk_s),.clr(CLR_L),.EN(cy_3),.cy(cy_4),.Q(Q_4));
modu6_counter u7(.clk(clk_s),.clr(CLR_L),.EN(cy_4),.cy(cy_5),.Q(Q_5));
//调用3位数码管显示模块
wire[3:0] disp_data_right0,disp_data_right1,disp_data_right2,disp_data_right3,disp_data_right4,disp_data_right5;
assign disp_data_right0 = Q_0;
assign disp_data_right1 = Q_1;
assign disp_data_right2 = Q_2;
assign disp_data_right3 = Q_3;
assign disp_data_right4 = Q_4;
assign disp_data_right5 = Q_5;
dynamic_led6 u8 (
.disp_data_right0(disp_data_right0),
.disp_data_right1(disp_data_right1),
.disp_data_right2(disp_data_right2),
.disp_data_right3(disp_data_right3),
.disp_data_right4(disp_data_right4),
.disp_data_right5(disp_data_right5),
.clk(clk_50M),
.seg(seg),
.dig(dig)
);
endmodule
时钟分频模块:
//分频器:
module clk_div(clk_in, clk_out);
input clk_in;
output reg clk_out=0;//用reg后面always中需要改变数值
reg [24:0] clk_div_cnt=0;
//分频为100Hz的信号
always @ (posedge clk_in)
begin
if (clk_div_cnt == 249999)
begin
clk_out = ~clk_out;
clk_div_cnt = 0;
end
else
clk_div_cnt = clk_div_cnt+1;
end
endmodule
计数器模块:用两种计数模块,一种模10,一种模6。
总共六个计数器,1,2,3,5这四个数码管用模10;4,6这两个数码管用模6。
//模10计数器:
module modu10_counter(clk, clr, EN, Q, cy);
input clk,clr;
input EN=0; //使能信号
output cy; //计数器进位输出
output reg [3:0] Q=0; // 计数器的输出
always @(posedge clk or negedge clr) //异步清零
begin
if (~clr) //清零有效
begin
Q <= 0;
end //完成清零操作,计数器输出为0
else if(EN == 1) //使能有效
begin
if (Q == 9) //计数+1,若低位已经到最大数9
begin
Q <= 0; //输出跳转到最小数0
end
else Q <= Q+1; //若输出未到最大数,则只加1
end
end
//计到最大数9,同时使能有效,输出Cy为1
assign cy = ((EN == 1) && (Q == 9))?1'b1:1'b0;
endmodule
//模6计数器:
module modu6_counter(clk, clr, EN, Q, cy);
input clk, clr;
input EN=0; //使能信号
output cy; //计数器进位输出
output reg [3:0] Q=0; // 计数器的输出
always @(posedge clk or negedge clr) //异步清零
begin
if (~clr) //清零有效
begin
Q <= 0;
end //完成清零操作,计数器输出为0
else if(EN == 1) //使能有效
begin
if (Q == 5) //计数+1,若低位已经到最大数5
begin
Q <= 0; //输出跳转到最小数0
end
else Q <= Q+1; //若输出未到最大数,则加1
end
end
//计到最大数5,同时使能有效,输出Cy为1
assign cy = ((EN == 1) && (Q == 5))?1'b1:1'b0;
endmodule
时钟动态显示模块:
//动态显示模块:
module dynamic_led6(disp_data_right0,disp_data_right1,disp_data_right2,disp_data_right3,disp_data_right4,disp_data_right5,clk,seg,dig);
input [3:0] disp_data_right0;
input [3:0] disp_data_right1;
input [3:0] disp_data_right2;
input [3:0] disp_data_right3;
input [3:0] disp_data_right4;
input [3:0] disp_data_right5;
input clk;
output reg [7:0] seg;
output reg [5:0] dig;
//分频为1KHz,十分之一倍的最小计时单位
reg [24:0] clk_div_cnt=0;
reg clk_div=0;
always @ (posedge clk)
begin
if (clk_div_cnt == 24999)
begin
clk_div = ~(clk_div);
clk_div_cnt = 0;
end
else
clk_div_cnt = clk_div_cnt+1;
end
//6进制计数器
reg [2:0] num=0;
always @ (posedge clk_div)
begin
if (num >= 5)
num = 0;
else
num = num+1;
end
//译码器
always @ (num)
begin
case(num)
0:dig = 6'b111110;
1:dig = 6'b111101;
2:dig = 6'b111011;
3:dig = 6'b110111;
4:dig = 6'b101111;
5:dig = 6'b011111;
default: dig = 0;
endcase
end
//选择器,确定显示数据
reg [3:0] disp_data=0;
always @ (num)
begin
case(num)
0:disp_data = disp_data_right0;
1:disp_data = disp_data_right1;
2:disp_data = disp_data_right2;
3:disp_data = disp_data_right3;
4:disp_data = disp_data_right4;
5:disp_data = disp_data_right5;
default: disp_data = 0;
endcase
end
//显示译码器
always@(disp_data)
begin
case(disp_data)
4'h0: seg = 8'h3f;// DP,GFEDCBA
4'h1: seg = 8'h06;
4'h2: seg = 8'h5b;
4'h3: seg = 8'h4f;
4'h4: seg = 8'h66;
4'h5: seg = 8'h6d;
4'h6: seg = 8'h7d;
4'h7: seg = 8'h07;
4'h8: seg = 8'h7f;
4'h9: seg = 8'h6f;
4'ha: seg = 8'h77;
4'hb: seg = 8'h7c;
4'hc: seg = 8'h39;
4'hd: seg = 8'h5e;
4'he: seg = 8'h79;
4'hf: seg = 8'h71;
default: seg = 0;
endcase
end
endmodule
五、仿真测试程序
这里主要对seg能否正确计数进行了仿真,如果还想仿真其他功能,可以自行设计。我在仿真这里也卡了一段时间来着,大家如果遇到问题也不必着急,慢慢来就好。我当时的问题是seg始终输出错误,起初我以为是模块太多关系复杂,最后是因为我在有一个模块里没有对reg信号初始化的原因,上面的代码是更改过后的了,应该没有问题。(写在这里也提醒下我自己....┭┮﹏┭┮)
module clk_sim();
reg clk_50M;
reg CLR_L=1;
reg start_stop=1;
wire [7:0] seg;
wire [5:0] dig;
final_top test1(clk_50M,CLR_L,start_stop,seg,dig);
initial begin
clk_50M = 0;
end
always #10 clk_50M = ~(clk_50M); //每隔10ns反相一次,即50MHZ
endmodule
这是我的仿真波形:
六、约束文件
set_property PACKAGE_PIN G12 [get_ports {dig[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[0]}]
set_property PACKAGE_PIN H13 [get_ports {dig[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[1]}]
set_property PACKAGE_PIN M12 [get_ports {dig[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[2]}]
set_property PACKAGE_PIN N13 [get_ports {dig[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[3]}]
set_property PACKAGE_PIN N14 [get_ports {dig[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[4]}]
set_property PACKAGE_PIN N11 [get_ports {dig[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {dig[5]}]
set_property PACKAGE_PIN L13 [get_ports {seg[7]}]
set_property PACKAGE_PIN M14 [get_ports {seg[6]}]
set_property PACKAGE_PIN P13 [get_ports {seg[5]}]
set_property PACKAGE_PIN K12 [get_ports {seg[4]}]
set_property PACKAGE_PIN K13 [get_ports {seg[3]}]
set_property PACKAGE_PIN L14 [get_ports {seg[2]}]
set_property PACKAGE_PIN N12 [get_ports {seg[1]}]
set_property PACKAGE_PIN P11 [get_ports {seg[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {seg[0]}]
set_property PACKAGE_PIN D4 [get_ports {clk_50M}]
set_property IOSTANDARD LVCMOS33 [get_ports {clk_50M}]
set_property PACKAGE_PIN F3 [get_ports {CLR_L}]
set_property IOSTANDARD LVCMOS33 [get_ports {CLR_L}]
set_property PACKAGE_PIN H4 [get_ports {start_stop}]
set_property IOSTANDARD LVCMOS33 [get_ports {start_stop}]
七、开发板结果展示
我们通过与互联网在线秒表器进行了比较,在线秒表器如图所示:
互联网在线秒表器时间精度为0.001毫秒,非常适合用来检验我们的课设结果。
检验手法如下:考虑到比较过程中可以通过计算不同时刻二者的差值来检验误差是否增大,所以二者是否同时开始并不重要。
我们通过拍摄不同时刻的对比图来进行误差分析,首先记录了11s,36s,1min,2min四个时刻,如下图所示:
整理如下表:
互联网在线秒表器t1(0.001s) |
课程设计秒表计时器t2(0.01s) |
差值Δt/s (t2-t1) |
误差/s (t2-t1-Δt) |
平均误差/s |
11.387 |
11.57 |
0.193 |
0 |
0.027 |
36.598 |
36.80 |
0.202 |
0.009 |
|
76.033 |
76.24 |
0.207 |
0.014 |
|
151.502 |
151.78 |
0.278 |
0.085 |
在0.1s级以下,且除第四项读数较为模糊导致的相对误差较大以外,误差变化不大,平均误差更是非常小,达到了0.027s,短时间内人眼无法分辨,可以认为达到了很好的精度。
进而,考虑到所记录时刻均在较为窄的时间内,可能误差还不足以体现,故下面又记录了7分、14分、19分和31分的四个时刻,类比上述分析方法进行分析,记录结果如下图所示:
整理如下表(仅记录秒.毫秒):
互联网在线秒表器t1(0.001s) |
课程设计秒表计时器t2(0.01s) |
差值Δt/s (t2-t1) |
误差/s (t2-t1-Δt) |
平均误差/s |
46.781 |
46.96 |
0.179 |
0 |
0.014 |
53.900 |
54.09 |
0.190 |
0.011 |
|
18.527 |
18.73 |
0.203 |
0.024 |
|
04.330 |
04.53 |
0.200 |
0.021 |
同样均在0.1s级以下,相比反而误差均有所减小,平均误差更是减少了一半,变为0.014s。
综上,课程设计秒表计时器体现出的误差特征为:在较小或较大的时间尺度内,均能有效地保证其精度,误差均在0.1s数量级内;但同时,随着时间的增长,体现为误差有逐渐减小的趋势,在量程范围内不断趋近互联网在线秒表器的趋势。
通过查阅相关文献[1],总结可能的原因如下:
“由于信号在FPGA的内部走线和通过逻辑单元时造成的延迟,在多路信号变化的瞬间,组合逻辑的输出常常产生小的尖峰,即毛刺信号,这是由FPGA内部结构特性决定的。”
对应到课程设计中,本次设计电路中存在计数器以及译码器,必然伴随着多路信号的同时跳变,因为电路有些复杂,各支路到达终点的延时必然有很多不一致,会出现不少毛刺现象。
对应的改进措施如下:
对于计数器毛刺现象,可以改用格雷码计数消除计数型毛刺;对于动态扫描译码模块毛刺现象,可以采用锁存法或采样法消除毛刺。
八、关于改进/扩展
① 增加秒与 0.1s 之间的分隔符“.”号的点亮:
对应原设计程序只需修改动态显示模块,使得位码DIG4,段码DP即秒(个位)信号所在七段数码管小数点位置点亮即可。
故增加一个八位小数点信号decimal_point,改进后dynamic_led6程序如下:
module dynamic_led6(disp_data_right0,disp_data_right1,disp_data_right2,disp_data_right3,disp_data_right4,disp_data_right5,clk,seg,dig);
input [3:0] disp_data_right0;
input [3:0] disp_data_right1;
input [3:0] disp_data_right2;
input [3:0] disp_data_right3;
input [3:0] disp_data_right4;
input [3:0] disp_data_right5;
input clk;
output reg [7:0] seg;
output reg [5:0] dig;
//分频为1KHz
reg [24:0] clk_div_cnt=0;
reg clk_div=0;
always @ (posedge clk)
begin
if (clk_div_cnt == 24999)
begin
clk_div = ~(clk_div);
clk_div_cnt = 0;
end
else
clk_div_cnt = clk_div_cnt+1;
end
//6进制计数器
reg [2:0] num=0;
always @ (posedge clk_div)
begin
if (num >= 5)
num = 0;
else
num = num+1;
end
//译码器
always @ (num)
begin
case(num)
0:dig = 6'b111110;
1:dig = 6'b111101;
2:dig = 6'b111011;
3:dig = 6'b110111;
4:dig = 6'b101111;
5:dig = 6'b011111;
default: dig = 0;
endcase
end
//选择器,确定显示数据
reg [3:0] disp_data=0;
reg [7:0] decimal_point=0;
always @ (num)
begin
decimal_point = 0;
case(num)
0:disp_data = disp_data_right0;
1:disp_data = disp_data_right1;
2:
begin
disp_data = disp_data_right2;
decimal_point = 8'h80;
end
3:disp_data = disp_data_right3;
4:disp_data = disp_data_right4;
5:disp_data = disp_data_right5;
default: disp_data = 0;
endcase
end
//显示译码器
always@(disp_data)
begin
case(disp_data)
4'h0: seg = 8'h3f + decimal_point;// DP,GFEDCBA
4'h1: seg = 8'h06 + decimal_point;
4'h2: seg = 8'h5b + decimal_point;
4'h3: seg = 8'h4f + decimal_point;
4'h4: seg = 8'h66 + decimal_point;
4'h5: seg = 8'h6d + decimal_point;
4'h6: seg = 8'h7d + decimal_point;
4'h7: seg = 8'h07 + decimal_point;
4'h8: seg = 8'h7f + decimal_point;
4'h9: seg = 8'h6f + decimal_point;
4'ha: seg = 8'h77 + decimal_point;
4'hb: seg = 8'h7c + decimal_point;
4'hc: seg = 8'h39 + decimal_point;
4'hd: seg = 8'h5e + decimal_point;
4'he: seg = 8'h79 + decimal_point;
4'hf: seg = 8'h71 + decimal_point;
default: seg = 0;
endcase
end
endmodule
对应结果呈现为:
② 取消 0.1s,0.01s 显示,增加小时形成“时分.秒”的显示方式
对应此时分频应从100HZ变到1HZ,从模6、模10、模6、模10、模10、模10的计数器分布变为模10、模10、模6、模10、模6、模10的计数器分布。
因为设计模块中采用了顶层模块例化多个设计模块的方式,故改进显示方式只需在顶层模块中修改例化以及修改分频模块即可,较为方便简单,此处不再赘述,仅以第一种改进方式做主要阐述与展示。
③ 其它改进/扩展方式,在分和秒之间再加一个小数点:
使用①方式改进确实能够有效区分出秒和0.1秒之间的关系,但根据其改进结果同时又发现分和秒之间的关系在开发板上体现的并不清晰,故考虑进一步改进,即在分和秒之间再添加一个小数点。
该改进方法对应修改原设计程序十分简单,仅需要将①中所修改好的dynamic_led6程序中的选择器部分的这条语句:
4:disp_data = disp_data_right4;
修改为:
4:begin
disp_data = disp_data_right4;
decimal_point = 8'h80;
end
即可。
对应改进结果呈现为:
可以很方便地区分出“分.秒.毫秒”,以及对应②中显示出“时.分.秒”的格式,改进效果较为明显,视觉效果更加舒适。
九、写在最后
初来乍到,水平有限,大家多多指正呀!向电子科大uu们挥挥手,很高兴来到CSDN o(* ̄▽ ̄*)ブ,让我们一起学习,一起努力叭!ε=( o`ω′)ノ
补充:若对uu们有用,请在报告中注明参考来源哦~养成习惯,投币点赞(bushi文章来源:https://www.toymoban.com/news/detail-458986.html
引用格式如下:https://blog.csdn.net/witch_poster/article/details/118068350文章来源地址https://www.toymoban.com/news/detail-458986.html
到了这里,关于Vivado设计秒表计时器实现00分00.00秒到59分59.99秒的计时(verilog语言)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!