按鍵消抖原因
使用機械彈性開關,當機械觸點閉合/斷開時,由於機械觸點的彈性作用,一個按鍵開關在閉合時不會馬上穩定的接通,在斷開時也不會馬上斷開。而是會在閉合/斷開的瞬間伴隨一連串的抖動,為避免這種現象帶來的問題,需要進行按鍵消抖。
硬件消抖
在按鍵個數較少時可以使用硬件方法消除抖動。下圖所示為使用RS觸發器進行硬件消抖,當按鍵未按下時,輸出為0;當按鍵按下時,輸出為1。此時,即使按鍵因為彈性抖動而產生瞬時斷開(抖動從B跳開),只要按鍵不回到原始狀態A,雙穩態觸發器的狀態也不會改變(保持為0),從而不產生抖動的波形。
軟件消抖
原理
在按鍵數量較多的情況下,通常采用軟件方法進行消抖。在檢測到按鍵閉合后開始執行一個延時程序,根據抖動的時間通常為5~10ms,去產生一個20ms的延時,讓前沿抖動消失后再一次檢測按鍵的狀態。如果仍保持閉合狀態電平,則認定為真正有鍵按下。
消抖過程中的問題
1、消除機械抖動導致的毛刺影響
由於機械抖動關系,在按鍵按下和松開過程中會出現按鍵抖動現象,從而導致計數器技術時間難以確定。
解決方法:計數器的開始由最后一次低電平(按鍵信號)決定。若按鍵信號key_in在到達產生flag之前置高,則將前一次脈沖認為是抖動,計數器清零,在下一次key_in置低后計數器再開始計數。換言之就是:系統檢測到key_in為低電平的時候開始計數,檢測到高電平時計數器清零。
2、按鍵時間過久導致出現多個flag信號的現象
假設使用50MHz的晶振進行計算,計數器計數20ms需要計數999_999個周期,當計數器計滿后習慣清零,從而導致flag信號到達999_999后產生1個時鍾周期寬度的flag信號,同時計數器歸零。當按鍵時間過久,可能導致計數器存在多個0→999_999→0狀態,同時,每次到達999_999后會產生一個按鍵被按下的flag,從而導致出現多個flag的脈沖。
解決方法:計數器的歸零由按鍵信號決定,當按鍵信號為高電平(表示按鍵被松開)時,計數器才從999_999歸零。
同時為確保flag信號僅保持一個周期,當計數器為999_998時,產生flag信號。否則flag會一直保持高電平直到計數器歸零。
按鍵消抖代碼

1 module key_fileter #(parameter CNT_MAX=20'd999_999)( 2 input sys_clk , // 系統時鍾50MHz 3 input sys_rst_n , // 全局復位 4 input key_in , // 按鍵輸入信號 5 output reg key_flag // 為1時表示消抖后檢測到按鍵被按下,為0表示沒有檢測到被按下 6 ); 7 reg [19:0] cnt_20ms; 8 // cnt_20ms:如果時鍾的上升沿檢測到外部按鍵輸入的值為低電平時,開始計數 9 always @(posedge sys_clk or negedge sys_rst_n) 10 if(!sys_rst_n) 11 cnt_20ms <= 20'b0; 12 else if(key_in) 13 cnt_20ms <= 20'b0; 14 else if(cnt_20ms==CNT_MAX && key_in) 15 cnt_20ms <= cnt_20ms; 16 else 17 cnt_20ms <= cnt_20ms + 1'b1; 18 19 // key_flag:當計數滿20ms后產生按鍵有效標志位,且key_flag在999_999時拉高,維持一個周期的高電平 20 always @(posedge sys_clk or negedge sys_rst_n) 21 if(!sys_rst_n) 22 key_flag <= 1'b0; 23 else if(cnt_20ms== CNT_MAX-1'b1) 24 key_flag <= 1'b1; 25 else 26 key_flag <= 1'b0; 27 28 endmodule
按鍵消抖testbench

1 `timescale 1ns/1ns 2 3 module tb_key_filter(); 4 // I/O port 5 reg sys_clk; 6 reg sys_rst_n; 7 reg key_in; 8 wire key_flag; 9 10 reg [21:0] tb_cnt; 11 12 // 為縮短仿真時間,將參數化的時間值改小,但位寬定參數名的寬度 13 parameter CNT_1MS = 20'd19; 14 parameter CNT_11MS = 21'd69; 15 parameter CNT_41MS = 22'd149; 16 parameter CNT_51MS = 22'd199; 17 parameter CNT_60MS = 22'd249; 18 19 // 初始化系統時鍾、復位信號和輸入信號 20 initial 21 begin 22 sys_clk <= 1'b1; 23 sys_rst_n <= 1'b0; 24 key_in <= 1'b0; 25 #20 26 sys_rst_n <= 1'b1; 27 end 28 29 // 定義系統時鍾,50MHz 30 always #10 sys_clk = ~sys_clk; 31 32 // tb_cnt: 按鍵過程計數器,用於模擬按鍵抖動過程 33 always @(posedge sys_clk or negedge sys_rst_n) 34 if(!sys_rst_n) 35 tb_cnt <= 22'b0; 36 else if(tb_cnt==CNT_60MS) // 計數器計到60MS是完成一次按鍵從按下到釋放的過程 37 tb_cnt <= 22'b0; 38 else 39 tb_cnt <= tb_cnt + 1'b1; 40 41 // key_in:產生輸入隨機數,模擬按鍵情況 42 always @(posedge sys_clk or negedge sys_rst_n) 43 if(!sys_rst_n) 44 key_in <= 1'b1; // 按鍵未按下時的狀態為高電平 45 else if((tb_cnt>=CNT_1MS && tb_cnt<=CNT_11MS) || (tb_cnt>=CNT_41MS && tb_cnt<=CNT_51MS)) 46 key_in <= {$random} % 2; 47 else if(tb_cnt>=CNT_11MS && tb_cnt<=CNT_41MS) 48 key_in <= 1'b0; 49 else 50 key_in <= 1'b1; 51 52 //--------------------key_filter_inst------------------------------ 53 key_fileter #( 54 .CNT_MAX (20'd24 ) 55 ) 56 key_fileter_inst( 57 .sys_clk (sys_clk ), 58 .sys_rst_n (sys_rst_n ), 59 .key_in (key_in ), 60 .key_flag (key_flag ) 61 ); 62 63 endmodule
波形圖
時鍾頻率為50MHz,當key_in保持24個周期的低電平時,產生一個周期的key_flag脈沖。波形圖如下圖所示。