zigbee組網過程淺析

       zigbee協議棧使用的是zstack版本,該協議棧的整體功能有點類似於操作系統。下面以SimpleApp例程爲例,對協議棧的組網流程進行描述。

       協議棧是用C語言實現的,由於C語言的入口都是main函數,因此需要找到main函數,下圖爲協議棧各層列表(主要包括應用層、硬件層、MAC層、網絡層、安全層、服務層等),TI公司的編程比較規範,文件的命名就意味着相關的功能。


圖1 協議棧的整體架構

       可以看到,ZMain文件下面有ZMain.c文件,而該文件就是整個協議棧的入口地址。打開ZMain.c文件,可以看到函數intmain( void );該函數就是整個協議棧最開始的入口。在main函數裏面可以看到語句:

 // Initialize the operating system

  osal_init_system();

       該語句的實際含義是初始化zigbee協議棧。

       進入函數osal_init_system()的內部(具體方法:使鼠標停留在osal_init_system上,並且單擊右鍵,在彈出的選項中選擇“go todefinition of osal_init_system”),定位到下列語句:

 // Initialize the system tasks.

  osalInitTasks();

       從這個函數的名字就可以知道它是用於初始化系統任務的。在zigbee協議棧中,一個非常重要而且貫穿協議棧生命週期的概念就是任務,也就是說協議棧的信息處理和數據傳輸等過程都是通過任務來實現的,即如果某個節點需要傳輸一個數據包,它會通過調用相關任務通知操作系統需要發送數據包。

既然任務是個非常重要的概念,那麼就很有必要進入到osalInitTasks()函數內部,看看這個函數究竟是初始化那些任務!!

       類似,點擊右鍵進入osalInitTasks函數內部,下面是該函數的內容:

voidosalInitTasks( 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++ );

#ifdefined( MT_TASK )

  MT_TaskInit( taskID++ );

#endif

  APS_Init( taskID++ );

  ZDApp_Init( taskID++ );

  SAPI_Init( taskID );

}

 

       函數內部表明,函數執行了協議棧各層的初始化操作,包括mac層、網絡層、硬件層等各層初始化。這裏看到了此語句:ZDApp_Init(taskID++ );該語句的作用是初始化zigbee設備對象(ZDO),那zigbee設備對象是用來幹什麼的呢?爲什麼要對它進行初始化呢?

       一句話,應用層可以通過ZigBee設備對象(ZDO)對網絡層參數進行配置和訪問。也就是說ZDO會對要組建的zigbee網絡進行各種配置。

       那ZDApp_Init()內部又是怎麼實現的呢?同理進入到ZDApp_Init()內部,可以看到ZDApp_Init()就是對網絡的各種初始化配置,定位到下列語句:

  if ( devState != DEV_HOLD )//在本人的zstack上devState不是DEV_HOLD

  {

    ZDOInitDevice( 0 );

  }

所以就執行了語句:ZDOInitDevice(0 );該函數是什麼作用呢??它是負責啓動網絡。在函數ZDOInitDevice()內,定位到下列語句:

 // Trigger the network start

  ZDApp_NetworkInit( extendedDelay );

到這裏就設置了定時觸發啓動網絡了;

 

而ZDApp_NetworkInit()函數內部是怎麼樣出發網絡啓動的呢??

右鍵點擊ZDApp_NetworkInit(),進入到ZDApp_NetworkInit()內部,它的函數體如下:

 

voidZDApp_NetworkInit( uint16 delay )

{

  if ( delay )

  {

    // Wait awhile before starting the device

    osal_start_timerEx( ZDAppTaskID,ZDO_NETWORK_INIT, delay );

  }

  else

  {

    osal_set_event( ZDAppTaskID,ZDO_NETWORK_INIT );

  }

}

在ZDApp_NetworkInit()內部有個if-else選擇分支,if分支是經過一段時間後,再添加網絡初始化任務,而else分支則是直接執行網絡初始化任務,即else分支是直接執行語句osal_set_event(ZDAppTaskID, ZDO_NETWORK_INIT),而if分支經過一個延時後,再執行osal_set_event(ZDAppTaskID, ZDO_NETWORK_INIT)語句。說到底,ZDApp_NetworkInit()的內部就是執行語句:osal_set_event(ZDAppTaskID, ZDO_NETWORK_INIT),即觸發zigbee網絡初始化,而且傳送過來的參數是ZDO_NETWORK_INIT。

 

       協議棧觸發了網絡初始化任務的事件後,是不是需要有個函數來執行這個任務呢??答案是肯定的,協議棧會自動調用zigbee專門的事件處理函數:ZDApp_event_loop(uint8 task_id, UINT16 events),該函數的功能就是處理各種事件。進入ZDApp_event_loop()函數的內部,發現該函數實際上是多個if分支構成,我們把osal_set_event()函數傳遞過來的參數ZDO_NETWORK_INIT和ZDApp_event_loop()函數的if分支進行匹配,發現下列的if分支:

 

if( events & ZDO_NETWORK_INIT )

  {

    // Initialize apps and start the network

    devState = DEV_INIT;

    ZDO_StartDevice((uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,EFAULT_BEACON_ORDER, EFAULT_SUPERFRAME_ORDER );

    // Return unprocessed events

    return (events ^ ZDO_NETWORK_INIT);

  }

即存在着網絡事件初始化的if分支,具體是怎麼樣初始化的呢?此外由於zigbee網絡存在路由器節點、協調器節點和終端節點等各種類型節點,網絡初始化會不會針對不同類型節點有不同的配置呢??

 

答案也是肯定的。首先,下面的語句就是網絡初始化的具體入口:

 ZDO_StartDevice((uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,EFAULT_BEACON_ORDER, EFAULT_SUPERFRAME_ORDER );

而且可以看到,ZDO_StartDevice中的確是根據不同節點的類型,其配置有所不同(ZDO_Config_Node_Descriptor.LogicalType,類型節點)。進入ZDO_StartDevice()函數的內部(方法類似,鼠標右擊該函數,點擊go todefinition of ZDO_StartDevice)。可以看到ZDO_StartDevice()也是一系列的if分支。

 

在zigbee網絡建網過程中,首先啓動的一定是協調器節點,因爲只有此種節點才能創建和配置網絡,其他的節點只能加入網絡,而不能創建網絡。

所以,這裏首先介紹協調器創建網絡過程。下列的if分支是coordinator組網代碼:

 

if(ZG_BUILD_COORDINATOR_TYPE&&

logicalType==NODETYPE_COORDINATOR )

  {

       //我們這裏的startMode是MODE_HARD,

    if ( startMode == MODE_HARD )

{

//所以程序執行此分支

      devState = DEV_COORD_STARTING;

      ret = NLME_NetworkFormationRequest(zgConfigPANID, zgApsUseExtendedPANID, zgDefaultChannelList, zgDefaultStartingScanDuration,beaconOrder, superframeOrder, false );

    }

    else if ( startMode == MODE_RESUME )

    {

      // Just start the coordinator

      devState = DEV_COORD_STARTING;

      ret = NLME_StartRouterRequest(beaconOrder, beaconOrder, false );

    }

    else

    {

#ifdefined( LCD_SUPPORTED )

      HalLcdWriteScreen( "StartDeviceERR", "MODE unknown" );

#endif

    }

  }

 

在協調器的創建網絡和配置網絡過程中,下列語句是zigbee的原語:

 ret = NLME_NetworkFormationRequest(zgConfigPANID, zgApsUseExtendedPANID, zgDefaultChannelList, zgDefaultStartingScanDuration,beaconOrder, superframeOrder, false );

 

       何爲原語?說白了也是個函數,只是處理過程不開源,簡單點說,就是gap這邊有個請求(信息處理命令),zigbee協議棧內部將請求原語傳遞到gap那邊進行處理,並反饋結果,而這個過程是不開源的。

 

圖2 原語請求與反饋

 

也就是說協調器節點想協議棧內部發出了組建網絡的請求原語(NLME_NetworkFormationRequest),那協議棧的回覆原語是什麼呢?zigbee的回覆原語是ZDO_NetworkFormationConfirmCB(ZStatus_t Status ),進入到函數ZDO_NetworkFormationConfirmCB的內部,可以知道,如果協議棧同意協調器節點建網並且網絡建立成功,則會點亮LED燈,同時函數最後還會觸發事件:osal_set_event(ZDAppTaskID, ZDO_NETWORK_START )。

 

相信大家對函數osal_set_event()都不會陌生了,協議棧對osal_set_event()是怎麼處理的呢?協議棧是調用函數ZDApp_event_loop(uint8 task_id, UINT16 events )對函數進行處理的,同樣進入ZDApp_event_loop的內部看看,這次又是怎麼操作的呢?這次傳遞過來的實參是ZDO_NETWORK_START。

在下列分支找到了參數爲ZDO_NETWORK_START的分支:

 

 if ( ZSTACK_ROUTER_BUILD )

  {

    if ( events & ZDO_NETWORK_START )

    {

      ZDApp_NetworkStartEvt();

      // Return unprocessed events

      return (events ^ ZDO_NETWORK_START);

    }

    if ( events & ZDO_ROUTER_START )

    {

      if ( nwkStatus == ZSuccess )

      {

        if ( devState == DEV_END_DEVICE )

          devState = DEV_ROUTER;

        osal_pwrmgr_device( PWRMGR_ALWAYS_ON );

      }

      else

      {

        // remain as end device!!

      }

      osal_set_event( ZDAppTaskID,ZDO_STATE_CHANGE_EVT );

      //Return unprocessed events

      return (events ^ ZDO_ROUTER_START);

    }

  }

 

首先簡單解釋下ZSTACK_ROUTER_BUILD,選擇go todefinition of ZSTACK_ROUTER_BUILD,就會發現它實際上是一串的宏定義判斷:

 

       #if ( ZG_BUILD_RTR_TYPE ) 

  #if ( ZG_BUILD_ENDDEVICE_TYPE )

    #define ZSTACK_ROUTER_BUILD         (ZG_BUILD_RTR_TYPE &&ZG_DEVICE_RTR_TYPE)

  #else

    #define ZSTACK_ROUTER_BUILD         1

  #endif

#else 

  #define ZSTACK_ROUTER_BUILD           0

#endif

 

由於現在啓動的是協調器節點,所以ZG_BUILD_RTR_TYPE爲真(驗證方法:go todefinition of ZG_BUILD_RTR_TYPE),而ZG_BUILD_ENDDEVICE_TYPE爲假,自然而然,程序就執行了宏定義 #define ZSTACK_ROUTER_BUILD        1,因此就得到了ZSTACK_ROUTER_BUILD爲真的信息。同時由於上面傳遞過來的參數是ZDO_ROUTER_START,因此,程序很自然的就執行語句:ZDApp_NetworkStartEvt();

同樣進入到函數ZDApp_NetworkStartEvt()內部,如果網絡啓動成功的話,在函數ZDApp_NetworkStartEvt()內部執行下列分支:

 

  if ( nwkStatus == ZSuccess )

  {

    // Successfully started a ZigBee network

    if ( devState == DEV_COORD_STARTING )

    {

      devState = DEV_ZB_COORD;

    }

    osal_pwrmgr_device( PWRMGR_ALWAYS_ON );

    osal_set_event( ZDAppTaskID,ZDO_STATE_CHANGE_EVT );

  }

 

這裏網絡啓動後,觸發了ZDO設備狀態改變的事件,osal_set_event(ZDAppTaskID, ZDO_STATE_CHANGE_EVT );同樣,跳轉到函數內部ZDApp_event_loop()內部看看,此時發現與ZDO_STATE_CHANGE_EVT 相對應的if分支執行函數ZDO_UpdateNwkStatus(devState );即更新網絡狀態信息。

       函數ZDO_UpdateNwkStatus()內部是如何更新網絡信息的呢?同樣進入ZDO_UpdateNwkStatus()內部,發現協議棧在配置了網絡短地址等相關信息後,會發送網絡狀態更新信息到每一個註冊的端點號(端點號類似應用端口),發送信息的函數爲:osal_msg_send(*(epDesc->epDesc->task_id), (uint8 *)msgPtr )。

協議棧收到該信息後怎麼處理呢?協議棧會調用函數MT_ProcessIncomingCommand()來處理相關信息,到這裏爲止,協調器節點基本上就已經創建和配置好了一個無線網絡。

 

       在介紹了協調器組建zigbee無線網絡的過程後,緊接着介紹下路由節點和終端節點加入網絡的過程。

       由於終端節點和路由節點加入網絡過程中,都是從main函數爲入口,再繼續執行。和協調器節點創建網絡,啓動協議棧的過程部分有重複,爲了簡單起見,協議棧啓動初期的部分重複內部就不再贅述。圖4中的第(1)步到第(8)步爲路由器節點、終端節點和協調器節點都需要組網(入網)經過的步驟,所以就略過描述了。

 

圖 3協調器、路由器和終端節點相同執行部分

 

我們直接從第(8)步開始講述路由器節點和終端節點入網的執行過程。即執行到:ZDO_StartDevice((uint8)ZDO_Config_Node_Descriptor.LogicalType,devStartMode,DEFAULT_BEACON_ORDER,DEFAULT_SUPERFRAME_ORDER),進入函數內部,找到適合於類型是終端節點或路由節點的分支,如下:

 if ( ZG_BUILD_JOINING_TYPE &&(logicalType == NODETYPE_ROUTER || logicalType == NODETYPE_DEVICE) )

 

我們已經對MANAGED_SCAN進行了宏定義,因此對終端節點和路由節點if分支內部執行如下分支,該分支的功能是發現網絡:

 #if defined( MANAGED_SCAN )

      ZDOManagedScan_Next();

      ret = NLME_NetworkDiscoveryRequest(managedScanChannelMask, BEACON_ORDER_15_MSEC );

 

同樣,zigbee對於發現網絡原語的請求,也是採用原語進行迴應的;迴應的原語如下:ZStatus_tZDO_NetworkDiscoveryConfirmCB( uint8 ResultCount,networkDesc_t *NetworkList ),進入函數ZDO_NetworkDiscoveryConfirmCB的內部,定位到最後一句話,也就是:

 ZDApp_SendMsg( ZDAppTaskID, ZDO_NWK_DISC_CNF,sizeof(ZDO_NetworkDiscoveryCfm_t), (uint8 *)&msg );這裏記住發送的參數是ZDO_NWK_DISC_CNF。

 

對於ZDApp_SendMsg()函數發送信息之後,

協議棧利用ZDApp_ProcessOSALMsg()進行處理的;具體ZDApp_ProcessOSALMsg()內部如何處理的呢?同樣,進入到該函數的內部,發現函數ZDApp_ProcessOSALMsg()主要是switch選擇語句,由於前面發送過來的參數是ZDO_NWK_DISC_CNF,因此在ZDApp_ProcessOSALMsg()找到相對於的case分支,由於這裏是節點加入網絡的過程,所以case分支內部會執行分支if (devStartMode == MODE_JOIN),程序繼續往下執行,最後會執行到下面分支,

if (NLME_JoinRequest( ((ZDO_NetworkDiscoveryCfm_t *)msgPtr)->extendedPANID,BUILD_UINT16(((ZDO_NetworkDiscoveryCfm_t *)msgPtr)->panIdLSB, ((ZDO_NetworkDiscoveryCfm_t*)msgPtr)->panIdMSB ), ((ZDO_NetworkDiscoveryCfm_t*)msgPtr)->logicalChannel, ZDO_Config_Node_Descriptor.CapabilityFlags ) !=ZSuccess )

 

可以看到,這裏是一個節點申請加入網絡的原語,對於此原語的迴應,協議棧會自動調用voidZDO_JoinConfirmCB( uint16 PanId, ZStatus_t Status)原語進行回覆。

 

同樣,在回覆原語ZDO_JoinConfirmCB()中,也存在着發送加入網絡信息的語句:ZDApp_SendMsg(ZDAppTaskID, ZDO_NWK_JOIN_IND, sizeof(osal_event_hdr_t), (byte*)NULL );這裏記住發送的參數是ZDO_NWK_JOIN_IND,後面的ZDApp_ProcessOSALMsg()會用到;

 

而對於該語句的回覆,協議棧同樣是調用語句ZDApp_ProcessOSALMsg()進行回覆,由於ZDApp_ProcessOSALMsg()內部執行的是switch語句,所以找到ZDO_NWK_JOIN_IND相對應的case分支,如下:

caseZDO_NWK_JOIN_IND:

      if (ZG_BUILD_JOINING_TYPE &&ZG_DEVICE_JOINING_TYPE )

      {

        ZDApp_ProcessNetworkJoin();

      }

      break;

很自然地,協議棧會執行了函數ZDApp_ProcessNetworkJoin();這個函數是處理節點入網的過程。

 

這裏對於路由器節點和終端節點,協議棧執行的方法各有不同;

首先介紹下對於路由節點,協議棧是如何應對函數ZDApp_ProcessNetworkJoin()的呢?

對於路由節點,協議棧調用voidZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr )來回復ZDApp_ProcessNetworkJoin()函數。

前面已經分析過了ZDApp_ProcessOSALMsg()函數內部執行的是swtich選擇分支,而且對於路由節點而言,該switch選擇分支執行是建立路由器節點的相關分支,因此定位到分支if (ZSTACK_ROUTER_BUILD ),如下:

if ( ZSTACK_ROUTER_BUILD )

      {

        // 節點類型不是終端節點

        if (ZDO_Config_Node_Descriptor.LogicalType!= NODETYPE_DEVICE )

        {

          NLME_StartRouterRequest( 0, 0, false);

        }

      }

所以程序很自然的執行了建立路由器請求原語:NLME_StartRouterRequest(0, 0, false );而zigbee協議棧對該路由請求原語,自動調用函數ZDO_StartRouterConfirmCB( ZStatus_t Status )進行回覆。

在函數ZDO_StartRouterConfirmCB()內部,又觸發了設置任務事件的語句:

osal_set_event( ZDAppTaskID,ZDO_ROUTER_START );

而對於該語句,協議棧會調用函數ZDApp_event_loop()進行回覆。進入函數ZDApp_event_loop()內部找到相應於ZDO_ROUTER_START的if分支,如下:

if ( events &ZDO_ROUTER_START )

    {

      if ( nwkStatus == ZSuccess )

      {

        if ( devState == DEV_END_DEVICE )

          devState = DEV_ROUTER;

 

        osal_pwrmgr_device( PWRMGR_ALWAYS_ON );

      }

      else

      {

        // remain as end device!!

      }

      osal_set_event( ZDAppTaskID,ZDO_STATE_CHANGE_EVT );

 

      // Return unprocessed events

      return (events ^ ZDO_ROUTER_START);

}

到這裏爲止,路由器就加入網絡併成功啓動了!!

 

對於終端節點,協議棧是如何應對函數ZDApp_ProcessNetworkJoin()的呢?

進入到ZDApp_ProcessNetworkJoin()函數內部,定位到下列if分支:if( nwkStatus == ZSuccess ),此if分支的含義是:如果終端節點企圖加入節點成功,則執行此分支。此分支內部有語句:osal_set_event(ZDAppTaskID, ZDO_STATE_CHANGE_EVT );這裏是網絡狀態更新事件。同樣,對於該語句,協議棧自動調用函數ZDApp_event_loop()進行處理。進入到函數內部,找到相對於ZDO_STATE_CHANGE_EVT的if分支。該if分支內部執行了函數ZDO_UpdateNwkStatus(devState ),即更新網絡狀態函數,此函數內部有語句:osal_msg_send(*(epDesc->epDesc->task_id), (uint8 *)msgPtr ),協議棧是如何處理該語句的呢?

協議棧是自動調用函數MT_ProcessIncomingCommand(mtOSALSerialData_t *msg ),而該函數內部執行的是一個switch選擇結構,找到分支:caseZDO_STATE_CHANGE,則是內部執行了函數: MT_ZdoStateChangeCB((osal_event_hdr_t*)msg);

 

到這裏,終端節點加入網絡的過程也就介紹完了!關於協調器建網、路由器節點和終端節點入網的過程也就全部介紹完了!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章