基于verilog按键消抖设计
關于鍵盤的基礎知識,我就以下面的一點資料帶過,因為這個實在是再基礎不過的東西了。然后我引兩篇我自己的博文,都是關于按鍵消抖的,代碼也正是同目錄下project里的。這兩篇博文都是ednchina的博客精華,并且在其blog首頁置頂多日,我想對大家會很有幫助的。
?
鍵盤的分類
????? 鍵盤分編碼鍵盤和非編碼鍵盤。鍵盤上閉合鍵的識別由專用的硬件編碼器實現,并產生鍵編碼號或鍵值的稱為編碼鍵盤,如計算機鍵盤。而靠軟件編程來識別的稱為非編碼鍵盤。
在單片機組成的各種系統中,用的最多的是非編碼鍵盤。也有用到編碼鍵盤的。非編碼鍵盤有分為:獨立鍵盤和行列式(又稱為矩陣式)鍵盤。
按鍵在閉合和斷開時,觸點會存在抖動現象:
?
從上面的圖形我們知道,在按鍵按下或者是釋放的時候都會出現一個不穩定的抖動時間的,那么如果不處理好這個抖動時間,我們就無法處理好按鍵編碼,所以如何才能有效的消除按鍵抖動呢?讓下面的兩篇博文日志給你答案吧。
?
?
?
經典的verilog鍵盤掃描程序
?
?????? 拿到威百仕( VibesIC )的板子后就迫不及待的開始我的學習計劃,從最基礎的分頻程序開始,但看到這個鍵盤掃描程序后,直呼經典,有相見恨晚的感覺,還想說一句:威百仕( VibesIC ),我很看好你!WHY?待我慢慢道來,這個程序的綜合后是0error,0warning。想想自己編碼的時候那個warning是滿天飛,現在才明白HDL設計有那么講究了,代碼所設計的不僅僅是簡單的邏輯以及時序的關系,更重要的是你要在代碼中不僅要表現出每一個寄存器,甚至每一個走線。想想我寫過的代碼,只注意到了前者,從沒有注意過后者,還洋洋自得以為自己也算是個高手了,現在想來,實在慚愧啊!學習學習在學習,這也重新激發了我對HDL設計的激情,威百仕給了我一個方向,那我可要開始努力嘍!
?????? 廢話說了一大堆,看程序吧:(本代碼經過ise7.1i綜合并下載到SP306板上驗證通過)
//當三個獨立按鍵的某一個被按下后,相應的LED被點亮;再次按下后,LED熄滅,按鍵控制LED亮滅
?
`timescale 1ns/1ns
?
module keyscan(
??? clk,??????
??? rst_n,
??? sw1_n,
??? sw2_n,
??? sw3_n,
??? //output
??? led_d3,
??? led_d4,
??? led_d5
??? );
?
? input?? clk;?????????? //主時鐘信號,48MHz
? input?? rst_n; //復位信號,低有效
? input?? sw1_n,sw2_n,sw3_n; //三個獨立按鍵,低表示按下
? output? led_d3,led_d4,led_d5; //發光二極管,分別由按鍵控制
?
? // ---------------------------------------------------------------------------
?
? reg [19:0]? cnt;????? //計數寄存器
? always @ (posedge clk? or negedge rst_n)
??? if (!rst_n) ????????? //異步復位
????? 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中
????? 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_d5 = d1 ? 1'b1 : 1'b0;????????????? //LED翻轉輸出
? assign led_d3 = d2 ? 1'b1 : 1'b0;
? assign led_d4 = d3 ? 1'b1 : 1'b0;
?
endmodule
?
?????? 也許初看起來這段代碼似乎有點吃力,好多的always好多的wire啊,而我們通常用得最多的判斷轉移好像不是主流。的確是這樣,一個好的verilog代碼,用多個always語句來分攤一個大的always來執行,會使得綜合起來更快,這也是接前兩篇日志說到代碼優化的一個值得學習的方面。其次是wire連線很多,你要是仔細研究代碼,不難發現所有的鎖存器的連線關系編程者都考慮到了,這樣就不會平白無故的生成意想不到的寄存器了,這也是一個優秀代碼的必備要素。
?????? 上面說的是代碼風格,下面就看程序的編程思想吧。前兩個always語句里其實是做了一個20ms的計數,每隔20ms就會讀取鍵值,把這個鍵值放到寄存器low_sw中,接下來的一個always語句就是把low_sw的值鎖存到low_sw_r里,這樣以來,low_sw和low_sw_r就是前后兩個時鐘周期里的鍵值了,為什么要這樣呢?看下一個語句吧:?
wire [2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);
?????? 仔細分析,你會發現當沒有鍵按下時,low_sw=low_sw_r=3’b111,此時的led_ctrl=3’b000;只有當low_sw和low_sw_r的某一位分別為0和1時,才可能使led_ctrl的值改變(也就是把led_ctrl的某一位拉高)。那么這意味著當鍵值由1跳變到0時才可能把led_ctrl拉高。回顧前面的20ms賦鍵值,也就是說每20ms內如果出現按鍵被按下,那么有一個時鐘周期里led_ctrl是會被拉高的,而再看后面的程序,led_ctrl的置高就使得相應的LED燈的亮滅做一次改變,這就達到了目的。
代碼是特權同學的,我將其理解后加上了注釋。去抖的原理和單片機是一樣的,即通過延時來過濾掉按鍵抖動產生的毛刺信號。不同的是判斷按鍵按下的條件不同,單片機通常是已知按鍵不按時IO口的電平(如高電平),當IO口電平發生改變時(如低電平),則開啟定時器進行延時,延時20ms后再次讀取IO口的電平,若仍為低電平,則說明有按鍵按下;若為高電平,則說明是按鍵抖動,沒有按鍵按下。FPGA的采樣頻率很高,它可以在每個時鐘周期的上升沿到來時對IO口的電平進行一次讀取。通過讀取相鄰2個時鐘周期內IO口的電平值并進行比較,若電平值發生改變(此代碼中是判斷電平從0變為1),則計數器清零,若持續20ms后電平值沒有發生改變,則讀取按鍵的鍵值,同時將這一鍵值存儲起來,當下一個20ms后再次讀取鍵值,將2次鍵值進行比較,若鍵值發生改變,則說明按鍵有動作(要么按下,要么松手)。此代碼中是判斷鍵值從0變為1,即松手檢測。
// 按鍵去抖 // 實現一個簡單的三個按鍵分別控制三個發光二極管亮或暗的控制。 // 例如,按鍵1控制發光二極管1。上電初始發光二極管1不亮, // 當檢測到按鍵1被按下后,發光二極管1則點亮, // 按鍵1再次被按下時,發光二極管1則不亮,如此反復。 // 該實驗需要把握好按鍵消抖檢測的設計技巧。 // 注:此代碼的按鍵操作是包括松手檢測的, // 即按鍵按下后要等到松手才算一次按鍵操作 module key_debounce( clk,rst_n, key1_n,key2_n,key3_n, led1_n,led2_n,led3_n ); input clk; input rst_n; input key1_n,key2_n,key3_n; output led1_n,led2_n,led3_n; reg [2:0] key_rst; always @(posedge clk or negedge rst_n) begin if(!rst_n) key_rst <= 3'b111; else key_rst <= {key3_n,key2_n,key1_n}; // 讀取當前時刻的按鍵值 end reg [2:0] key_rst_r; always @(posedge clk or negedge rst_n) begin if(!rst_n) key_rst_r <= 3'b111; else key_rst_r <= key_rst; ?// 將上一時刻的按鍵值進行存儲 end wire [2:0]key_an = key_rst_r & (~key_rst); // 當鍵值從0到1時key_an改變 //wire [2:0]key_an = key_rst_r ^ key_rst; ?// 注:也可以這樣寫 reg [19:0] cnt; ?// 延時用計數器 always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 20'd0; else if(key_an) cnt <= 20'd0; else cnt <= cnt + 20'd1; end reg [2:0] key_value; always @(posedge clk or negedge rst_n) begin if(!rst_n) key_value <= 3'b111; else if(cnt == 20'hfffff) // 2^20*1/(50MHZ)=20ms key_value <= {key3_n,key2_n,key1_n}; // 去抖20ms后讀取當前時刻的按鍵值 end reg [2:0] key_value_r; always @(posedge clk or negedge rst_n) begin if(!rst_n) key_value_r <= 3'b111; else key_value_r <= key_value; // 將去抖前一時刻的按鍵值進行存儲 end wire [2:0] key_ctrl = key_value_r & (~key_value); // 當鍵值從0到1時key_ctrl改變 reg d1; reg d2; reg d3; always @(posedge ?clk or negedge rst_n) begin if(!rst_n) begin ?// 一個if內有多條語句時不要忘了begin end d1 <= 0;? d2 <= 0; d3 <= 0; end else begin if(key_ctrl[0]) d1 <= ~d1; if(key_ctrl[1]) d2 <= ~d2; if(key_ctrl[2]) d3 <= ~d3; end end assign led1_n = d1? 1'b1:1'b0; // 此處只是為了將LED輸出進行翻轉,RTL級與下面注釋代碼無差別 assign led2_n = d2? 1'b1:1'b0; assign led3_n = d3? 1'b1:1'b0; endmodule總結
以上是生活随笔為你收集整理的基于verilog按键消抖设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: defparam的语法
- 下一篇: SRAM(静态随机存储器)