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);
到这里,终端节点加入网络的过程也就介绍完了!关于协调器建网、路由器节点和终端节点入网的过程也就全部介绍完了!