基於EP4CE6F17C8的FPGA可調校數碼管時鐘例項

fxzq發表於2024-04-06

一、電路模組

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秒,並開始走時。

相關文章