單片機(jī)系統(tǒng)中PS/2鍵盤驅(qū)動程序設(shè)計
摘要分析PS/2協(xié)議;介紹PS/2標(biāo)準(zhǔn)鍵盤的第二套掃描碼和命令集,并給出在單片機(jī)系統(tǒng)中支持PS/2鍵盤的硬件連接方式和利用Keil C51語言實現(xiàn)的驅(qū)動程序設(shè)計及部分代碼。該驅(qū)動程序可以方便地移植到其他單片機(jī)或嵌入式系統(tǒng)中。
關(guān)鍵詞 PS/2協(xié)議 PS/2鍵盤單片機(jī)驅(qū)動程序
在單片機(jī)系統(tǒng)中,經(jīng)常使用的鍵盤都是專用鍵盤。這類鍵盤都是單獨設(shè)計制作的,成本高,連線多,且可靠性不高。這些問題在那些要求鍵盤按鍵較多的應(yīng)用系統(tǒng)中顯得更加突出。與此相比,在PC系統(tǒng)中廣泛使用的PS/2鍵盤具有價格低。通用可靠,且使用的連線少(僅使用2根信號線)的特點,并可滿足多數(shù)系統(tǒng)的要求。因此,在單片機(jī)系統(tǒng)中應(yīng)用PS/2鍵盤是一種很好的選擇。
本文在分析PS/2協(xié)議和PS/2鍵盤工作原理與特點的基礎(chǔ)上,給出在AT89C51單片機(jī)上實現(xiàn)對PS/2鍵盤支持的硬件連接方法以及驅(qū)動程序的設(shè)計實現(xiàn)。
1PS/2協(xié)議
現(xiàn)在PC機(jī)廣泛采用的PS/2接口為miniDIN 6引腳的連接器。其引腳如圖1所示。
1—數(shù)據(jù)線(DATA);2—未用;3—電源地(GND);
4—電源(+5 V);5—時鐘(CLK);6—未用。
圖1PS/2連接器PS/2設(shè)備有主從之分,主設(shè)備采用female插座,從設(shè)備采用male插座,F(xiàn)在廣泛使用的PS/2鍵盤鼠標(biāo)均工作在從設(shè)備方式下。PS/2接口的時鐘與數(shù)據(jù)線都是集電極開路結(jié)構(gòu)的,必須外接上拉電阻。一般上拉電阻設(shè)置在主設(shè)備中。主從設(shè)備之間數(shù)據(jù)通信采用雙向同步串行方式傳輸,時鐘信號由從設(shè)備產(chǎn)生。
(1)從設(shè)備到主設(shè)備的通信
當(dāng)從設(shè)備向主設(shè)備發(fā)送數(shù)據(jù)時,首先會檢查時鐘線,以確認(rèn)時鐘線是否是高電平。如果是高電平,從設(shè)備就可以開始傳輸數(shù)據(jù);否則,從設(shè)備要等待獲得總線的控制權(quán),才能開始傳輸數(shù)據(jù)。傳輸?shù)拿恳粠?1位組成,發(fā)送時序及每一位的含義如圖2所示。
圖2從設(shè)備到主設(shè)備的通信每一幀數(shù)據(jù)中開始位總是為0,數(shù)據(jù)校驗采用奇校驗方式,停止位始終為1。從設(shè)備到主設(shè)備通信時,從設(shè)備總是在時鐘線為高時改變數(shù)據(jù)線狀態(tài),主設(shè)備在時鐘下降沿讀入數(shù)據(jù)線狀態(tài)。
(2)主設(shè)備到從設(shè)備的通信
主設(shè)備與從設(shè)備進(jìn)行通信時,主設(shè)備首先會把時鐘線和數(shù)據(jù)線設(shè)置為“請求發(fā)送”狀態(tài)。具體方式為:首先下拉時鐘線至少100μs來抑制通信,然后下拉數(shù)據(jù)線“請求發(fā)送”,最后釋放時鐘線。在此過程中,從設(shè)備在不超過10μs的間隔內(nèi)就要檢查這個狀態(tài)。當(dāng)設(shè)備檢測到這個狀態(tài)時,將開始產(chǎn)生時鐘信號。
此時數(shù)據(jù)傳輸?shù)拿恳粠?2位構(gòu)成,其時序和每一位含義如圖3所示。
圖3主設(shè)備到從設(shè)備的通信與從設(shè)備到主設(shè)備通信相比,其每幀數(shù)據(jù)多了一個ACK位。這是從設(shè)備應(yīng)答接收到的字節(jié)的應(yīng)答位,由從設(shè)備通過拉低數(shù)據(jù)線產(chǎn)生,應(yīng)答位ACK總是為0。主設(shè)備到從設(shè)備通信過程中,主設(shè)備總是在時鐘為低電平時改變數(shù)據(jù)線的狀態(tài),從設(shè)備在時鐘的上升沿讀入數(shù)據(jù)線狀態(tài)。
2PS/2鍵盤的編碼與命令集
(1) PS/2鍵盤的編碼
現(xiàn)在PC機(jī)使用的PS/2鍵盤都默認(rèn)采用第二套掃描碼集。該掃描碼集可參考文獻(xiàn)\[1\]。掃描碼有兩種不同的類型:通碼(make code)和斷碼(break code)。當(dāng)一個鍵被按下或持續(xù)按住時,鍵盤會將該鍵的通碼發(fā)送給主機(jī);而當(dāng)一個鍵被釋放時,鍵盤會將該鍵的斷碼發(fā)送給主機(jī)。
根據(jù)鍵盤按鍵掃描碼的不同,在此可將按鍵分為如下幾類:
第一類按鍵,通碼為1字節(jié),斷碼為0xF0+通碼形式。如A鍵,其通碼為0x1C,斷碼為0xF0 0x1C。
第二類按鍵,通碼為2字節(jié)0xE0+0xXX形式,斷碼為0xE0+0xF0+0xXX形式。如right ctrl鍵,其通碼為0xE0 0x14,斷碼為0xE0 0xF0 0x14。
第三類特殊按鍵有兩個,print screen鍵通碼為0xE0 0x12 0xE0 0x7C,斷碼為0xE0 0xF0 0x7C 0xE0 0xF0 0x12; pause鍵通碼為0x E1 0x14 0x77 0xE1 0xF0 0x14 0xF0 0x77,斷碼為空。
組合按鍵的掃描碼發(fā)送按照按鍵發(fā)生的次序,如以下面順序按左SHIFT+A鍵:1按下左SHIFT鍵,2按下A鍵,3釋放A鍵,4釋放左SHIFT鍵,那么計算機(jī)上接收到的一串?dāng)?shù)據(jù)為0x12 0x1C 0xF0 0x1C 0xF0 0x12。
在驅(qū)動程序設(shè)計中,就是根據(jù)這樣的分類來對不同的按鍵進(jìn)行不同處理的。
(2) PS/2鍵盤的命令集
主機(jī)可以通過向PS/2鍵盤發(fā)送命令來對鍵盤進(jìn)行設(shè)置或者獲得鍵盤的狀態(tài)等操作。每發(fā)送一個字節(jié),主機(jī)都會從鍵盤獲得一個應(yīng)答0xFA(“重發(fā)resend”和“回應(yīng)echo”命令例外)。下面簡要介紹驅(qū)動程序在鍵盤初始化過程中所用的指令(詳細(xì)鍵盤命令集見參考文獻(xiàn)\[1\]):
0xED主機(jī)在本命令后跟隨發(fā)送一個參數(shù)字節(jié),用于指示鍵盤上num lock, caps lock, scroll lock led的狀態(tài);
0xF3主機(jī)在這條命令后跟隨發(fā)送一個字節(jié)參數(shù)來定義鍵盤機(jī)打的速率和延時;
0xF4用于在當(dāng)主機(jī)發(fā)送0xF5禁止鍵盤后,重新使能鍵盤。
3PS/2鍵盤與單片機(jī)的連接電路
PS/2鍵盤與AT89C51單片機(jī)的連接方式如圖4所示。P1.0接PS/2數(shù)據(jù)線,P3.2(INT0)接PS/2時鐘線。因為單片機(jī)的P1.P3口內(nèi)部是帶上拉電阻的,所以PS/2的時鐘線和數(shù)據(jù)線可以直接與單片機(jī)的P1.P3相連接。
4驅(qū)動程序設(shè)計
驅(qū)動程序使用Keil C51語言,Keil uVision2編程環(huán)境。PS/2 104鍵盤驅(qū)動程序的主要任務(wù),是實現(xiàn)單片機(jī)與鍵盤間PS/2通信,以及將接收到的按鍵掃描碼轉(zhuǎn)換為該按鍵的鍵值KeyVal,提供給系統(tǒng)上層軟件使用。
(1)單片機(jī)與鍵盤間PS/2通信的程序設(shè)計
在PS/2通信過程中,主設(shè)備(單片機(jī))是在時鐘信號為低時發(fā)送和接收數(shù)據(jù)信號的。因為單片機(jī)到鍵盤發(fā)送的是指令,需要鍵盤回應(yīng),所以這部分程序采用查詢方式;而單片機(jī)接收鍵盤數(shù)據(jù)時,數(shù)據(jù)線上的信號在時鐘為低時已經(jīng)穩(wěn)定,所以這部分程序采用中斷方式,且不需要在程序中加入延時程序。單片機(jī)的鍵盤發(fā)送接口程序見本刊網(wǎng)站http://www.dpj.com.cn
(2)鍵盤掃描碼轉(zhuǎn)換程序設(shè)計
由于鍵盤掃描碼無規(guī)律可循,因此由鍵盤掃描碼獲得相應(yīng)按鍵的鍵值(字符鍵為其ASCII值,控制鍵如F1.CTRL等為自定義值),只能通過查表的方式。由于按鍵的三種類型及部分按鍵對應(yīng)著兩個鍵值(如A鍵的鍵值根據(jù)CAPS和SHIFT鍵狀態(tài)有0x41(A)和0x61(a)兩種),因此綜合考慮查表轉(zhuǎn)換速度和資源消耗,設(shè)計中使用4個鍵盤表:鍵盤掃描碼轉(zhuǎn)換基本集和切換集kb_plain_map\[NR_KEYS\]與kb_shift_map\[NR_KEYS\];包含E0前綴的鍵盤掃描碼轉(zhuǎn)換基本集和切換集kbe0_plain_map\[NR_KEYS\]與kbe0_shift_map\[NR_KEYS\]。PS/2 104鍵盤按鍵掃描碼最大值為0x83,所以設(shè)置NR_KEYS為132。所有四個鍵盤表的定義均為如下形式:KB_MAP\[MAKE CODE\]=KEYVAL,如果掃描碼對應(yīng)的按鍵為空,如KB_MAP\[0x00\],則定義相應(yīng)鍵值為NULL_KEY(0x00)。以下是鍵盤掃描碼基本集的部分代碼實例:kb_plain_map\[NR_KEYS\]={……NULL_KEY;0x2C;0x6B;0x69;0x6F;0x30;0x39;NULL_KEY;//掃描碼0x40~0x47file://對應(yīng)按鍵空,逗號,K,I,O,0,9,空file://對應(yīng)鍵值 0x00,’,’,’k’,’i’,’o’,’0’,’9’,0x00……};圖4硬件連接電路如此設(shè)計鍵盤轉(zhuǎn)換表的另一個好處在于,以后如需擴(kuò)展支持有ACPI.Windows多媒體按鍵鍵盤時,只需要將鍵表中相應(yīng)處修改即可。如ACPI power按鍵通碼為0xE0 0x37,修改kbe0_plain_map\[0x37\]=KB_ACPI_PWR即可。
特殊按鍵PAUSE使用單獨程序處理,如果接收到0xE1就轉(zhuǎn)入這段程序;而print screen鍵則將其看作是兩個通碼分別為0xE0 0x12和0xE0 0x7C的“虛鍵”的組合鍵來處理。
在驅(qū)動程序中聲明如下全局變量:led_status其bit0-scroll lock led關(guān)0.開1;bit1-num lock led關(guān)為0,開為1;bit2-caps lock led關(guān)為0,開為1;bit3~bit7總是0;agcs_status記錄左右shift ctrl gui alt狀態(tài),bit0-左shift鍵,bit1-左ctrl鍵,bit2-左gui鍵,bit3-左alt鍵,bit4-右shift鍵,bit5-右ctrl鍵,bit6-右gui鍵,bit7-右alt鍵,相應(yīng)鍵按下則對應(yīng)位為1,釋放為0。E0_FLAG接到0xE0置1;E1_FLAG接收到0xE1置1;F0_FLAG接收到0xF0置1。按鍵鍵值通過KeyVal提供給上層使用。
PS/2鍵盤掃描碼鍵值轉(zhuǎn)換程序ps2_codetrans()流程如圖5所示。
圖5掃描碼鍵值轉(zhuǎn)換程序流程第一類按鍵的掃描碼鍵值轉(zhuǎn)換程序代碼:if (F0_FLAG){//接收掃描碼為斷碼
switch (mcu_revchar){//處理控制鍵
case 0x11: agcs_status&=0xF7;break;//左alt釋放
case 0x12: agcs_status&=0xFE;break;//左shift釋放
case 0x14: agcs_status&=0xFD;break;//左ctrl釋放
case 0x58: if(led_status&0x04)
led_status&=0x03;//caps lock鍵
else led_status =0x04;
ps2_ledchange();
break;
case 0x59: agcs_status&=0xEF;break;//右shift釋放
case 0x77: if(led_status&0x02)
led_status&=0x05;//num lock鍵
else led_status =0x02;
ps2_ledchange();
break;
case 0x7E: if(led_status&0x01)
led_status&=0x06;//scroll lock鍵
else led_status =0x01;
ps2_ledchange();
break;
default:break;
}
F0_FLAG = 0;
}
else{//接收掃描碼為通碼
if (led_status & 0x04) caps_flag = 1; else caps_flag = 0;
if (led_status & 0x02) num_flag = 1; else num_flag = 0;
if (scga_status & 0x11) shift_flag = 1; else shift_flag = 0;
file://掃描碼鍵值轉(zhuǎn)換
if ((caps_flag == shift_flag) (!num_flag)) KeyVal=kb_plain_map\[mcu_revchar\];
else KeyVal=kb_shift_map\[mcu_revchar\];
switch(mcu_revchar){//處理控制鍵或狀態(tài)鍵
case 0x11: agcs_status = 0x08;//左alt按下
case 0x12: agcs_status = 0x01;//左shift按下
case 0x14: agcs_status = 0x02;//左ctrl按下
case 0x59: agcs_status = 0x10;//右shift按下
default: break;
}
}第二類按鍵的掃描碼鍵值轉(zhuǎn)換程序與上相似。要注意的是在退出該程序段時對E0_FLAG和F0_FLAG標(biāo)志的清0。
PAUSE鍵的處理程序:如果接收到0xE1,置E1_FLAG=1,然后順次將后續(xù)接收到的7個字節(jié)數(shù)據(jù)和PAUSE的通碼后7個字節(jié)比較,一致則返回KeyVal=KB_PAUSE。在比較完所有7個字節(jié)后清除E1_FLAG標(biāo)志。
鍵盤初始化程序kb_init()流程:
①上電后,接收鍵盤上電自檢通過信號0xAA,或者自檢出錯信號0xFC。單片機(jī)接收為0xAA,進(jìn)入下一步,否則,進(jìn)行出錯處理。
②關(guān)LED指示,單片機(jī)發(fā)送0xED,然后接收鍵盤回應(yīng)0xFA,接著發(fā)送送0x00接收0xFA。
③設(shè)置機(jī)打延時和速率。單片機(jī)發(fā)送0xF3,接收0xFA,發(fā)送0x00(250ms,2.0cps),接收0xFA。
④檢查LED,發(fā)送0xED,接收0xFA,發(fā)送0x07(開所有LED),接收0xFA。發(fā)送0xED,接收0xFA,發(fā)送0x00(關(guān)LED),接收0xFA。
⑤允許鍵盤發(fā)送0xF4,接收0xFA。
鍵盤LED改變ps2_ledchange()函數(shù)流程:發(fā)送0xED→接收0xFA→發(fā)送led_status→接收0xFA。
結(jié)語
該驅(qū)動程序經(jīng)Keil uVision2編譯,在AT89C51單片機(jī)上運行通過,實現(xiàn)了對PS/2 104鍵盤的支持,以及對字符按鍵大小寫切換,num lock切換,控制鍵及組合按鍵的支持。該程序?qū)ζ渌度胧交騿纹瑱C(jī)系統(tǒng)中PS/2鍵盤的應(yīng)用也有借鑒意義。
參考文獻(xiàn)
1Adam Chapweske. The ATPS/2 Keyboard Interface. http://panda.cs.ndsu.nodak.edu/%7Eachapwes/PICmicro/keyboard/atkeyboard.html
2Adam Chapweske. PS/2 Mouse/Keyboard Protocol. http://govschl.ndsu.nodak.edu/~achapwes/PICmicro/PS2/ps2.htm
3Network Technologies Incorporated. PS/2 Keyboard & Mouse Protocols. http://www.networktechinc.com/ps2prots.html
4 Linux 2.4.10內(nèi)核程序 defkeymap.c dn_keyb.c kbd.c keybdev.c keyboard.c kbd_kern.h kd.h keyboard.