rtl代碼如下:
//說明:當三個獨立按鍵的某一個被按下後,相應的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; //每個時鐘週期的上升沿將low_sw信號鎖存到low_sw_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);
//---------------------------------------------------------------------------
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
代碼分析如下:
-
首先定義三個獨立按鍵,分別爲sw1_n, sw2_n, sw3_n,作爲三個led控制的按鍵,並將這三個1bit位寬的input整合到位寬爲3的key_rst信號中,用位拼接符表示key_rst = {sw3_n, sw2_n, sw1_n};
-
將key_rst信號打一拍,產生key_rst_r信號,並和key_rst的非相與,由此產生下降沿脈衝,因爲key_rst是3位,故最多能夠產生3個下降沿脈衝。下降沿脈衝信號存儲在位寬爲3的key_an信號之中,換言之,key_an就是檢測下降沿的。
-
在代碼運行伊始,由於所有的always塊是並行的,所以復位結束後,計數器cnt一直在計數,只不過cnt清零有兩個條件:一是在復位信號有效時候清零;二是當key_an信號有效。注意這裏的key_an是3bit位寬,判斷條件成立的話,並不是需要key_an == 3'b111;而只需要key_an中有一個電平信號爲高即可,比如key_an == 3'b001;
-
以一個按鍵sw1_n爲例,在按下按鍵的過程中,由於按鍵抖動,sw1_n一直在高低電平之間跳變,於是每檢測到一次下降沿,電路就將計數器cnt清零,直到電路在按鍵抖動的過程中檢測到最後一次下降沿,此時計數器不再清零,一直計數到cnt滿足計數條件(cnt == 20'hfffff,即20ms),則將sw1_n, sw2_n, sw3_n的電平信號置於3bit位寬的low_sw中,同時cnt由於溢出而回到20'h0狀態。換言之,key_an啓動計數器,當按鍵不再抖動時(cnt記滿20ms後),將此時按鍵的狀態置於low_sw中。
-
將low_sw打一拍,得到low_sw_r;並和low_sw的非相與,得到按鍵穩定後對LED的控制信號led_ctrl,對應的LED等將亮或滅。
總結而言就是:key_rst一直記錄三個按鍵的變化,並檢測出每個按鍵的下降沿(也就是按下這個動作以及之後的一系列抖動),每次抖動的下降沿觸發計數器,使計數器清零並重新計數,直到最後一次抖動,計數器能夠記滿20ms,20ms之後按鍵穩定,不再抖動,此時將每個按鍵的狀態置於low_sw中,並由此產生控制LED燈亮滅的led_ctrl信號。記錄變化的和點亮LED的分工特別明確,這是特權同學非常好的點子。
需要注意的是cnt計數滿20ms之後,傳遞給low_sw的值到底是什麼?以sw1_n爲例分析如下:
-
假設在最後一個下降沿之後,sw1_n爲0,表明對應的按鍵確實按下去了,則20ms後,low_sw[0] = 0,對應的LED燈亮。
-
若此時一直按着sw1_n,則20ms後,sw1_n依然爲0,LED燈依然亮着,不會引起電路新動作。
-
假設在最後一個下降沿之後,緊跟這一個上升沿,表明對應的按鍵沒有被按下去,或者說按了一會又鬆開了,則sw1_n爲1,由於之後再沒有下降沿,則cnt不再會被清零,則當計數滿20ms後,low_sw[0]仍然會被賦值,但是此時賦的值爲1,即low_sw[0] = 1,對應的LED燈不會被點亮。
補充一點:
爲什麼只是檢測下降沿,爲什麼不能是上升沿檢測?或者說爲什麼釋放時不用上升沿檢測?
答:不論按下還是釋放按鍵,其過程必有抖動,對於下降沿檢測來說,當按鍵按下,只要檢測到最後一個下降沿,cnt計數就會一直記到20ms纔會將此時的按鍵狀態當做穩定狀態傳遞給low_sw中;當按鍵釋放,最後一個下降沿之後cnt會一直記滿20ms,之後將穩定值傳遞給low_sw中。上升沿檢測也是同理。它倆都是爲了規定一個記錄20ms的開始時間,20ms之後將穩定值記錄下來,跟用何種方式實現20ms的計數並無關係。所以沒有必要在按下時用下降沿檢測,釋放時用上升沿檢測,也就是說,沒必要用兩套計數方案。
PS:這個寫法比小梅哥的狀態機消抖要好很多,代碼簡潔,電路邏輯簡單。
另外建議看一下這篇文章:https://blog.csdn.net/joygo007/article/details/71075014