简易数字钟

前言

        使用FPGA开发简易数字钟项目旨在通过实现时钟信号生成、秒分小时计时、以及数字显示等功能,帮助我们掌握FPGA的编程和电路设计。具体来说,我们将设计FPGA以产生稳定的时钟信号,创建计时模块处理时间进位,控制显示器展示当前时间,并设计用户接口来设置和调整时间。这个项目不仅加深对FPGA操作的理解,还提供了将理论应用于实际电子设计的机会。

正文

一、 简易数字钟

        1.项目需求

        利用六位八段共阳极数码管显示时间,显示时间的格式为:小时:分钟:秒;小时,分钟,秒各占用两个数码管,时间的进制为24小时进制,那么在数码管上看到时间最大值为:23:59:59。

        2.技术介绍

        本工程要使用到数码管,如何用FPGA驱动数码管:数码管驱动电路讲解

需要6个八段数码,下面介绍一位数码管怎么驱动:

        1.静态驱动方式

led静态显示时,其公共端接地(共阴极)或接电源(共阳极),各段选线分别与I/O口接线相连。 要显示字符,直接在I/O线发送相应的字段码。

优点: 静态显示结构简单,显示方便,要显示某个字符直接在IO线上发送相应的字段码

缺点: 一根数码管需要8根IO线,数码管比较多时候,非常占用IO线。

        2.动态驱动方式

        动态驱动方式是将所有的数码管的段选线并接在一起,用一个IO接口控制,公共端并不是直接接地(共阴极)或者电源(共阳极),而是通过相应的IO接口控制。如下图

        六位数码管动态驱动方式连接图

        每个数码管的公共端与FPGA一个IO相连,第一步使最右边一个数码管的公共端为1(位选端),其余数码管公共端为0(位选端),同时在(a,b,c,d,e,f,g,dp)(段选端)端上发送右边第一个数码管的字段码,这时候只有右边的第一个数码管显示,其余不显示;

        第二步使右边第二个数码管的公共端为1(位选端),其余的公共端为0(位选端),同时在(a,b,c,d,e,f,g,dp)(段选端)端上发送右边第二个数码管的字段码,这时候,只有右边第二个数码管显示,其余不显示;

        以此类推,直到最后一个,这样子4个数码管轮流显示相应的信息,一遍显示完毕,隔一段时间,又这样循环显示。

        从计算机角度,每个数码管隔一段时间才显示一次,但是由于人的视觉暂留效应,只要隔离时间足够短,循环的周期足够长,美妙达到24次以上,看起来数码管就一直稳定显示了,这就是动态显示原理。这里我们为节省IO将六个位选端口用三个位选信号的译码表示:

位选信号位选端口
其他0(000000)
0011(100000)
0102(010000)
0113(001000)
1004(000100)
1015(000010)
1106(000001)

数码管显示码表:(网上只找到共阴极数码管表,共阳极只需要取反就行:1变0,0变1)

        3.数字钟驱动原理

        秒数据包含秒十位数据sec1[3:0],秒个位数据sec0[3:0],秒个位数据变化范围在:0到9,秒十位数据变化范围在:0到5;通过定义一个1秒钟的延迟计数器cnt,当其计数到最大值时秒个位数据加一;秒十位数据加一的条件为:秒个位数据等于9且延迟计数器计数到最大值。

       分钟数据包含分钟十位数据min1[3:0](0到5)和分钟个位数据min0[3:0](0到9),分钟个位变化一次的条件为:秒个位数据为9且秒十位数据为5且延迟计数器等于最大值,分钟十位数据变化一次的条件为:分钟个数数据等于9且秒个位数据为9且秒十位数据为5且延迟计数器等于最大值;

       小时数据包含小时十位数据hour1[3:0](0到2),小时个位数据hour0[3:0](0到9),小时个位数据变化一次的条件为:需要先判断小时十位数据,如果小时十位数据小于2,那么小时个位数据的变化范围就在0到9,反之,小时个位数据的变化范围就在0到3;个位数据变化一次的条件为:分钟十位数据等于5且分钟个数数据等于9且秒个位数据为9且秒十位数据为5且延迟计数器等于最大值;

        小时十位数据变化的条件为:如果小时十位数据小于2,那么其变化一次的条件为:小时个位数据等于9且分钟十位数据等于5且分钟个数数据等于9且秒个位数据为9且秒十位数据为5且延迟计数器等于最大值;如果小时十位数据等于2,那么其变化一次的条件为:小时个位数据等3且分钟十位数据等于5且分钟个数数据等于9且秒个位数据为9且秒十位数据为5且延迟计数器等于最大值;

        3.顶层架构

        顶层框架:整体分两大模块数字时钟控制模块与显示模块

数字时钟控制模块

        通过one_sec对基础50MHZ时钟分频出1hz时钟,作为sec0_ctrl(秒个位)驱动时钟,该模块的进位信号作为sec1_ctrl(秒十位)的驱动时钟......直到驱动时钟十位hour1_ctrl,数字时钟设计完毕。

        4.端口描述

clk基础时钟(50MHZ)
rst_n复位按键(低电平有效)
[2:0] sel位选信号
[7:0] seg段选信号

二、代码验证

        one_sec模块:产生1s信号,时钟分频

/*一秒钟控制模块*/
module one_sec(

	input clk,
	input rst_n,
	
	output flag//1s时间输出
);

reg [25:0] cnt;//1S
parameter MAX = 26'd50_000_000;
//50Mhz,一个周期20ns
//记50M次后,1s的时间到

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		cnt <= 26'd0;
	else
		if(cnt < MAX - 1)
			cnt <= cnt + 26'd1;
		else
			cnt <= 26'd0;//记50M次后
end 

assign flag = (cnt == MAX - 1)?1'b1:1'b0;//组合逻辑描述,计时1s时间到

endmodule 

      sec0_ctrl模块:秒个位模块,产生秒个位数码管数据,秒十位驱动信号  

/*秒个位数据控制模块*/
module sec0_ctrl(

	input clk,
	input rst_n,
	input flag,
	
	output reg [3:0] sec0,//秒个位数据信号
	output flag_10sec//进位信号
);

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		sec0 <= 4'd0;
	else
		if(flag == 1)
			if(sec0 == 9)//最大记9
				sec0 <= 4'd0;
			else
				sec0 <=  sec0 + 4'd1;
		else
			sec0 <= sec0;
end 

assign flag_10sec = ((sec0 == 9)&&(flag == 1))?1'b1:1'b0;
//计数器记到9,下一个秒信号到来时,产生秒十位信号

endmodule 

        sec1_ctrl模块:秒十位控制模块,产生秒十位数码管信号,分钟个位驱动信号

/*秒十位控制模块*/
module sec1_ctrl(

	input clk,
	input rst_n,
	input flag,
	
	output reg [3:0] sec1,//秒十位数据信号
	output flag_min//分钟个位使能信号
);

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		sec1 <= 4'd0;
	else
		if(flag == 1)
			if(sec1 < 5)//最大5
				sec1 <= sec1 + 4'd1;
			else
				sec1 <= 4'd0;
		else
			sec1 <= sec1;
end 

assign flag_min = ((sec1 == 5)&&(flag == 1))?1'b1:1'b0;
//秒十位记到5,下一个秒个位进位信号到来时,产生分钟个位使能信号

endmodule 

        min0_ctrl模块:产生分钟个位数据信号,分钟十位使能信号

/*分钟个位控制模块*/
module min0_ctrl(

	input clk,
	input rst_n,
	input flag,
	
	output reg [3:0] min0,//分钟个位数据信号
	output flag_10min//分钟十位使能信号
);

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		min0 <= 4'd0;
	else
		if(flag == 1)
			if(min0 < 9)//最大记9
				min0 <= min0 + 4'd1;
			else
				min0 <= 4'd0;
		else
			min0 <= min0;
end 

assign flag_10min = ((min0 == 9)&&(flag == 1))?1'b1:1'b0;
//分钟个位记到9,下一个秒十位进位信号到来,产生分钟十位使能信号

endmodule 

        min1_ctrl模块:产生秒十位数据信号,小时个位使能信号

/*分钟十位控制模块*/
module min1_ctrl(

	input clk,
	input rst_n,
	input flag,
	
	output reg [3:0] min1,//秒十位数据信号
	output flag_hour//小时个位使能信号
);

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		min1 <= 4'd0;
	else
		if(flag == 1)
			if(min1 < 5)//最大记到5
				min1 <= min1 + 4'd1;
			else
				min1 <= 4'd0;
		else
			min1 <= min1;
end 

assign flag_hour = ((min1 == 5)&&(flag == 1))?1'b1:1'b0;
//分钟十位记到5,下一个分钟个位进位信号到来,产生小时个位使能信号

endmodule 

        hour0_ctrl模块:产生小时个位数据信号,小时十位使能信号

/*小时个位控制模块*/
module hour0_ctrl(

	input clk,
	input rst_n,
	input flag,
	input [3:0] hour1,
	
	output reg [3:0] hour0,//小时个位数据信号
	output reg flag_10hour//小时十位使能信号
);

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		hour0 <= 4'd0;
	else
		if(hour1 < 2)//当小时十位信号小于2时,个位最大记到9
			begin
				if(flag == 1)
					if(hour0 < 9)
						hour0 <= hour0 + 4'd1;
					else
						hour0 <= 4'd0;
				else
					hour0 <= hour0;
			end 
		else//当小时十位信号等于2时,个位最大记到3
			begin
				if(flag == 1)
					if(hour0 < 3)
						hour0 <= hour0 + 4'd1;
					else
						hour0 <= 4'd0;
				else
					hour0 <= hour0;			
			end 
end 

always @(*) begin
//进位信号控制,根据小时十位的不同数据,产生不同的进位信号条件
	if(rst_n == 0)
		flag_10hour <= 1'b0;
	else
		if(flag == 1)
			begin
				case(hour1)
					4'd0: begin
								if(hour0 == 9)//十位信号为0,个位逢9进一
									flag_10hour <= 1'b1;
								else
									flag_10hour <= 1'b0;
							end 
					4'd1: begin
								if(hour0 == 9)//十位信号为1,个位逢9进一
									flag_10hour <= 1'b1;
								else
									flag_10hour <= 1'b0;
							end 
					4'd2: begin
							if(hour0 == 3)//十位信号为2,个位逢3进一
								flag_10hour <= 1'b1;
							else
								flag_10hour <= 1'b0;
						end 		
					default	:	flag_10hour <= 1'b0;
				endcase
			end 
		else
			flag_10hour <= 1'b0;
end  

endmodule 

        hour1_ctrl模块:产生小时十位数据信号

/*小时十位控制模块*/
module hour1_ctrl(

	input clk,
	input rst_n,
	input flag,
	
	output reg [3:0] hour1//小时十位数据信号
);

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		hour1 <= 4'd0;
	else
		if(flag == 1)
			if(hour1 < 2)//24小时制,最大记到2
				hour1 <= hour1 + 4'd1;
			else
				hour1 <= 4'd0;
		else
			hour1 <= hour1;
end 

endmodule 

        time_ctrl模块:时钟数据产生模块,连接时钟各个模块

module time_ctrl(

	input clk,
	input rst_n,
	
	output [23:0] data_out
);
//时钟数据产生模块
//连接时钟各个模块

wire flag;
wire flag_10sec;
wire [3:0] sec0;
wire flag_min;
wire [3:0] sec1;
wire flag_10min;
wire [3:0] min0;
wire flag_hour;
wire [3:0] min1;
wire flag_10hour;
wire [3:0] hour1;
wire [3:0] hour0;

one_sec /*#(.MAX(5))*/one_sec_inst(

			.clk(clk),
			.rst_n(rst_n),
			
			.flag(flag)
);

sec0_ctrl sec0_ctrl_inst(

			.clk(clk),
			.rst_n(rst_n),
			.flag(flag),
			
			.sec0(sec0),
			.flag_10sec(flag_10sec)
);

sec1_ctrl sec1_ctrl_inst(

			.clk(clk),
			.rst_n(rst_n),
			.flag(flag_10sec),
			
			.sec1(sec1),
			.flag_min(flag_min)
);

min0_ctrl min0_ctrl_inst(

			.clk(clk),
			.rst_n(rst_n),
			.flag(flag_min),
			
			.min0(min0),
			.flag_10min(flag_10min)
);

min1_ctrl min1_ctrl_inst(

			.clk(clk),
			.rst_n(rst_n),
			.flag(flag_10min),
			
			.min1(min1),
			.flag_hour(flag_hour)
);

hour0_ctrl hour0_ctrl_inst(

			.clk(clk),
			.rst_n(rst_n),
			.flag(flag_hour),
			.hour1(hour1),
			
			.hour0(hour0),
			.flag_10hour(flag_10hour)
);

hour1_ctrl hour1_ctrl_inst(

			.clk(clk),
			.rst_n(rst_n),
			.flag(flag_10hour),
			
			.hour1(hour1)
);

assign data_out = {hour1,hour0,min1,min0,sec1,sec0};//合并数据

endmodule 

        seg_driver模块:数码管驱动,产生数码管位选和段选信号

module seg_driver(

	input clk,
	input rst_n,
	input [23:0] data_in,//接收数字时钟信号
	
	output reg [2:0] sel,//位选信号
	output reg [7:0] seg//段选信号
);

reg [18:0] cnt;//1MS
parameter MAX = 19'd50_000;
//20ns记50000次
//产生1ms的延时
//位选信号1ms变换一次

reg [3:0] temp;

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		cnt <= 19'd0;
	else
		if(cnt < MAX - 1)//产生1ms的延时
			cnt <= cnt + 19'd1;
		else
			cnt <= 19'd0;
end 

//数码管位选信号控制逻辑
always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		sel <= 3'd0;
	else
		if(cnt == MAX - 1)//位选信号6位,1ms的延时到变化一次
			if(sel < 5)
				sel <= sel + 3'd1;
			else 
				sel <= 3'd0;
		else 
			sel <= sel;	
end 

always @(*) begin
	if(rst_n == 0)
		temp <= 4'd0;
	else
		case(sel)//位选信号到将数据输出到对应输出位上
			3'd0	:	temp <= data_in[23:20];
			3'd1	:	temp <= data_in[19:16];
			3'd2	:	temp <= data_in[15:12];
			3'd3	:	temp <= data_in[11:8];
			3'd4	:	temp <= data_in[7:4];
			3'd5	:	temp <= data_in[3:0];
			default	:	temp <= 4'd0;
		endcase
end 

always @(*) begin
	if(rst_n == 0)
		seg <= 8'hff;
	else
		case(temp)//段选信号,对应的数码管信号
			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'd10	:	seg <= 8'b1000_1000;//A
			4'd11	:	seg <= 8'b1000_0011;//b
			4'd12	:	seg <= 8'b1100_0110;//C
			4'd13	:	seg <= 8'b1010_0001;//d
			4'd14	:	seg <= 8'b1000_0110;//E
			4'd15	:	seg <= 8'b1000_1110;//F
			default	:	seg <= 8'hff;
		endcase 
end 

endmodule 

        easy_digital_clock模块:工程顶层模块进行连线

module easy_digital_clock(

	input clk,
	input rst_n,
	
	output [2:0] sel,
	output [7:0] seg
);

wire [23:0] data_out;//连线

time_ctrl time_ctrl_inst(

			.clk(clk),
			.rst_n(rst_n),
			
			.data_out(data_out)
);

seg_driver seg_driver_inst(

			.clk(clk),
			.rst_n(rst_n),
			.data_in(data_out),
			
			.sel(sel),
			.seg(seg)
);

endmodule 

        仿真文件

`timescale 1ns/1ps
module easy_digital_clock_tb;

	reg clk;
	reg rst_n;
	
	wire [2:0] sel;
	wire [7:0] seg;

easy_digital_clock easy_digital_clock_inst(

			.clk(clk),
			.rst_n(rst_n),
			
			.sel(sel),
			.seg(seg)
);

initial clk = 1;
always #10 clk = ~clk;

initial begin
	rst_n = 0;
	#200
	rst_n = 1;
	#20000000//100*60*60*24
	$stop;
end 

endmodule 

三、仿真验证

        工程模块较多,这里直接验证数字钟产生的数据是否正确:

观察小时信号的数据,再十位为2时个位最大记到3,为1时最大记到9,

观察黄线标记处,该时刻之前为02:59:59,后一秒时间为003:00:00,满足数字钟设计要求,

此处数字钟信号为23:59:59到00:00:00的跳转,即一天结束。

参考资料

数码管驱动

数字电子时钟

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张明阳.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值