PS2鍵盤也是一個經(jīng)典的實驗,可能很多人接觸如何對通信協(xié)議、時序編程就是從這個實驗開始學(xué)習(xí)的。USB鍵盤已經(jīng)很普及,現(xiàn)在市場上還是有一些USB轉(zhuǎn)PS2的轉(zhuǎn)接頭,還有一些轉(zhuǎn)換芯片。這個實驗雖然簡單,不過不知道您有考慮過單按一次輸出一個有效脈沖、短按、長按等這些是如何實現(xiàn)的么?這就涉及到一些時鐘、邊沿檢測等設(shè)計問題。
PS2協(xié)議實現(xiàn)
我們見到的PS2的接口電路應(yīng)該都是這樣的:
一根時鐘線、一根數(shù)據(jù)線完成通信,PS2通信的幀格式如下所示,時鐘的下降沿數(shù)據(jù)有效:
按鍵在被按下時,會發(fā)送一個字節(jié),這個碼就是通碼;按鍵在釋放時,會發(fā)送兩個字節(jié),這個碼就做斷碼(當(dāng)然也有例外)。每一個按鍵都有唯一的通碼和斷碼,據(jù)此進(jìn)行判斷按下的是哪個鍵,從而執(zhí)行對應(yīng)的功能。如一部分按鍵的通碼和斷碼如下所示:
可以看出斷碼其實就是在通碼前加了一個F0,比如A的通碼是1C,則它的斷碼是F01C。另外一些特殊功能的按鍵,在通碼和斷碼前都會加個E0。PS2解碼的代碼如下所示:
//-----------------ps2_clk下降沿捕獲--------------------
//clk相當(dāng)于中間采樣點的作用,第一個下降沿到來說明起始位開始
reg ps2_clk0, ps2_clk1, ps2_clk2;//緩沖寄存器
wire ps2_clk_neg; //1表示檢測到下降沿
reg ps2_state;
always @ (posedge clk or negedge rst_n)
if (!rst_n)
{ps2_clk0, ps2_clk1, ps2_clk2} 《= 3‘d0;
else
begin
ps2_clk0 《= ps2_clk;
ps2_clk1 《= ps2_clk0;
ps2_clk2 《= ps2_clk1;
end
assign ps2_clk_neg = ~ps2_clk1 & ps2_clk2;
//----------------------數(shù)據(jù)接收----------------------------
reg [3:0]num; //移位控制
reg [7:0]data_temp;//當(dāng)前接收的數(shù)據(jù)
always @ (posedge clk or negedge rst_n)
if (!rst_n)
begin
num 《= 4’d0;
data_temp 《= 8‘d0;
end
else if (ps2_clk_neg)
begin
if (num == 0) num 《= num + 1’b1;//跳過起始位
else if (num 《= 8) //數(shù)據(jù)位賦值
begin
num 《= num + 1‘b1;
data_temp[num-1] 《= ps2_data;
end
else if (num == 9) num 《= num + 1’b1;//跳過校驗位
else num 《= 4‘d0; //清0
end
//--------------------按鍵按下/松開檢測-------------------------
reg ps2_loosen;//1表示按鍵松開
reg [7:0]ps2_byte;//ps2一個字節(jié)數(shù)據(jù)
always @ (posedge clk or negedge rst_n)
if (!rst_n)
begin
ps2_state 《= 1’b0;
ps2_loosen《= 1‘b0;
end
//每接收完一個數(shù)據(jù)就進(jìn)行按鍵檢測
else if (num == 4’d10)
if (data_temp == 8‘hf0) ps2_loosen 《= 1’b1;//斷碼標(biāo)識符
else
begin
if (ps2_loosen) //1表示按鍵松開,下一次接收數(shù)據(jù)后清0
begin
ps2_state 《= 1‘b0;
ps2_loosen《= 1’b0;
end
else //loosen變0后的下一個數(shù)據(jù)表示按鍵被按下
begin
ps2_state 《= 1‘b1;
ps2_byte 《= data_temp; //把讀取到的值賦給ps2_out
end
end
由于PS2通信是在PS2時鐘的下降沿有效,因此第一個always使用三個寄存器對PS2的CLK做一個下降沿捕獲,并輸出一個ps2下降沿的有效信號。
捕捉到了ps2時鐘的下降沿,第二個always便是使用一個計數(shù)器在下降沿信號有效時讀取并存儲數(shù)據(jù)線上的數(shù)據(jù)。計數(shù)器的值正好對應(yīng)著一幀中的通信格式,因此在計數(shù)器為0時為通信的起始位,1~8為數(shù)據(jù)位,9為校驗位,10為停止位。計數(shù)器處于數(shù)據(jù)位期間內(nèi),將數(shù)據(jù)位依次存儲到一個寄存器中。
得到了數(shù)據(jù),第三個always進(jìn)行的便是通信數(shù)據(jù)的判斷,這里進(jìn)行的是斷碼的判斷。每當(dāng)完成一幀通信時,即計數(shù)器計數(shù)到10(停止位)時,便對通信數(shù)據(jù)做判斷,如果是f0,則為斷碼的第一個碼,那么下一次通信來的必然是按鍵的鍵值碼。因此將收到f0后的下一個通信數(shù)據(jù)作為按鍵的鍵值碼存到一個寄存器中,同時將按鍵有效信號ps2_state置高,表示按下一次按鍵。
這樣便完成了PS2的通信。
PS2按鍵判斷
設(shè)想一個問題,假設(shè)兩個模塊,他們的時鐘是一樣的,模塊一用來進(jìn)行PS2鍵盤檢測,模塊二根據(jù)按鍵按下的有效信號來決定是否執(zhí)行對應(yīng)的操作。如果模塊二采用同步設(shè)計,即由時鐘來控制(通常也是這么做的),如果模塊一輸出的按鍵有效信號不能做到恰好只維持一個時鐘的脈沖寬度,那么模塊二就會多次檢測到按鍵按下并觸發(fā)多次對應(yīng)的控制操作。這也是新手常遇到的問題。
如果模塊一的時鐘是模塊二時鐘的兩倍呢?如果這個時候模塊一輸出的按鍵有效信號仍然只有一個脈沖,那么模塊二就會恰好檢測不到。因此模塊一輸出的按鍵有效信號應(yīng)該維持兩個時鐘的脈沖寬度。而這可以用一個計數(shù)器來控制。
我這里舉一個只輸出一個時鐘長度的有效信號的例子:
reg ps2state_reg;
wire flag;
always @ (posedge clk)
ps2state_reg 《= ps2_state;
assign flag = (ps2state_reg) & (~ps2_state);
//---------------------根據(jù)鍵盤掃描碼輸出按鍵有效信號?--------------------------
always @ (posedge clk or negedge rst_n)
if (!rst_n)
begin left 《= 0; right 《= 0; up 《= 0; down 《= 0; end
else if (flag) //每當(dāng)松開按鍵時才進(jìn)行輸出
case (ps2_byte)
8’h1C: begin left 《= 1; end //a
8‘h23: begin right 《= 1; end //d
8’h1D: begin up 《= 1; end //w
8‘h1B: begin down 《= 1; end //s
default: begin left 《= 0; right 《= 0; up 《= 0; down 《= 0; end
endcase
else if (left) left 《= 0; //有按鍵有效信號輸出一個脈沖后馬上清零
else if (right) right 《= 0;
else if (up) up 《= 0;
else if (down) down 《= 0;
首先對按鍵狀態(tài)ps2_state做一級寄存,然后進(jìn)行邊沿檢測,那么在alwasy中檢測到邊沿有效時則表示按鍵按下了一次,根據(jù)鍵碼將相應(yīng)的按鍵有效信號置1。而當(dāng)檢測到有效信號為高時,在下一個時鐘馬上就拉低,從而實現(xiàn)只輸出一個時鐘的脈沖寬度。這樣就不會引起錯誤的檢測到多次按下的問題。
我們在玩游戲的時候還會碰到這種情況,需要長按一個鍵幾秒鐘才會有相應(yīng)的反應(yīng),其實解決了上面的問題后我們對這種短按、長按的控制思路就很清楚了。簡而言之,在模塊一中使用計數(shù)器來控制輸出的有效信號的時鐘長度,在模塊二中使用相同的計數(shù)器對這個有效信號的時鐘長度進(jìn)行判斷,進(jìn)而識別這個鍵到底是短按還是長按,以選擇不同的操作。
-
FPGA
+關(guān)注
關(guān)注
1640文章
21910瀏覽量
611618
發(fā)布評論請先 登錄
相關(guān)推薦
評論