摘要:
本節將單 Bit 數據的異步信號同以及邊沿檢測方法引入到 FPG A中常用 的按鍵消抖設計,並在仿真測試吉利文件中引入隨機數發生函數
消抖的實現分為硬件實現和軟件實現,
產生的抖動次數以及間隔時間均是不可預期的,這就需要通過
濾波來消除抖動可能對外部其他設備造成的影響。一般情況下抖動的總時間會持
續20ms 以內。這種抖動,可以通過硬件電路或者邏輯設計的方式來消除,也可
以通過軟件的方式完成。其中硬件電路消除抖動適用於按鍵數目較少的場合。
下面先來講硬件實現:
使用基於與非門的RS 觸發器來消除抖動的電路圖,如圖 3.7-4 所示。假設
初始狀態開關與B 接通,此時S=1,R=0,觸發器置0 即Q=0;當B 切換到A 的
過程中,存在開關既沒有與A 也沒有與B 接觸,此時S=1,R=1,觸發器保持即
Q=0;當切換到A 瞬間時有R=1,S=0,觸發器置1 即Q=1。此時即使當A 出現抖
動即S=1,觸發器的狀態仍能保持當前狀態即Q=1 不變。整個過程波形圖如圖所
示Q。同理,可以分析出開關由A 切換到B 時觸發器的狀態變化。
請注意:這只適合於單刀雙擲開關。
而對於兩腳和四腳的按鍵目前常用的是RC電路和555定時器組成的單穩態觸發器。
接下來是軟件實現:
在按鍵沒有按下時,處於高電平狀態。
代碼實現:

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;//按鍵狀態 //因為按鍵是異步信號,先同步到系統時鍾域 reg key_in_r,key_in_rr; always@(posedge clk or negedge rst_n) if(!rst_n)begin key_in_r<=0; key_in_rr<=0; end else begin key_in_r<=key_in; key_in_rr<=key_in_r; end //按鍵檢測用到上升沿和下降沿檢測 reg key_in_rr_1,key_in_rr_11; wire pedge,nedge; always@(posedge clk or negedge rst_n) if(!rst_n)begin key_in_rr_1<=0; key_in_rr_11<=0; end else begin key_in_rr_1<=key_in_rr; key_in_rr_11<=key_in_rr_1; end assign pedge=(!key_in_rr_11) && key_in_rr_1; assign nedge=key_in_rr_11 && (!key_in_rr_1); //抖動計數包含使能模塊和計數模塊(按20ms來濾除,需要計數到1000000-1) reg [19:0]cnt; reg cnt_full; reg en_cnt; always@(posedge clk or negedge rst_n) if(!rst_n) cnt<=20'd0; else if(en_cnt) cnt<=cnt+1'b1; else cnt<=20'd0; always@(posedge clk or negedge rst_n) if(!rst_n) cnt_full<=1'b0; else if(cnt==20'd999_999) cnt_full<=1'b1; else cnt_full<=1'b0; //狀態機 localparam IDLE=4'b0001,FILTER0=4'b0010,DOWN=4'b0100,FILTER1=4'b1000; reg [3:0]state; always@(posedge clk or negedge rst_n) if(!rst_n)begin en_cnt<=1'b0; state<=IDLE; key_flag<=1'b0; key_state<=1'b1; end else begin case(state) IDLE:begin key_flag=1'b0; if(nedge)begin en_cnt<=1'b1; state<=FILTER0; end else state<=IDLE; end FILTER0:begin if(cnt_full)begin en_cnt<=1'b0; state<=DOWN; key_flag<=1'b1; key_state<=1'b0; end else if(pedge)begin state<=IDLE; en_cnt<=1'b0; end else state<=FILTER0; end DOWN:begin key_flag<=1'b0; if(pedge)begin en_cnt<=1'b1; state<=FILTER1; end else state<=DOWN; end FILTER1:begin if(cnt_full)begin en_cnt<=1'b0; state<=IDLE; key_flag<=1'b1; key_state<=1'b1; end else if(nedge) state<=DOWN; else state<=FILTER1; end default:state<=IDLE; endcase end endmodule
tb:

`timescale 1ns/1ns module key_filter_tb; reg clk; reg rst_n; reg key_in; wire key_flag;//按鍵狀態切換標志 wire key_state;//按鍵狀態 key_filter key_filter( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); initial clk=0; always #10 clk=~clk; initial begin rst_n=0; #21; rst_n=1; //模擬抖動在20ms以內 key_in=0;#1000; key_in=1;#2000; key_in=0;#1400; key_in=1;#2600; key_in=0;#1300; key_in=1;#200; //產生一個低電平大於20ms,代表穩定按下 key_in=0;#20_000_100; #30_000_000; //模擬釋放抖動20ms以內 key_in=1;#1000; key_in=0;#2000; key_in=1;#1400; key_in=0;#2600; key_in=1;#1300; key_in=0;#200; //產生一個高電平大於20ms,代表穩定釋放 key_in=1;#20_000_100; #30_000_000; $stop; end endmodule
tb_task:

`timescale 1ns/1ns module key_filter_tb_task; reg clk; reg rst_n; reg key_in; wire key_flag;//按鍵狀態切換標志 wire key_state;//按鍵狀態 key_filter key_filter( .clk(clk), .rst_n(rst_n), .key_in(key_in), .key_flag(key_flag), .key_state(key_state) ); integer myrand; initial clk=0; always #10 clk=~clk; task press_key; begin repeat(50)begin myrand={$random}%65536; #myrand key_in=~key_in; end key_in=0; #50_000_000; repeat(50)begin myrand={$random}%65536; #myrand key_in=~key_in; end key_in=1; #50_000_000; end endtask initial begin rst_n=0; key_in=1; #21; rst_n=1; press_key;#10000; press_key;#10000; press_key;#10000; $stop; end endmodule
波形圖:
抖動放大: