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