(河南城建學(xué)院 計算機與數(shù)據(jù)科學(xué)學(xué)院,平頂山 467036)
當前在實現(xiàn)ZigBee Pro標準組成的WSN無線傳感網(wǎng)技術(shù)方案中,有TI公司的CC2530+Z-Stack方案,也有ST公司的STM32W108+EmberZnet方案。TI公司的CC2530是基于8051內(nèi)核,其Z-Stack協(xié)議棧被廣泛應(yīng)用在ZigBee通信設(shè)備中,研究其工作原理有著重要意義。
Z-Stack是一款業(yè)界領(lǐng)先的商業(yè)級協(xié)議棧,它把底層(尤其是MAC層)做成lib庫文件封裝起來(不開源),供其它層調(diào)用[1]。其中,HAL硬件抽象層、MAC層位于最底層,與硬件相關(guān);NWK網(wǎng)絡(luò)層、OSAL操作系統(tǒng)抽象層、APS應(yīng)用支持子層、AF應(yīng)用框架層、ZDO ZigBee設(shè)備對象以及安全層建立在HAL和MAC層之上,并且完全與硬件無關(guān);整個協(xié)議棧的最頂層就是用戶的應(yīng)用程序?qū)覣PP。HAL提供各種硬件模塊的驅(qū)動,包括定時器Timer、通用I/O口、UART、ADC等應(yīng)用程序接口API,提供各種服務(wù)的擴展集。Z-Stack協(xié)議?;谑录?qū)動和消息傳遞的機制,協(xié)議棧中的每一層都設(shè)計了一個事件處理函數(shù),用來處理與這一層操作相關(guān)的各種事件,將這些事件處理函數(shù)看成是與協(xié)議棧每一層相對應(yīng)的任務(wù),由ZigBee協(xié)議棧中OSAL來進行調(diào)度管理,這樣不管何時發(fā)生了何種事件,都可以通過調(diào)度協(xié)議棧相應(yīng)層的事件處理函數(shù)/任務(wù)來進行處理[2]。
圖1示例解釋了Z-Stack協(xié)議棧中main()函數(shù)和osal_run_system()函數(shù)主循環(huán)的執(zhí)行流程。
圖1 Z-Stack協(xié)議棧整體流程
Z-Stack中總共定義了7個按鍵,其中SW1~SW5屬于Joystick的UP上、RT右、DN下、LT左、PUSH/CENTER中間5個按鍵,SW6和SW7屬于2個獨立的按鍵開關(guān),當SW6按下時,相應(yīng)P0.1引腳為低電平,彈起時靠上拉電阻處于高電平。在Z-Stack源代碼HALinclude目錄下的文件hal_key.h和hal_key.c中有按鍵的定義。Joystick按鍵對應(yīng)于圖2中的S3按鍵,通過組合邏輯SN74HC32D芯片輸出JOYSTICK_INT信號給P2.0引腳,通過其電平從低到高的變化來判斷按鍵是否按下;同時將各個按鍵按下時的不同電位通過同相比例放大器輸出按鍵信號JOYSTICK_ADC輸入給ADC,經(jīng)過ADC轉(zhuǎn)換之后獲取不同的按鍵值,以此判斷Joystick具體是哪個按鍵按下的動作。Joystick按鍵原理圖下半部分如圖3所示。
圖2 Joystick按鍵原理圖上半部
圖3 Joystick按鍵原理圖下半部
Z-Stack源代碼的hal_key.h中定義了按鍵的鍵值,hal_key.c中定義各個按鍵對應(yīng)的I/O引腳、終端觸發(fā)的方式、Joystick按鍵對應(yīng)的ADC通道等相關(guān)寄存器信息。
根據(jù)Z-Stack協(xié)議棧工作流程,main()函數(shù)調(diào)用HalDriverInit()函數(shù)進行硬件驅(qū)動的初始化,HalDriverInit()函數(shù)調(diào)用HalKeyInit()進行按鍵初始化,其中定義了全局變量HalKeyConfigured,用來作為按鍵是否已配置的標志,其初始化為FALSE。
在main()函數(shù)執(zhí)行第二次InitBoard()函數(shù),也就是板子的最終初始化時,傳入的參數(shù)為OB_READY,因此調(diào)用HalKeyConfig()函數(shù)來進行按鍵的配置。
void InitBoard( uint8 level ){
if ( level == OB_COLD ){
……
}
else// !OB_COLD{
/*調(diào)用HalKeyConfig函數(shù)對按鍵進行配置*/
HalKeyConfig(HAL_KEY_INTERRUPT_DISABLE, OnBoard_KeyCallback);
}
}
按鍵配置函數(shù)HalKeyConfig的定義如下:
void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback){
/*通過傳入的參數(shù)interruptEnable決定是否采用中斷的方式*/
Hal_KeyIntEnable = interruptEnable;
/*通過傳入的參數(shù)cback來注冊按鍵處理回調(diào)函數(shù)指針pHalKeyProcessFunction*/
pHalKeyProcessFunction = cback;
/*如果采用按鍵中斷的方式*/
if (Hal_KeyIntEnable)
{/*SW_6鍵(P0.1引腳) 上升沿或者下降沿觸發(fā)中斷的配置*/
……
/*Joystick鍵 判斷按鍵是否動作(P2.0引腳) 上升沿或者下降沿觸發(fā)中斷的配置*/
HAL_KEY_JOY_MOVE_ICTL &=~(HAL_KEY_JOY_MOVE_EDGEBIT);
/* Clear the edge bit */
……
}
else/*沒有采用中斷方式,也就是按鍵輪詢方式*/
{/*SW_6鍵禁止中斷*/
……
osal_set_event(Hal_TaskID, HAL_KEY_EVENT);
/*產(chǎn)生HAL_KEY_EVENT事件*/
}
HalKeyConfigured = TRUE;
/*HalKeyConfigured變量最終配置為TRUE*/
}
可以看出,HalKeyConfig函數(shù)針對按鍵采用輪詢方式或者中斷方式進行了兩種配置,并且對按鍵處理回調(diào)函數(shù)指針pHalKeyProcessFunction進行賦值,指定相應(yīng)的回調(diào)處理函數(shù),最終HalKeyConfig全局變量設(shè)置為TRUE,代表按鍵配置完畢。在按鍵輪詢的方式中,調(diào)用了osal_set_event(Hal_TaskID, HAL_KEY_EVENT)函數(shù),產(chǎn)生HAL_KEY_EVENT事件,交給HAL層去處理。
Z-Stack中提供了兩種方式采集按鍵數(shù)據(jù):輪詢方式和中斷方式。
在輪詢方式中,每隔一定時間(默認周期為100 ms)產(chǎn)生定時事件HAL_KEY_EVENT,OSAL調(diào)用HAL層的Hal_ProcessEvent()函數(shù)處理該事件,然后調(diào)用HalKeyPoll()函數(shù)來檢測按鍵狀態(tài),如果按鍵有變化,則交給pHalKeyProcessFunction()回調(diào)函數(shù)進行相應(yīng)的處理。
uint16 Hal_ProcessEvent(uint8 task_id, uint16 events){
……
if (events & HAL_KEY_EVENT){
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
HalKeyPoll();/*按鍵檢測狀態(tài)函數(shù)*/
/*如果是按鍵輪詢方式,則通過定時器觸發(fā)HAL_KEY_EVENT事件來進行下一輪輪詢*/
if (!Hal_KeyIntEnable){/*100ms定時周期發(fā)送HAL_KEY_EVENT事件*/
osal_start_timerEx(Hal_TaskID,HAL_KEY_EVENT,100);
}
#endif // HAL_KEY
return events ^ HAL_KEY_EVENT;
}
……
}
函數(shù)HalKeyPoll()的定義如下:
void HalKeyPoll (void){
uint8 keys = 0;/*keys用來儲存鍵值*/
/*Joystick的P2.0引腳電平是高電平,表明Joystick有按鍵按下*/
if ((HAL_KEY_JOY_MOVE_PORT & HAL_KEY_JOY_MOVE_BIT)){
/*通過halGetJoyKeyInput函數(shù)返回Joystick按鍵鍵值*/
keys = halGetJoyKeyInput();
}
/*如果是按鍵輪詢方式,通過當前按鍵狀態(tài)與之前的狀態(tài)進行比較來判斷是否有按鍵按下*/
if (!Hal_KeyIntEnable){
if (keys == halKeySavedKeys){
/*如果當前按鍵狀態(tài)沒有變化,直接返回退出*/
return;
}
halKeySavedKeys = keys;
/*存儲當前的按鍵值用于下一次的比較*/
}
……
/*通過P0.1引腳電平的高低來設(shè)置keys鍵值*/
if (HAL_PUSH_BUTTON1()){/*等價于if(P0_1) */
keys |= HAL_KEY_SW_6;
}
/*如果有按鍵按下,調(diào)用按鍵的回調(diào)函數(shù)處理按鍵事務(wù)*/
if (keys && (pHalKeyProcessFunction)){
(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
}
}
在中斷方式中,按鍵觸發(fā)按鍵中斷,在按鍵中斷ISR中調(diào)用halProcessKeyInterrupt()函數(shù),其通過延遲定時25 ms(按鍵消抖)后,產(chǎn)生按鍵HAL_KEY_EVENT事件,交給HAL層的Hal_ProcessEvent()函數(shù)進行相應(yīng)處理,后續(xù)流程同輪詢方式。
通過上面的分析,可以看出不論是輪詢方式還是中斷方式,均通過HalKeyPoll()函數(shù)來判定按鍵狀態(tài)的變化,其中Joystick依靠halGetJoyKeyInput()函數(shù)來獲取鍵值。在分析該函數(shù)之前,先來看看Joystick按鍵工作的原理。
由圖2可知,SN74HC32D是1個4組2輸入1輸出的或門,由其硬件電路可以推導(dǎo)出組合邏輯表達式:
JOYSTICK_INT=3Y=3A+3B=PUSH+4Y=
PUSH+4A+4B=PUSH+2Y+1Y=
PUSH+2A+2B+1A+1B=
PUSH+LT+RT+UP+DN
這意味著,JOYSTICK_INT(P2.0引腳)是Joystick的SW1~SW5這5個按鍵狀態(tài)的邏輯或,只要有任意1個按鍵按下(Joystick按鍵的機械結(jié)構(gòu)決定了5個鍵中只能有1個按下,不可能有多個按鍵同時按下),那么P2.0引腳上就是高電平;否則當5個按鍵都不按下,P2.0引腳為低電平;反過來,如果P2.0引腳上是高電平,只知道這5個鍵中有某個鍵被按下,但不知道是具體哪一個。
具體按鍵的判斷是通過ADC轉(zhuǎn)換器獲取不同鍵值來判定的。當UP鍵按下時,圖3可以等效為圖4。
圖4 UP鍵按下時的等效電路
R//=(R16+R27)//(R11+R29+R30)//(R17+R36+R37)=300k//500k//764k=150.55k,由理想運放的“虛斷”可知,U1的同相輸入端電位:
由理想運放的“虛短”可知,U1的反向輸入端電位:
V-=V+=1.03125V
對于U1的反向輸入端節(jié)點,由基爾霍夫節(jié)點電流定律可知:
對于運放U2,構(gòu)成了一個同相比例放大電路,其電壓增益為:
因此,U2輸出端JOYSTICK_ADC電位是:
JOYSTICK_ADC=Vout1×Av=
0.2394V×1.42553=0.3413V
這表明,當UP鍵按下時,通過2個運放放大之后,JOYSTICK_ADC電位是0.341 3 V。
同理,可以依次計算,當DN、LT、RT、CENTER鍵按下時,不同的JOYSTICK_ADC電位值,由于Z-Stack協(xié)議棧中,對Joystick按鍵的ADC采用8位有效數(shù)字(最高位0代表正),參考電壓為3.3 V,那么經(jīng)過A/D轉(zhuǎn)換之后的值,如表1所列。
在之前的按鍵代碼分析中提到,HalKeyPoll()函數(shù)用來判定按鍵狀態(tài)的變化,其中Joystick依靠halGetJoyKeyInput()函數(shù)來獲取鍵值,halGetJoyKeyInput()函數(shù)的代碼如下,可以看到與表1的理論計算結(jié)果完美對應(yīng)。
表1 理論上Joystick按鍵對應(yīng)的電位值和A/D轉(zhuǎn)換值
uint8 halGetJoyKeyInput(void){
……
/*讀取Joystick按鍵ADC直到兩次連續(xù)ADC值相同(穩(wěn)定)為止*/
adc = HalAdcRead (HAL_KEY_JOY_CHN, HAL_ADC_RESOLUTION_8);//ADC 8位有效數(shù)字
if ((adc >= 2) && (adc <= 38)){ // UP/13
ksave0 |= HAL_KEY_UP;
}
else if ((adc >= 74) && (adc <= 88)){ // RT/77
ksave0 |= HAL_KEY_RIGHT;
}
else if ((adc >= 60) && (adc <= 73)){// LT/68
ksave0 |= HAL_KEY_LEFT;
}
else if ((adc >= 39) && (adc <= 59)){// DN/49
ksave0 |= HAL_KEY_DOWN;
}
else if ((adc >= 89) && (adc <= 100)){// CENTER/90
ksave0 |= HAL_KEY_CENTER;
}
……
}
在按鍵狀態(tài)檢測函數(shù)HalKeyPoll()中,如果發(fā)現(xiàn)按鍵狀態(tài)有變化,就會通過 (pHalKeyProcessFunction) (keys,HAL_KEY_STATE_NORMAL)來實現(xiàn)對按鍵事務(wù)回調(diào)函數(shù)OnBoard_KeyCallback()的調(diào)用。OnBoard_KeyCallback()函數(shù)又通過調(diào)用OnBoard_SendKeys(keys,shift),將按鍵值和按鍵狀態(tài)打包成KEY_CHANGE事件,發(fā)送到RegisterForKeys()函數(shù)注冊層,交給該層進行按鍵事務(wù)的處理。
在TI官方SampleApp項目例程中,SampleApp應(yīng)用層的任務(wù)初始化函數(shù)SampleApp_Init()通過RegisterForKeys(SampleApp_TaskID)把按鍵的最終處理交給應(yīng)用層。一旦有按鍵KEY_CHANGE事件,就會觸發(fā)應(yīng)用層的事件處理函數(shù)SampleApp_ProcessEvent()來進行按鍵處理,如下所示:
uint16 SampleApp_ProcessEvent(uint8 task_id, uint16 events){
……
switch (MSGpkt->hdr.event){ //判斷消息包頭的事件是否是KEY_CHANGE
case KEY_CHANGE:
//應(yīng)用層處理按鍵事務(wù),交給SampleApp_HandleKeys函數(shù),可根據(jù)不同的鍵值做相應(yīng)處理
SampleApp_HandleKeys(((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys);
break;
……
}
……
}
簡單來說,Z-Stack中提供了兩種按鍵機制:輪詢方式和中斷方式,在HalKeyConfig()函數(shù)中按兩種機制分別進行按鍵配置。兩者均調(diào)用HalKeyPoll()函數(shù)來檢測按鍵狀態(tài),如果按鍵有變化,則交給回調(diào)函數(shù)處理,同時回調(diào)函數(shù)會向RegisterForKeys()函數(shù)注冊層(一般是應(yīng)用層APP)發(fā)送事件KEY_CHANGE,最終交給該層的事件處理函數(shù)APP_ProcessEvent()集中處理[1,3]。
需要注意的地方總結(jié)一下:①按鍵默認為輪詢的方式,如果想修改按鍵為中斷方式,則需要將OnBoard.c中的函數(shù)void InitBoard()中的語句修改為HalKeyConfig(HAL_KEY_INTERRUPT_ENABLE,OnBoard_KeyCallback);②Joystick通過或門器件實現(xiàn)了多個按鍵的按下判斷,并通過獲取ADC按鍵值來判定具體哪個按鍵被按下,通過這種方式,多按鍵的時候可以通過ADC外加2個I/O引腳來實現(xiàn)多按鍵驅(qū)動。