設(shè)計(jì)規(guī)劃
開(kāi)發(fā)板上使用的機(jī)械按鍵在閉合及斷開(kāi)的瞬間均伴隨有一連串的抖動(dòng),按鍵抖動(dòng)會(huì)引起一次按鍵被誤讀多次,需要進(jìn)行消抖處理:在按鍵閉合穩(wěn)定時(shí)讀取按鍵的狀態(tài),并且必須判別到按鍵釋放穩(wěn)定后再作處理 。
如果按鍵個(gè)數(shù)少,可以用硬件消抖,按鍵多時(shí)需要用軟件消抖:檢測(cè)出按鍵閉合后執(zhí)行一個(gè) 20ms的延時(shí)程序 (抖動(dòng)的時(shí)間為5ms~10ms)再一次檢測(cè)鍵的狀態(tài),如果仍保持閉合狀態(tài)電平,則確認(rèn)為真正有鍵按下。硬件消抖需要有額外的電路,軟件消抖沒(méi)有這種顧慮。
先用波形圖模擬出按鍵被按下和釋放時(shí)抖動(dòng)的毛刺狀態(tài):
計(jì)數(shù)器一節(jié)中已經(jīng)介紹過(guò)計(jì)數(shù)的實(shí)現(xiàn)方法,這里20ms的延遲需要一個(gè)20ms的計(jì)數(shù)器cnt_20ms。系統(tǒng)檢測(cè)到按鍵輸入為低電平就開(kāi)始計(jì)數(shù),如果20ms(50MHz的晶振需要計(jì)數(shù)個(gè)數(shù)為999_999)內(nèi)檢測(cè)出高電平說(shuō)明是一個(gè)抖動(dòng),計(jì)數(shù)器清零。計(jì)數(shù)器計(jì)滿999_999之后,key_flag拉高,一個(gè)時(shí)鐘周期后變低,因此key_flag是脈沖信號(hào)。計(jì)數(shù)器計(jì)滿后的狀態(tài)至關(guān)重要,如果清零,當(dāng)輸入是低電平的時(shí)間過(guò)長(zhǎng)就會(huì)造成一次按鍵卻有多個(gè)key_flag脈沖的情況。
如果計(jì)滿后不清零,到kin_in為高電平時(shí)再清零也會(huì)有新的問(wèn)題,就是key_flag維持高電平時(shí)間太長(zhǎng),不再是一個(gè)脈沖信號(hào)。
此時(shí)如果令計(jì)數(shù)器記到999_998,就可以達(dá)到預(yù)期的效果。
編寫(xiě)代碼
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時(shí)表示消抖后檢測(cè)到按鍵被按下
//key_flag為0時(shí)表示沒(méi)有檢測(cè)到按鍵被按下
);
//reg define
reg [19:0] cnt_20ms ; //計(jì)數(shù)器
//cnt_20ms:如果時(shí)鐘的上升沿檢測(cè)到外部按鍵輸入的值為低電平時(shí),計(jì)數(shù)器開(kāi)始計(jì)數(shù)
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
首先定義了參數(shù)CNT_MAX是計(jì)數(shù)器的最大值,然后定義了輸入輸出和計(jì)數(shù)器。2^19<999999<2^20,因此需要20位的計(jì)數(shù)器。然后,還是要分析兩個(gè)信號(hào)的變化,一個(gè)是計(jì)數(shù)器的狀態(tài),一個(gè)是標(biāo)志位的狀態(tài)。他們變化的條件都是時(shí)鐘上升沿和復(fù)位有效(下降沿)。
cnt_20ms的變化是,如果復(fù)位有效就清零;如果檢測(cè)到輸入為高電平就清零;如果計(jì)滿且輸入為低電平就保持;如果沒(méi)計(jì)滿就+1。
key_flag的變化是,復(fù)位有效就清零,計(jì)數(shù)到999999-1就拉高,其他時(shí)候都為0。
編寫(xiě)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:模擬系統(tǒng)時(shí)鐘,每10ns電平翻轉(zhuǎn)一次,周期為20ns,頻率為50MHz
always #10 sys_clk = ~sys_clk;
//tb_cnt:按鍵過(guò)程計(jì)數(shù)器,通過(guò)該計(jì)數(shù)器的計(jì)數(shù)時(shí)間來(lái)模擬按鍵的抖動(dòng)過(guò)程
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:產(chǎn)生輸入隨機(jī)數(shù),模擬按鍵的輸入情況
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in <= 1'b1; //按鍵未按下時(shí)的狀態(tài)為為高電平
else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)
||(tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
key_in <= {$random} % 2; //隨機(jī)數(shù)模擬抖動(dòng)
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