圖1 KEY原理圖
圖2 LED原理圖
key_led.v
module key_led(
input CLOCK_50,
input Q_KEY,
input [4:1] KEY,
output reg [4:1] LED
);
//++++++++++++++++++++++++++++++++++++++
// 獲取鍵值 開始
//++++++++++++++++++++++++++++++++++++++
wire [4:1] key_val; // 鍵值
key_debounce u0(
.i_clk (CLOCK_50),
.i_rst_n (Q_KEY),
.i_key (KEY),
.o_key_val (key_val) // 按下為0,鬆開為1
);
//--------------------------------------
// 獲取鍵值 結束
//--------------------------------------
//++++++++++++++++++++++++++++++++++++++
// 按下鍵後開關LED 開始
//++++++++++++++++++++++++++++++++++++++
always @ (posedge CLOCK_50, negedge Q_KEY)
if (!Q_KEY)
LED <= 4'hF; // 0滅1亮
else
case (1'b0)
key_val[1] : LED[1] <= ~LED[1];
key_val[2] : LED[2] <= ~LED[2];
key_val[3] : LED[3] <= ~LED[3];
key_val[4] : LED[4] <= ~LED[4];
default : LED <= LED ; // 預設亮滅情況不變
endcase
//--------------------------------------
// 按下鍵後開關LED 結束
//--------------------------------------
endmodule
key_debounce.v
module key_debounce( input i_clk, input i_rst_n, input [4:1] i_key, // 按下為0,鬆開為1 output reg [4:1] o_key_val // 鍵值 ); //++++++++++++++++++++++++++++++++++++++ reg [4:1] key_samp1, key_samp1_locked; // 將i_key採集至key_samp1 always @ (posedge i_clk, negedge i_rst_n) if(!i_rst_n) key_samp1 <= 4'hF; else key_samp1 <= i_key; // 將key_samp1鎖存至key_samp1_locked always @ (posedge i_clk, negedge i_rst_n) if(!i_rst_n) key_samp1_locked <= 4'hF; else key_samp1_locked <= key_samp1; //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ wire [4:1] key_changed1; // 當key_samp1由1變為0時 // key_changed1由0變為1,只維持一個時鐘週期 assign key_changed1 = key_samp1_locked & (~key_samp1); //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ reg [19:0] cnt; // 一旦有按鍵按下,cnt立即被清零 always @ (posedge i_clk, negedge i_rst_n) if(!i_rst_n) cnt <= 20'h0; else if(key_changed1) cnt <= 20'h0; else cnt <= cnt + 1'b1; //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ reg [4:1] key_samp2, key_samp2_locked; // 只有當按鍵不變化(不抖動),且維持20ms以上時 // 才將i_key採集至key_samp2 always @ (posedge i_clk, negedge i_rst_n) if(!i_rst_n) key_samp2 <= 4'hF; else if(cnt == 20'hF_FFFF) // 0xFFFFF/50M = 20.9715ms key_samp2 <= i_key; // 將key_samp2鎖存至key_samp2_locked always @ (posedge i_clk, negedge i_rst_n) if(!i_rst_n) key_samp2_locked <= 4'hF; else key_samp2_locked <= key_samp2; //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ wire [4:1] key_changed2; // 當key_samp2由1變為0時 // key_changed2由0變為1,只維持一個時鐘週期 assign key_changed2 = key_samp2_locked & (~key_samp2); //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ // 每次按鍵穩定後,輸出鍵值 // 按下為0,鬆開為1 always @ (posedge i_clk, negedge i_rst_n) if(!i_rst_n) o_key_val <= 4'hF; else o_key_val <= ~key_changed2; //-------------------------------------- endmodule
HDL思路
圖3 按鍵抖動
如圖3所示,按鍵在按下和釋放的時候都會有一定的抖動。本程式碼僅消除了按下抖動,沒有對釋放抖動進行處理。
程式碼中使用了一個計數器來實現延時。只要這個計數器正常計數到20'hF_FFFF,我們就取樣一個值key_samp2,並將其鎖存進key_samp2_locked。一旦有下降沿來臨,就輸出按鍵改變值key_changed2。同時,該模組也在使用高頻時鐘i_clk不停地採集鍵值到key_samp1,並鎖存進key_samp1_locked;一旦下降沿存在,輸出按鍵變化值key_changed1,這樣計數器就會被清零。假設沒有key_changed1,我們將0xFFFFF/50M = 20.9715ms取樣一次鍵值,並檢測其下降沿,輸出鍵值。但是由於抖動的原因,採用高頻時鐘i_clk取樣的key_samp1就有可能導致key_changed1為一,進而使得低頻的20.9715ms取樣延時執行。也就是說,只有按鍵處於穩定的狀態時,key_samp2才被正常取樣,才有可能檢測到key_changed2。
參考資料
1. 特權.基於verilog按鍵消抖設計