基於EP4CE6F17C8的FPGA數碼管動態顯示例項

fxzq發表於2024-03-16

一、電路模組

1、數碼管

開發闆闆載了6個數碼管,全部為共陽型,原理圖如下圖所示,段碼端引腳為DIG[0]~DIG[7]共8位(包含小數點),位選端引腳為SEL[0]~SEL[5]共6位。埠均為低電平有效。

其實物圖如下所示。

數碼管引腳分配見下表。

2、時鐘晶振

開發闆闆載了一個50MHz的有源晶振,為系統提供時鐘。

其實物圖如下所示。

時鐘輸出引腳分配見下表。

3、按鍵

開發闆闆載了4個獨立按鍵,其中有3個使用者按鍵(KEY1~KEY3),1個功能按鍵(RESET)。按鍵按下為低電平(0),釋放為高電平(1),4個按鍵的原理圖如下圖所示。本例中只使用了RESET鍵。

其實物圖如下所示。

按鍵的引腳分配見下表。

二、實驗程式碼

本例實現6個數碼管顯示123456,程式碼使用Verilog編寫,採用例化的形式,共有兩個檔案。

先編寫數碼管實現字形的程式,模組名稱為seg,檔名稱為seg.v,程式碼如下。

module seg(
    input clk,                       //板載50HMz系統時鐘
    input[3:0]  data,                //顯示的字形,可顯示0~F十六個字形,所以需要4位
    output reg[7:0] seg              //字形編碼,包含小數點,共8位
);

always@(posedge clk)                 //敏感訊號為時鐘上沿
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的編碼
        4'ha:seg <= 8'b1000_1000;    //字形A的編碼
        4'hb:seg <= 8'b1000_0011;    //字形B的編碼
        4'hc:seg <= 8'b1100_0110;    //字形C的編碼
        4'hd:seg <= 8'b1010_0001;    //字形D的編碼
        4'he:seg <= 8'b1000_0110;    //字形E的編碼
        4'hf:seg <= 8'b1000_1110;    //字形F的編碼
        default:seg <= 7'b111_1111;  //預設不顯示
    endcase
end
endmodule

接下來編寫數碼管顯示的程式,模組名稱為seg_show,檔名稱為seg_show.v,程式碼如下。

module seg_show(
    input clk,                    //板載50HMz系統時鐘
    input rst,                    //復位按鍵
    output reg[7:0] seg7,         //段碼埠
    output reg[5:0] bit           //位選埠
);

reg[19:0]     cnt;                //定義20位時鐘計數器//下面定義6個存放顯示字形的變數
wire[7:0]     seg_data0,seg_data1,seg_data2,seg_data3,seg_data4,seg_data5;
//下面定義6個要顯示的數字
wire[3:0]     data0 = 4'd1;    //data0賦值1
wire[3:0]     data1 = 4'd2;    //data1賦值2
wire[3:0]     data2 = 4'd3;    //data2賦值3
wire[3:0]     data3 = 4'd4;    //data3賦值4
wire[3:0]     data4 = 4'd5;    //data4賦值5
wire[3:0]     data5 = 4'd6;    //data5賦值6

//以下例化了6個數碼管seg0~seg5,把各自的顯示數字與字形編碼聯絡起來
seg seg0(.clk(clk), .data(data0), .seg(seg_data0));
seg seg1(.clk(clk), .data(data1), .seg(seg_data1));
seg seg2(.clk(clk), .data(data2), .seg(seg_data2));
seg seg3(.clk(clk), .data(data3), .seg(seg_data3));
seg seg4(.clk(clk), .data(data4), .seg(seg_data4));
seg seg5(.clk(clk), .data(data5), .seg(seg_data5));

//20毫秒迴圈計數
always@(posedge clk or negedge rst)     //敏感訊號為時鐘上沿或復位下沿
begin
    if(!rst)                            //低電平復位
        cnt <= 20'd0;                   //復位時時鐘計數器清零
    else if(cnt == 20'd999_999)         //時鐘計數器到達20毫秒時
        cnt <= 20'd0;                   //時鐘計數器清零
    else
        cnt <= cnt + 1;                 //否則時鐘計數器加1,即來一次時鐘脈衝加一次
end

//數碼管掃描顯示
always@(posedge clk or negedge rst)    //敏感訊號為時鐘上沿或復位下沿
begin
    if(!rst)                           //低電平復位時數碼管全滅
    begin
        bit <= 6'b111111;
        seg7 <= 8'hff;
    end
    else if(cnt == 20'd166_666)        //時鐘到3毫秒時,數碼管0顯示1
    begin
        bit <= 6'b111110;
        seg7 <= seg_data0;
    end
    else if(cnt == 20'd333_333)        //時鐘到6毫秒時,數碼管1顯示2
    begin
        bit <= 6'b111101;
        seg7 <= seg_data1;
    end
    else if(cnt == 20'd499_999)        //時鐘到10毫秒時,數碼管2顯示3
    begin
        bit <= 6'b111011;
        seg7 <= seg_data2;
    end
    else if(cnt == 20'd666_666)        //時鐘到13毫秒時,數碼管3顯示4
    begin
        bit <= 6'b110111;
        seg7 <= seg_data3;
    end
    else if(cnt == 20'd833_333)        //時鐘到16毫秒時,數碼管4顯示5
    begin
        bit <= 6'b101111;
        seg7 <= seg_data4;
    end
    else if(cnt == 20'd999_999)        //時鐘到20毫秒時,數碼管5顯示6
    begin
        bit <= 6'b011111;
        seg7 <= seg_data5;
    end
end
endmodule

三、程式碼說明

1、本例中使用了計數器來分頻時鐘,總計約20ms的迴圈,並在此間分配了6個數碼管進行顯示,每個數碼管大約分得3.3ms左右的顯示時間。
2、時鐘計數器選取了20位,對50MHz時鐘進行分頻,最大能達到20.9ms,剛好大於所需要的20ms,所以取20位進行計數比較合適,太多了浪費硬體資源。
3、時鐘分頻還可以採用PLL的方式,待頻率降下來了之後,再使用少量的計數器來得到所需要的時間,可進一步節約資源,但會消耗一個PLL。
4、程式中使用了數碼管例化的形式,共例化出了6個數碼管。例化的數碼管負責解碼,即把要顯示的數值解碼成顯示的字形編碼,再輸出給數碼管的段碼埠。
5、在定義的變數中,data0~data5分別給了固定值1~6,seg_data0~seg_data5分別接收解碼後的字形編碼,並在下面的過程語句中,分別送給相應數碼管的段埠進行顯示。以0號數碼管為例,它要顯示字元1,先把1賦值給變數data0,然後透過例化seg seg0(.clk(clk), .data(data0), .seg(seg_data0));把data0的值傳給元件內部變數data(即檔案seg.v中的data),而後得到字形編碼,輸出給變數seg_data0,然後當時鍾計數cnt達到20'd166_666時,把seg_data0的資料送給seg7埠,同時開啟0號數碼管的位選通埠(bit <= 6'b111110;),此時該數碼管顯示出字形1。其他數碼管的顯示原理以此類推。
6、由上可以看出,元件例化有點類似於C語句中的函式呼叫及返回,但元件的每一個例化都是要產生出實際的電路的(要消耗邏輯器件),可檢視生成的RTL電路圖。

四、實驗步驟

FPGA開發的詳細步驟請參見“基於EP4CE6F17C8的FPGA開發流程(以半加器為例)”一文,本例只對一同之處進行說明。

本例工程放在D:\EDA_FPGA\Exam_3資料夾下,工程名稱為Exam_3。有兩個模組檔案,一個名稱為seg_show.v,設定為頂層實體,另一個名稱為seg.v,用於提供例化。其餘步驟與“基於EP4CE6F17C8的FPGA開發流程”中的一樣。

接下來看管腳約束,本例中6個數碼管一共有16個引腳,再加上時鐘晶振和復位按鈕,一共18個。具體的埠分配如下圖所示。

對於未用到的引腳設定為三態輸入方式,多用用途引腳全部做為普通I/O埠,電壓設定為3.3-V LVTTL(與”基於EP4CE6F17C8的FPGA開發流程“中的一樣)。需要注意,程式中的每個埠都必須為其分配管腳,如果系統中存在未分配的I/O,軟體可能會進行隨機分配,這將造成不可預料的後果,存在燒壞FPGA晶片的風險。

接下來對工程進行編譯,編譯完成後,可檢視一下邏輯器件的消耗情況,如下圖所示。

另外,還可以點選選單Tools->Netlist Viewers->RTL Viewer,檢視一下生成的RTL電路圖。

最後進行程式下載,並檢視結果。下圖為6個數碼我們管的動態顯示效果。

當按下復位鍵後,所有數碼管均熄滅,如下圖所示。

相關文章