一、電路模組
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秒,並開始走時。