Arm的鍵盤驅(qū)動實驗
一、實驗?zāi)康?br>1. 學(xué)習(xí)鍵盤驅(qū)動原理。
2. 掌握通過CPU 的I/O 擴展鍵盤的方法。
二、實驗內(nèi)容
通過ARM 的rPDATC(低四位)和rPDATE(4~7 位)擴展4×4 的鍵盤,編程實現(xiàn)鍵
盤的驅(qū)動,通過按鍵可以在超級終端上和液晶屏上顯示相應(yīng)的鍵值(不帶操作系統(tǒng)的實驗僅
要求在超級終端上顯示)。
三、預(yù)備知識
1. 掌握在ARM SDT 2.5 集成開發(fā)環(huán)境中編寫和調(diào)試程序的基本過程。
2. 會使用Source Insight 3 編輯C 語言源程序。
3. 了解ARM 應(yīng)用程序的框架結(jié)構(gòu)。
4. 了解UC/OS-II 多任務(wù)的原理。
四、實驗設(shè)備及工具
硬件:ARM 嵌入式開發(fā)板、用于ARM7TDMI 的JTAG 仿真器、PC 機Pentumn100 以
上。
軟件:PC 機操作系統(tǒng)win98、ARM SDT 2.51 集成開發(fā)環(huán)境、仿真器驅(qū)動程序、Source
Insight 3.0
五、實驗原理
實現(xiàn)鍵盤有兩種方案:一是采用現(xiàn)有的一些芯片實現(xiàn)鍵盤掃描;再就是用軟件實現(xiàn)鍵盤
掃描。作為一個嵌入系統(tǒng)設(shè)計人員,總是會關(guān)心產(chǎn)品成本。目前有很多芯片可以用來實現(xiàn)鍵
盤掃描,但是鍵盤掃描的軟件實現(xiàn)方法有助于縮減一個系統(tǒng)的重復(fù)開發(fā)成本,且只需要很少
的CPU 開銷。嵌入式控制器的功能很強,可以充分利用這一資源,這里就介紹一下軟鍵盤
的實現(xiàn)方案。
圖4-1 簡單鍵盤電路
通常在一個鍵盤中使用了一個瞬時接觸開關(guān),并且用如圖4-1 所示的簡單電路,微處理
器可以容易地檢測到閉合。當開關(guān)打開時,通過處理器的I/O 口的一個上拉電阻提供邏輯1;
當開關(guān)閉合時,處理器的I/O 口的輸入將被拉低到邏輯0。可遺憾的是,開關(guān)并不完善,因
為當它們被按下或者被釋放時,并不能夠產(chǎn)生一個明確的1 或者0。盡管觸點可能看起來穩(wěn)
定而且很快地閉合,但與微處理器快速的運行速度相比,這種動作是比較慢的。當觸點閉合
時,其彈起就像一個球。彈起效果將產(chǎn)生如圖4-2 所示的好幾個脈沖。彈起的持續(xù)時間通常
將維持在5ms∼30ms 之間。如果需要多個鍵,則可以將每個開關(guān)連接到微處理器上它自己的
輸入端口。然而,當開關(guān)的數(shù)目增加時,這種方法將很快使用完所有的輸入端口。
圖4-2 按鍵抖動
鍵盤上排列這些開關(guān)最有效的方法(當需要5 個以上的鍵時)就形成了一個如圖4-3 所示的
二維矩陣。當行和列的數(shù)目一樣多時,也就是方型的矩陣,將產(chǎn)生一個最優(yōu)化的布列方式(I/O
端被連接的時候)。一個瞬時接觸開關(guān)(按鈕)放置在每一行與每一列的交叉點。矩陣所需
的鍵的數(shù)目顯然根據(jù)應(yīng)用程序而不同。每一行由一個輸出端口的一位驅(qū)動,而每一列由一個
電阻器上拉且供給輸入端口一位。
圖4-3 矩陣鍵盤
鍵盤掃描過程就是讓微處理器按有規(guī)律的時間間隔查看鍵盤矩陣,以確定是否有鍵被按
下。一旦處理器判定有一個鍵按下,鍵盤掃描軟件將過濾掉抖動并且判定哪個鍵被按下。每
個鍵被分配一個稱為掃描碼的唯一標識符。應(yīng)用程序利用該掃描碼,根據(jù)按下的鍵來判定應(yīng)
該采取什么行動。換句話說,掃描碼將告訴應(yīng)用程序按下哪個鍵。
某一時刻按下多個鍵(意外地或者故意地)的情況被稱為轉(zhuǎn)滾。能夠正確識別一個新鍵
被按下(即使n-1 個鍵已經(jīng)被按下)的任何算法被稱為具有n 鍵轉(zhuǎn)滾的能力。本章提出的矩
陣鍵盤系統(tǒng)設(shè)計,在這種系統(tǒng)中用戶輸入可能發(fā)生相繼按鍵。這些系統(tǒng)通常不需要具有像終
端或者計算機系統(tǒng)上的鍵盤的全部特征那樣的鍵盤。
鍵盤掃描算法:
在初始化階段,所有的行(輸出端口)被強行設(shè)置為低電平。在沒有任何鍵按下時。所
有的列(輸入端口)將讀到高電平。任何鍵的閉合將造成其中的一列變?yōu)榈碗娖健榱瞬榭?br>是否有一個鍵已經(jīng)被按下,微處理器僅僅需要查看任一列的值是否變成低電平。一旦微處理器檢測到有鍵被按下,就需要找出是哪一個鍵。過程很簡單,微處理器只需在其中一列上輸
出一個低電平。如果它在輸入端口上發(fā)現(xiàn)一個0 值,該微處理器就知道在所選擇行上產(chǎn)生了
鍵的閉合。相反,如果輸入端口全是高電平,則被按下的鍵就不在那一行,微處理器將選擇
下一行,并重復(fù)該過程直到它發(fā)現(xiàn)了該行為止。一旦該行被識別出來,則被按下鍵的具體的
列可以通過鎖定輸入端口上唯一的低電位來確定。微處理器執(zhí)行這些步驟所需要的時間與最
小的狀態(tài)閉合時間相比是非常短的,因此它假設(shè)該鍵在這個時間間隔中將維持按下的狀態(tài)。
比如:當發(fā)現(xiàn)某列變?yōu)榈碗娖綍r,此時微處理器僅在某一行上輸出低電平,再查看列的狀態(tài),
如果此時在輸入端口上發(fā)現(xiàn)了一個0,則就可以斷定就是此行上的鍵按下了,反之,如果輸
入端口上全為1,則就不是這一行上按下了鍵。根據(jù)第一步和第二步中得到的值,便可以得
到相應(yīng)的掃描碼。比如,第一步中行全為零時列輸入B1 為零,當將輸出的第二行B2 置為
零時,如果此時的列輸入B1 仍為零,則可得到掃描碼為×××。
為了過濾回彈的問題,微處理器以規(guī)定的時間間隔對鍵盤進行采樣,這個間隔通常在
20ms~100ms 之間(被稱為去除回彈周期),它主要取決于所使用開關(guān)的回彈特征。
另外一個的特點就是所謂的自動重復(fù)。自動重復(fù)允許一個鍵的掃描碼可以重復(fù)地被插入
緩沖區(qū),只要按著這個鍵或者直到緩沖區(qū)滿為止。自動重復(fù)功能非常有用的,當你打算遞增
或者遞減一個參數(shù)(也就是一個變量)值時,不必重復(fù)按下或者釋放該鍵。如果該鍵被按住
的時間超過自動重復(fù)的延遲時間,這個按鍵將被重復(fù)的確認按下。
在我們的開發(fā)板上,鍵盤的接法如下圖所示:
本次實驗實現(xiàn)的是4×4 的鍵盤掃描。分別將每一列置零,如果這時有鍵按下,則對應(yīng)
的行將為低電平,將4 次得到的結(jié)果放到一個16 位變量中,該變量的哪一位為零則對應(yīng)一
個按鍵,如果沒有鍵按下則該變量的值為0xffff。做一個數(shù)組存儲鍵盤的映射碼,這樣方便
修改按鍵對應(yīng)的值。為了過濾回彈的問題,微處理器以規(guī)定的時間間隔對鍵盤進行采樣,這
個間隔通常在20ms~100ms 之間(被稱為去除回彈周期),它主要取決于所使用開關(guān)的回彈
特征。本次實驗中取為50ms。另個一個的特點就是所謂的自動重復(fù)。自動重復(fù)允許一個鍵
的掃描碼可以重復(fù)地被插入緩沖區(qū),只要按著這個鍵或者直到緩沖區(qū)滿為止。本次實驗的自
動重復(fù)按鍵的第一次和第二次之間的延時為1 秒鐘,以后的重復(fù)速度為50 毫秒。
六、實驗步驟
1. 不帶操作系統(tǒng)的鍵盤驅(qū)動的實現(xiàn)
① 新建工程,將44b.h 和Option.h 兩個頭文件加入主文件中(這兩個文件中定義
了ARM 的寄存器等信息),將Uhal.h 文件中的函數(shù)以頭文件的方式加入主文
件中(因為我們要將鍵盤掃描碼發(fā)送到串口,在超級終端中顯示出來)。
② 在主函數(shù)中定義鍵盤映射表:
unsigned char MykeyBoard_KeyMap[]={1,4,7,10,2,5,8,0,3,6,9,11,12,13,14,15};
//10-退格,11-*/.,12-↑,13-↓,14-確定,15-取消
unsigned short MyGetScanKey();
通過查找鍵盤映射表來確定鍵盤掃描碼對應(yīng)的按鍵值。
③ 定義鍵盤掃描函數(shù),此函數(shù)主要實現(xiàn)返回鍵盤掃描碼。
unsigned short MyGetScanKey()
{
unsigned short key;
unsigned int i,temp;
for(i=1;i<0x10;i<<=1)
{
rPDATE|=0xf0;初始化端口
rPDATE&=~(i<<4);//向列所在端口分別發(fā)送1110,1101,1011,0111
key<<=4;//右移四位
Delay(10);//延時,等待響應(yīng)
temp=rPDATC;//讀各行狀態(tài)值
key|=(temp&0xf);//將四次所得結(jié)果保存起來
}
return key;
}
④ 定義鍵盤驅(qū)動函數(shù),此函數(shù)調(diào)用鍵盤掃描函數(shù),通過判斷鍵盤掃描函數(shù)返回的
掃描碼,實現(xiàn)去抖動和自動重復(fù)功能,并將其轉(zhuǎn)化為對應(yīng)鍵的鍵值。
unsigned int MyGetKey()
{
int i;
unsigned short key,tempkey=1;
static unsigned short oldkey=0xffff;
static unsigned char keystatus=0;
unsigned char keycnt=0;
while(1)
{
key=0xffff;
while(1)
{
key=MyGetScanKey();
if(key!=0xffff)//有鍵按下
break;
oldkey=0xffff;
}
Delay(500);//去抖動
if(key!=MyGetScanKey())
continue;//如果兩次的鍵制不同,重新掃描
if(oldkey!=key)
keystatus=0;
if(keystatus==0) //第一次按下此鍵
{
keycnt=0;
keystatus=1;
}
else if(keystatus==1) //第二次重復(fù)此鍵
{
keycnt++;
if(keycnt==20)
keystatus=2;
else
continue;
}
oldkey=key;
break;
}
for(i=0;i<16;i++)//查找按鍵,不包括功能鍵
{
if((key&tempkey)==0)
break;
tempkey<<=1;
}
return MykeyBoard_KeyMap[i];
}
⑤ 編寫主程序。將得到的鍵值轉(zhuǎn)化為ASCII 碼發(fā)送到串口。
int Main(int argc, char **argv)
{
unsigned int key;
unsigned int tempkey=0;
Uart_SendByte(0,0xa);//向串口發(fā)送0xa,表示換行
Uart_SendByte(0,0xd);//回車
for (;;)
{
key=MyGetKey();
key&=0x000f;
if(key>9)
{
Uart_SendByte(0,0x31);
tempkey=key-10;
Uart_SendByte(0,tempkey|=0x0030);
}
else
Uart_SendByte(0,key|0x0030);
Uart_SendByte(0,0x2c);
}
return 0;
}
2.帶操作系統(tǒng)的鍵盤驅(qū)動的實現(xiàn)。
① 在主函數(shù)中定義鍵盤映射表,定義鍵盤掃描函數(shù),定義鍵盤驅(qū)動函數(shù)。
② 定義鍵盤響應(yīng)函數(shù),將得到的鍵值在液晶屏上顯示。
void onKey(int nkey, int fnkey)//鍵盤響應(yīng)函數(shù)
{
char temp[3];//轉(zhuǎn)換成ASC-II 的鍵值數(shù)組
if(nkey>9)
{
temp[0]=0x31;
temp[1]=(nkey-10)|0x30;
temp[2]=0;
}
else
{
temp[0]=nkey+0x30;
temp[1]=0;
}
LCD_printf(temp);//在液晶屏上顯示鍵值
LCD_printf("\n");
}
③ 定義鍵盤掃描任務(wù)。
OS_STK My_Key_Scan_Stack[STACKSIZE]={0, }; //定義鍵盤掃描任務(wù)的堆棧大小
void My_Key_Scan_Task(void *Id); //定義鍵盤掃描任務(wù)
#define MyKey_Scan_Task_Prio 58 //定義鍵盤掃描任務(wù)的優(yōu)先級
OSTaskCreate(My_Key_Scan_Task,(void*)0,(OS_STK*)&My_Key_Scan_Stack[STACKSIZE-1],
MyKey_Scan_Task_Prio );//在主函數(shù)中創(chuàng)建鍵盤掃描任務(wù)
void My_Key_Scan_Task(void *Id)//鍵盤掃描任務(wù)
{
U32 key;
u32 tempkey=0;
POSMSG pmsg;//創(chuàng)建消息
Uart_Printf("begin key task \n");
for (;;)
{
key=MyGetKey();
key&=0x000f;
if(key>9)
{
Uart_SendByte(0,0x31);
tempkey=key-10;
Uart_SendByte(0,tempkey|=0x0030);
}
else
Uart_SendByte(0,key|0x0030);
Uart_Printf(",");
pmsg=OSCreateMessage(NULL, OSM_KEY,key,key);//創(chuàng)建鍵盤消息
if(pmsg)
SendMessage(pmsg);//發(fā)送鍵盤消息
}
}
④ 定義主任務(wù)
OS_STK Main_Stack[STACKSIZE*8]={0, };//定義主任務(wù)的堆棧大小
void Main_Task(void *Id); //定義主任務(wù)
#define Main_Task_Prio 12 //定義主任務(wù)的優(yōu)先級
OSTaskCreate(Main_Task,(void*)0,(OS_STK*)&Main_Stack[STACKSIZE*8-1],
Main_Task_Prio);//在主函數(shù)重創(chuàng)建主任務(wù)
void Main_Task(void *Id) //主任務(wù)
{
POSMSG pMsg=0;//創(chuàng)建消息
LCD_ChangeMode(DspTxtMode);//將液晶屏設(shè)為文本顯示模式
LCD_Cls();//清屏
for(;;)
{
pMsg=WaitMessage(0); //等待消息
switch(pMsg->Message)
{
case OSM_KEY:
onKey(pMsg->WParam,pMsg->LParam);
break;
Delay(200);
}
DeleteMessage(pMsg);//刪除消息,釋放資源
}
}
注意:
由于本操作系統(tǒng)已封裝了鍵盤驅(qū)動程序,要使用我們自己的鍵盤驅(qū)動程序需要在
OSAddTask.h 中將“//#define OS_KeyBoard_Scan_Task”去掉,這樣才不會產(chǎn)生重復(fù)定義的
錯誤。
七、思考題
1. 鍵盤掃描的原理是什么?
2. 用其它方法實現(xiàn)鍵盤驅(qū)動。
評論
查看更多