• <li id="00i08"><input id="00i08"></input></li>
  • <sup id="00i08"><tbody id="00i08"></tbody></sup>
    <abbr id="00i08"></abbr>
  • 新聞中心

    EEPW首頁 > 嵌入式系統 > 牛人業話 > 小梅哥和你一起深入學習FPGA之PS2鍵盤驅動

    小梅哥和你一起深入學習FPGA之PS2鍵盤驅動

    作者: 時間:2015-08-18 來源:網絡 收藏

      五、 代碼分析

    本文引用地址:http://www.czjhyjcfj.com/article/278903.htm

      這里,解碼的關鍵是接口的時鐘信號,該時鐘為異步時鐘,我們需要通過邊沿檢測的方式來檢測其下降沿,以便根據下降沿的個數來確定每個時鐘我們因該做什么,邊沿檢測的電路,前面幾個實驗已經講過很多次了,這里便不再做過多的解釋,貼上代碼即可:

      以下是代碼片段:

      reg _Clk_Tmp0,_Clk_Tmp1,PS2_Clk_Tmp2,PS2_Clk_Tmp3;

      wire nedge_PS2_Clk; /*PS2從機時鐘下降沿檢測標志信號*/

      always @ (posedge Clk or negedge Rst_n)

      if(!Rst_n) begin

      PS2_Clk_Tmp0 <= 1'b0;

      PS2_Clk_Tmp1 <= 1'b0;

      PS2_Clk_Tmp2 <= 1'b0;

      PS2_Clk_Tmp3 <= 1'b0;

      end

      else begin

      PS2_Clk_Tmp0 <= PS2_Clk;

      PS2_Clk_Tmp1 <= PS2_Clk_Tmp0;

      PS2_Clk_Tmp2 <= PS2_Clk_Tmp1;

      PS2_Clk_Tmp3 <= PS2_Clk_Tmp2;

      end

      /*-------獲取PS時鐘信號的下降沿-------------*/

      assign nedge_PS2_Clk = !PS2_Clk_Tmp0 & !PS2_Clk_Tmp1 & PS2_Clk_Tmp2 & PS2_Clk_Tmp3;

      一個PS2的數據包總共由11位組成,因此會有11個時鐘下降沿,因此我們必須對下降沿的個數準確計數,才能保證我們能夠解碼得到正確的數據,這里,使用我們的PS2時鐘下降沿標志信號來使能我們的計數器自加,當計數器加到11后,表示一個數據包接收完成,將計數器清零,等待下一個下降沿的到來,相關代碼如下:

      以下是代碼片段:

      /*------------PS2時鐘下降沿個數計數器-----------------------*/

      always @(posedge Clk or negedge Rst_n)

      if(!Rst_n)

      Cnt1 <= 4'd0;

      else if(Cnt1 == 4'd11)

      Cnt1 <= 4'd0;

      else if(nedge_PS2_Clk)

      Cnt1 <= Cnt1 + 1'b1;

      接下來,就是根據時鐘下降沿的計數個數,來讀取對應位的數據了,因為采用了非阻塞賦值的方式,因此,PS2時鐘下降沿到來時,此時Cnt1執行自加1操作,但同時如果也來用Cnt1的值來確定數據位數,就一定會造成錯誤,因為此時,Cnt1的加1操作并沒有執行,而是會在下一個時鐘上升沿到來之時才變,因此,為了保證我們使用的Cnt1的數據是已經更新了的,我們需要在Cnt1已經變化之后再來使用其值做判斷,即在PS2時鐘下降沿檢測成功后,滯后一個系統時鐘周期后再來讀取PS2_Din上的值,比較簡單的操作方式就是將PS2時鐘下降沿檢測標志信號再用寄存器打一拍,對應代碼如下:

      以下是代碼片段:

      always @(posedge Clk)nedge_PS2_Clk_Shift <= nedge_PS2_Clk;

      可能這里相對比較難以理解,希望大家結合仿真結果自學揣摩體會。

      接下來就是根據Cnt1的計數值來讀取每一位的數據了,這部分代碼很簡單,如下所示:

      以下是代碼片段:

      /*--------------讀取8位數據位---------------*/

      always @ (posedge Clk or negedge Rst_n)

      if(!Rst_n)

      Data_tmp <= 8'd0;

      else if(nedge_PS2_Clk_Shift) begin

      case(Cnt1)

      4'd2:Data_tmp[0] <= PS2_Din;

      4'd3:Data_tmp[1] <= PS2_Din;

      4'd4:Data_tmp[2] <= PS2_Din;

      4'd5:Data_tmp[3] <= PS2_Din;

      4'd6:Data_tmp[4] <= PS2_Din;

      4'd7:Data_tmp[5] <= PS2_Din;

      4'd8:Data_tmp[6] <= PS2_Din;

      4'd9:Data_tmp[7] <= PS2_Din;

      default:Data_tmp <= Data_tmp;

      endcase

      end

      else

      Data_tmp <= Data_tmp;

      通過以上操作,我們就能正確的解碼PS2鍵盤發送過來的每一個字節的數據了,但是,這些數據代表了什么呢,如果是斷碼標志,或者是長碼標志,我們又該如何進行操作呢,這里,小梅哥先貼上我的處理代碼:

      以下是代碼片段:

      always @ (posedge Clk or negedge Rst_n)

      if(!Rst_n) begin

      Break_r <= 1'b0;

      Key_Valve <= 10'd0;

      Key_Flag <= 1'b0;

      Long_Code_r <= 1'b0;

      end

      else if(Cnt1 == 4'd11) begin

      if(Data_tmp == 8'hE0) /*判斷是否為長碼*/

      Long_Code_r <= 1'b1; /*將長碼標志置1*/

      else if(Data_tmp == 8'hF0) /*判斷是否為斷碼*/

      Break_r <= 1'b1; /*將斷碼標志置1*/

      else begin /*檢測到的數據為通碼*/

      Key_Valve <= {Break_r,Long_Code_r,Data_tmp};/*將長碼標志、斷碼標志和解碼到的按鍵碼輸出*/

      Key_Flag <= 1'b1; /*產生解碼成功標志信號*/

      Long_Code_r <= 1'b0; /*清零長碼標志*/

      Break_r <= 1'b0; /*清零斷碼標志*/

      end

      end

      else begin

      Key_Valve <= Key_Valve;

      Key_Flag <= 1'b0;

      Break_r <= Break_r;

      Long_Code_r <= Long_Code_r;

      end

      這里,小梅哥使用了兩個標志寄存器,當檢測數據完成后,即Cnt1=11時,就對解碼到到數據進行判斷,如果Data_tmp == 8'hE0,則解碼到到數據為長碼(擴展碼)標志,此時便將長碼標志寄存器置1,如果Data_tmp == 8'hF0,則解碼到到數據為斷碼標志,此時便將斷碼標志寄存器置1。然后,當解碼到其他數據(如單字節通碼或雙子節通碼的第二個字節)后,便將解碼到的數據連同斷碼和長碼標志寄存器的狀態輸出,并給出按鍵檢測成功標志(Key_Flag置1)。

      六、 仿真分析

      為了對小梅哥設計的PS2鍵盤解碼驅動進行驗證,小梅哥編寫了一個Testbench來模擬鍵盤發送數據,通過觀察鍵盤解碼驅動的輸出來驗證該解碼模塊的正確性,關于模擬鍵盤發送數據,在一份介紹PS2協議的手冊中有如下描述:

      我推薦仿真鍵盤/鼠標采用下面的過程發送一字節的數據到主機:

      1) 等待Clock線為高電平, 即等待主機釋放Clock線;

      2) 延時50us;

      3) 判斷Clock線是否為高電平?

      No―― 跳到第1步;

      4) Data線是否為高電平?

      No―― 放棄(跳到從主機讀取字節的程序中) 。

      5) 延遲20us,輸出起始位(0) , 然后延遲20us, 再拉低Clock線保持40us后釋放Clock線, 形成一個脈沖;

      6) 延時20us, 測試Clock線是否為高電平?No―― 跳到第1步;

      7) 輸出第1個數據位, 然后延時20us, 再拉低Clock線保持40us后釋放Clock線, 形成一個脈沖;

      8) 重復6-7步發送剩下的7個數據位和校驗位;

      9) 延時20us, 測試Clock線是否為高電平?

      No―― 跳到第1步;

      因此,我們的模擬鍵盤發送數據的過程只需要依照上面的流程來即可,這里貼上小梅哥編寫的testbench:

      以下是代碼片段:

      `timescale 1ns/1ns

      module PS2_Key_Board_Driver_tb;

      reg Clk;/*system clock*/

      reg Rst_n;/*復位信號*/

      reg PS2_Din;/*PS2鍵盤數據線*/

      reg PS2_Clk;/*PS2鍵盤時鐘線*/

      wire Key_Flag;/*解碼得到鍵值標志信號*/

      wire [9:0] Key_Valve;/*解碼結果,其中最高位為通/斷碼識別位,0為通碼,1為斷碼,低八位為碼值*/

      PS2_Key_Board_Driver u1(

      .Clk(Clk),

      .Rst_n(Rst_n),

      .PS2_Din(PS2_Din),

      .PS2_Clk(PS2_Clk),

      .Key_Flag(Key_Flag),

      .Key_Valve(Key_Valve)

      );

      initial begin

      Clk = 1;

      Rst_n = 0;

      PS2_Din = 1;

      PS2_Clk = 1;

      #200;

      Rst_n = 1;

      Key_Event(8'h1A); /* Z */

      #400;

      Key_Event(8'h35); /* X */

      #800;

      Key_Event(8'h44); /* O */

      #1320;

      Key_Event(8'h4D); /* P */

      #2560;

      Key_Event(8'h24); /* E */

      #1230;

      Key_Event(8'h31); /* N */

      #20000;

      Long_Key_Event(8'h70); /* "INSERT" */

      #400;

      Long_Key_Event(8'h6c); /* "HOME" */

      #800;

      Long_Key_Event(8'h7d); /* "PAGE UP" */

      #1320;

      Long_Key_Event(8'h71); /* "DELETE" */

      #2560;

      Long_Key_Event(8'h69); /* "END" */

      #1230;

      Long_Key_Event(8'h7a); /* "PAGE DOWN" */

      #2000000;

      $stop;

      end

      /*---------生成工作時鐘-----------*/

      always #10 Clk = ~Clk;

      /*----任務:以PS2協議發送一個字節的數據-----*/

      task Send_data;

      input [7:0]Data;

      begin

      PS2_Din = 0; /*發送起始位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = Data[0];/*發送第0位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = Data[1];/*發送第1位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = Data[2];/*發送第2位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = Data[3];/*發送第3位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = Data[4];/*發送第4位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = Data[5];/*發送第5位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = Data[6];/*發送第6位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = Data[7];/*發送第7位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = 0;/*暫時忽略校驗位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      #20000;PS2_Din = 1;/*停止位*/

      #20000;PS2_Clk = 0;

      #40000;PS2_Clk = 1;

      end

      endtask

      /*-----任務:模擬按下按鍵的操作------*/

      task press_key;

      input [7:0]Key_Number;

      begin

      Send_data(Key_Number);

      #50000;

      end

      endtask

      /*-----任務:模擬釋放按鍵的操作------*/

      task release_key;

      input [7:0]Key_Number;

      begin

      Send_data(8'hF0);

      #50000;

      Send_data(Key_Number);

      #50000;

      end

      endtask

      /*----任務:模擬一次短碼的按下和釋放操作-----*/

      task Key_Event;

      input [7:0]Key_Number;

      begin

      press_key(Key_Number);

      #30000;

      release_key(Key_Number);

      end

      endtask

      /*----任務:模擬一次長碼的按下和釋放操作-----*/

      task Long_Key_Event;

      input [7:0]Key_Number;

      begin

      press_key(8'he0);

      #30000;

      press_key(Key_Number);

      #30000;

      press_key(8'he0);

      #30000;

      release_key(Key_Number);

      end

      endtask

      endmodule

      testbench中使用了一個主任務來模擬鍵盤的數據發送,并使用了其他幾個基于此任務的任務來模擬按鍵按下、按鍵釋放、普通按鍵按下+釋放、擴展按鍵按下+釋放的過程,通過模擬進行部分按鍵的按下和釋放操作,來觀察解碼模塊的結果,便可獲知解碼是否成功。

      以下為小梅哥的仿真結果,與我發送的數據一致,因此表明我的PS2解碼是成功的。

      

     

      七、 下板驗證

      這里,小梅哥在至芯科技ZX2的板子上驗證通過,如下圖:

      其中,第三個數碼管,為0表示普通按鍵通碼,為2表示普通按鍵斷碼,為1表示擴展按鍵通碼,為3表示擴展按鍵斷碼。

      

     

      

     

      

    fpga相關文章:fpga是什么


    蜂鳴器相關文章:蜂鳴器原理

    上一頁 1 2 下一頁

    關鍵詞: FPGA PS2

    評論


    相關推薦

    技術專區

    關閉
    主站蜘蛛池模板: 舟山市| 阿图什市| 加查县| 上杭县| 秀山| 会理县| 嘉荫县| 平南县| 同仁县| 怀远县| 巩留县| 仲巴县| 时尚| 当阳市| 儋州市| 建湖县| 新疆| 合山市| 灵璧县| 安阳县| 乃东县| 南康市| 绩溪县| 夹江县| 山阳县| 曲靖市| 军事| 柳江县| 安徽省| 留坝县| 钟祥市| 渭南市| 唐河县| 宾川县| 普格县| 建宁县| 南郑县| 铁岭县| 都昌县| 鄄城县| 丰城市|