FPGA按鍵消抖

2023-07-29 12:00:25

簡介

按鍵

按鍵是輸入裝置,一般來說,按鍵在沒有按下的時候是高電平;當按鍵按下的時候,為低電平。

DE2-70 User Manual

Each switch provides a high logic level (3.3 volts) when it is not pressed, and provides a low logic level (0 volts) when depressed. Since the pushbutton switches are debounced, they are appropriate for use as clock or reset inputs in a circuit.

這裡介紹到了按鍵抖動(Button Bouncing)和按鍵消抖(Button Debouncing)。

按鍵消抖

按鍵消抖通常的按鍵所用開關為機械彈性開關,當機械觸點斷開、閉合時,由於機械觸點的彈性作用,一個按鍵開關在閉合時不會馬上穩定地接通,在斷開時也不會一下子斷開。因而在閉合及斷開的瞬間均伴隨有一連串的抖動,為了不產生這種現象而作的措施就是按鍵消抖。

上圖描述的是硬體的按鍵消抖。可見,消抖後,一次按鍵,只產生了一次電平變化。

這對我們接下來利用按鍵意義重大。

物理消抖

簡單介紹下實現上圖的消抖。

電容濾波

將電容並聯在按鍵的兩端,利用電容的放電的延時特性。將產生抖動的電平通過電容吸收掉。從而達到消抖的作用。

RS觸發器

利用RS觸發器來吸收按鍵的抖動。一旦有鍵按下,觸發器立即翻轉,觸電的抖動便不會再對輸出產生影響。

程式消抖

按鍵在FPGA中必不可少,我們需要利用按鍵對一些變數進行累加或累減。比如頻率、分數等。

未消抖

在一開始的學習中,我們可能只用到了開關(switch),編寫開關控制的程式類似於將開關當作一個布林(Boolean)變數。

但是按鍵與開關不同,以下面這段程式碼為例,期望作用是 按鍵按下後,LED燈狀態改變

module Test(
    input       sys_clk,
    input       rst_n,
    input       key,
    output  reg led
);
    always@(posedge sys_clk or negedge rst_n)
    begin
        if(rst_n == 1'b0) 
            led <= 1'b0;
        else if(key == 1'b0)
            led <= ~led;
        else
            led <= led;
    end
endmodule

然而,上板測試發現,LED狀態燈一直處於不亮的狀態。

假設,我們的時脈頻率是50MHz,那麼它的週期就是20ns

那麼,在你按按鈕的全過程中,按下的狀態持續了多長時間呢?

顯然,遠大於20ns,所以LED會多次取反。所以這種簡單粗暴的方法是不適合按鍵的。

消抖

我們剛剛的問題是,短時間內重複檢測,從而多次執行了按鍵後的行為。

可以想到,給這個按鍵定個時:當有按鍵按下,計數器不斷自增,若因為抖動,則計數器會清空,重新計數,當不抖動的時候,就會計滿,此時才會判定為按鍵按下。

程式碼

// 定時器消除抖動
module key_filter(
    input	wire            sys_clk,          // 50M時鐘
    input   wire         	sys_rst_n,        // 復位訊號,低電平有效
    input   wire      		key,              // 按鍵輸入

    output 	reg       		key_flag,         // 按鍵訊號有效訊號
	output 	reg       		key_value         // 消抖後的按鍵訊號  
   );
 
    reg [31:0] delay_cnt;	// 32位元定時器
	reg        key_reg; 

always @(posedge sys_clk or negedge sys_rst_n) begin 
    if (!sys_rst_n) begin 
        key_reg   <= 1'b1;
        delay_cnt <= 32'd0;
    end
    else begin
        key_reg <= key;
        if(key_reg != key)             // 檢測到按鍵狀態發生變化(按下 或 釋放)
            delay_cnt <= 32'd1000000;  // 計數器裝載初始值(計數時間為20ms)
        else if(key_reg == key) begin  // 按鍵狀態穩定時,計數器遞減,開始20ms倒計時
            if(delay_cnt > 32'd0) 	   // 不穩定時,重新計時
                     delay_cnt <= delay_cnt - 1'b1;
                 else
                     delay_cnt <= delay_cnt;
             end           
    end   
end

always @(posedge sys_clk or negedge sys_rst_n) begin 
    if (!sys_rst_n) begin 			   // 復位
        key_flag  <= 1'b0;			   // 無效
        key_value <= 1'b1;             // 高電平為未按下
    end
    else begin
        if(delay_cnt == 32'd1) begin   // 按鍵穩定狀態維持了20ms
            key_flag  <= 1'b1;         // 此時消抖過程結束,訊號有效
            key_value <= key;          // 儲存此時按鍵訊號
        end							   
        else begin
            key_flag  <= 1'b0;
            key_value <= key_value; 
        end  
    end   
end
    
endmodule 

如此,我們再重新寫下剛剛的未消抖程式。

module Test(
      input   	    	sys_clk,     // 系統時鐘
      input   	    	rst_n,       // 復位訊號,低電平有效
      input       	    key,	     // 按鍵訊號       
	  output    reg     led			 // LED燈
  );
  
wire key_value;
wire key_flag;
// 例化
key_filter	key_filter_inst(
    .sys_clk		(sys_clk),     // 50M時鐘
    .sys_rst_n		(sys_rst_n),   // 復位訊號,低電平有效
    .key            (key),         // 按鍵輸入)

    .key_flag		(key_flag),    // 按鍵訊號有效訊號
    .key_value      (key_value)    // 按鍵消抖後的資料  
);
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)	
	begin
		led <= 1'b0;
	end
    else if(key_flag && (~key_value))  // 按鍵是否有效按下
	begin						
		 led  <= ~led;
	end
    else
    begin
        led <= led;
    end
end
endmodule 

成功,按鍵可以控制LED燈亮與熄滅。模擬略。

這是簡單的計時消抖,讀者有興趣可閱讀學習狀態機的方法

參考文獻

[1] 按鍵的硬體消抖電路原理詳解

[2] FPGA要按鍵實現四個數碼管加減計數怎麼搞?

[3] FPGA學習—按鍵控制

[4] DE2-70 User Manual