基於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.引腳繫結