基於EP4CE6F17C8的FPGA雙數碼管六十進位制秒計數例項

fxzq發表於2024-03-25

一、電路模組

本例的電路模組與“基於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電路圖,如下圖所示。

最後進行程式下載,並檢視結果。下面是兩位數碼管動態顯示秒計數圖片的其中幾張。

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

相關文章