1. 程式人生 > >基於FPGA的數字時鐘

基於FPGA的數字時鐘

一.數字時鐘設計

1.硬體資源:LCD1602液晶屏一塊,FPGA開發板一塊(A_C8V4);

2. 開發板資源:3顆獨立按鍵,LCD1602介面;

3. 功能設計:三種功能:a.時鐘功能;b.鬧鐘功能;c.校時功能;

4. 按鍵功能設計:按鍵1切換數字鐘模式,按鍵2調整數字鐘時鐘顯示(包括鬧鐘調時),按鍵3調整數字時鐘分鐘顯示(包括鬧鐘調分);

二.數字時鐘程式碼

1.數字時鐘頂層模組RTL檢視


1) 說明:這個為數字時鐘的頂層模組,按鍵消抖模組debkey,數字鐘功能模組digitalclk,LCD1602液晶顯示模組lcd_ip;

2) 埠

輸入:clk,reset,key;

輸出:lcd_e,lcd_rw,lcd_rs,lcd_data,beep;

3) 程式碼

//數字時鐘總模組
module digitalclk_top(clk,reset,key,lcd_rw,lcd_e,lcd_rs,lcd_data,beep);
	input clk;
	input reset;
	input [2:0]key;
	output lcd_rw;
	output lcd_rs;
	output lcd_e;
	output [7:0]lcd_data;
	output beep;
	
	wire [2:0]debkey;
	wire [255:0]disp;
	
	debkey U0
			(.clk(clk),
			.reset(reset),
			.key(key),
			.debkey(debkey));
				
	digitalclk U1
			(.clk(clk),
			.reset(reset),
			.key(debkey),
			.lcd_data_disp(disp),
			.beep(beep));
			
	lcd_ip U3
			(.clk(clk),
			.rst(reset),
			.data_buf(disp),
			.lcd_rw(lcd_rw),
			.lcd_rs(lcd_rs),
			.lcd_e(lcd_e),
			.lcd_data(lcd_data));
			
endmodule

2.按鍵消抖模組


1) 說明:這個模組為按鍵消抖模組,三顆按鍵;

2) 埠

輸入:clk,reset,key;

輸出:debkey;

3) 程式碼

請參考按鍵消抖

3. LCD模組


1) 說明:這個為LCD1602模組,其中data_buf為256位,每一個格為8位,一共有32個,所以32x8=256;

2) 埠

輸入:clk,rst,data_buf;

輸出:lcd_e,lcd_rs,lcd_rw,lcd_data;

 3)程式碼

請參考LCD模組

4.時鐘鍾功能模組



1) 說明:這個模組為數字鐘功能模組,包括數字鐘模式模組digitalclk_mode,數字鐘時鐘模組clock,數字鐘鬧鐘模組alarm,數字鐘校時模組adj,數字鐘資料處理選擇模組disp_sel,還有一個不和諧的鬧鈴鈴聲模組beep;

2) 埠

輸入:clk,reset,key;

輸出:beep,lcd_data_disp;

3) 程式碼

//數字時鐘頂層模組
module digitalclk(clk,reset,key,lcd_data_disp,beep);
	input clk;
	input reset;
	input [2:0]key;
	output [255:0]lcd_data_disp;
	output beep;
	
	wire [2:0]mode;
	wire [255:0]clock_disp,alarm_disp,adj_disp;
	wire [255:0]sync_clock,sync_adj;
	wire alarm_en;
	
	digitalclk_mode U4
			(.clk(clk),
			.reset(reset),
			.key(key),
			.mode(mode));
			
	clock U5
			(.clk(clk),
			.reset(reset),
			.mode(mode),
			.sync_clock(sync_clock),
			.lcd_data_disp(clock_disp));
			
	alarm U6
			(.clk(clk),
			.reset(reset),
			.mode(mode),
			.key(key),
			.lcd_data_disp(alarm_disp));
			
	adj U7
			(.clk(clk),
			.reset(reset),
			.mode(mode),
			.key(key),
			.sync_adj(sync_adj),
			.lcd_data_disp(adj_disp));
	
	disp_sel U8
			(.clk(clk),
			.mode(mode),
			.alarm_disp(alarm_disp),
			.clock_disp(clock_disp),
			.adj_disp(adj_disp),
			.sync_clock(sync_clock),
			.sync_adj(sync_adj),
			.lcd_data_disp(lcd_data_disp),
			.alarm_en(alarm_en));
			
	beep U9 
			(.clk(clk),
			.reset(reset),
			.alarm_en(alarm_en),
			.beep(beep));
	
endmodule

5.數字鐘digitalclk_mode模組

        

1) 說明:這個為數字鐘的模式切換模組

2) 埠

輸入:clk,reset,key;

輸出;mode;

3) 程式碼

//數字時鐘功能模組
module digitalclk_mode(clk,reset,key,mode);
	input clk;
	input reset;
	input [2:0]key;
	output [2:0]mode;
	//---------------------------------------------------------------	
	parameter clock=2'd0,alarm=2'd1,adj=2'd2;
	//---------------------------------------------------------------	
	reg [2:0]func;
	reg [2:0]mode;
	always @(posedge key[0] or negedge reset)
		if(!reset)
			func <= 1'b0;
		else
			begin
			func <= func + 1'b1;		//產生不同的mode碼
				if(func == 2'd2)
					func <= 1'b0;
			end		
	//---------------------------------------------------------------				
	always @(posedge clk)
		case(func)
			clock : mode = 3'b001;		//時鐘模式
			alarm : mode = 3'b010;		//鬧鐘模式
			adj   : mode = 3'b100;		//校時模式
			default : mode = 3'b001;
		endcase

endmodule

6.時鐘模組


1) 說明:這個是數字鐘的時鐘模組,其中sync_clock是用來後臺同步校時功能的時鐘和分鐘

2) 埠

輸入:clk,reset,mode,sync_clock;

輸出:lcd_data_displ;

3) 程式碼

//mode為0010時為時鐘功能
//時鐘功能
module clock(clk,reset,mode,sync_clock,lcd_data_disp);
	input clk;
	input reset;
	input [2:0]mode;
	input [255:0]sync_clock;
	output [255:0]lcd_data_disp;
	//---------------------------------------------------------------	
	//分頻模組
	reg clk_1Hz;
	reg clk_100Hz;
	integer i,j;
	always @(posedge clk)
		begin
			i <= i + 1'b1;
			if(i==32'd249999)
				begin i <= 1'b0; clk_100Hz <= ~clk_100Hz;end	
			
			j <= j + 1'b1;
			if(j==32'd24999999)
				begin j <= 1'b0; clk_1Hz <= ~clk_1Hz; end	
		end
	//---------------------------------------------------------------	
	//閃爍模組 1秒閃一次
	always @(posedge clk)
		if(mode==3'b001)
			begin 
				clock_disp[23:16] <= clk_1Hz ? " " : ":";
				clock_disp[47:40] <= clk_1Hz ? " " : ":";
				clock_disp[71:64] <= clk_1Hz ? " " : ":";
			end
	//---------------------------------------------------------------	
	//百分秒模組
	reg [255:0]clock_disp;
	always @(posedge clk_100Hz or negedge reset)
		if(!reset)
			clock_disp[15:0] <= 1'b0;
		else
			begin
				clock_disp[7:0] <= clock_disp[7:0] + 1'b1;			//百分秒的個位
				if(clock_disp[7:0]==4'h9)
					begin
						clock_disp[7:0] <= 1'b0;
						clock_disp[15:8] <= clock_disp[15:8] + 1'b1;	//百分秒的十位
						if(clock_disp[15:8]==4'h9)
							clock_disp[15:8] <= 1'b0;
					end
			end
	//---------------------------------------------------------------	
	//計時模組
	always @(posedge clk_1Hz or negedge reset)
		if(!reset)	//重置清零
			begin
				clock_disp[39:24] <= 1'b0;
				clock_disp[63:48] <= 1'b0;
				clock_disp[87:72] <= 1'b0;
			end
			else
			begin
				clock_disp[255:88] = "MODE  H  M  S MSCLOCK";
				//秒計時模組
				clock_disp[31:24] <= clock_disp[31:24] + 1'b1;		//秒的個位
				if(clock_disp[31:24]==8'h9)
					begin
						clock_disp[31:24] <= 1'b0;
						clock_disp[39:32] <= clock_disp[39:32] + 1'b1;	//秒的十位
		//---------------------------------------------------------------						
				//分計時模組
				if(clock_disp[39:32]==8'h5)
					begin
						clock_disp[39:32] <= 1'b0;
						clock_disp[55:48] <= clock_disp[55:48] + 1'b1;		//分鐘的個位
						if(clock_disp[55:48]==8'h9)
						begin
							clock_disp[55:48] <= 1'b0;
							clock_disp[63:56] <= clock_disp[63:56] + 1'b1;	//分鐘的十位
		//---------------------------------------------------------------							
				//小時計時模組					
				if(clock_disp[63:56]==8'h5)	
					begin
						clock_disp[63:56] <= 1'b0;
						clock_disp[79:72] <= clock_disp[79:72] + 1'b1;		//時鐘的個位
						if(clock_disp[79:72]==8'h9)
						begin
							clock_disp[79:72] <= 1'b0;
							clock_disp[87:80] <= clock_disp[87:80] + 1'b1;		//時鐘的十位
						end
					end
		//---------------------------------------------------------------				
						end
					end	
					end				
		//---------------------------------------------------------------					
				//如果記到23:59:59 時清零 00:00:00
				else if(clock_disp[87:72]==16'h0204)		//0000 0010 0000 0100
					clock_disp[87:72] <= 1'b0;					
		//----------------------------------------------------------
				//後臺同步校準模式的時鐘和分鐘	
				else if(mode==3'b100)	
					begin
						clock_disp[87:72] = sync_clock[87:72];
						clock_disp[63:48] = sync_clock[63:48];
					end		
			end	
	//----------------------------------------------------------	
	assign lcd_data_disp = clock_disp ;
	
endmodule

7.鬧鐘模組


    1)說明:這個是數字鐘的鬧鐘模組,其中key是用來調鬧鐘的時鐘和分鐘;

    2)埠

       輸入:clk,reset,mode,key;

       輸出:lcd_data_disp;

3)程式碼

//mode=3'b010時為鬧鐘功能
//校時可以長按按鍵,可以快速加數字
//鬧鐘功能
module alarm(clk,reset,mode,key,lcd_data_disp);
	input clk;
	input reset;
	input [2:0]key;
	input [2:0]mode;
	output [255:0]lcd_data_disp;	
	//---------------------------------------------------------------	
	//校時分頻200ms的頻率用來更新時間
	integer i;
	reg clk_alarm;
	always @(posedge clk)
		begin
			i <= i + 1'b1;
			if(i==32'd4999999)
				begin	i <= 1'b0; clk_alarm <= ~clk_alarm; end
		end
	//---------------------------------------------------------------		
	reg [255:0]alarm_disp;	
	always @(posedge clk_alarm or negedge reset)
	if(!reset)	//復位時清零
		alarm_disp[87:72] <= 1'b0;		
		else if(mode==3'b010)
		begin 
			//設定一些常量,沒使用到的要把初始值賦為0,可以減少警告
			alarm_disp[255:88] <= "MODE  H  M  S MSALARM";
			alarm_disp[71:64] <= ":";
			alarm_disp[47:40] <= ":";
			alarm_disp[39:24] <= 1'b0;
			alarm_disp[23:16] <= ":";
			alarm_disp[15:0] <= 1'b0;
	//---------------------------------------------------------------			
			//鬧鐘設定時鐘
			if(!key[1])	
			begin
				alarm_disp[79:72] <= alarm_disp[79:72] + 1'b1;	//時鐘的個位
				if(alarm_disp[79:72]==4'h9)
					begin
						alarm_disp[79:72] <= 1'b0;
						alarm_disp[87:80] <= alarm_disp[87:80] + 1'b1;	//時鐘的十位
						if(alarm_disp[87:80]==4'h5)
							alarm_disp[87:80] <= 1'b0;
					end
				else if(alarm_disp[87:72]==16'h0203)		//時鐘計算到23的時候回00
					alarm_disp[87:72] <= 1'b0;
			end
	//---------------------------------------------------------------			
			//鬧鐘設定分鐘	
			if(!key[2])		
				begin
					alarm_disp[55:48] <= alarm_disp[55:48] + 1'b1;		//時鐘的個位
					if(alarm_disp[55:48]==4'h9)
						begin
							alarm_disp[55:48] <= 1'b0;
							alarm_disp[63:56] <= alarm_disp[63:56] + 1'b1;		//時鐘的十位
							if(alarm_disp[63:56]==4'h5)
								alarm_disp[63:56] <= 1'b0;
						end
				end
		end
	//---------------------------------------------------------------		
	assign lcd_data_disp = alarm_disp ;
				
endmodule



8. 校時模組


1)說明:這個是數字鐘的校時模快,其中key是用來調整校時模式下的時鐘和分鐘,sync_adj是用來後臺同步時鐘模式下的時鐘資料

    2)埠

       輸入:clk,reset,mode,key,sync_adj;

       輸出:lcd_data_disp;

3)程式碼

//mode=3'b100時為校時功能
//校時可以長按按鍵,可以快速加數字
//校時功能
module adj(reset,clk,mode,key,sync_adj,lcd_data_disp);
	input clk;
	input reset;
	input [2:0]key;
	input [2:0]mode;
	input [255:0]sync_adj;
	output [255:0]lcd_data_disp;
	//---------------------------------------------------------------	
	//校時分頻100ms的頻率用來更新時間,
	integer i,j;
	reg clk_adj,clk_1Hz;
	always @(posedge clk)
		begin
			i <= i + 1'b1;
			if(i==32'd4999999)
				begin i <= 1'b0; clk_adj <= ~clk_adj; end
				
			j <= j + 1'b1;
			if(j==32'd24999999)
				begin j <= 1'b0; clk_1Hz <= ~clk_1Hz; end
		end
	//---------------------------------------------------------------		
	reg [255:0]adj_disp;	
	always @(posedge clk_adj or negedge reset)
	if(!reset)	//復位時清零
		adj_disp[87:72] <= 1'b0;	
		else if(mode==3'b100)
			begin 
				//設定一些常量,沒使用到的要把初始值賦為0,可以減少警告
				adj_disp[255:88] <= "MODE  H  M  S MSADJ  ";	
				adj_disp[71:64] <= ":";
				adj_disp[47:40] <= ":";
				adj_disp[23:16] <= ":";
				adj_disp[15:0] <= 1'b0;
		//---------------------------------------------------------------			
				//校時狀態下調節時鐘
				if(!key[1])
					begin
						adj_disp[79:72] <= adj_disp[79:72] + 1'b1;		//時鐘的個位
						if(adj_disp[79:72]==4'h9)
						begin
							adj_disp[79:72] <= 1'b0;
							adj_disp[87:80] <= adj_disp[87:80] + 1'b1;		//時鐘的十位
							if(adj_disp[87:80]==4'h5)
								adj_disp[87:80] <= 1'b0;
						end
						else if(adj_disp[87:72]==16'h0203)			//時鐘計算到23的時候回00
							adj_disp[87:72] <= 1'b0;
					end
		//---------------------------------------------------------------					
				//校時狀態下調節分鐘
				if(!key[2])
					begin
						adj_disp[55:48] <= adj_disp[55:48] + 1'b1;		//時鐘的個位
						if(adj_disp[55:48]==4'h9)
						begin
							adj_disp[55:48] <= 1'b0;
							adj_disp[63:56] <= adj_disp[63:56] + 1'b1;		//時鐘的十位
							if(adj_disp[63:56]==4'h5)
								adj_disp[63:56] <= 1'b0;
						end
					end
			end
		//---------------------------------------------------------------	
		else if(mode==3'b001)	//後臺同步時鐘
			begin
				adj_disp[87:72] = sync_adj[87:72];
				adj_disp[63:48] = sync_adj[63:48];
			end
	//---------------------------------------------------------------
	//校準模式下分鐘顯示
	always @(posedge clk_1Hz or negedge reset)
		if(!reset)
			adj_disp[39:24] <= 1'b0;
		else
		begin
			adj_disp[31:24] <= adj_disp[31:24] + 1'b1;			//分鐘的個位
			if(adj_disp[31:24]==8'h9)
			begin
				adj_disp[31:24] <= 1'b0;
				adj_disp[39:32] <= adj_disp[39:32] + 1'b1;		//分鐘的十位
				if(adj_disp[39:32]==8'h5)	
					adj_disp[39:32] <= 1'b0;
			end
		end
	//---------------------------------------------------------------	
	assign lcd_data_disp = adj_disp ;
			
endmodule

9.資料處理選擇模組


1)說明:這個是用來處理時鐘,鬧鐘,校時的資料,同時提供兩條訊號線sync_clock,sync_adj來給時鐘和鬧鐘同步資料;其中alarm_en是鬧鐘鈴聲使能訊號;

    2)埠

       輸入:clk,mode,alarm_disp,clock_disp,adj_disp;

       輸出:alarm_en,sync_clock,sync_a,lcd_data_disp;

3)程式碼

//LCD資料選擇
module disp_sel(clk,mode,alarm_disp,clock_disp,adj_disp,sync_clock,sync_adj,lcd_data_disp,alarm_en);
	input clk;
	input [2:0]mode;
	input [255:0]clock_disp;
	input [255:0]alarm_disp,adj_disp;
	output reg[255:0]sync_clock;
	output reg[255:0]sync_adj;
	output [255:0]lcd_data_disp;
	output alarm_en;
	//---------------------------------------------------------------		
	reg[255:0] result_disp;
	always @(posedge clk)
		begin
			if(mode==3'b001)	//時鐘功能時選擇時鐘的的資料
				begin
					result_disp <= clock_disp ;
					sync_adj <= clock_disp;
				end
			else if(mode==3'b010)	//鬧鐘功能時選擇鬧鐘的資料
				begin
					result_disp <= alarm_disp;
				end
			else if(mode==3'b100)	//校時功能時選擇鬧鐘的資料
				begin
					result_disp <= adj_disp;
					sync_clock <= adj_disp;
				end
		end
	//---------------------------------------------------------------	
	//鬧鐘響蜂鳴器模組
	reg alarm_en = 0;
	always @(posedge clk)
		if(mode==3'b001)
			begin
			if((clock_disp[63:48]!==16'b0) || (clock_disp[87:72]!==16'b0))//還沒想到好的方法處理半夜0點尖叫,只能採用折中方法 
				begin																		//這類似幾年前ios系統的0點不響事件,這是一個bug//
					if(clock_disp[39:24]==alarm_disp[39:24])		//響一分鐘模組
					begin
						if((clock_disp[87:72]==alarm_disp[87:72]) & (clock_disp[63:48]==alarm_disp[63:48]))
							alarm_en <= 1;
						else
							alarm_en <= 0;
					end
				end
			end
		else alarm_en <= 0;
	//---------------------------------------------------------------	
	//資料處理
	assign lcd_data_disp[255:88] = result_disp[255:88];
	assign lcd_data_disp[87:80] = result_disp[87:80] + 8'd48;
	assign lcd_data_disp[79:72] = result_disp[79:72] + 8'd48;
	assign lcd_data_disp[71:64] = result_disp[71:64];
	assign lcd_data_disp[63:56] = result_disp[63:56] + 8'd48;
	assign lcd_data_disp[55:48] = result_disp[55:48] + 8'd48;
	assign lcd_data_disp[47:40] = result_disp[47:40];
	assign lcd_data_disp[39:32] = result_disp[39:32] + 8'd48;
	assign lcd_data_disp[31:24] = result_disp[31:24] + 8'd48;
	assign lcd_data_disp[23:16] = result_disp[23:16];
	assign lcd_data_disp[15:8] = result_disp[15:8] + 8'd48;
	assign lcd_data_disp[7:0] = result_disp[7:0] + 8'd48;	

endmodule

10.鬧鈴模組


    1)說明:這個是鬧鐘的鬧鈴模組;

    2)埠

       輸入:clk,reset,alarm_en;

       輸出:beep;

3)程式碼

//蜂鳴器模組
module beep(clk,reset,alarm_en,beep);
	input clk;
	input reset;
	input alarm_en;
	output beep;
	//---------------------------------------------------------------	
	parameter  IDLE= 8'b00000001,
					DO = 8'b00000010,
					RE = 8'b00000100,
					MI = 8'b00001000,
					FA = 8'b00010000,
					SO = 8'b00100000,
					LA = 8'b01000000,
					SI = 8'b10000000;
	//---------------------------------------------------------------	
	reg[31:0] cnt_1Hz;
	always @(posedge clk or negedge reset)
		if(!reset)
			cnt_1Hz <= 32'd0;
		else
			begin
				if(cnt_1Hz >=  32'd49_999_99)
					cnt_1Hz <= 32'd0;
				else
					cnt_1Hz <= cnt_1Hz + 32'd1;
			end
	//---------------------------------------------------------------		
	reg[7:0]beep_status;
	always @(posedge clk or negedge reset)
		if(!reset)
			beep_status <= IDLE;
		else
			begin
				if( cnt_1Hz == 32'd49_999_99)
					beep_status[7:0] <= {beep_status[6:0],beep_status[7]}; 
			end
	//---------------------------------------------------------------		
	reg[31:0]cnt;	
	reg beep;
	always @(posedge clk or negedge reset)
		begin
			if(!reset)
				begin
					cnt <= 0;
					beep <= 1;
				end
			else if(alarm_en)
				case(beep_status)
				  IDLE: beep <= 1;
						
					DO : begin if(cnt>=11944) begin cnt <= 0; beep <= ~beep ;end	//1
									else cnt <= cnt + 1; end
						
					RE : begin if(cnt>=10642) begin cnt <= 0; beep <= ~beep ;end	//2
									else cnt <= cnt + 1; end	
						
					MI : begin if(cnt>=9480) begin cnt <= 0; beep <= ~beep ;end		//3
									else cnt <= cnt + 1; end	
									
					FA : begin if(cnt>=8947) begin cnt <= 0; beep <= ~beep ;end		//4
									else cnt <= cnt + 1;	end	
						
					SO : begin if(cnt>=7971) begin cnt <= 0; beep <= ~beep ;end		//5
									else cnt <= cnt + 1; end	
							
					LA : begin if(cnt>=7102) begin cnt <= 0; beep <= ~beep ;end		//6
									else cnt <= cnt + 1; end	
						
					SI	: begin if(cnt>=6327) begin cnt <= 0; beep <= ~beep ;end		//7
									else cnt <= cnt + 1; end		
			endcase
			else beep <= 1;
		end

endmodule


11.使用平臺Altera CycloneII EP2C8Q208C8 

開發板A_C8V4;


12.引腳繫結