鍵盤掃描模塊有兩種工作方式, 一種為自動的由時鐘模塊調用, 另一種是由程序員自行調用。 1) 由時鐘模塊自動調用的方式 將時鐘模塊實現文件(Timer.h)及鍵盤掃描模塊的實現文件(KBScan。c)包含進工程, 在Config.h 文件中添加TIMER_KBSCANDELAY宏。 時鐘模塊自動對時鐘中斷進行計數, 當達到TIMER_KBSCANDELAY宏所定義的值后, 自動調用鍵盤掃描模塊中的函數KBScanProcess()進行鍵盤掃描,也就是說,這個宏的值可以決定按鍵消抖動的時間。 用戶應該提供兩個回調函數OnKBScan()及onKeysPressed()。 在函數OnKBScan中進行鍵盤掃描, 并返回掃描碼。 掃描碼的類型缺省為BYTE, 當鍵盤規模較大時, BYTE不能夠完全包含鍵盤信息時, 可在Config.h文件中重定義宏KBVALUE, 如下: #define KBVALUE WORD 這樣, 就可以使用16位的鍵盤掃描碼, 如果此時還達不到要求, 可以將鍵盤掃描碼定義成一個結構, 但這樣做將會增加代碼量及消耗更多的RAM資源, 故不推薦。 掃描模塊調用OnKBScan取得掃描碼, 并調用用戶可以重定義的宏IsNoKeyPressed來判斷是否有鍵按下, 缺省的IsNoKeyPressed實現如下: #define IsNoKeyPressed(x) ((x) == 0x00) 即認為OnKBScan返回0掃描碼時為沒有鍵按下, 如果掃描函數返回其它非零掃描碼做為無鍵按下的掃描碼時, 可以在Config.h文件中重定義IsNoKeyPressed宏的實現。 8位鍵盤掃描碼(缺省值)時, 相應的掃描函數為: BYTE OnKBScan() 當掃描模塊經過軟件消抖動之后, 發現有鍵按下, 就會調用另一個回調函數onKeysPressed。 函數的聲明應該如下: void onKeyPressed(BYTE byKBValue, BYTE byState) 其中中的參數byKBValue的類型為BYTE, 此為缺省值, 如果使用其它類型的掃描碼, 就將此參數變為相應類型。 這個值由OnKBScan返回。 另一個參數byState在通常情況下為零。 但當用戶在Config.h中定義宏KBSCAN_BRUSTCOUNT, 同時鍵盤上的某鍵被按住不放時, 掃描模塊對它自己的調用(注意這里和TIMER_KBSCANDELAY宏不同, TIMER_KBSCANDELAY是時鐘中斷足夠的次數后調用掃描模塊, 而KBSCAN_BRUSHCOUNT為掃描模塊自身的被調用次數)進行計數,當達到KBSCAN_BRUSTCOUNT時,掃描模塊調用onKeysPressed,此時第一個參數的含義不變, 而byState變成1, 同時計數器復位,又經過一段時間后,用值為3的byState 調用onKeysPressed。 這樣就可以很方便的實現多功能鍵或者檢測某鍵的長時間被按下。 2)由用戶自行調用 由用戶自行在程序中調用掃描模塊,而不是由時鐘中斷自行調用。其它與方式1相同。
注意: 1) 函數KBScanProcess為非阻塞函數,它將在很快的時間內返回,等待再次分配給它執行的機會。 2) 函數KBScanProcess是在時鐘中斷外部運行的,它的過程可以被任何中斷打斷,但不影響系統運行。 3) byState的最大值為250,之后被復位為零。 現在來舉例說明上述幾個模塊的使用方法。 硬件環境描述: 為了控制一盞燈,需要單片機提供一個做控制功能的開關量,這里不描述外部接口電路,只說明當單片機的P10腳為高電平時,燈滅,當P10腳為低電平時,燈亮。 可以通過計算機由串口發送命令來控制,或通過一個按鍵(push button不是自鎖式的按鍵)來手動控制(按鍵接在P11腳上,當鍵沒有按下時,P11電平為高,鍵按下時,引腳電平被接低),當使用按鍵手動控制的時候,需要給計算機發送通知。 設定串口通訊指令如下: 數據包由0xff做包頭,4個字節長,第二個字節為命令代碼,第三個字節為數據,最后一個字節為校驗位。 命令和數據代碼有如下組合: (計算機發給單片機) 0x10 0x01: 計算機控制燈亮。(數據位是非零值即可) 0x10 0x00: 計算機控制燈滅。 (單片機發給計算機) 0x11 0x01:單片機正常執行控制指令,返回。(數據位是非零值即可) 0x11 0x00: 單片機不能夠正常執行控制指令,或控制指令錯(不明含義的數據包或校驗錯等)。 0x12 0x01:手動控制燈亮。(數據位是非零值即可) 0x12 0x00: 手動控制燈滅。
建立工程: 在硬盤上建立文件夾Projects,在Projects下建立Common文件夾及Example文件夾。將各模塊的頭文件及實現文件拷貝到Common文件夾下(推薦使用這樣的文件組織結構,其它工程也可以建立在Projects下,各工程共享Common文件夾中的代碼)。 啟動KeilC的IDE,在Example下建立新工程,將各模塊的實現文件包含進工程。 在Example文件夾下建立Output文件夾,更改工程設置,將Output作為輸出文件和List文件的輸出文件夾(推薦使用這樣的結構,當保存工程文件時,可以簡單的刪除Output文件夾中的內容而不會誤刪有用的工程文件)。 建立工程配置頭文件Config.h及工程主文件Example.c,并將Exmaple.c文件加入工程。
輸入代碼: 代碼的具體編寫過程略。下面是最后的Config.h文件及Example.c文件。 // // file: Config.h // #ifndef _CONFIG_H_ #define _CONFIG_H_ #include <Atmel/At89x52.h> // 使用AT89C52做控制 #include “../Common/Common.h” // 使用自定義的數據類型 #define TIMER_RELOAD 922 // 11.0592MHz晶振,1ms中斷周期 #define TIMER_KBSCANDELAY 40 // 40ms重檢測按鍵狀態,即40ms消抖 #define SCOMM_AsyncInterface // 使用異步通訊服務 #define IsPackageHeader(x) ((x) == 0xff) // 判斷包頭是不是0xff #define IsPackageTailer(x, y, z) ((y) <= (z)) // 判斷包的長度是不是足夠 #endif // _CONFIG_H_
// // file: Example.c // #include <Atmail/At89x52.h> #include “../Common/Common.h” #include “../Common/Timer.h” #include “../Common/Scomm.h” #include “../Common/KBScan.h”
BIT gbitLampState = 1 ; // 燈的狀態,缺省為off
static void Initialize() { InitTimerModule() ; // 初始化時鐘模塊 InitSCommModule(0xfd, TRUE) ; // 初始化通訊模塊,11.0592MHz晶振, // 波特率為19200 EA = 1; // 開中斷 }
void main() { Initialize() ; // 初始化 while(TRUE) // 主循環 { ImpTimerService() ; // 實現時鐘中斷服務,如鍵盤掃描 AsyncRecePackage(4) ; // 接收4個字節長的數據包 } }
// 在中斷外部響應時鐘中斷事件 void OnTimerEvent() { // do nothing }
// 控制外部燈 static void TriggerLamp(BIT bEnable) { P10 = ~bEnable ; // 需要反相控制 }
// 鍵掃描回調函數 BYTE KBScan() { BIT b ; P11 = 1 ; // 讀之前拉高引腳電平 b = P11 ; // 讀入引腳狀態 return ~b ; // 數據反相做掃描碼 }
// 計算校驗和 static BYTE CalcCheckSum(BYTE* pbyBuf, BYTE byLen) { BYTE by, bySum = 0 ; for(by = 0 ; by < byLen ; by++) bySum += pbyBuf[by] ; return 0 – bySum ; }
// 接收到鍵盤消息回調函數 void onKeyPressed(BYTE byvalue, BYTE byState) { BYTE by[4] ; if(byState == 0) { switch(byvalue) { case 0x01: gbitLampState = ~g bitLampState ; // 燈狀態取反 TriggerLamp(gbitLampState) ; // 執行控制 by[0] = 0xff ; // 構造數據包 by[1] = 0x12 ; by[2] = (BYTE)gbitLampState ; by[3] = CalcCheckSum(by, 3) ; // 求校驗和 SendPackage(by, 4) ; // 發送數據包 break ; // 處理其它掃描碼 default: break ; } }
// 接收到數據包回調函數 void OnRecePackage(BYTE* pbyBuf, BYTE byBufLen) { BYTE by[4] ; by[0] = 0xff ; by[1] = 0x11 ; if(byBufLen != 4 || pbyBuf[3] != CalcCheckSum(pbyBuf, 3)) { by[2] = 0 ; by[3] = CalcCheckSum(by, 3) ; SendPackage(by, 4) ; // 處理長度或校驗和不正確 }
switch(pbyBuf[1]) { case 0x10: gbitLampState = (BIT)pbyBuf[2] ; TriggerLamp(gbitLampState) ; by[2] = 1 ; by[3] = CalcCheckSum(by, 3) ; SendPackage(by, 4) ; // 發送成功執行通知 break ;
default: // 不知道的命令 by[2] = 0 ; by[3] = CalcCheckSum(by, 3) ; SendPackage(by, 4) ; // 發送沒有成功執行通知 break ; } } |