設計規劃
開發板上使用的機械按鍵在閉合及斷開的瞬間均伴隨有一連串的抖動,按鍵抖動會引起一次按鍵被誤讀多次,需要進行消抖處理:在按鍵閉合穩定時讀取按鍵的狀態,并且必須判別到按鍵釋放穩定后再作處理 。
如果按鍵個數少,可以用硬件消抖,按鍵多時需要用軟件消抖:檢測出按鍵閉合后執行一個 20ms的延時程序 (抖動的時間為5ms~10ms)再一次檢測鍵的狀態,如果仍保持閉合狀態電平,則確認為真正有鍵按下。硬件消抖需要有額外的電路,軟件消抖沒有這種顧慮。
先用波形圖模擬出按鍵被按下和釋放時抖動的毛刺狀態:
計數器一節中已經介紹過計數的實現方法,這里20ms的延遲需要一個20ms的計數器cnt_20ms。系統檢測到按鍵輸入為低電平就開始計數,如果20ms(50MHz的晶振需要計數個數為999_999)內檢測出高電平說明是一個抖動,計數器清零。計數器計滿999_999之后,key_flag拉高,一個時鐘周期后變低,因此key_flag是脈沖信號。計數器計滿后的狀態至關重要,如果清零,當輸入是低電平的時間過長就會造成一次按鍵卻有多個key_flag脈沖的情況。
如果計滿后不清零,到kin_in為高電平時再清零也會有新的問題,就是key_flag維持高電平時間太長,不再是一個脈沖信號。
此時如果令計數器記到999_998,就可以達到預期的效果。
編寫代碼
module key_filter
#(
parameter CNT_MAX = 20'd999_999
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_in ,
output reg key_flag //key_flag為1時表示消抖后檢測到按鍵被按下
//key_flag為0時表示沒有檢測到按鍵被按下
);
//reg define
reg [19:0] cnt_20ms ; //計數器
//cnt_20ms:如果時鐘的上升沿檢測到外部按鍵輸入的值為低電平時,計數器開始計數
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(key_in == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
首先定義了參數CNT_MAX是計數器的最大值,然后定義了輸入輸出和計數器。2^19<999999<2^20,因此需要20位的計數器。然后,還是要分析兩個信號的變化,一個是計數器的狀態,一個是標志位的狀態。他們變化的條件都是時鐘上升沿和復位有效(下降沿)。
cnt_20ms的變化是,如果復位有效就清零;如果檢測到輸入為高電平就清零;如果計滿且輸入為低電平就保持;如果沒計滿就+1。
key_flag的變化是,復位有效就清零,計數到999999-1就拉高,其他時候都為0。
編寫testbench
`timescale 1ns/1ns
module tb_key_filter();
parameter CNT_1MS = 20'd19 ,
CNT_11MS = 21'd69 ,
CNT_41MS = 22'd149 ,
CNT_51MS = 22'd199 ,
CNT_60MS = 22'd249 ;
wire key_flag ;
reg sys_clk ;
reg sys_rst_n ;
reg key_in ;
reg [21:0] tb_cnt ;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
key_in <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
//sys_clk:模擬系統時鐘,每10ns電平翻轉一次,周期為20ns,頻率為50MHz
always #10 sys_clk = ~sys_clk;
//tb_cnt:按鍵過程計數器,通過該計數器的計數時間來模擬按鍵的抖動過程
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tb_cnt <= 22'b0;
else if(tb_cnt == CNT_60MS)
tb_cnt <= 22'b0;
else
tb_cnt <= tb_cnt + 1'b1;
//key_in:產生輸入隨機數,模擬按鍵的輸入情況
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in <= 1'b1; //按鍵未按下時的狀態為為高電平
else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)
||(tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
key_in <= {$random} % 2; //隨機數模擬抖動
else if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS)
key_in <= 1'b0;
else
key_in <= 1'b1;
//------------------------key_filter_inst------------------------
key_filter
#(
.CNT_MAX (20'd24 )
)
key_filter_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.key_in (key_in ), //input key_in
.key_flag (key_flag ) //output key_flag
);
endmodule