[FPGA]Verilog 60s秒表計時器(最大可計時間長達9min)


[FPGA]Verilog 60s秒表計時器

1.引述

    這次的實驗來自於本人本科課程數電結課時的自選題目。由於這次上傳是后知后覺,學校已將小腳丫板子回收,所以在這篇文章中沒法貼出代碼結果的效果圖了,但最終效果已經過測試,可放心食用。那么下面就貼上代碼並略加講解供大家參考。

2.分頻模塊

我們要實現一個秒表,自然要將實驗板中的時鍾脈沖clk分頻為一個周期為1s的脈沖,已知小腳丫板子的晶振為12MHz。下面貼上分頻模塊的代碼。

module divide #
(                            //parameter是verilog里參數定義
parameter WIDTH    =    24,    //計數器的位數,計數的最大值為 2**(WIDTH-1)
parameter N        =    12_000_000 //分頻系數,請確保 N<2**(WIDTH-1),否則計數會溢出
)
(
input clk, //clk頻率為12MHz
input rst_n, //復位信號,低有效,
output clkout //輸出信號,可以連接到LED觀察分頻的時鍾
);
reg    [WIDTH-1:0]    cnt_p,cnt_n; //cnt_p為上升沿觸發時的計數器,cnt_n為下降沿觸發時的計數器
reg    clk_p,clk_n; //clk_p為上升沿觸發時分頻時鍾,clk_n為下降沿觸發時分頻時鍾
//上升沿觸發時計數器的控制
always @(posedge clk or negedge rst_n)    
    begin        
        if(!rst_n)
            cnt_p <= 1'b0;
        else if(cnt_p == (N-1))
            cnt_p <= 1'b0;
        else 
            cnt_p <= cnt_p + 1'b1; //計數器一直計數,當計數到N-1的時候清零,這是一個模N的計數器
    end
//上升沿觸發的分頻時鍾輸出,如果N為奇數得到的時鍾占空比不是50%;如果N為偶數得到的時鍾占空比為50%
always @(posedge clk or negedge rst_n)
    begin
        if(!rst_n)
            clk_p <= 1'b0;
        else if(cnt_p < (N>>1)) //N>>1表示右移一位,相當於除以2取商
            clk_p <= 1'b0;
        else 
            clk_p <= 1'b1; //得到的分頻時鍾正周期比負周期多一個clk時鍾
    end
//下降沿觸發時計數器的控制            
always @(negedge clk or negedge rst_n)
    begin
        if(!rst_n)
            cnt_n <= 1'b0;
        else if(cnt_n == (N-1))
            cnt_n <= 1'b0;
        else 
            cnt_n <= cnt_n + 1'b1;
    end
//下降沿觸發的分頻時鍾輸出,和clk_p相差半個clk時鍾
always @(negedge clk or negedge rst_n)
    begin
        if(!rst_n)
            clk_n <= 1'b0;
        else if(cnt_n < (N>>1))  
            clk_n <= 1'b0;
        else 
            clk_n <= 1'b1;    //得到的分頻時鍾正周期比負周期多一個clk時鍾
    end
wire    clk1 = clk; //當N=1時,直接輸出clk
wire    clk2 = clk_p; //當N為偶數也就是N的最低位為0,N[0]=0,輸出clk_p
wire    clk3 = clk_p & clk_n; //當N為奇數也就是N最低位為1,N[0]=1,輸出clk_p&clk_n。正周期多所以是相與
assign clkout = (N==1)? clk1:(N[0]? clk3:clk2);    //條件判斷表達式
endmodule

3.八位數碼管顯示模塊 

小腳丫板子上有兩個八位數碼管顯示,本實驗中用來顯示從00s到59s的顯示。下面貼上數碼管顯示模塊的代碼。

module segment
(
input  wire [3:0] seg_data_1, //四位輸入數據信號
input  wire [3:0] seg_data_2, //四位輸入數據信號
output wire [8:0] segment_led_1, //數碼管1,MSB~LSB = SEG,DP,G,F,E,D,C,B,A
output wire [8:0] segment_led_2  //數碼管2,MSB~LSB = SEG,DP,G,F,E,D,C,B,A
);
reg[8:0] seg [15:0]; //存儲7段數碼管譯碼數據
initial 
    begin
        seg[0] = 9'h3f;   //  0
        seg[1] = 9'h06;   //  1
        seg[2] = 9'h5b;   //  2
        seg[3] = 9'h4f;   //  3
        seg[4] = 9'h66;   //  4
        seg[5] = 9'h6d;   //  5
        seg[6] = 9'h7d;   //  6
        seg[7] = 9'h07;   //  7
        seg[8] = 9'h7f;   //  8
        seg[9] = 9'h6f;   //  9
        seg[10]= 9'h77;   //  A
        seg[11]= 9'h7C;   //  b
        seg[12]= 9'h39;   //  C
        seg[13]= 9'h5e;   //  d
        seg[14]= 9'h79;   //  E
        seg[15]= 9'h71;   //  F
    end
   assign segment_led_1 = seg[seg_data_1];
   assign segment_led_2 = seg[seg_data_2];
endmodule

4.功能講解

    在主模塊中除了要例化上述的兩個模塊之外,還需給這個秒表添磚加瓦一下!標題中提到這是一個60s秒表,而我們數碼管顯示只從00到59,但最大計時量程卻達到了9min,這是怎么辦到的呢?這里我們就用到了小腳丫上的一排八位LED燈,每當計到59s時,下一秒數碼管顯示回到00,點亮八位LED燈中的一個,達到表示已計過了1min的作用。一共有八位LED燈,當八個燈都被點亮后,數碼管還有一次從00到59的顯示機會,這樣我們就的得到了一個最大計時量程為9min的秒表啦!下面貼上八位LED燈顯示部分的代碼。

always@(posedge clk)
     if(cnt1==4'b0)
         LED[7:0]<=8'b11111111;
     else if(cnt1==4'b0001)
         LED[7:0]<=8'b11111110;
     else if(cnt1==4'b0010)
         LED[7:0]<=8'b11111100;
     else if(cnt1==4'b0011)
         LED[7:0]<=8'b11111000;
     else if(cnt1==4'b0100)
         LED[7:0]<=8'b11110000;
     else if(cnt1==4'b0101)
         LED[7:0]<=8'b11100000;
     else if(cnt1==4'b0110)
         LED[7:0]<=8'b11000000;
     else if(cnt1<=4'b0111)
         LED[7:0]<=8'b10000000;
     else if(cnt1<=4'b1000)
         LED[7:0]<=8'b00000000;

        此外作為一個秒表自然就要有暫停和開始計時的功能(當然清零功能也是有噠!主模塊中就用rst復位鍵來實現,這里不多贅述。)暫停和開始計時這里我就用同一個按鍵實現。小腳丫板子上還有兩個RGB三色燈,既然有這么好的資源存在,我們就要物盡其用!按下開始計時鍵時,秒表開始計時,數碼管顯示開始變化,此處我們讓RGB三色燈中的一個等亮綠燈,表示處於正常計時狀態中;當再次按鍵開啟鍵時,秒表暫停,我們讓另一個RGB三色燈亮紅色,表示處於暫停狀態。下面貼上包含三色燈點亮的部分代碼。

always @(posedge clk1h or negedge rst) //產生60進制計數器
    begin    //數碼管顯示要按照十進制的方式顯示
        if(!rst)
        begin
            cnt <= 8'h00; //復位初值顯示00
            cnt1<=4'b0;
        end
        else if(flag)
            begin
            G_LED2<=1'b0;
            R_LED1<=1'b1;
                if(cnt[3:0] == 4'd9) //個位滿九?
                    begin
                        cnt[3:0] <= 4'd0; //個位清零
                        if(cnt[7:4] == 4'd5 ) //十位滿五?
                        begin
                            cnt[7:4] <= 4'd0; //十位清零
                            cnt1<=cnt1+1;
                        end
                        else
                        begin
                            cnt[7:4] <= cnt[7:4] + 1'b1; //十位加一
                            cnt1<=cnt1;
                        end
                    end
                else cnt[3:0] <= cnt[3:0] + 1'b1; //個位加一
            end
        else
        begin
            cnt <= cnt;
            G_LED2<=1'b1;
            R_LED1<=1'b0;
        end
    end

5.主模塊

最后貼上主模塊的代碼,完成整個秒表的實現。

module counter60 
(
input clk,rst, //時鍾和復位輸入
input key, //啟動暫停按鍵
output wire [8:0] segment_led_1,segment_led_2, //數碼管輸出
output reg [7:0] LED, //八位LED燈
output reg R_LED1,G_LED2 //RGB三色燈,此處用紅色表示處於暫停狀態中,綠色表示處於正常計時中
);
wire clk1h;    //1秒時鍾
reg    [7:0] cnt;    //計時計數器
reg [3:0] cnt1; //分鍾計數器
reg    flag; //啟動暫停標志
 
divide #  //例化分頻器產生1秒時鍾信號
(
.WIDTH(24),
.N(12_000_000)
) u1
(
.clk(clk),
.rst_n(rst),
.clkout(clk1h)
);
segment u2 //例化數碼管顯示模塊
(
.seg_data_1        (cnt[7:4]),  //seg_data input
.seg_data_2        (cnt[3:0]),  //seg_data input
.segment_led_1    (segment_led_1),  //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
.segment_led_2    (segment_led_2)   //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
);
always @(posedge clk or negedge rst) //產生標志信號
begin
if(!rst)
        flag = 1'b0;
else if(!key)
     begin
        flag = ~flag;
        end
    else
    begin
        flag = flag;
        end
end
always @(posedge clk1h or negedge rst) //產生60進制計數器
    begin    //數碼管顯示要按照十進制的方式顯示
        if(!rst)
        begin
            cnt <= 8'h00; //復位初值顯示00
            cnt1<=4'b0;
        end
        else if(flag)
            begin
            G_LED2<=1'b0;
            R_LED1<=1'b1;
                if(cnt[3:0] == 4'd9) //個位滿九?
                    begin
                        cnt[3:0] <= 4'd0; //個位清零
                        if(cnt[7:4] == 4'd5 ) //十位滿五?
                        begin
                            cnt[7:4] <= 4'd0; //十位清零
                            cnt1<=cnt1+1;
                        end
                        else
                        begin
                            cnt[7:4] <= cnt[7:4] + 1'b1; //十位加一
                            cnt1<=cnt1;
                        end
                    end
                else cnt[3:0] <= cnt[3:0] + 1'b1; //個位加一
            end
        else
        begin
            cnt <= cnt;
            G_LED2<=1'b1;
            R_LED1<=1'b0;
        end
    end
always@(posedge clk)
     if(cnt1==4'b0)
         LED[7:0]<=8'b11111111;
     else if(cnt1==4'b0001)
         LED[7:0]<=8'b11111110;
     else if(cnt1==4'b0010)
         LED[7:0]<=8'b11111100;
     else if(cnt1==4'b0011)
         LED[7:0]<=8'b11111000;
     else if(cnt1==4'b0100)
         LED[7:0]<=8'b11110000;
     else if(cnt1==4'b0101)
         LED[7:0]<=8'b11100000;
     else if(cnt1==4'b0110)
         LED[7:0]<=8'b11000000;
     else if(cnt1<=4'b0111)
         LED[7:0]<=8'b10000000;
     else if(cnt1<=4'b1000)
         LED[7:0]<=8'b00000000;
endmodule

module divide #
(                            //parameter是verilog里參數定義
parameter WIDTH    =    24,    //計數器的位數,計數的最大值為 2**(WIDTH-1)
parameter N        =    12_000_000 //分頻系數,請確保 N<2**(WIDTH-1),否則計數會溢出
)
(
input clk, //clk頻率為12MHz
input rst_n, //復位信號,低有效,
output clkout //輸出信號,可以連接到LED觀察分頻的時鍾
);
reg    [WIDTH-1:0]    cnt_p,cnt_n; //cnt_p為上升沿觸發時的計數器,cnt_n為下降沿觸發時的計數器
reg    clk_p,clk_n; //clk_p為上升沿觸發時分頻時鍾,clk_n為下降沿觸發時分頻時鍾
//上升沿觸發時計數器的控制
always @(posedge clk or negedge rst_n)    
    begin        
        if(!rst_n)
            cnt_p <= 1'b0;
        else if(cnt_p == (N-1))
            cnt_p <= 1'b0;
        else 
            cnt_p <= cnt_p + 1'b1; //計數器一直計數,當計數到N-1的時候清零,這是一個模N的計數器
    end
//上升沿觸發的分頻時鍾輸出,如果N為奇數得到的時鍾占空比不是50%;如果N為偶數得到的時鍾占空比為50%
always @(posedge clk or negedge rst_n)
    begin
        if(!rst_n)
            clk_p <= 1'b0;
        else if(cnt_p < (N>>1)) //N>>1表示右移一位,相當於除以2取商
            clk_p <= 1'b0;
        else 
            clk_p <= 1'b1; //得到的分頻時鍾正周期比負周期多一個clk時鍾
    end
//下降沿觸發時計數器的控制            
always @(negedge clk or negedge rst_n)
    begin
        if(!rst_n)
            cnt_n <= 1'b0;
        else if(cnt_n == (N-1))
            cnt_n <= 1'b0;
        else 
            cnt_n <= cnt_n + 1'b1;
    end
//下降沿觸發的分頻時鍾輸出,和clk_p相差半個clk時鍾
always @(negedge clk or negedge rst_n)
    begin
        if(!rst_n)
            clk_n <= 1'b0;
        else if(cnt_n < (N>>1))  
            clk_n <= 1'b0;
        else 
            clk_n <= 1'b1;    //得到的分頻時鍾正周期比負周期多一個clk時鍾
    end
wire    clk1 = clk; //當N=1時,直接輸出clk
wire    clk2 = clk_p; //當N為偶數也就是N的最低位為0,N[0]=0,輸出clk_p
wire    clk3 = clk_p & clk_n; //當N為奇數也就是N最低位為1,N[0]=1,輸出clk_p&clk_n。正周期多所以是相與
assign clkout = (N==1)? clk1:(N[0]? clk3:clk2);    //條件判斷表達式
endmodule

module segment
(
input  wire [3:0] seg_data_1, //四位輸入數據信號
input  wire [3:0] seg_data_2, //四位輸入數據信號
output wire [8:0] segment_led_1, //數碼管1,MSB~LSB = SEG,DP,G,F,E,D,C,B,A
output wire [8:0] segment_led_2  //數碼管2,MSB~LSB = SEG,DP,G,F,E,D,C,B,A
);
reg[8:0] seg [15:0]; //存儲7段數碼管譯碼數據
initial 
    begin
        seg[0] = 9'h3f;   //  0
        seg[1] = 9'h06;   //  1
        seg[2] = 9'h5b;   //  2
        seg[3] = 9'h4f;   //  3
        seg[4] = 9'h66;   //  4
        seg[5] = 9'h6d;   //  5
        seg[6] = 9'h7d;   //  6
        seg[7] = 9'h07;   //  7
        seg[8] = 9'h7f;   //  8
        seg[9] = 9'h6f;   //  9
        seg[10]= 9'h77;   //  A
        seg[11]= 9'h7C;   //  b
        seg[12]= 9'h39;   //  C
        seg[13]= 9'h5e;   //  d
        seg[14]= 9'h79;   //  E
        seg[15]= 9'h71;   //  F
    end
   assign segment_led_1 = seg[seg_data_1];
   assign segment_led_2 = seg[seg_data_2];
endmodule

 

6.總結

       到這里整個秒表就完成啦。最后再次向讀者們道歉,不能貼上實驗效果圖了。身邊有實驗板的讀者們可以將代碼燒錄進板子觀察現象。本人編程水平、時間有限,這篇文章到這里就要結束啦,歡迎廣大讀者評論留言,更歡迎大家指出本人的不足,希望能通過交流自身得到提高。最后感謝大家的耐心閱讀!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM