一、電路模組
1、LED
開發闆闆載了4個使用者LED發光二極體,其原理圖如下所示,當 FPGA的引腳輸出為邏輯 0時,LED會熄滅。輸出為邏輯1時,LED被點亮。
其實物圖如下所示。
LED的引腳分配見下表。
2、時鐘晶振
開發闆闆載了一個50MHz的有源晶振,為系統提供時鐘。
其實物圖如下所示。
時鐘輸出引腳分配見下表。
3、按鍵
開發闆闆載了4個獨立按鍵,其中有3個使用者按鍵(KEY1~KEY3),1個功能按鍵(RESET)。按鍵按下為低電平(0),釋放為高電平(1),4個按鍵的原理圖如下圖所示。
其實物圖如下所示。
按鍵的引腳分配見下表。
二、實驗程式碼
本例實現透過3個按鍵控制3個LED的亮滅,按鍵按下對應的LED點亮,再次按下LED熄滅,如此迴圈。模組名稱為key_led,檔名稱為key_led.v,程式碼如下。
module key_led( input clk, //板載50HMz系統時鐘 input rst, //復位按鍵 input [2:0] key_h, //三個使用者按鍵 output reg[2:0] led //三個LED ); //按鍵抖動判斷邏輯 wire key = key_h[0] & key_h[1] & key_h[2]; //定義三個按鍵相與 reg[1:0] keyr; //定義按鍵儲存變數 always@(posedge clk or negedge rst) //敏感訊號為時鐘上沿或復位下沿 begin if(!rst) //低電平復位 keyr <= 2'b11; //復位時按鍵值為全1 else keyr <= {keyr[0], key}; //並位,相當於每個時鐘之後用key值向左填充keyr end wire key_neg = ~keyr[0] & keyr[1]; //按鍵按下之後判定有下降沿 wire key_pos = keyr[0] & ~keyr[1]; //按鍵釋放之後判定有上升沿 //定時計數20ms時間,用於對按鍵的消抖判斷 reg[19:0] cnt; always@(posedge clk) begin if(!rst) //低電平復位時計數值清零 cnt <= 20'd0; if(key_pos || key_neg) //如果有上升沿或下降沿發生,計數值清零 cnt <= 20'd0; else if(cnt < 20'd999_999) //如果未計到20ms,則繼續加1計數 cnt <= cnt + 20'd1; else cnt <= 20'd0; //到20ms,計數值清零 end //定時採集按鍵值 reg[2:0] key_halue[1:0]; //定義兩個健值儲存變數 always@(posedge clk or negedge rst) //敏感訊號為時鐘上沿或復位下沿 begin begin if(!rst) //低電平復位時健值變數全部置1 begin key_halue[0] <= 3'b111; key_halue[1] <= 3'b111; end else begin key_halue[1] <= key_halue[0]; //兩次鍵值相差一個時鐘節拍,用於在不相同時產生一個變化脈衝 if(cnt == 20'd999_999) key_halue[0] <= key_h; //到20ms後,獲取外部按鍵值 end end end /*key_halue[1] <= key_halue[0]一個時鐘執行一次,當到20ms時,執行了key_halue[0] <= key_h,此時 key_halue[1]與key_halue[0]的值是不同的,直到下一個時鐘到來時,才相同。因此,透過下面語句會產生 出一個時鐘週期的窄脈衝,依靠這個窄脈衝,就可判斷是哪個按鍵被按下了。由於些窄脈衝的寬度只有一個時鐘 週期,所以本次時鐘過後,又恢復低電平狀態,因此不會被反覆觸發。且在釋放按鍵時,不會產生此窄脈衝。 */ wire[2:0] key_press = key_halue[1] & ~key_halue[0]; //按鍵值按下時產生一個變化脈衝 //wire[3:0] key_press = ~key_halue[1] & key_halue[0]; //按鍵值釋放時產生一個變化脈衝 //LED控制 always@(posedge clk or negedge rst) //敏感訊號為時鐘上沿或復位下沿 begin if(!rst) //低電平復位時LED全滅 led <= 3'b000; else if(key_press[0]) //若按鍵0按下,則LED0取反 led[0] <= ~led[0]; else if(key_press[1]) //若按鍵1按下,則LED1取反 led[1] <= ~led[1]; else if(key_press[2]) //若按鍵2按下,則LED2取反 led[2] <= ~led[2]; end endmodule
三、程式碼說明
1、本例主要討論按鍵消抖的設計方法,其主要思想為,當按鍵按下時,進行20毫秒的延時,若在20毫秒內檢測到按鍵的邊沿,則計數清零,重新開始延時,直到20毫秒結束,則獲取鍵。
2、為了判斷是否有鍵按下,先把三個按鍵值相與,若結果key為0,則表明有鍵被按下。
3、為了產生電平跳變的邊沿,先進行keyr <= {keyr[0], key}操作,相當於每個時鐘來時用key值向左填充keyr。由於keyr的初始值為11,當key等於1時,相當於沒有變化,即沒有按鍵按下。當key等於0時,並位操作後keyr的值為10,此時只要把前後兩位的值進行~keyr[0] & keyr[1]的操作,其結果為1,表示有下降沿產生。而當下一個時鐘來時,keyr的值變為了00,進行~keyr[0] & keyr[1]操作後的結果為0,表明沒有邊沿產生(因為按鍵一直處於按下狀態)。當按鍵釋放時,key等於1,並位後keyr的值為01,此時進行keyr[0] & ~keyr[1]的操作,其結果為1,表示有上升沒產生。而當下一個時鐘來時,keyr的值變為了11,進行keyr[0] & ~keyr[1]操作後的結果為0,表明沒有邊沿產生(因為按鍵一直處於釋放狀態)。
4、根據以上第3步,就可得到按鍵的下降沿或上升沿(程式碼中的key_neg或key_pos),且其高電平的維持時間只有一個時鐘週期,故可以作為按鍵邊沿訊號來使用。這樣做的好處在於,無論按下(或釋放)哪個按鍵,都統一做一個邊沿量來處理。
5、然後進行20毫秒的迴圈計數,在計數過程中,如果遇到前面的邊沿量(key_neg或key_pos)有效,則計數清零重新計數,直到溢位。
6、20毫秒溢位後,表示肯定有按鍵按下了,此時需要計算出鍵值,即哪個按鍵被按下。先定義兩個健值儲存變數key_halue[1:0],初始值都為111,然後一個時鐘執行一次key_halue[1] <= key_halue[0]。當計數到了20毫秒後,執行key_halue[0] <= key_h,即獲取外部按鍵。由於已經是在20毫秒之後才執行的該語句,所以說明有鍵被按下,即key_halue[0]的值不為全1了(key_halue[1]的值仍沒變,為全1,因為兩個值相差一個時鐘節拍),此時進行key_halue[1] & ~key_halue[0]的處理之後,其結果就是被按下的那一位的值為1,其餘為0,這樣就能找出按下的鍵值(程式碼中key_press的值)。同上面第3步中的情況一樣,在下一個是時鐘來時,key_press的值恢復為全0,即按鍵的鍵值只存在一個時鐘週期。
7、若需要檢測的是按鍵的釋放,則換成~key_halue[1] & key_halue[0]的處理方式,其餘一樣。
8、綜合以上步驟,最終獲得的按鍵值key_press,是經過消抖處理之後,按位排列的鍵值(即一個按鍵佔用一個位),當某位為1時表明該按鍵被觸發(可能是按下或是釋放),且只存在一個時鐘的高電平寬度。
9、這樣的按鍵設計,不僅能消除抖動引起的誤觸發,還解決了按鍵按下(或釋放)一次,卻得到多次鍵值的弊病。即一次按鍵操作,只產生一個鍵值訊號,保證了按鍵操作的有效性。
10、為了容易理解,上述在獲取按鍵並進行並位操作時,沒有進行時鐘打拍操作。在實際應用時,為了消除FPGA的亞穩態,一般需要再打兩拍時鐘,再進行其他操作(即keyr <= {keyr[2:0], key}),可自行分析。
四、實驗步驟
FPGA開發的詳細步驟請參見“基於EP4CE6F17C8的FPGA開發流程(以半加器為例)”一文,本例只對不同之處進行說明。
本例工程放在D:\EDA_FPGA\Exam_7資料夾下,工程名稱為Exam_7。模組檔名稱為key_led.v,並設定為頂層實體。其餘步驟與“基於EP4CE6F17C8的FPGA開發流程”中的一樣。
接下來看管腳約束,本例中使用了3個按鍵和3個LED,一共6個引腳,再加上覆位和時鐘晶振,一共8個。具體的埠分配如下圖所示。
對於未用到的引腳設定為三態輸入方式,多用用途引腳全部做為普通I/O埠,電壓設定為3.3-V LVTTL(與”基於EP4CE6F17C8的FPGA開發流程“中的一樣)。需要注意,程式中的每個埠都必須為其分配管腳,如果系統中存在未分配的I/O,軟體可能會進行隨機分配,這將造成不可預料的後果,存在燒壞FPGA晶片的風險。
接下來對工程進行編譯,編譯完成後,可檢視一下邏輯器件的消耗情況,如下圖所示。
另外,還可以點選選單Tools->Netlist Viewers->RTL Viewer,檢視一下生成的RTL電路圖,如下圖所示。
最後進行程式下載,並檢視結果。下圖是按下key1時點亮led1的圖片。
下圖為按下key2時點亮led2的圖片。
下圖為按下key3時點亮led3的圖片。
當按下reset鍵時,所有led均熄滅,如下圖。