關於特權同學第9課——消抖電路rtl的分析

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

代碼分析如下:

  1. 首先定義三個獨立按鍵,分別爲sw1_n, sw2_n, sw3_n,作爲三個led控制的按鍵,並將這三個1bit位寬的input整合到位寬爲3的key_rst信號中,用位拼接符表示key_rst = {sw3_n, sw2_n, sw1_n};

  2. 將key_rst信號打一拍,產生key_rst_r信號,並和key_rst的非相與,由此產生下降沿脈衝,因爲key_rst是3位,故最多能夠產生3個下降沿脈衝。下降沿脈衝信號存儲在位寬爲3的key_an信號之中,換言之,key_an就是檢測下降沿的。

  3. 在代碼運行伊始,由於所有的always塊是並行的,所以復位結束後,計數器cnt一直在計數,只不過cnt清零有兩個條件:一是在復位信號有效時候清零;二是當key_an信號有效。注意這裏的key_an是3bit位寬,判斷條件成立的話,並不是需要key_an == 3'b111;而只需要key_an中有一個電平信號爲高即可,比如key_an == 3'b001;

  4. 以一個按鍵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中。

  5. 將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爲例分析如下:

  1. 假設在最後一個下降沿之後,sw1_n爲0,表明對應的按鍵確實按下去了,則20ms後,low_sw[0] = 0,對應的LED燈亮。

  2. 若此時一直按着sw1_n,則20ms後,sw1_n依然爲0,LED燈依然亮着,不會引起電路新動作。

  3. 假設在最後一個下降沿之後,緊跟這一個上升沿,表明對應的按鍵沒有被按下去,或者說按了一會又鬆開了,則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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章