喝水不忘挖井人
轉: http://rf.eefocus.com/module/forum/thread-555910-1-1.html
Zstack協議棧是由TI公司在2007年4月推出的Zigbee無線通訊協議,是一種半開源式的協議棧,歷經多年發展,功能不斷完善,當前最新版本為2.5.1-a。網上資料有的說zstack協議棧是開源的,但是實際上zstack中的很多關鍵程式碼都是以庫檔案的形式給出的,我們並不能知道這些程式碼的真實內容,這也給學習這個協議棧帶來了一定的困難。現在也有幾個真正開源的Zigbee協議棧,如msstatePAN協議棧、freakz協議棧,但是同這些協議棧相比zstack的真正優勢則是其搭載的硬體平臺:TI的Zigbee無線通訊晶片:CC2520、CC2530等。
筆者最早接觸Zigbee也是從學習TI的開發套件開始的,經過幾年的沉積,對於CC2530和ZStack協議棧的掌握也可以算是細緻入微了,現在打算把自己對協議棧的理解和認識拿出來和大家一起分享。
首先我們從Zigbee的特性說起。在網上搜尋zigbee,緊跟在它後面的多半都是近距離、低功耗、自組織這些名詞,這也是Zigbee最重要的特點。說它近距離是硬體模組的載波頻率是2.4GHz,波長短,穿透性差,另外它和藍芽、wifi是工作在同一頻段的,容易受到干擾,因此傳輸距離不遠。室內的話筆者做過的模組一般只能傳輸40到50米,網上那些不加PA的透傳模組聲稱室內能傳百米的,筆者認識都是在扯淡。低功耗是Zigbee非常重要的特點,CC2530的發射電流為29mA,在低功耗休眠模式下,只消耗不到1uA的電流,這個還是非常給力的。當然,隨著藍芽4.0的推出,Zigbee的這一優勢將大打折扣。自組織以及與之相關的自癒合、自動路由等等技術則是Zigbee的真正核心競爭力所在,筆者也是在學習和使用Zigbee的過程中逐漸體會到這些功能的強大之處,也是接下來筆者所要闡述的主要內容。
當你第一次開啟Zstack協議棧,檢視程式程式碼,第一感覺也許會是:我靠,這該不是唐僧取來的梵文真經吧。然後轉念一想,飛盤扔的再遠,最後總會回到手中;風箏飛的再高,總是逃脫不了牽引它的線and so on(此處省略1萬字)。眼前的一切都是虛妄,只有main才是你永遠的家。搜尋main,果然,熟悉的面孔讓當年的筆者有了一絲的寬慰。
int main( void ) { // Turn off interrupts osal_int_disable( INTS_ALL ); // Initialization for board related stuff such as LEDs HAL_BOARD_INIT(); disable_2401(); // 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(); // Initialize the operating system osal_init_system(); // Allow interrupts osal_int_enable( INTS_ALL ); #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; }
筆者就是從這個入口函式開始,一步步的走入了Zstack的那繽紛複雜的世界不能自拔。方法只有2個:看文件和單步跟蹤除錯檢驗,廢話少說,這裡筆者就對這些函式來進行粗略的解釋。
上次我們粗略描述了ZStack協議棧main()函式呼叫的一些函式,在main()函式的最後,呼叫的是osal_start_system(),從字面意思我們不難理解,這個函式是要啟動“系統”的意思。啟動系統??也許你要問了,這個系統難不成是作業系統的意思麼?好吧,其實,你也可以理解成就是一個“作業系統”了。這時候可能你可能就來興致了:ZStack執行的是哪門子作業系統?它是如何工作的?
檢視osal_start_system()如下:
void osal_start_system( void ) { for(;;) { osal_run_system(); } } void osal_run_system( void ) { do { if (tasksEvents[idx]) // Task is highest priority that is ready. { break; } }while (++idx < tasksCnt); if (idx < tasksCnt) { uint16 events; halIntState_t intState; HAL_ENTER_CRITICAL_SECTION(intState); events = tasksEvents[idx]; tasksEvents[idx] = 0; // Clear the Events for this task. HAL_EXIT_CRITICAL_SECTION(intState); activeTaskID = idx; events = (tasksArr[idx])( idx, events ); activeTaskID = TASK_NO_TASK; HAL_ENTER_CRITICAL_SECTION(intState); tasksEvents[idx] |= events; // Add back unprocessed events to the current task. HAL_EXIT_CRITICAL_SECTION(intState); } }
檢視osal_init_system()如下:
uint8 osal_init_system( void ) { // Initialize the message queue osal_qHead = NULL; // Initialize the timers osalTimerInit(); // Initialize the Power Management System osal_pwrmgr_init(); // Initialize the system tasks. osalInitTasks(); return ( SUCCESS ); } void osalInitTasks( void ) { uint8 taskID = 0; tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); macTaskInit( taskID++ ); nwk_init( taskID++ ); Hal_Init( taskID++ ); APS_Init( taskID++ ); ZDNwkMgr_Init( taskID++ ); zcl_Init( taskID++ ); zclZHome_Init( taskID++ ); #if defined (USE_SMART_GATEWAY_MODE) utpTaskInit(taskID++); //1/ 1. register zigbee process func. 2. process uart:(1, check status.2 let deice leave.3, check_pan_resp) ArpComm_Init(taskID++); //1.annuce: update arp table(send (mac,ip) to uart), 2.check_pan:farward to uart. Gateway_Init(taskID); //reset assoc table, send startup to uart. #else zhomeDevMgr_Init(taskID++); DevNwkMgr_Init(taskID++); #endif } const pTaskEventHandlerFn tasksArr[] = { macEventLoop, nwk_event_loop, Hal_ProcessEvent, APS_event_loop, ZDApp_event_loop, zcl_event_loop, zclZHome_ProcessEvent, #if defined (USE_SMART_GATEWAY_MODE) utpProcessEvent, ArpComm_ProcessEvent, Gateway_ProcessEvent, #else zhomeDevMgr_ProcessEvent, DevNwkMgr_ProcessEvent, #endif };
聰明的朋友也許已經看出來了,任務陣列各成員的名稱和剛才初始化過程中各個初始化函式的名稱有某些契合。呵呵,不契合就怪了(好吧,這裡有點廢話了)。
上面的圖示列出的是某一時刻tasksEvents各成員的數值。可以看到tasksEvents成員值可以有多個非0值,即有多個任務需要進行處理。優先順序較高的nwk_event_loop將首先被執行,然後是級別較低的Hal_ProcessEvent。
協調器的組網過程詳解
這一講我要詳細說一下協調器的組網過程。在Zstack中,網路組網是從ZDApp_Init函式開始的。具體的執行流程為:Main()->osal_init_system()->osalInitTasks()->ZDApp_Init()。進入到ZDApp_Init中:
{
...
}
我們在以前的文章裡曾提到過,裝置在復位之後,之前的網路資訊就會丟失,那麼裝置將以全新的狀態組建或者加入到網路中,這在實際使用過程中非常不方便。很多的應用場景中都要求裝置能夠在復位後仍舊按照上次入網的狀態重新連線到網路中,Zstack也提供了實現該機制的方法。在工程的Options->C/C++ Complier->reprocessor的Defined symbols中我們填入:NV_RESTORE。再回到剛才說到的程式碼中,程式會呼叫HalKeyRead(),來判斷SW5的狀態。如果SW5沒有被按下,程式會呼叫ZDApp_RestoreNetworkState,取出上一次儲存在flash中的網路狀態資訊,裝置將根據這些資訊重新連線到網路中;如果SW5被按下,那麼即便我們使用了NV_RESTORE,程式將呼叫NLME_InitNV()和NLME_SetDefaultNV(),清除之前儲存的網路資訊,然後以全新的狀態加入網路中。在完成以上操作之後,程式會呼叫ZDApp_NetworkInit函式,觸發ZDO_NETWORK_INIT事件。
ZStack協調器網路組建過程怎樣實現?
ZDApp_NetworkInit函式觸發了ZDO_NETWORK_INIT任務,這個任務是由ZDApp_event_loop來處理的:
{
...
}
可以看到,這個函式的主要作用是呼叫了ZDO_StartDevice函式,引數ZDO_Config_Node_Descriptor.LogicalType在之前已經被設定為NODETYPE_COORDINATOR,表明這個裝置的型別為協調器。
ZDO_StartDevice函式的內容非常多,也特別的重要,第一次看的話有點吃力,但是其中最為重要的部分是呼叫了NLME_NetworkFormationRequest函式,發出網路建立請求。NLME_NetworkFormationRequest函式的詳細部分是放在庫檔案中的,這裡我們沒有辦法列出它的具體程式碼。
在ZStack中大量使用了回撥函式來處理各種任務請求,網路建立請求的回撥函式為:
void ZDO_NetworkFormationConfirmCB( ZStatus_t Status )
{
...
}