用笨筆頭書寫青春年華,尋覓真理,三人行,必有我師,交換思想,從點滴做起。
1.獨立按鍵模型如下:
2.分析:在按鍵按下時,圖中電路形成通路,在實際電路設計中將按鍵的一側接到系統電源的GND上,另一側接到FPGA芯片的管腳上,這樣便可以通過FPGA IO口的狀態判斷按鍵是否按下,為了保證FPGA的管腳在按鍵沒有被按下時是一個確定的電平,所以在電路設計時加上一個上拉電阻,這樣當獨立按鍵沒有被按下時,FPGA管腳默認會檢測到高電平1,按鍵被按下時,FPGA的管腳直接與地接通,FPGA管腳就會檢測到低電平0。
3.通過以上分析,從而可以畫出按鍵從沒有按下到按下FPGA (I/O)口上的電平的波形如下:
4.實際波形是否如上圖所示呢?通過使用示波器對按鍵連接在FPGA管腳的線路進行波形抓取發現實際波形並非上圖所示,實際波形如下:
5.通過觀察理想波形與實際波形了解到在按鍵按下與按鍵抬起時都有明顯的抖動過程,通過示波器抓取抖動時間,發現一般為20ms抖動階段(按鍵按下、按鍵抬起)。由於FPGA工作速度是非常快,例如:系統時鍾為50MHz,周期為20ns,即FPGA會以20ns的速度去采樣管腳上的電平狀態,當檢測到抖動階段的下降沿時,FPGA會認為發生了一次按鍵按下,讓led燈加1很短的時間又彈回到高電平,在經過很短的時間又出現下降沿,FPGA又會誤認為又來了一次按鍵事件,讓led燈狀態再次加1,在這樣的情況下,一次按鍵便會觸發led燈多次變化,並且不可預期,釋放時同理,不再分析。因此便需要對這樣的不穩定期作濾波處理,即按鍵消抖。(以按鍵驅動led燈為例)
6.本次設計利用狀態機,對獨立按鍵進行消抖,因此需要分析在實際電路中,獨立按鍵分為哪幾個狀態,如下所示:
7.通過以上分析顯而易見共有四個狀態,分別為:空閑狀態,按下抖動濾除狀態(按下抖動狀態),按下穩定狀態,釋放抖動濾除狀態(抬起抖動狀態)。
分析完狀態之后,需明確分析,這幾個狀態之間是怎么進行跳轉的,即狀態跳轉條件是什么。狀態轉移圖如下:
8.通過對狀態轉移圖的分析,相信讀者可以輕松寫出代碼,代碼如下。
module key_filter(clk,rst_n,key_in,key_flag,key_state); input clk; input rst_n; input key_in; output reg key_flag; output reg key_state; localparam IDEL = 4'b0001, FILTER= 4'b0010, DOWN =4'b0100, FILTER1= 4'b1000; reg [3:0] state; reg key_tempa; reg key_tempb; reg cnt_fult; //---擠滿標志信號 reg[19:0]cnt; //20ms計數器 reg en_cnt; wire pedge,nedge; reg key_in_sa; reg key_in_sb; //-----------對外部輸入信號進行同步處理------------------------ always @(posedge clk,negedge rst_n) if(!rst_n)begin key_in_sa <= 0; key_in_sb <= 0; end else begin key_in_sa <= key_in; key_in_sb <= key_in_sa; end //-使用D觸發器存儲兩個相鄰時鍾上升沿時外部輸入信號(已經同步到系統時鍾域中)的電平狀態 always @(posedge clk,negedge rst_n) if(!rst_n) begin key_tempa <= 0; key_tempb <= 0; end else begin key_tempa <= key_in_sb; key_tempb <= key_tempa; end assign nedge = !key_tempa & key_tempb; assign pedge = key_tempa & (!key_tempb); // -------狀態機程序------------------------ always @(posedge clk,negedge rst_n) if(!rst_n) cnt <= 0; else if(en_cnt) cnt <= cnt + 20'd1; else cnt <= 0; always @(posedge clk,negedge rst_n) if(!rst_n) cnt_fult <= 0; else if(cnt == 999_999) cnt_fult <= 1; else cnt_fult <= 0; //------------------------------------------- always@(posedge clk,negedge rst_n) if(!rst_n)begin en_cnt <= 0; state <= IDEL; key_flag <= 0; key_state <= 1; end else begin case(state) IDEL: begin key_flag <= 0; if(nedge)begin state <= FILTER; en_cnt <= 1; end else state <= IDEL; end FILTER: if(cnt_fult)begin key_flag <= 1; key_state <= 0; en_cnt <= 0; state <= DOWN; end else if(pedge )begin state <= IDEL; en_cnt <= 0; end else state <= FILTER; DOWN: begin key_flag <= 0; if(pedge)begin state <= FILTER1; en_cnt <= 1; end else state <= DOWN; end FILTER1: if(cnt_fult)begin state <= IDEL; key_flag <= 1; key_state <= 1; en_cnt <= 0; end else if(nedge)begin en_cnt <= 0; state <= DOWN; end else state <= FILTER1; default: begin state <= IDEL; en_cnt <= 0; key_flag <= 0; key_state <= 1; end endcase end endmodule
9:其實細讀代碼可以發現里邊還涉及了邊沿檢測代碼的思想,這里不再累贅,會在后面的博文中提到。