基於EP4CE6F17C8的FPGA鍵控燈例項

fxzq發表於2024-04-05

一、電路模組

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均熄滅,如下圖。

相關文章