當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > ZigBee協(xié)議棧之osal淺析
ZigBee是目前比較流行的一種低功耗無線組網(wǎng)技術(shù),主要用于智能家居控制以及智能工業(yè)生產(chǎn)。ZigBee大的特點就是低功耗、自組網(wǎng)。
本文引用地址://www.mairao.cn/emb/Column/7541.html
說到ZigBee就不得不提IEEE802.15和ZigBee聯(lián)盟,他們公共制定了ZigBee協(xié)議棧的標(biāo)準(zhǔn)。組網(wǎng)過程就是基于ZigBee協(xié)議棧,協(xié)議棧完成了絕大部分的工作,留給用戶的就是應(yīng)用程序接口。協(xié)議棧就像一個操作系統(tǒng)一樣,用戶只需要定制應(yīng)用程序就可以使用。
先來看一下ZigBee協(xié)議棧架構(gòu),操作系統(tǒng)下面的可以當(dāng)做BootLoader,操作系統(tǒng)上面的可以看做應(yīng)用程序。對于用戶來說,只要了解操作系統(tǒng),會定制task,那么就可以使用協(xié)議棧了。
接下來我們以TI公司的ZigBee協(xié)議棧為標(biāo)準(zhǔn),了解一下osal操作系統(tǒng)機(jī)制,以方便后續(xù)定制task。
Osal源于一種簡單的操作系統(tǒng)思想---輪詢。在ZigBee協(xié)議棧中,OSAL負(fù)責(zé)調(diào)度各個任務(wù)的運(yùn)行,如果有事件發(fā)生了,則會調(diào)用相應(yīng)的事件處理函數(shù)進(jìn)行處理。那么,事件和任務(wù)的事件處理函數(shù)是如何聯(lián)系起來的呢?
ZigBee中采用的方法是:建立一個事件表,保存各個任務(wù)的對應(yīng)的事件,建立另一個函數(shù)表,保存各個任務(wù)的事件處理函數(shù)的地址,然后將這兩張表建立某種對應(yīng)關(guān)系,當(dāng)某一事件發(fā)生時則查找函數(shù)表找到對應(yīng)的事件處理函數(shù)即可。
在ZigBee協(xié)議棧中,有三個變量至關(guān)重要。
● tasksCnt—該變量保存了任務(wù)的總個數(shù)。
該變量的聲明為:uint8 tasksCnt,其中uint8的定義為:typedef unsigned char uint8
● tasksEvents—這是一個指針,指向了事件表的首地址。
該變量的聲明為:uint16 *tasksEvents,其中uint16的定義為:typedef unsigned short uint16
● tasksArr—這是一個數(shù)組,數(shù)組的每一項都是一個函數(shù)指針,指向了事件處理函數(shù)。
該數(shù)組的聲明為:const pTaskEventHandlerFn tasksArr[],其中pTaskEventHandlerFn的定義為:typedef unsigned short (*pTaskEventHandlerFn)( unsigned char task_id, unsigned short event ),這是定義了一個函數(shù)指針。tasksArr數(shù)組的每一項都是一個函數(shù)指針,指向了事件處理函數(shù)。
事件表和函數(shù)表的關(guān)系如下圖
我們現(xiàn)在來總結(jié)下OSAL的工作原理:通過tasksEvents指針訪問事件表的每一項,如果有事件發(fā)生,則查找函數(shù)表找到事件處理函數(shù)進(jìn)行處理,處理完后,繼續(xù)訪問事件表,查看是否有事件發(fā)生,無限循環(huán)。OSAL就是一種基于事件驅(qū)動的輪詢式操作系統(tǒng)。事件驅(qū)動是指發(fā)生事件后采取相應(yīng)的事件處理方法,輪詢指的是不斷地查看是否有事件發(fā)生。
下面從代碼中看一下osal運(yùn)行機(jī)制。在Zmain文件夾下有個Zmain.c文件,打開該文件可以
找到main()函數(shù),這就是整個協(xié)議棧的入口點。main()函數(shù)原型如下:
int main( void )
{
// Turn off interrupts
osal_int_disable( INTS_ALL );
// Initialization for board related stuff such as LEDs
HAL_BOARD_INIT();
// Make sure supply voltage is high enough to run
zmain_vdd_check();
// Initialize board I/O
InitBoard( OB_COLD );
// Initialze HAL drivers
HalDriverInit();
// Initialize NV System
osal_nv_init( NULL );
// Initialize the MAC
ZMacInit();
// Determine the extended address
zmain_ext_addr();
// Initialize basic NV items
zgInit();
#ifndef NONWK
// Since the AF isn't a task, call it's initialization routine
afInit();
#endif
// Initialize the operating system
osal_init_system();
// Allow interrupts
osal_int_enable( INTS_ALL );
// Final board initialization
InitBoard( OB_READY );
// Display information about this device
zmain_dev_info();
/* Display the device info on the LCD */
#ifdef LCD_SUPPORTED
zmain_lcd_init();
#endif
#ifdef WDT_IN_PM1
/* If WDT is used, this is a good place to enable it. */
WatchDogEnable( WDTIMX );
#endif
osal_start_system(); // No Return from here
return 0; // Shouldn't get here.
} // main()
在osal_start_system()函數(shù)之前的函數(shù)都是對板載硬件以及協(xié)議棧進(jìn)行的初始化,直到調(diào)用osal_start_system()函數(shù),整個ZigBee協(xié)議棧才算是真正地運(yùn)行起來了。硬件驅(qū)動不需要多看,我們跳轉(zhuǎn)到osal_start_system(),查看一下它的原型
void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
{
uint8 idx = 0;
osalTimeUpdate();
// This replaces MT_SerialPoll() and osal_check_timer().
Hal_ProcessPoll();
do {
// Task is highest priority that is ready.
if (tasksEvents[idx])
{
break;
}
} while (++idx < tasksCnt);
if (idx < tasksCnt)
{
uint16 events;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState);
events = tasksEvents[idx];
// Clear the Events for this task.
tasksEvents[idx] = 0;
HAL_EXIT_CRITICAL_SECTION(intState);
events = (tasksArr[idx])( idx, events );
HAL_ENTER_CRITICAL_SECTION(intState);
// Add back unprocessed events to the current task.
tasksEvents[idx] |= events;
HAL_EXIT_CRITICAL_SECTION(intState);
}
#if defined( POWER_SAVING )
// Complete pass through all task events with no activity?
else
{
// Put the processor/system into sleep
osal_pwrmgr_powerconserve();
}
#endif
}
}
首先看到,真?zhèn)osal是在一個for循環(huán)中執(zhí)行的,這就意味正后面的事件一直在不停的重復(fù),也是osal的基礎(chǔ)。
第6行,定義了一個變量idx,用來在事件表中索引。
第7-9行,更新系統(tǒng)時鐘,同時查看硬件方面是否有事件發(fā)生,如串口是否收到數(shù)據(jù)、是否有按鍵按下等信息,這部分內(nèi)容在此可以暫時不予考慮。
第10-16行,使用do-while循環(huán)查看事件表是否有事件發(fā)生。分析一下這個循環(huán),如果有事件發(fā)生,那么就跳出循環(huán),去后面的代碼處理事件。如果沒有事件,那么久繼續(xù)掃描表中的下一項。當(dāng)然真?zhèn)循環(huán)的次數(shù)不得多于tasksCnt,因為它記錄著事件的總數(shù)。
第18~35行是事件的處理過程,23和27行規(guī)定了一個臨界區(qū)。24行取出事件,保存在events變量。26行則將表中的事件清零,因為事件已經(jīng)被取出將要處理。27行根據(jù)id找到對應(yīng)的函數(shù)表,執(zhí)行處理函數(shù),而且拿到返回值(后面解釋返回值)。31和34行又是一個臨界區(qū),33行又將事件重新賦值。
上面了解了osal的基本原理,就是兩張表的查詢和對應(yīng)。那么其中又有一個events成了重點,為什么處理完事件時候又返回一個events(24行),而且還把events放回到事件表(33行)。
ZigBee協(xié)議棧使用一個unsigned short型的變量,因為unsigned short類型占兩個字節(jié),即16個二進(jìn)制位,因此,可以使用每個二進(jìn)制位表示一個事件,我們來看下協(xié)議棧定義的系統(tǒng)事件SYS_EVENT_MSG,十六進(jìn)制:0x8000,二進(jìn)制:0b1000000000000000。它用的就是高位來表示該事件:
可以看出在一個任務(wù)中多只能有16個事件,因為events是一個16位數(shù)據(jù)。
在系統(tǒng)初始化時,所有任務(wù)的事件初始化為0,因此,第10行通過tasksEvents[idx]是否為0來判斷是否有事件發(fā)生,如果有事件發(fā)生了,則跳出循環(huán)。
29行執(zhí)行完事件處理函數(shù)后,需要將未處理的事件返回,也就是說事件處理函數(shù)的返回值保存了未處理的事件,將該事件在寫入事件表中,以便于下次進(jìn)行處理?匆幌孪旅娴奶幚砗瘮(shù)
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
{
// Send the periodic message
SampleApp_SendPeriodicMessage();
// Setup to send message again in normal period (+ a little jitter)
osal_start_timerEx( SampleApp_TaskID, SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
(SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT + (osal_rand() & 0x00FF)) );
// return unprocessed events
return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
}
// Discard unknown events
return 0;
}
前面已經(jīng)說到,事件用一個二進(jìn)制位1來表示,那么一個“與”操作就可以判斷出來到底有沒有事件。后return采用“異或”,這樣可以將已經(jīng)處理的事件清除掉。不清楚的小伙伴可以舉一個實際的例子,仔細(xì)計算一番之后你會發(fā)現(xiàn)的確是這種寫法。。。
在ZigBee協(xié)議棧中,用戶可以定義自己的事件,但是,協(xié)議棧同時也給出了幾個已經(jīng)定義好的事件,由協(xié)議棧定義的事件成為系統(tǒng)強(qiáng)制事件(Mandatory Events),SYS_EVENT_MSG就是其中的一個事件,SYS_EVENT_MSG的定義如下:
那SAMPLEAPP_SEND_PERIODIC_MSG_EVT事件 是不是就是用戶自己定義的事件了?是的!我們來看下它的定義:
提到事件,我們就不得不提到消息。事件是驅(qū)動任務(wù)去執(zhí)行某些操作的條件,當(dāng)系統(tǒng)中產(chǎn)生了一個事件,OSAL將這個事件傳遞給相應(yīng)的任務(wù)后,任務(wù)才能執(zhí)行一個相應(yīng)的操作(調(diào)用事件處理函數(shù)去處理)。
通常某些事件發(fā)生時,又伴隨著一些附加信息的產(chǎn)生,例如:從天線接收到數(shù)據(jù)后,會產(chǎn)生AF_INCOMING_MSG_CMD消息,但是任務(wù)的事件處理函數(shù)在處理這個事件的時候,還需要得到收到的數(shù)據(jù)。
因此,這就需要將事件和數(shù)據(jù)封裝成一個消息,將消息發(fā)送到消息隊列,然后在事件處理函數(shù)中就可以使用osal_msg_receive,從消息隊列中得到該消息。如下代碼可以獲得指向從消息隊列中得到消息的指針。
在使用ZigBee協(xié)議棧進(jìn)行應(yīng)用程序開發(fā)時,如何在應(yīng)用程序中添加一個新任務(wù)呢?
打開OSAL_SampleApp.c文件,可以找到數(shù)組tasksArr[]和函數(shù)osalInitTasks()。tasksArr[]數(shù)組里存放了所有的事件處理函數(shù)的地址;osalInitTasks()是OSAL的任務(wù)初始化函數(shù),所有任務(wù)的初始化工作都在這里邊完成,并且自動給每個任務(wù)分配一個ID。
因此,要添加新任務(wù),只需要編寫兩個函數(shù):
● 新任務(wù)的初始化函數(shù)。
● 新任務(wù)的事件處理函數(shù)。
將事件處理函數(shù)的地址加入tasksArr[]數(shù)組,如下代碼所示。
將新任務(wù)的初始化函數(shù)添加在osalInitTasks()函數(shù)的后,如下代碼所示。
我們需要注意的是:
tasksArr[]數(shù)組里各事件處理函數(shù)的排列順序要與osalInitTasks函數(shù)中調(diào)用各任務(wù)初始化函數(shù)的順序保持一致,只有這樣才能保證當(dāng)任務(wù)有事件發(fā)生時會調(diào)用每個任務(wù)對應(yīng)的事件處理函數(shù)。為了保存osalInitTasks()函數(shù)所分配的任務(wù)ID,需要給每一個任務(wù)定義一個全局變量。如在SampleApp.c文件中定義了一個全局變量SampleApp_TaskID,并且在osalInitTasks()函數(shù)中進(jìn)行了賦值。
我們現(xiàn)在總結(jié)下OSAL的運(yùn)行機(jī)理:
● 通過不斷地查詢事件表來判斷每個任務(wù)中是否有事件發(fā)生,如果有事件發(fā)生,則查找函數(shù)表找到對應(yīng)的事件處理函數(shù)對事件進(jìn)行處理。
● 事件表使用數(shù)組來實現(xiàn),數(shù)組的每一項對應(yīng)一個任務(wù)的事件,每一位表示一個事件;函數(shù)表使用函數(shù)指針數(shù)組來實現(xiàn),數(shù)組的每一項是一個函數(shù)指針,指向了事件處理函數(shù)。