一、電路模組
本例的電路模組與“基於EP4CE6F17C8的FPGA數碼管動態顯示例項”中的完全一樣,此處就不再給出了。
二、實驗程式碼
本例實現2個數碼管迴圈顯示00~59,顯示間隔為1秒,程式碼使用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
接下來編寫秒計數程式,共有兩個模組,名稱分別為count_m10和count_m6。先看count_m10的模組,檔名稱為count_m10.v,程式碼如下。
module count_m10( input clk, //板載50HMz系統時鐘 input rst_n, //復位按鍵 input en, //計數使能位 output reg[3:0]data, //計數值,從0~9共10位,所以用4位 output reg t //進位位 ); always@(posedge clk or negedge rst_n) //敏感訊號為時鐘上沿或復位下沿 begin if(rst_n==0) //低電平復位 begin data <= 4'd0; //復位時計數值及進位位清零 t <= 1'd0; end else if(en) //如果計數使能,則執行計數,否則保持上一次的值不變 begin if(data==4'd9) //如果計數到達9時 begin t<= 1'b1; //進位位置1 data <= 4'd0; //計數值清零 end else begin t <= 1'b0; //否則進位位清零,計數值加1 data <= data + 4'd1; end end else //如果計數不使能,進位位置0 t <= 1'b0; end endmodule
再來看count_m6的模組,檔名稱為count_m6.v,程式碼如下。
module count_m6( input clk, //板載50HMz系統時鐘 input rst_n, //復位按鍵 input en, //計數使能位 output reg[3:0]data, //計數值,從0~9共10位,所以用4位 output reg t //進位位 ); always@(posedge clk or negedge rst_n) //敏感訊號為時鐘上沿或復位下沿 begin if(rst_n==0) //低電平復位 begin data <= 4'd0; //復位時計數值及進位位清零 t <= 1'd0; end else if(en) //如果計數使能,則執行計數,否則保持上一次的值不變 begin if(data==4'd5) //如果計數到達5時 begin t<= 1'b1; //進位位置1 data <= 4'd0; //計數值清零 end else begin t <= 1'b0; //否則進位位清零,計數值加1 data <= data + 4'd1; end end else //如果計數不使能,進位位置0 t <= 1'b0; end endmodule
最後編寫數碼管顯示程式,並設定為頂層模組,模組名稱為seg_count,檔名稱為seg_count.v,程式碼如下。
module seg_count( input clk, //板載50HMz系統時鐘 input rst, //復位按鍵 output reg[7:0] seg7, //段碼埠 output reg[1:0] bit //位選埠 ); reg [25:0] cnt; //定義26位時鐘計數器 reg sec; //定義秒訊號 always@(posedge clk or negedge rst) //敏感訊號為時鐘上沿或復位下沿 begin if(rst == 0) //低電平復位時秒計數清零 begin sec <= 1'b0; end else 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 wire t0; //定義個位的進位位 wire [3:0] count_data0,count_data1; //定義顯示的值(個位、十位共兩個) wire [7:0] seg_0,seg_1; //定義顯示字形碼的值(個位、十位共兩個) //下面例化秒的個位計數單元(十進位制) count_m10 u0(.clk(clk), .rst_n(rst), .en(sec), .data(count_data0), .t(t0)); //下面例化秒的十位計數單元(六進位制) count_m6 u1(.clk(clk), .rst_n(rst), .en(t0), .data(count_data1), .t()); //下面例化秒的個位字形解碼單元 seg_decode seg0(.data(count_data0), .seg(seg_0)); //下面例化秒的十位字形解碼單元 seg_decode seg1(.data(count_data1), .seg(seg_1)); 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'd3) //如果掃描位置計數器已經到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顯示秒的個位 begin bit <= 6'b111110; seg7 <= seg_0; end 4'd1: //數碼管1顯示秒的十位 begin bit <= 6'b111101; seg7 <= seg_1; end default: //數碼管全部熄滅 begin bit <= 6'b111111; seg7 <= 8'hff; end endcase end endmodule
三、程式碼說明
1、要實現60進位制的秒計數,可以拆分為個位上的10進位制計數和十位上的6進位制計數。個位透過計秒訊號,直到9後,再計一次數清零,同時產生一個進位訊號,十位透過計個位的進位訊號,直到5後,再計一次數清零。如此就可實現從00~59的計數了。
2、count_m10模組主要負責10進位制計數,在系統時鐘的同步下,來一個en脈衝計一次數,計滿後回零併產生進位訊號t。計數的快慢取決於en埠輸入的脈衝週期,產生的計數值則透過data埠向外輸出,產生的進位訊號透過t埠向外輸出。
3、count_m6模組主要負責6進位制計數,在系統時鐘的同步下,來一個進位訊號t計一次數,計滿後回零併產生分的進位訊號t。計數的快慢取決於個位進位訊號t的週期,產生的計數值則透過data埠向外輸出,產生的進位訊號透過t埠向外輸出。
4、seg_count模組為頂層模組,負責產生秒時鐘訊號(即en脈衝),透過例化10進位制計數單元,把秒訊號en傳入,之後獲得按秒變化的計數值count_data0。同時透過例化數碼管字形解碼單元,把計數值count_data0傳入,之後獲得個位上的字形編碼seg7。同時,透過例化6進位制計數單元,把個位上來的進位訊號t傳入,之後獲得按十秒變化的計數值count_data1。同時透過例化數碼管字形解碼單元,把計數值count_data1傳入,之後獲得十位上的字形編碼seg7。
5、數碼管的掃描方式請參考“基於EP4CE6F17C8的FPGA數碼管動態顯示例項”一文,這裡就不再贅述了。
四、實驗步驟
FPGA開發的詳細步驟請參見“基於EP4CE6F17C8的FPGA開發流程(以半加器為例)”一文,本例只對不同之處進行說明。
本例工程放在D:\EDA_FPGA\Exam_5資料夾下,工程名稱為Exam_5。有四個模組檔案,一個名稱為seg_count.v,設定為頂層實體,另外三個名稱分別為seg_decode.v、count_m10.v和count_m6.v,用於提供例化。其餘步驟與“基於EP4CE6F17C8的FPGA開發流程”中的一樣。
接下來看管腳約束,本例中只用到了兩個數碼管,一共有10個引腳,再加上時鐘晶振和復位按鈕,一共12個。具體的埠分配如下圖所示。
對於未用到的引腳設定為三態輸入方式,多用用途引腳全部做為普通I/O埠,電壓設定為3.3-V LVTTL(與”基於EP4CE6F17C8的FPGA開發流程“中的一樣)。需要注意,程式中的每個埠都必須為其分配管腳,如果系統中存在未分配的I/O,軟體可能會進行隨機分配,這將造成不可預料的後果,存在燒壞FPGA晶片的風險。
接下來對工程進行編譯,編譯完成後,可檢視一下邏輯器件的消耗情況,如下圖所示。
另外,還可以點選選單Tools->Netlist Viewers->RTL Viewer,檢視一下生成的RTL電路圖,如下圖所示。
最後進行程式下載,並檢視結果。下面是兩位數碼管動態顯示秒計數圖片的其中幾張。
當按下復位鍵後,所有數碼管熄滅,如下圖所示。