小梅哥和你一起深入學(xué)習FPGA之PS2鍵盤(pán)驅動(dòng)
五、 代碼分析
本文引用地址:http://dyxdggzs.com/article/278903.htm這里,解碼的關(guān)鍵是PS2接口的時(shí)鐘信號,該時(shí)鐘為異步時(shí)鐘,我們需要通過(guò)邊沿檢測的方式來(lái)檢測其下降沿,以便根據下降沿的個(gè)數來(lái)確定每個(gè)時(shí)鐘我們因該做什么,邊沿檢測的電路,前面幾個(gè)實(shí)驗已經(jīng)講過(guò)很多次了,這里便不再做過(guò)多的解釋?zhuān)N上代碼即可:
以下是代碼片段:
reg PS2_Clk_Tmp0,PS2_Clk_Tmp1,PS2_Clk_Tmp2,PS2_Clk_Tmp3;
wire nedge_PS2_Clk; /*PS2從機時(shí)鐘下降沿檢測標志信號*/
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時(shí)鐘信號的下降沿-------------*/
assign nedge_PS2_Clk = !PS2_Clk_Tmp0 & !PS2_Clk_Tmp1 & PS2_Clk_Tmp2 & PS2_Clk_Tmp3;
一個(gè)PS2的數據包總共由11位組成,因此會(huì )有11個(gè)時(shí)鐘下降沿,因此我們必須對下降沿的個(gè)數準確計數,才能保證我們能夠解碼得到正確的數據,這里,使用我們的PS2時(shí)鐘下降沿標志信號來(lái)使能我們的計數器自加,當計數器加到11后,表示一個(gè)數據包接收完成,將計數器清零,等待下一個(gè)下降沿的到來(lái),相關(guān)代碼如下:
以下是代碼片段:
/*------------PS2時(shí)鐘下降沿個(gè)數計數器-----------------------*/
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;
接下來(lái),就是根據時(shí)鐘下降沿的計數個(gè)數,來(lái)讀取對應位的數據了,因為采用了非阻塞賦值的方式,因此,PS2時(shí)鐘下降沿到來(lái)時(shí),此時(shí)Cnt1執行自加1操作,但同時(shí)如果也來(lái)用Cnt1的值來(lái)確定數據位數,就一定會(huì )造成錯誤,因為此時(shí),Cnt1的加1操作并沒(méi)有執行,而是會(huì )在下一個(gè)時(shí)鐘上升沿到來(lái)之時(shí)才變,因此,為了保證我們使用的Cnt1的數據是已經(jīng)更新了的,我們需要在Cnt1已經(jīng)變化之后再來(lái)使用其值做判斷,即在PS2時(shí)鐘下降沿檢測成功后,滯后一個(gè)系統時(shí)鐘周期后再來(lái)讀取PS2_Din上的值,比較簡(jiǎn)單的操作方式就是將PS2時(shí)鐘下降沿檢測標志信號再用寄存器打一拍,對應代碼如下:
以下是代碼片段:
always @(posedge Clk)nedge_PS2_Clk_Shift <= nedge_PS2_Clk;
可能這里相對比較難以理解,希望大家結合仿真結果自學(xué)揣摩體會(huì )。
接下來(lái)就是根據Cnt1的計數值來(lái)讀取每一位的數據了,這部分代碼很簡(jiǎn)單,如下所示:
以下是代碼片段:
/*--------------讀取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;
通過(guò)以上操作,我們就能正確的解碼PS2鍵盤(pán)發(fā)送過(guò)來(lái)的每一個(gè)字節的數據了,但是,這些數據代表了什么呢,如果是斷碼標志,或者是長(cháng)碼標志,我們又該如何進(jìn)行操作呢,這里,小梅哥先貼上我的處理代碼:
以下是代碼片段:
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) /*判斷是否為長(cháng)碼*/
Long_Code_r <= 1'b1; /*將長(cháng)碼標志置1*/
else if(Data_tmp == 8'hF0) /*判斷是否為斷碼*/
Break_r <= 1'b1; /*將斷碼標志置1*/
else begin /*檢測到的數據為通碼*/
Key_Valve <= {Break_r,Long_Code_r,Data_tmp};/*將長(cháng)碼標志、斷碼標志和解碼到的按鍵碼輸出*/
Key_Flag <= 1'b1; /*產(chǎn)生解碼成功標志信號*/
Long_Code_r <= 1'b0; /*清零長(cháng)碼標志*/
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
這里,小梅哥使用了兩個(gè)標志寄存器,當檢測數據完成后,即Cnt1=11時(shí),就對解碼到到數據進(jìn)行判斷,如果Data_tmp == 8'hE0,則解碼到到數據為長(cháng)碼(擴展碼)標志,此時(shí)便將長(cháng)碼標志寄存器置1,如果Data_tmp == 8'hF0,則解碼到到數據為斷碼標志,此時(shí)便將斷碼標志寄存器置1。然后,當解碼到其他數據(如單字節通碼或雙子節通碼的第二個(gè)字節)后,便將解碼到的數據連同斷碼和長(cháng)碼標志寄存器的狀態(tài)輸出,并給出按鍵檢測成功標志(Key_Flag置1)。
六、 仿真分析
為了對小梅哥設計的PS2鍵盤(pán)解碼驅動(dòng)進(jìn)行驗證,小梅哥編寫(xiě)了一個(gè)Testbench來(lái)模擬鍵盤(pán)發(fā)送數據,通過(guò)觀(guān)察鍵盤(pán)解碼驅動(dòng)的輸出來(lái)驗證該解碼模塊的正確性,關(guān)于模擬鍵盤(pán)發(fā)送數據,在一份介紹PS2協(xié)議的手冊中有如下描述:
我推薦仿真鍵盤(pán)/鼠標采用下面的過(guò)程發(fā)送一字節的數據到主機:
1) 等待Clock線(xiàn)為高電平, 即等待主機釋放Clock線(xiàn);
2) 延時(shí)50us;
3) 判斷Clock線(xiàn)是否為高電平?
No―― 跳到第1步;
4) Data線(xiàn)是否為高電平?
No―― 放棄(跳到從主機讀取字節的程序中) 。
5) 延遲20us,輸出起始位(0) , 然后延遲20us, 再拉低Clock線(xiàn)保持40us后釋放Clock線(xiàn), 形成一個(gè)脈沖;
6) 延時(shí)20us, 測試Clock線(xiàn)是否為高電平?No―― 跳到第1步;
7) 輸出第1個(gè)數據位, 然后延時(shí)20us, 再拉低Clock線(xiàn)保持40us后釋放Clock線(xiàn), 形成一個(gè)脈沖;
8) 重復6-7步發(fā)送剩下的7個(gè)數據位和校驗位;
9) 延時(shí)20us, 測試Clock線(xiàn)是否為高電平?
No―― 跳到第1步;
因此,我們的模擬鍵盤(pán)發(fā)送數據的過(guò)程只需要依照上面的流程來(lái)即可,這里貼上小梅哥編寫(xiě)的testbench:
以下是代碼片段:
`timescale 1ns/1ns
module PS2_Key_Board_Driver_tb;
reg Clk;/*system clock*/
reg Rst_n;/*復位信號*/
reg PS2_Din;/*PS2鍵盤(pán)數據線(xiàn)*/
reg PS2_Clk;/*PS2鍵盤(pán)時(shí)鐘線(xiàn)*/
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
/*---------生成工作時(shí)鐘-----------*/
always #10 Clk = ~Clk;
/*----任務(wù):以PS2協(xié)議發(fā)送一個(gè)字節的數據-----*/
task Send_data;
input [7:0]Data;
begin
PS2_Din = 0; /*發(fā)送起始位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[0];/*發(fā)送第0位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[1];/*發(fā)送第1位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[2];/*發(fā)送第2位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[3];/*發(fā)送第3位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[4];/*發(fā)送第4位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[5];/*發(fā)送第5位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[6];/*發(fā)送第6位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = Data[7];/*發(fā)送第7位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = 0;/*暫時(shí)忽略校驗位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
#20000;PS2_Din = 1;/*停止位*/
#20000;PS2_Clk = 0;
#40000;PS2_Clk = 1;
end
endtask
/*-----任務(wù):模擬按下按鍵的操作------*/
task press_key;
input [7:0]Key_Number;
begin
Send_data(Key_Number);
#50000;
end
endtask
/*-----任務(wù):模擬釋放按鍵的操作------*/
task release_key;
input [7:0]Key_Number;
begin
Send_data(8'hF0);
#50000;
Send_data(Key_Number);
#50000;
end
endtask
/*----任務(wù):模擬一次短碼的按下和釋放操作-----*/
task Key_Event;
input [7:0]Key_Number;
begin
press_key(Key_Number);
#30000;
release_key(Key_Number);
end
endtask
/*----任務(wù):模擬一次長(cháng)碼的按下和釋放操作-----*/
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中使用了一個(gè)主任務(wù)來(lái)模擬鍵盤(pán)的數據發(fā)送,并使用了其他幾個(gè)基于此任務(wù)的任務(wù)來(lái)模擬按鍵按下、按鍵釋放、普通按鍵按下+釋放、擴展按鍵按下+釋放的過(guò)程,通過(guò)模擬進(jìn)行部分按鍵的按下和釋放操作,來(lái)觀(guān)察解碼模塊的結果,便可獲知解碼是否成功。
以下為小梅哥的仿真結果,與我發(fā)送的數據一致,因此表明我的PS2解碼是成功的。

七、 下板驗證
這里,小梅哥在至芯科技ZX2的板子上驗證通過(guò),如下圖:
其中,第三個(gè)數碼管,為0表示普通按鍵通碼,為2表示普通按鍵斷碼,為1表示擴展按鍵通碼,為3表示擴展按鍵斷碼。



fpga相關(guān)文章:fpga是什么
蜂鳴器相關(guān)文章:蜂鳴器原理
評論