FPGA學習之按鍵去抖


按鍵仿真的例程,仿照黑金開發板03_key_detect_1例程,分為兩個模塊,分別是detect模塊、delay模塊,detect模塊檢測輸入key_in信號的變化,delay模塊負責延時去抖;

兩個模塊並不復雜,但是在test bench上仿真花費相當多的時間,波形圖一直不是我想要的輸出類型,折騰了一天,決定先下載到開發板上看看效果,結果效果卻是我想要的輸出類型,這就更加讓我郁悶了,怎么仿真都不行呢?

按說當時應該可以明確的一點就是程序本省應該沒有多大的問題,但是當時並沒有意識到這里,這點以后還是要加強啊。隨后我又多次查看的程序本身,並單獨對detect模塊進行仿真測試,波形是正確的,delay模塊的波形始終有問題,經過分析,我將原來的test bench文件又看了看,並對應波形比較了比較,發現問題出現仿真的周期上。看下test bench文件,

`timescale 10ns / 1ps

module key_tf;

 

    // Inputs

    reg clk;

    reg rst;

    reg key_in;

 

    parameter T = 5'd20;

    parameter TMS = 21'd1500_000;        //15ms

    // Outputs

    wire LED;

 

    // Instantiate the Unit Under Test (UUT)

    key uut (

        .clk(clk),

        .rst(rst),

        .key_in(key_in),

        .LED(LED)

    );

    

    always begin

            clk = 1;

            # (T/2);

            clk = 0;

            # (T/2);

        end

    

    always begin

            key_in = 1;

            #(TMS);

            key_in = 0;

            #(TMS*10);

        end

        

    initial begin

        // Initialize Inputs

        clk = 0;

        rst = 1;

        key_in = 0;

 

        // Wait 100 ns for global reset to finish

        #100;

     rst = 0;

        // Add stimulus here

 

    end

 

endmodule

 

其實這里沒什么東西,很簡單的語句,就是自己在clk這里犯了渾,搞錯了,而detect模塊中基本上算是沒有用到clk,所以delay模塊出現問題時沒有及時的找到問題的根源,我們知道test bench文件中已經設定clk的周期,這是我們通過always文件自己定義的,現在仿真的周期是10ns,clk的周期是我們設定的T = 20*10 = 200ns,而我卻把10ns作為了我delay模塊的時鍾周期,所以造成了波形時鍾延后很長一段時間,造成邏輯因此出現錯誤,在這個仿真周期上我算是栽了一個跟頭,今后一定要引以為戒。把周期調整之后,波形就正確了,但是有一定的延時,估計是布線或者其他原因引起的問題,我暫時還沒有學習到這一塊,加油,繼續FPGA之旅。

 

 

 

看了許久的按鍵消除抖動的例程,特權的、黑金的,對於按鍵的消抖有了一些了解,首先先說說我們單片機通常的消抖是怎樣做的,read按鍵值,如果按鍵被按下,延時10ms,再次讀取按鍵值,如果按鍵確實被按下,則確認按鍵動作執行。這種按鍵去抖的方式有時候並不是特別管用,可以采用按鍵按下,然后抬起作為一次按鍵操作,執行流程是如下程序,

uint8_t keydata = 0;

static uint8_t keyup = 0;    

if(keyup && (keydata ^ 0xf0))

    {

        keyup = 0;

        //延時10ms

        Delay(10);

        //讀取按鍵值

        keydata = GPIO_ReadInputPin(GPIOG, (GPIO_Pin_TypeDef)(HR1|HR2|HR3));

        if(keydata ^ 0xf0)

        {

            return keydata;

        }    

    }

//無按鍵按下時至keyup標志位    

else if((keydata ^ 0xf0) == 0)

    {

        keyup = 1;

        return 0;

    }

這個程序是我用的最多的按鍵去抖程序,他的優點很明顯就是能夠防止按鍵誤動作,不會出現按一次按鍵出現多次按鍵值的情況,缺點就是按鍵按一次只能有一次按鍵值,不能做連續的按鍵值輸出,比如說有需要按鍵按下按鍵值能夠連續相加的情況。

FPGA的按鍵去抖與單片機的去抖就會有些不同,這里也需要對按鍵進行去抖,當然消抖必然會進行延時,畢竟這些抖動信號是我們不需要的,黑金的例程中按鍵消抖處理感覺有點小問題,這里先看按鍵去抖的例程:

去抖程序分為兩個模塊,detect_module.v為電平檢查模塊

module detect_module

(

CLK, RSTn, Pin_In, H2L_Sig, L2H_Sig

);

 

input CLK;

     input RSTn;

     input Pin_In;

     output H2L_Sig;

     output L2H_Sig;

      

     /**********************************/

      

     parameter T100US = 11'd4_999;//DB4CE15開發板使用的晶振為50MHz,50M*0.0001-1=4_999

      

     /**********************************/

      

     reg [10:0]Count1;

     reg isEn;

      

     always @ ( posedge CLK or negedge RSTn )

     if( !RSTn )

         begin

         Count1 <= 11'd0;

         isEn <= 1'b0;

                end

     else if( Count1 == T100US )

                isEn <= 1'b1;

         else

         Count1 <= Count1 + 1'b1;

                

/********************************************/

      

     reg H2L_F1;

     reg H2L_F2;

     reg L2H_F1;

     reg L2H_F2;

      

     always @ ( posedge CLK or negedge RSTn )

     if( !RSTn )

         begin

                 H2L_F1 <= 1'b1;

                     H2L_F2 <= 1'b1;

                     L2H_F1 <= 1'b0;

                     L2H_F2 <= 1'b0;

             end

         else

         begin

                     H2L_F1 <= Pin_In;

                     H2L_F2 <= H2L_F1;

                     L2H_F1 <= Pin_In;

                     L2H_F2 <= L2H_F1;

                end

                

/***********************************/

      

     assign H2L_Sig = isEn ? ( H2L_F2 & !H2L_F1 ) : 1'b0;

     assign L2H_Sig = isEn ? ( !L2H_F2 & L2H_F1 ) : 1'b0;

      

     /***********************************/

    

Endmodule

這里先說這個isEn,因為電平檢測模塊是非常敏感,在復位的一瞬間,電平容易處於不穩定的狀態,我們需要延遲 100us。 isEn = 1 寄存器是表示 100us 的延遲已經完成。H2L_F1, H2L_F2, L2H_F1, L2H_F2四個reg變量主要是用來檢測電平變化的H2L_F2 & !H2L_F1這里是檢測高電平到低電平的變化,也就是按鍵按下時電平會由高電平下降到低電平,這時信號為輸出為高,一個時鍾節拍之后信號輸出為低電平(信號持續一個時鍾周期),!L2H_F2 & L2H_F1這里是檢測低電平到高電平的變化,也就是按鍵抬起時電平會由低電平上升至高電平,電平變化的信號有了,后面的delay_module.v模塊就能夠進行延時輸出信號了。

module delay_module

(

CLK, RSTn, H2L_Sig, L2H_Sig, Pin_Out

);

 

input CLK;

     input RSTn;

     input H2L_Sig;

     input L2H_Sig;

     output Pin_Out;

      

     /****************************************/

      

     parameter T1MS = 16'd49_999;//DB4CE15開發板使用的晶振為50MHz,50M*0.001-1=49_999

      

     /***************************************/

      

     reg [15:0]Count1;

 

     always @ ( posedge CLK or negedge RSTn )

     if( !RSTn )

         Count1 <= 16'd0;

         else if( isCount && Count1 == T1MS )

         Count1 <= 16'd0;

         else if( isCount )

         Count1 <= Count1 + 1'b1;

         else if( !isCount )

         Count1 <= 16'd0;

      

/****************************************/    

                

reg [3:0]Count_MS;

      

     always @ ( posedge CLK or negedge RSTn )

if( !RSTn )

         Count_MS <= 4'd0;

         else if( isCount && Count1 == T1MS )

         Count_MS <= Count_MS + 1'b1;

         else if( !isCount )

         Count_MS <= 4'd0;

    

    /******************************************/

    reg isCount;

    reg rPin_Out;

    reg [1:0]i;

    

    always @ ( posedge CLK or negedge RSTn )

     if( !RSTn )

         begin

         isCount <= 1'b0;

                    rPin_Out <= 1'b0;

                    i <= 2'd0;

             end

         else

         case ( i )

                 2'd0 :

                     if( H2L_Sig ) i <= 2'd1;

                     else if( L2H_Sig ) i <= 2'd2;

                 2'd1 :

                     if( Count_MS == 4'd10 ) begin isCount <= 1'b0; rPin_Out <= 1'b1;

i <= 2'd0; end

                 else    

isCount <= 1'b1;

                     2'd2 :

                     if( Count_MS == 4'd10 ) begin isCount <= 1'b0; rPin_Out <= 1'b0;

i <= 2'd0; end

                 else    isCount <= 1'b1;

                    

                endcase

/********************************************/

     assign Pin_Out = rPin_Out;

     /********************************************/

 

Endmodule

延時程序在接收到H2L_Sig和L2H_Sig時會跳轉到相應的case條件,如果是H2L_Sig高到低信號產生,首先延時10ms,然后Pin_Out輸出高電平,當接收到L2H_Sig低到高信號產生,同樣首先延時10ms,然后Pin_Out輸出低電平,這樣程序就會在按鍵按下時點亮LED燈,按鍵抬起后熄滅LED燈,延時10ms達到消除抖動的目的。

這樣的延時消抖是不是存在弊端?在延時之后並沒有查看按鍵的當前值,如果出現誤觸發的情況,時間小於10ms,這樣就會本來是誤動作卻引起輸出為高電平的情況(L2H_Sig由於延時並不能響應),當然這是理論上的情況,實際中很難發生。

黑金的按鍵去抖程序總體而言是非常清晰,容易理解,也比較實用,接下來看看特權的按鍵消抖程序。

/說明:當三個獨立按鍵的某一個被按下后,相應的LED被點亮;

// 再次按下后,LED熄滅,按鍵控制LED亮滅

 

module sw_debounce(

clk,rst_n,

sw1_n,sw2_n,sw3_n,

led_d1,led_d2,led_d3

);

input clk;                 //主時鍾信號,50MHz

input rst_n;                  //復位信號,低有效

input sw1_n,sw2_n,sw3_n; //三個獨立按鍵,低表示按下

output led_d1,led_d2,led_d3; //發光二極管,分別由按鍵控制

//---------------------------------------------------------------------------

reg[2:0] key_rst;

always @(posedge clk or negedge rst_n)

if (!rst_n) key_rst <= 3'b111;

else key_rst <= {sw3_n,sw2_n,sw1_n};

 

reg[2:0] key_rst_r;

always @ ( posedge clk or negedge rst_n )

if (!rst_n) key_rst_r <= 3'b111;

else key_rst_r <= key_rst;

 

//當寄存器key_rst由1變為0時,led_an的值變為高,維持一個時鍾周期

wire[2:0] key_an = key_rst_r & ( ~key_rst);

//wire [2:0]key_an = key_rst_r ^ key_rst; // 注:也可以這樣寫

//---------------------------------------------------------------------------

reg[19:0] cnt; //計數寄存器

 

always @ (posedge clk or negedge rst_n)

if (!rst_n) cnt <= 20'd0; //異步復位

else if(key_an) cnt <=20'd0;

else cnt <= cnt + 1'b1;

 

reg[2:0] low_sw;

always @(posedge clk or negedge rst_n)

if (!rst_n) low_sw <= 3'b111;

else if (cnt == 20'hfffff) //滿20ms,將按鍵值鎖存到寄存器low_sw,cnt == 20'hfffff

low_sw <= {sw3_n,sw2_n,sw1_n};

 

//---------------------------------------------------------------------------

reg [2:0] low_sw_r; //每個時鍾周期的上升沿將low_sw信號鎖存到low_sw_r中

always @ ( posedge clk or negedge rst_n )

if (!rst_n) low_sw_r <= 3'b111;

else low_sw_r <= low_sw;

 

//當寄存器low_sw由1變為0時,led_ctrl的值變為高,維持一個時鍾周期

wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);

 

reg d1;

reg d2;

reg d3;

 

always @ (posedge clk or negedge rst_n)

if (!rst_n) begin

d1 <= 1'b0;

d2 <= 1'b0;

d3 <= 1'b0;

end

else begin //某個按鍵值變化時,LED將做亮滅翻轉

if ( led_ctrl[0] ) d1 <= ~d1;

if ( led_ctrl[1] ) d2 <= ~d2;

if ( led_ctrl[2] ) d3 <= ~d3;

end

 

assign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻轉輸出

assign led_d2 = d2 ? 1'b1 : 1'b0;

assign led_d1 = d3 ? 1'b1 : 1'b0;

 

endmodule

其實這段程序並沒有什么比較難理解的邏輯,與黑金的程序有所不同,這里的特權的程序是確定按鍵按下之后產生一個信號,LED輸出一個狀態(黑金是兩個狀態),程序的執行順序是,按鍵掃描到按鍵值由1變位0時,通過移位比較的方式得到電平變化的信號,信號觸發計數器重新計數,當計數值為20ms時,讀取按鍵值,再次進行移位比較,如果按鍵值由1變位0,則確實為按鍵按下,執行led信號輸出(信號反轉)。這段程序中我自己認為比較難以理解的地方,主要是兩次移位比較,像黑金那樣進行高到低,低到高的移位比較比較容易理解,這里只是進行了高到低的移位比較,並且進行了兩次,因此理解起來可能有點不太習慣(我自己就是這樣的)。

wire[2:0] key_an = key_rst_r & ( ~key_rst);

這里明顯是1到0時key_an為高,這里與單個信號是操作不同的是key_rst用的是取反而不是取非操作,狀態由1到0是按鍵剛按下的時刻,key_an輸出為高才能進行下面的操作。

wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);

這里與黑金的有所不同,黑金是對兩種狀態1到0和0到1,產生兩種信號,而這里與上次產生的key_an信號都是由1到0產生,也上個信號存在一個20ms的時間差,這樣就起到了延時去抖的目的,這里說明一點是,延時過程中low_sw信號是沒有更新的,也就是說一旦key_an信號產生,low_sw信號20ms之后才能更新,這樣就能夠保證low_sw_r是20ms之前的,也就是未發生改變的按鍵值,而延時之后跟新的low_sw則是最新的值,這樣就能夠判斷是否是1到0的信號變化了。led_ctrl信號為高就會產生led信號反轉,也就是LED的亮或滅。也黑金比較的話這里能夠真正起到延時去抖的作用,按鍵按下會產生一個觸發信號。另外一點必須要說的的是這個延時else if (cnt == 20'hfffff)這里,20ms的延時時間不算長,在key_an為低電平時,這里的計數值也是在增加的,也是達到20'hfffff,這樣low_sw也是在更新之中,這也是通常會忽視的地方,這里的更新能夠很好的記錄下按鍵未觸發之前的狀態,雖然不能保證很實時吧(畢竟20ms的更新速度),相對於按鍵觸發來說很短暫的,如果將led_ctrl修改為由0到1的信號觸發其實也能達到按下一次按鍵led點亮或者熄滅,理論與實踐我都測試了,通過信號波形也看到了結果,確實可行,只不過是兩個產生的原因是不同的,不是key_an激發的,是按鍵本身觸發的,畢竟有20ms延時的采樣。

按鍵去抖是一個簡單的小程序,但是有時候一點改變影響的效果確實是不一樣的,就像用C語言寫的按鍵去抖,一個抬起有效的按鍵去抖方式就能很好解決按鍵抖動的問題,FPGA也一樣。

 


免責聲明!

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



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