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