本人刚接触zigbee不久,在学习中将不断记录每天的进步,期待大家的指导!!
在这里指出我还是参考了ZStack-CC2530-2.3.1-1.4.0\Projects\zstack\Samples\GenericApp例程
在协议栈中关于bind的服务主要有一下几类。
End_Device_Bind_req ((uint16)0x0020)
Bind_req ((uint16)0x0021)
Unbind_req ((uint16)0x0022)
Bind_rsp (Bind_req | ZDO_RESPONSE_BIT)
End_Device_Bind_rsp (End_Device_Bind_req | ZDO_RESPONSE_BIT)
Unbind_rsp (Unbind_req | ZDO_RESPONSE_BIT)
我借由GenericApp中 End_Device_Bind_req 服务请求来一步步说明bind蛋疼的传来传去的流程。
假设一个bind节点1,它要发出End_Device_Bind_req请求。这个是在 GenericApp_HandleKeys发出的
if ( keys & HAL_KEY_SW_2 )
{
HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF );
// Initiate an End Device Bind Request for the mandatory endpoint
dstAddr.addrMode = Addr16Bit;
dstAddr.addr.shortAddr = 0x0000; // Coordinator
ZDP_EndDeviceBindReq( &dstAddr, NLME_GetShortAddr(),
GenericApp_epDesc.endPoint,
GENERICAPP_PROFID,
GENERICAPP_MAX_CLUSTERS, (cId_t *)GenericApp_ClusterList,
GENERICAPP_MAX_CLUSTERS, (cId_t *)GenericApp_ClusterList,
FALSE );
}
在ZIGBEE SPECIFICATION有如下的描述
The End_Device_Bind_req is generated from a Local Device wishing to perform End Device Bind with a Remote Device. The End_Device_Bind_req is generated, typically based on some user action like a button press. The destination addressing on
this command shall be unicast, and the destination address shall be that of the ZigBee Coordinator.
就是说这个End_Device_Bind_req 请求是发给我们的 Coordinator,代码也是这样写的。
ZDP_EndDeviceBindReq具体内容我就不贴出来了,这个函数很简单就是发送一个End_Device_Bind_req格式帧给Coordinator,帧格式为
LocalCoordinator(本地nwk地址) + SrcExtAddr (本地ieee地址)+ ep(请求bind的endpoint) + ProfileID + NumInClusters(簇的个数) + NumOutClusters(簇列表)
就这样这个End_Device_Bind_req请求到了Coordinator,接收部分我不想多说了,要说又是一大堆了。但要注意的是Coordinator的End_Device_Bind_req请求处理函数不是在CONST zdpMsgProcItem_t zdpMsgProcs[]数组,我们不能像Match_Desc_req那样轻松的借由AF_INCOMING_MSG_CMD进入 ZDP_IncomingData调用服务函数的。相比的要绕个弯子,我们先要注册一下。
void ZDApp_RegisterCBs( void )
{
#if defined ( ZDO_IEEEADDR_REQUEST ) || defined ( REFLECTOR )
ZDO_RegisterForZDOMsg( ZDAppTaskID, IEEE_addr_rsp );
#endif
#if defined ( ZDO_NWKADDR_REQUEST ) || defined ( REFLECTOR )
ZDO_RegisterForZDOMsg( ZDAppTaskID, NWK_addr_rsp );
#endif
#if ZG_BUILD_COORDINATOR_TYPE
ZDO_RegisterForZDOMsg( ZDAppTaskID, Bind_rsp );
ZDO_RegisterForZDOMsg( ZDAppTaskID, Unbind_rsp );
ZDO_RegisterForZDOMsg( ZDAppTaskID, End_Device_Bind_req ); //注册了End_Device_Bind_req 服务
#endif
#if defined ( REFLECTOR )
ZDO_RegisterForZDOMsg( ZDAppTaskID, Bind_req );
ZDO_RegisterForZDOMsg( ZDAppTaskID, Unbind_req );
#endif
}
这个注册函数和之前帖子提到的
ZDO_RegisterForZDOMsg( GenericApp_TaskID, End_Device_Bind_rsp );
ZDO_RegisterForZDOMsg( GenericApp_TaskID, Match_Desc_rsp );
功能一样。
End_Device_Bind_req 服务调用分为2步,首先在ZDP_IncomingData中的ZDO_SendMsgCBs标记ZDO_CB_MSG然后通过osal调用ZDApp_ProcessMsgCBs真正的完成。
ZDApp_ProcessMsgCBs这个函数之后我们会多次打交道,我在这只贴相出于End_Device_Bind_req有关的部分
switch ( inMsg->clusterID )
{.........
case End_Device_Bind_req:
if (ZG_DEVICE_COORDINATOR_TYPE)
{
ZDEndDeviceBind_t bindReq;
ZDO_ParseEndDeviceBindReq( inMsg, &bindReq );
ZDO_MatchEndDeviceBind( &bindReq );
// Freeing the cluster lists - if allocated.
if ( bindReq.numInClusters )
osal_mem_free( bindReq.inClusters );
if ( bindReq.numOutClusters )
osal_mem_free( bindReq.outClusters );
}
break;
强调只有COORDINATOR才能处理End_Device_Bind_req请求吧,与我们之前发送部分是吻合的。
还有个别的体会的可以与本人联系
首先请让我向大家介绍下强人小峰,看完他写的多篇博客感悟颇多哦!
可以看到调用了ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER );这里设备网络状态参数:
ZDO_Config_Node_Descriptor.LogicalType = NODETYPE_COORDINATOR
devStartMode = MODE_HARD
且协调器编译了ZDO_COORDINATOR
****************
void ZDO_StartDevice( byte logicalType, devStartModes_t startMode, byte beaconOrder, byte superframeOrder )
{
ZStatus_t ret;
ret = ZUnsupportedMode;
#if defined(ZDO_COORDINATOR)
if ( logicalType == NODETYPE_COORDINATOR )
{
if ( startMode == MODE_HARD ) //MODE_HARD
{
devState = DEV_COORD_STARTING; //Started as Zigbee Coordinator
//建网
ret = NLME_NetworkFormationRequest( zgConfigPANID, zgDefaultChannelList,
zgDefaultStartingScanDuration, beaconOrder,
superframeOrder, false );
}
else if ( startMode == MODE_RESUME ) //MODE_RESUME
{
// Just start the coordinator
devState = DEV_COORD_STARTING;
ret = NLME_StartRouterRequest( beaconOrder, beaconOrder, false );
}
else //错误,未知启动模式
{
#if defined( LCD_SUPPORTED )
//HalLcdWriteScreen( "StartDevice ERR", "MODE unknown" );
ClearScreen();
Print8(HAL_LCD_LINE_1,10,"StartDevice ERR",1);
Print8(HAL_LCD_LINE_2,10,"MODE unknown",1);
#endif
}
}
#endif // !ZDO_COORDINATOR
//#if !defined ( ZDO_COORDINATOR ) || defined( SOFT_START )
if ( logicalType == NODETYPE_ROUTER || logicalType == NODETYPE_DEVICE )
{
if ( (startMode == MODE_JOIN) || (startMode == MODE_REJOIN) )
{
devState = DEV_NWK_DISC; //Discovering PAN's to join
#if defined( MANAGED_SCAN )
ZDOManagedScan_Next();
ret = NLME_NetworkDiscoveryRequest( managedScanChannelMask, BEACON_ORDER_15_MSEC );
#else
ret = NLME_NetworkDiscoveryRequest( zgDefaultChannelList, zgDefaultStartingScanDuration );
#endif
}
else if ( startMode == MODE_RESUME ) //MODE_RESUME 恢复
{
if ( logicalType == NODETYPE_ROUTER )
{
ZMacScanCnf_t scanCnf;
devState = DEV_NWK_ORPHAN;
/* if router and nvram is available, fake successful orphan scan */
scanCnf.hdr.Status = ZSUCCESS;
scanCnf.ScanType = ZMAC_ORPHAN_SCAN;
scanCnf.UnscannedChannels = 0;
scanCnf.ResultListSize = 0;
nwk_ScanJoiningOrphan(&scanCnf);
ret = ZSuccess;
}
else
{
devState = DEV_NWK_ORPHAN; //孤儿
ret = NLME_OrphanJoinRequest( zgDefaultChannelList,
zgDefaultStartingScanDuration );
}
}
else
{
#if defined( LCD_SUPPORTED )
// HalLcdWriteScreen( "StartDevice ERR", "MODE unknown" );
Print8(HAL_LCD_LINE_1,10,"StartDevice ERR",1);
Print8(HAL_LCD_LINE_2,10,"MODE unknown",1);
#endif
}
}
//#endif //!ZDO COORDINATOR || SOFT_START
if ( ret != ZSuccess )
osal_start_timerEx(ZDAppTaskID, ZDO_NETWORK_INIT, NWK_RETRY_DELAY );
}
****************
通过参数可知协调器调用NLME_NetworkFormationRequest( zgConfigPANID, zgDefaultChannelList,
zgDefaultStartingScanDuration, beaconOrder,
superframeOrder, false )
进行网络的组建.
而对NLME_NetworkFormationRequest()的调用会产生一个回调函数ZDO_NetworkFormationConfirmCB()(见主要函数说明3),来看下:
****************
void ZDO_NetworkFormationConfirmCB( ZStatus_t Status )
{
#if defined(ZDO_COORDINATOR)
nwkStatus = (byte)Status;
if ( Status == ZSUCCESS )
{
// LED on shows Coordinator started
HalLedSet ( HAL_LED_3, HAL_LED_MODE_ON );
// LED off forgets HOLD_AUTO_START
HalLedSet (HAL_LED_4, HAL_LED_MODE_OFF);
#if defined ( ZBIT )
SIM_SetColor(0xd0ffd0);
#endif
if ( devState == DEV_HOLD )
{
// Began with HOLD_AUTO_START
devState = DEV_COORD_STARTING;
}
}
#if defined(BLINK_LEDS)
else
HalLedSet ( HAL_LED_3, HAL_LED_MODE_FLASH ); // Flash LED to show failure
#endif
osal_set_event( ZDAppTaskID, ZDO_NETWORK_START );
#endif //ZDO_COORDINATOR
}
****************
如果Status返回ZSUCCESS,建立网络成功,通过一些灯亮来来指示;不成功则通过闪烁灯来指示.最后触发任务ZDAppTaskID的ZDO_NETWORK_START事件,看下对ZDO_NETWORK_START的处理:
****************
#if defined (RTR_NWK)
if ( events & ZDO_NETWORK_START )
{
ZDApp_NetworkStartEvt();
// Return unprocessed events
return (events ^ ZDO_NETWORK_START);
}
****************
调用了ZDApp_NetworkStartEvt()
****************
void ZDApp_NetworkStartEvt( void )
{
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 );
}
else //网络建立不成功,则增加能量阀值重新建网.
{
// Try again with a higher energy threshold !!
if ( ( NLME_GetEnergyThreshold() + ENERGY_SCAN_INCREMENT ) < 0xff )
{
NLME_SetEnergyThreshold( (uint8)(NLME_GetEnergyThreshold() + ENERGY_SCAN_INCREMENT) );
osal_set_event( ZDAppTaskID, ZDO_NETWORK_INIT ); //重新初始化建立网络
}
else
{
// Failed to start network. Enter a dormant state (until user intervenes)
devState = DEV_INIT;
osal_set_event( ZDAppTaskID, ZDO_STATE_CHANGE_EVT );
}
}
}
****************
如果协调器建立网络成功,则触发ZDAppTaskID的ZDO_STATE_CHANGE_EVT事件.看下对 ZDO_STATE_CHANGE_EVT的处理:
****************
if ( events & ZDO_STATE_CHANGE_EVT )
{
ZDO_UpdateNwkStatus( devState );
// Return unprocessed events
return (events ^ ZDO_STATE_CHANGE_EVT);
}
****************
调用了ZDO_UpdateNwkStatus( devState ),网络状态改变,这个函数会更新和发送信息通知每个注册登记过的应用终端.
****************
void ZDO_UpdateNwkStatus( devStates_t state )
{
// Endpoint/Interface descriptor list.
epList_t *epDesc = epList;
byte bufLen = sizeof(osal_event_hdr_t);
osal_event_hdr_t *msgPtr;
ZDAppNwkAddr.addr.shortAddr = NLME_GetShortAddr();
(void)NLME_GetExtAddr(); // Load the saveExtAddr pointer.
while ( epDesc )
{
if ( epDesc->epDesc->endPoint != ZDO_EP )
{
msgPtr = (osal_event_hdr_t *)osal_msg_allocate( bufLen );
if ( msgPtr )
{
msgPtr->event = ZDO_STATE_CHANGE; // Command ID
msgPtr->status = (byte)state;
osal_msg_send( *(epDesc->epDesc->task_id), (byte *)msgPtr ); //发往应用任务
}
}
epDesc = epDesc->nextDesc;
}
}
****************
对SampleApp的协调器来说,这里触发应用任务SampleApp_TaskID的ZDO_STATE_CHANGE事件,看下对ZDO_STATE_CHANGE的处理:
****************
case ZDO_STATE_CHANGE:
SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status); //获取设备当前状态
if ( (SampleApp_NwkState == DEV_ZB_COORD)
|| (SampleApp_NwkState == DEV_ROUTER)
|| (SampleApp_NwkState == DEV_END_DEVICE) )
{
// Start sending the periodic message in a regular interval.
osal_start_timerEx( SampleApp_TaskID,
SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );
}
else
{
// Device is no longer in the network
}
break;
****************
可以看到当协调器建立网络成功,通过回调函数触发应用任务的ZDO_STATE_CHANGE事件,最终开启定时器发送周期信息.
3、
协调器(自启动模式)—以SampleApp的协调器为例,并假设初始化成功,网络建立成功.
程序大致流程:
main()->osal_init_system()->osalInitTasks()->ZDApp_Init()->ZDOInitDevice()->ZDApp_NetworkInit->触发ZDAppTaskID的ZDO_NETWORK_INIT->ZDO_StartDevice()->NLME_NetworkFormationRequest()->网络建立成功ZDO_NetworkFormationConfirmCB->触发ZDAppTaskID的ZDO_NETWORK_START->ZDApp_NetworkStartEvt()->触发ZDAppTaskID的ZDO_STATE_CHANGE_EVT->ZDO_UpdateNwkStatus()->触发SampleApp_TaskID的ZDO_STATE_CHANGE->开户周期信息发送的定时器.
4、注:
(1)自启动模式下SampleApp的终端和路由器总体流程基本一致.
(2)
以SampleApp为例,ZDO_StartDevice()函数的两个重要参数比较:
终端:
ZDO_Config_Node_Descriptor.LogicalType = NODETYPE_DEVICE
devStartMode = MODE_JOIN
路由器:
ZDO_Config_Node_Descriptor.LogicalType = NODETYPE_ROUTER
devStartMode = MODE_JOIN
协调器:
ZDO_Config_Node_Descriptor.LogicalType = NODETYPE_COORDINATOR
devStartMode = MODE_HARD
***************************************
***************************************
申明:这是转载一位高手的博客哦!!
ZDO层API
实现了所有ZDP(ZigBee Device Profile)定义的命令和回应所需要的函数。ZDP描述了ZDO如何实现普通ZigBee设备的特性,它定义了设备描述和簇,ZDP为ZDO和应用提供一下功能:
设备网络建立
设备和服务发现
节点设备邦定和解邦定服务
网络管理服务
设备发现是ZigBee设备发现其他ZigBee设备的过程。比如将已知的IEEE地址作为数据载荷广播到网络的NWK地址请求,相关设备必须回应并告知其网络地址。服务发现提供了PAN中一个设备发现其他设备提供的服务的能力。它利用多种描述符去指定设备的能力。
当用户需要邦定控制器与被控设备(比如开关和灯)时,将用到邦定和解邦定服务。特别是终端设备邦定支持利用用户的输入来定义控制/被控设备对的简单邦定方法。邦定和解邦服务可以创建和删除邦定表入口。
网络管理服务主要提供给用户和调试工具网络管理的能力,它能够从设备重新获得管理信息,包括网络发现结果,路由表的内容,邻居节点链路质量,邦定表内容。也可以通过解关联将设备从PAN中脱离来控制网络关联。
ZDO设备网络建立
在ZigBee网络中默认ZDApp_Init()[in ZDApp.c]开始器件的启动,但是应用能覆盖整个默认行为,为了使应用接管网络的启动,必须在编译选项中包含HOLD_AUTO_START,推荐同时包含NV_RESTORE(储存ZigBee网络状态到NV)编译选项,包含这个两个编译选项后就需要调用ZDOInitDevice() 来启动网络中的设备。
uint8 ZDOInitDevice( uint16 startDelay )
启动网络中的设备。如果应用想要强制一个“新的”加入(即不恢复网络状态),则应该先调用zgWriteStartupOptions(ZG_STARTUP_SET, ZCD_STARTOPT_DEFAULT_NETWORK_STATE)来设置ZCD_NV_STARTUP_OPTION 中的ZCD_STARTOPT_DEFAULT_NETWORK_STATE 位。
startDelay -启动设备的延时。这个延时有一个抖动:
(NWK_START_DELAY + startDelay) + (osal_rand() & EXTENDED_JOINING_RANDOM_MASK)
返回值:
ZDO_INITDEV_RESTORED_NETWORK_STATE 设备的网络状态恢复
ZDO_INITDEV_NEW_NETWORK_STATE 网络状态被初始化。即一个强制“新的”加入,或者没有可供恢复的状态
ZDO_INITDEV_LEAVE_NOT_STARTED 重置前,发送了网络脱离并且重加入被设置为TRUE。因此器件没有启动,但是下次调用此函数将启动。
ZDO消息回馈
通过ZDO_RegisterForZDOMsg()注册消息,应用就能接收任何空中(over the air)消息。
ZStatus_t ZDO_RegisterForZDOMsg( uint8 taskID, uint16 clusterID )
调用此函数请求over-the-air消息,此消息的备份将以OSAL消息发送到任务,接收到消息的任务可以自己解析消息,也可以调用ZDO解析函数来解析消息。只有响应消息有ZDO解析函数。
消息注册并被接收后(OTA),将作为ZDO_CB_MSG (OSAL Msg)发送到应用/任务。消息体(zdoIncomingMsg_t – defined in ZDProfile.h)包含有OTA消息。
taskID -用来发送OSAL消息的应用任务ID;
clusterID -想要接收的OTA消息的clusterID。如:NWK_addr_rsp。这些ID定义在ZDProfile.h
返回值:ZStatus_t -ZComDef.h中ZStatus_t定义的状态值
ZStatus_t ZDO_RemoveRegisteredCB( uint8 taskID, uint16 clusterID )
为OTA消息移除一个请求。
参数必须和ZDO_RegisterForZDOMsg()中的参数相同
ZDO发现API
这些API用来建立和发送ZDO设备和服务发现请求和回复。ZDO API和ZDP命令一一对应,ZDP命令可以在ZigBee协议规范中找到更详细的介绍。
ZDO API Function ZDP Discovery Command
ZDP_NwkAddrReq() NWK_addr_req
ZDP_NWKAddrRsp() NWK_addr_rsp
ZDP_IEEEAddrReq() IEEE_addr_req
ZDP_IEEEAddrRsp() IEEE_addr_rsp
ZDP_NodeDescReq() Node_Desc_req
ZDP_NodeDescRsp() Node_Desc_rsp
ZDP_PowerDescReq() Power_Desc_req
ZDP_PowerDescRsp() Power_Desc_rsp
ZDP_SimpleDescReq() Simple_Desc_req
ZDP_SimpleDescRsp() Simple_Desc_rsp
ZDP_ComplexDescReq() Complex_Desc_req
ZDP_ActiveEPIFReq() Active_EP_req
ZDP_ActiveEPIFRsp() Active_EP_rsp
ZDP_MatchDescReq() Match_Desc_req
ZDP_MatchDescRsp() Match_Desc_rsp
ZDP_UserDescSet() User_Desc_set
ZDP_UserDescConf() User_Desc_conf
ZDP_UserDescReq() User_Desc_req
ZDP_UserDescRsp() User_Desc_rsp
ZDP_EndDeviceAnnce() Device_annce
ZDP_ServerDiscReq() System_Server_Discovery_req
ZDP_ServerDiscRsp() System_Server_Discovery_rsp
afStatus_t ZDP_NwkAddrReq( byte *IEEEAddress, byte ReqType,byte StartIndex, byte SecuritySuite )
发送为一个IEEE地址已知的设备请求16位短地址的消息,消息将以广播的形式发送。
IEEEAddress -发送请求的设备的IEEE地址
ReqType -希望收到的答复类型
ZDP_NWKADDR_REQTYPE_SINGLE 返回设备的短地址和扩展地址
ZDP_NWKADDR_REQTYPE_EXTENDED 返回设备的短地址和扩展地址,以及所有关联设备的短地址
StartIndex -应答设备可能含有多条符合应答消息的应答条目,此参数指定请求开始检索的应答条目
SecuritySuite -消息的安全类型
返回值:afStatus_t -次函数用AF来发送消息,所以状态值为ZComDef.h中ZStatus_t定义的AF状态值
afStatus_t ZDP_NWKAddrRsp( byte TranSeq, zAddrType_t *dstAddr,
byte Status, byte *IEEEAddrRemoteDev,
byte ReqType, uint16 nwkAddr,
byte NumAssocDev, byte StartIndex,
uint16 *NWKAddrAssocDevList,
byte SecuritySuite );
这个函数实际上是调用ZDP_AddrRsp()的宏,用来建立和发送网络地址应答
TranSeq – 报文序号
DstAddr - 目标地址
Status – ZDP_SUCCESS 0
ZDP_INVALID_REQTYPE 1
ZDP_DEVICE_NOT_FOUND 2
Reserved 0x03-0xff
IEEEAddrRemoteDev - 远端设备的64位地址
ReqType – 请求类型
nwkAddr – 远端设备的16位地址
NumAssocDev - 和远端设备相关联的设备及其16位短地址的数目
StartIndex - 应答消息的开始序号
NWKAddrAssocDevList -相关联的16位地址列表
SecuritySuite - 消息安全类型
afStatus_t ZDP_IEEEAddrReq( uint16 shortAddr, byte ReqType,
byte StartIndex, byte SecuritySuite )
已知设备的16位短地址请求64位IEEE地址。
呵呵好多参数都是一看就知道是什么了呢,那就不写出来了,下面的函数也一样的处理了。
afStatus_t ZDP_IEEEAddrRsp( byte TranSeq, zAddrType_t *dstAddr,
byte Status, byte *IEEEAddrRemoteDev,
byte ReqType, uint16 nwkAddr,
byte NumAssocDev, byte StartIndex,
uint16 *NWKAddrAssocDevList,
byte SecuritySuite );
这个函数实际上是调用ZDP_AddrRsp()的宏,用来建立和发送IEEE地址应答
afStatus_t ZDP_NodeDescReq( zAddrType_t *dstAddr, uint16 NWKAddrOfInterest, byte SecuritySuite );
构建并向目标地址域发送节点描述请求
afStatus_t ZDP_NodeDescMsg( byte TransSeq, zAddrType_t *dstAddr, byte Status,
uint16 nwkAddr, NodeDescriptorFormat_t *pNodeDesc,
byte SecuritySuite );
DstAddr - 目标地址
NWKAddrOfInterest - 要搜寻的16位短地址
SecuritySuite - 消息的安全类型
afStatus_t ZDP_NodeDescMsg( byte TransSeq, zAddrType_t *dstAddr, byte Status,
uint16 nwkAddr, NodeDescriptorFormat_t *pNodeDesc,
byte SecuritySuite );
回应节点描述(Node Descriptor)请求。
Status - SUCCESS 0
DEVICE_NOT_FOUND 1
pNodeDesc -指向节点描述的指针(定义在AF.h中)
afStatus_t ZDP_PowerDescReq( zAddrType_t *dstAddr, int16 NWKAddrOfInterest,byte SecuritySuite );
看了好长一段时间的原理,终于手痒了,开动了第一个例子,感谢大家来敢看我的博客!!
1、 协议栈构架
首先打开程序代码,找到IAR工程 ,打开后可以看到TI ZStack的大体框架,如下图所示:
§ App:应用层目录,这也是用户创建各种不同工程的区域;
§ HAL:硬件层目录,包括着与硬件相关的配置及操作函数;
§ MAC:MAC层目录,包括着MAC层配置参数文件及MAC LIB库的函数接口文件;
§ MT:包括基于AF层的调试函数文件,主要包括串口等通信函数;§ NWK:网络层目录,包括着网络层配置参数文件及MAC LIB库的函数接口文件;
§ OSAL:系统目录,包括协议栈系统文档;
§ Profile:AF层目录,包括AF层处理函数文件;
§ Security:安全层目录,安全层处理函数,比如加密函数等;
§ Services:地址处理函数目录,包括着地址模式的定义及地址处理函数文档;Tools:工程配置目录,包括协议栈等配置文档;
§ ZDO ZDO ZDO ZDO:层目录,包括层处理函数文档;
§ ZMac:MAC层目录,包括MAC层参数配置及MAC层LIB库函数回调处理函数
§ ZMain:主函数目录,包括入口函数及硬件配置文件;
§ Output:输出文件目录,这是EW8051IDE自动生成的;
2、 程序的编译和下载
(1) 项目属性设置
选择菜单Project->Options、右击菜单options或者通过热键(ALT+F7)打开工程属性设置。
也可以鼠标右击workspace中的工程名,如下图:
打开属性设置后如图:
一般来说,如果是在TI的协议栈的进行修改,里面的设置就不用修改。如果要具体了解各参数,请参照IAR文档。
这里值得注意的是chipcon下的Download的标签,如果第一次用建议像如图那样选择,这样是将整个FALSH擦除后再Download,写完后最好用
软件来更改芯片的IEEE地址,否则可能带来你的程序无法运行。
具体见Zmain.c函数中的如下程序段:
led = HAL_LED_MODE_OFF;
while ( HAL_KEY_SW_5 == HalKeyRead() )
{
MicroWait( 62500 );
HalLedSet( HAL_LED_1, led^=HAL_LED_MODE_ON ); // Toggle the LED
MicroWait( 62500 );
}
HalLedSet( HAL_LED_1, HAL_LED_MODE_OFF );
如果你使用的硬件平台没有对这个按键5进行设置,或者就没有这个按键5,这段将过不去,LED1灯将一直闪。
如果你的系统没有按键,建议将上面的语句注释掉。
(2) 编译与烧写
选择Project->Debug或者热键(Ctrl+D)给开发板上的Zigbee模块下载程序。
也可以直接点IAR的如下图标来编译和下载程序 ,最后一个是编译并下载;
同时也可以在workspace中的工程名上点击鼠标右键来选择编译。
下图显示为DEBUG时的选项:
在调试程序时,DEBUG还是非常有用的,它能告诉你程序运行到哪了,程序为什么没有出现你预期的结果等,因此,选择一个仿真器还是有用的,并且有些仿真器加上一个节点就可以充当packet sniffer工具,对空中的包能进行实时跟踪。下图是在宁波中科仿真器抓到的本例子的数据包,以后会对这些包结构做一定解析,如果也可以参考ZigBee协议栈文档,上面对这些包结构有详细的说明。
从图中的RSSI中可以看到节点的性能,负数越接近0表明节点性能越好,通信距离越长,并且协调器已经给一个终端节点分配了短地址0x796F(该地址可以通过计算得到,见IEEE 802.15.4文档)。
3、 实验程序简单分析
在本实验中用户涉及的程序主要有OSAL_SendTest.c,SendTest.c,SendTest.h。其他程序协议栈程序只做简单修改就行。比如我使用的是GAINST-CC2430需要修改一下串口函数等。因为硬件平台的差异性,大家可以根据实际进行修改。下面主要介绍上面提到的三个函数。
OSAL_SendTest.c函数是协议栈操作系统处理函数,这个函数实现对本实验中需要的任务的添加,具体函数如下:
osalTaskAdd( nwk_init, nwk_event_loop, OSAL_TASK_PRIORITY_MED );
osalTaskAdd( APS_Init, APS_event_loop, OSAL_TASK_PRIORITY_LOW );
osalTaskAdd( ZDApp_Init, ZDApp_event_loop, OSAL_TASK_PRIORITY_LOW );
osalTaskAdd( SendTest_Init, SendTest_ProcessEvent, OSAL_TASK_PRIORITY_LOW );
最后一条语句是添加本实验任务的。osalTaskAdd()函数的参数请参照OSAL API_F8W-2003-0002_.PDF文档。
SendTest.c是本实验用户程序的具体实现。下面来分析一下该程序。
首先,本程序不考虑协议栈绑定等相关内容,在程序中屏蔽了绑定,实验数据只是从终端节点传送到协调器节点,对于ZIGBEE网络协调器节点的短地址始终为0x0000,因此在用发送函数时,目的地址写上0x0000就能传到协调器。
其次,两个设备的 endPoint要保持一致,否则将不能通信。在程序中将SendTest_DstAddr.endPoint = SendTest_ENDPOINT;(SendTest_ENDPOINT=10)。
程序实现结果是:协调器的串口上输出如图内容:
注意,要使用该串口助手才能显示中文,其他的串口可能不支持。
只有在终端节点加入网络后才能发送数据给协调器,一旦网络中断数据就不再发,这个机制是通过判断网络状态来实现的,代码如下:
if ( (SendTest_NwkState == DEV_END_DEVICE) ) //只在设备为终端节点时,才发送数据
{
// Start sending "the" message in a regular interval.
osal_start_timer( SendTest_SEND_MSG_EVT,
SendTest_SEND_MSG_TIMEOUT );
}
上面语句中SendTest_SEND_MSG_EVT是一个事件,只在定时器时间到才触发该事件,在程序中设置
#define SendTest_SEND_MSG_TIMEOUT 1000
也就是在网络建成后每一秒向协调器发送数据。
对于操作系统的介绍请参考文档,以后我也会就这个做些介绍。
SendTest_ProcessEvent函数为操作系统的一任务(上面已经介绍了怎么加入任务),总是被周期性轮询。当检测到一个event就执行相关程序。
在一个任务中有16个事件,用16位来表示,每一位代表一个事件,其中0x8000为系统事件SYS_EVENT_MSG,任务还和task_id有关。
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SendTest_TaskID );
程序段是将收到的消息存放到MSGpkt指定的区域中。
while ( MSGpkt )//消息不为空
{ switch ( MSGpkt->hdr.event )
{
case KEY_CHANGE://按键状态改(本实验不涉及)
SendTest_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
break;
case AF_DATA_CONFIRM_CMD://AF层数据发送完成后确认报告
// This message is received as a confirmation of a data packet sent.
// The status is of ZStatus_t type [defined in ZComDef.h]
// The message fields are defined in AF.h
afDataConfirm = (afDataConfirm_t *)MSGpkt;
sentEP = afDataConfirm->endpoint;
sentStatus = afDataConfirm->hdr.status;
sentTransID = afDataConfirm->transID;
(void)sentEP;
(void)sentTransID;
HAL_TOGGLE_LED1();//加入LED1来指示数据发送出
// Action taken when confirmation is received.
if ( sentStatus != ZSuccess )
{
// The data wasn't delivered -- Do something
}
break;
case AF_INCOMING_MSG_CMD://新的报文来
HAL_TOGGLE_LED1();//加入LED1来指示新报文来
SendTest_MessageMSGCB( MSGpkt );//新报文回调函数
break;
case ZDO_NEW_DSTADDR://ZDO终端地址改变,这个是在加入绑定后引起的,本实验不涉及
ZDO_NewDstAddr = (ZDO_NewDstAddr_t *)MSGpkt;
dstEP = ZDO_NewDstAddr->dstAddrDstEP;
dstAddr = &ZDO_NewDstAddr->dstAddr;
SendTest_DstAddr.addrMode = (afAddrMode_t)dstAddr->addrMode;
SendTest_DstAddr.endPoint = dstEP;
SendTest_DstAddr.addr.shortAddr = dstAddr->addr.shortAddr;
break;
case ZDO_STATE_CHANGE://网络状态改变,在这里启动第一次数据传输
SendTest_NwkState = (devStates_t)(MSGpkt->hdr.status);
if ( (SendTest_NwkState == DEV_END_DEVICE) ) //只在设备为终端节点时,才发送数据
{
// Start sending "the" message in a regular interval.
osal_start_timer( SendTest_SEND_MSG_EVT,
SendTest_SEND_MSG_TIMEOUT );
}//这个在上面已经有介绍
break;
default:
break;
}
// Release the memory
osal_msg_deallocate( (uint8 *)MSGpkt );
// Next
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SendTest_TaskID );
}
if ( events & SendTest_SEND_MSG_EVT )//如果事件为消息传送事件
{
// Send "the" message
SendTest_SendTheMessage();//调用消息发送函数
// Setup to send message again
if ( (SendTest_NwkState == DEV_END_DEVICE) )//解释同前
osal_start_timer( SendTest_SEND_MSG_EVT,
SendTest_SEND_MSG_TIMEOUT );
// return unprocessed events
return (events ^ SendTest_SEND_MSG_EVT);
}
消息回调函数实现:
void SendTest_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
switch ( pkt->clusterId )
{
case SendTest_CLUSTERID:
// "the" message
#if defined( LCD_SUPPORTED )
HalLcdWriteScreen( (char*)pkt->cmd.Data, "rcvd" );
#elif defined( WIN32 )
WPRINTSTR( pkt->cmd.Data );
#endif
HalUARTWrite( HAL_UART_PORT_1,(pkt->cmd).Data, (pkt->cmd).DataLength);//将接收到的数打印到串口上
break;
}
}
对于串口的初始化在SendTest_Init函数中,上面的红色显示函数就是将收到的数据信息打印在串口终端上。
下面介绍一下,发送函数AF_DataRequest
该函数用于发送数据
函数声明:
afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP,
uint16 cID, uint16 len, uint8 *buf, uint8 *transID,
uint8 options, uint8 radius );
具体参数:
dstAddr:目的地址指针,其中的地址模式是:afAddrNotPresent(用于绑定)、afAddrGroup(传送到一组目的节点)afAddrBroadcast(广播发送)、afAddr16Bit(直接发送),在本实验中afAddr16Bit地址模式,在SendTest_Init初始化中将SendTest_DstAddr.addrMode 设置为(afAddrMode_t)Addr16Bit;
srcEP:发送的endpoint的Endpoint描述的指针
cID:Cluster ID,实验中使用SendTest_CLUSTERID
len:发送数据包长度,该长度不包括ZIGBEE包中的帧头和帧尾,只是用户数据的长度
buf:用户发送数据区指针
transID:传输序号指针,该序号将随发送的次数增加而增加
options:具体参数参照下表:
Radius:最大跳数,协议栈默认值为10,可以根据实际情况修改,见nwk.h文件中
// the default network radius set twice the value of <nwkMaxDepth>
#define DEF_NWK_RADIUS 10
终于写完了,呵呵呵有点辛苦但是有收获哦
TI高性能模拟业务部高级副总裁Art George指出:“CC2480 ZigBee处理器不仅显著简化了新型低功耗无线产品的设计工作,还使客户能够方便地在现有产品中添加无线功能,帮助他们在无需编写应用代码的情况下完成系统网络组件的设计工作。TI不断推出创新产品、软件与支持,帮助客户以更短开发时间与更低成本设计高级低功耗无线系统。”
Z-Accel是一套全面的解决方案,TI的Z-Stack软件ZigBee-2006协议栈可在ZigBee处理器上运行,而应用程序则能在外部MCU上运行。CC2480能够处理所有时序关键型与处理密集型ZigBee协议任务,而将应用MCU的资源占用空间释放出来用于满足其他应用要求。CC2480不仅能够通过SPI或UART接口与各种MCU通信,还能与TI的MPS430超低功耗MCU等器件相结合。
CC2480广泛适用于各种环境下的ZigBee无线网络系统,其中包括家庭与楼宇自动化、工业监控、资产跟踪、低功耗无线传感器网络、机顶盒、远程控制、自动读表以及医疗应用等。
该器件支持SimpleAPI,只需掌握10个API调用即可,其无线电性能出色,而且功耗很低,还能在空闲期间自动进入低功耗模式。
供货情况与封装
CC2480现已开始供货,可通过TI及其授权分销商进行定购,该器件采用7毫米x7毫米48引脚QFN封装。
利用全面的演示套件立即开始设计工作。eZ430-RF2480是一种基于USB的无线演示工具,提供了评估CC2480网络处理器与MSP430MCU必需的所有软硬件。
低功耗RF开发商网络
TI低功耗RF开发商网络能帮助客户找到最佳的合作伙伴,以协助提供所需要的硬件设计、模块、嵌入式软件、网关、调试工具等。低功耗RF开发商网络包括TI推荐的公司、RF顾问以及独立设计机构,能提供丰富的硬件模块产品与设计服务(更多详情,敬请访问:
http://focus.ti.com.cn/cn/analog ... QS=vOT+home_p_rf_if)。
ZigBee:无线控制,简化工作
对于能够荣升为ZigBee联盟促进者级别(Promoter level)的成员,TI倍感骄傲。该联盟是一个基于全球开放标准,倡导可靠的、低成本、低功耗无线网络监控产品的公司联盟。ZigBee联盟成员由全球技术供应商与原始设备制造商组成。联盟会员资格面向所有企业开放。如欲了解更多信息,敬请访问: www.zigbee.org。
因为用现在这模块SerialApp没做成功,上电后按键没反应……两块无线龙小板子已经买来N年了.
自己想在SampleApp例子基础上修改实现串口透明传输:
串口调试助手1<————>模块1 <-----OTA-----> 模块2<————>串口调试助手2
程序修改主要如下:
****************************************************************************************
****************************************************************************************
1、
宏定义事件 #define UART_RX_CB_EVT 0x0002 (SampleApp.h)
全局变量声明: (SPIMgr.h)
extern uint8 rxlen; //接收数据长度
extern uint8* databuf; //接收数据指针
****************************************************************************************
****************************************************************************************
2、
串口回调函数rxCB: (SPIMgr.c)
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
uartConfig.callBackFunc = rxCB;
//uartConfig.callBackFunc = SPIMgr_ProcessZToolData; //回调函数
****************************************************************************************
****************************************************************************************
3、十六进制转字符函数 (SampleApp.c) 这两个函数由青竹提供.
uint8 hextoword1(uint8 t )
{
uint8 abc;
uint8 cba;
uint8 xx1;
abc=t;
cba=0xf0;
abc=(abc&cba)>>4;
if(abc<10)
{
xx1=abc+48;
}
else
{
xx1=abc+55;
}
return xx1;
}
uint8 hextoword2(uint8 t)
{
uint8 abc;
uint8 cba;
uint8 xx2;
abc=t;
cba=0x0f;
abc=abc&cba;
if(abc<10)
{
xx2=abc+48;
}
else
{
xx2=abc+55;
}
return xx2;
}
****************************************************************************************
****************************************************************************************
4、定义串口回调函数rxCB() (SPIMgr.c)
static void rxCB( uint8 port, uint8 event )
{
// uint8 rxlen; //接收数据长度
// uint8* dataybuf;//接收数据块指针
extern uint8 SampleApp_TaskID;
uint16 short_ddr;
uint8 short_ddr_H;
uint8 short_ddr_L;
// uint8 *pointer1;
// uint8 word_buffer[8];
short_ddr=NLME_GetShortAddr();
short_ddr_H=(uint8)((short_ddr&0xff00)>>8);
short_ddr_L=(uint8)short_ddr;
rxlen=Hal_UART_RxBufLen(SPI_MGR_DEFAULT_PORT); //接收缓冲区数据长度,字节为单位
databuf=osal_mem_alloc(rxlen+1+2); //多分配3字节,分配如下
databuf[0]=rxlen; //一字节存放数据长度
databuf[1]=short_ddr_H; //一字节存放源地址高8位
databuf[2]=short_ddr_L; //一字节存放源地址低8位
//databuf[rxlen+1]='\n'; //一字节存放换行符
HalUARTRead ( SPI_MGR_DEFAULT_PORT, databuf+3, rxlen); //读接收缓冲区数据到内存databuf+3
/* 回显数据(测试用)
word_buffer[0]='l';
word_buffer[1]='e';
word_buffer[2]='n';
word_buffer[3]=':';
word_buffer[4]=databuf[0]/100+48;
word_buffer[5]=(databuf[0]%100)/10+48;
word_buffer[6]=databuf[0]%10+48;
word_buffer[7]='\n';
pointer1=word_buffer;
// HalUARTWrite()写入串口正确说明数据已经正确地存储在databuf中!
HalUARTWrite ( SPI_MGR_DEFAULT_PORT, pointer1, 8 );
HalUARTWrite ( SPI_MGR_DEFAULT_PORT, databuf+1, rxlen+1 );//把数据送串口输出
*/
if(!rxlen)
osal_mem_free( databuf ); //释放内存
osal_set_event(SampleApp_TaskID,UART_RX_CB_EVT);
// rxCB_to_SampleApp( databuf, rxlen );
}
****************************************************************************************
****************************************************************************************
5、添加:事件处理函数 (SampleApp.c)
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
afIncomingMSGPacket_t *MSGpkt;
// 显示网络地址变量
uint16 short_ddr;
uint8 yy1;
uint8 yy2;
uint8 str_1[ ]="my short address is:";
#if defined(ZDO_COORDINATOR)
uint8 str_2[ ]="build the network successfully";
#else
uint8 str_2[ ]="join the network successfully ";
#endif
uint8 str_3[ ]={'\n'};
uint8 shortaddr[7];
uint8 *pointer1;
uint8 *pointer2;
uint8 *pointer3;
uint8 *pointer4;
…………(省略)
/*1、接收串口信息在SampleApp_MessageMSGCB()上进行修改*/
case AF_INCOMING_MSG_CMD:
SampleApp_MessageMSGCB( MSGpkt );
break;
/*2、设备建网/入网成功则显示本地网络地址*/
case ZDO_STATE_CHANGE:
SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
if ( (SampleApp_NwkState == DEV_ZB_COORD)
|| (SampleApp_NwkState == DEV_ROUTER)
|| (SampleApp_NwkState == DEV_END_DEVICE) )
{
//显示本地网络地址
short_ddr=NLME_GetShortAddr();
yy1=(uint8)((short_ddr&0xff00)>>8);
yy2=(uint8)short_ddr;
shortaddr[0]=48;
shortaddr[1]=120;
shortaddr[2]=hextoword1(yy1);
shortaddr[3]=hextoword2(yy1);
shortaddr[4]=hextoword1(yy2);
shortaddr[5]=hextoword2(yy2);
shortaddr[6]='\n';
pointer1=&shortaddr[0];
pointer2=&str_1[0];
pointer3=&str_2[0];
pointer4=&str_3[0];
HalUARTWrite(0,pointer4,1);
HalUARTWrite(0,pointer3,29);
HalUARTWrite(0,pointer4,1);
HalUARTWrite(0,pointer2,20);
HalUARTWrite(0,pointer1,7);
HalUARTWrite(0,pointer4,1);
//***************************************
}
else
{
// Device is no longer in the network
}
break;
…………(省略)
/*3、对接收的串口数据进行处理*/
if ( events & UART_RX_CB_EVT ) //串口数据处理
{
SampleApp_SPI_SendData( databuf, rxlen+1+2 );
return (events ^ UART_RX_CB_EVT);
}
}
****************************************************************************************
****************************************************************************************
6、定义AF层数据处理函数SampleApp_MessageMSGCB() (SampleApp.c)
默认采用的簇ID为SAMPLEAPP_PERIODIC_CLUSTERID
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
uint16 flashTime;
uint16 short_ddr;
uint8 *pointer1;
uint8 *pointer2;
uint8 *pointer3;
uint8 *pointer4;
uint8 *pointer5;
uint8 *pointer6;
uint8 *pointer7;
uint8 *pointer8;
uint8 *pointer9;
uint8 str_1[ ]="Source address:";
uint8 str_2[ ]="Destination address:";
uint8 str_3[ ]="Data length:";
uint8 str_4[ ]="Data:";
uint8 str_5[ ]={'\n'};
pointer1=&str_1[0];
pointer2=&str_2[0];
pointer3=&str_3[0];
pointer4=&str_4[0];
pointer9=&str_5[0];
uint8 Src_short_ddr_H;
uint8 Src_short_ddr_L;
uint8 Des_short_ddr_H;
uint8 Des_short_ddr_L;
uint8 word_buffer[4];
uint8 Src_shortaddr[7];
uint8 Des_shortaddr[7];
switch ( pkt->clusterId ) //判断簇ID
{
case SAMPLEAPP_PERIODIC_CLUSTERID:
/*####################################################*/
// flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
// HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
pointer5=&(pkt->cmd.Data[3]); //pointer5:具体数据首地址
word_buffer[0]=(pkt->cmd.Data[0])/100+48;
word_buffer[1]=((pkt->cmd.Data[0])%100)/10+48;
word_buffer[2]=(pkt->cmd.Data[0])%10+48;
word_buffer[3]='\n';
pointer6=word_buffer; //pointer6:数据长度
//----------------
Src_short_ddr_H=pkt->cmd.Data[1];
Src_short_ddr_L=pkt->cmd.Data[2];
Src_shortaddr[0]=48;
Src_shortaddr[1]=120;
Src_shortaddr[2]=hextoword1(Src_short_ddr_H);
Src_shortaddr[3]=hextoword2(Src_short_ddr_H);
Src_shortaddr[4]=hextoword1(Src_short_ddr_L);
Src_shortaddr[5]=hextoword2(Src_short_ddr_L);
Src_shortaddr[6]='\n';
pointer7=&Src_shortaddr[0]; //pointer7:源地址
//----------------
short_ddr=NLME_GetShortAddr();
Des_short_ddr_H=(uint8)((short_ddr&0xff00)>>8);
Des_short_ddr_L=(uint8)short_ddr;
Des_shortaddr[0]=48;
Des_shortaddr[1]=120;
Des_shortaddr[2]=hextoword1(Des_short_ddr_H);
Des_shortaddr[3]=hextoword2(Des_short_ddr_H);
Des_shortaddr[4]=hextoword1(Des_short_ddr_L);
Des_shortaddr[5]=hextoword2(Des_short_ddr_L);
Des_shortaddr[6]='\n';
pointer8=&Des_shortaddr[0]; //pointer8:目的地址
//----------------
HalUARTWrite ( 0, pointer1, 15 ); //源地址
HalUARTWrite ( 0, pointer7, 7 );
HalUARTWrite ( 0, pointer2, 20 ); //目的地址
HalUARTWrite ( 0, pointer8, 7 );
HalUARTWrite ( 0, pointer3, 12 ); //数据长度
HalUARTWrite ( 0, pointer6, 4 );
HalUARTWrite ( 0, pointer4, 5 ); //具体数据
HalUARTWrite ( 0, pointer5, pkt->cmd.Data[0] ); //pkt->cmd.Data[0]=rxlen,为原始长度
HalUARTWrite ( 0, pointer9, 1 ); //换行符
HalUARTWrite ( 0, pointer9, 1 );
//pointer1=&(pkt->cmd.Data[3]);
//HalUARTWrite ( 0, pointer1, 6 );
/*####################################################*/
break;
case SAMPLEAPP_FLASH_CLUSTERID://flash
flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
break;
}
}
****************************************************************************************
****************************************************************************************
我只有两个节点,所以这里采用最简单的单点传送方式.下载协调器程序时目标地址改为0x796F,下载终端程序时目标地址改为0x0000.默认ClusterID为SAMPLEAPP_PERIODIC_CLUSTERID.
void SampleApp_SPI_SendData( uint8 *buf, uint8 len )
{
SampleApp_SPI_SendData_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
SampleApp_SPI_SendData_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_SPI_SendData_DstAddr.addr.shortAddr = 0x796F; //0x796F;0x0000
// SampleApp_SendPeriodicMessage(); //测试用
if ( AF_DataRequest( &SampleApp_SPI_SendData_DstAddr,
(endPointDesc_t *)&SampleApp_epDesc,
SAMPLEAPP_PERIODIC_CLUSTERID,
len, buf,
&SampleApp_TransID,
0,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
osal_mem_free( databuf ); //必须释放内存,不然造成溢出!
}
else
{
osal_mem_free( databuf );
}
}
****************************************************************************************
****************************************************************************************
编译情况:
通信结果1:(关于开头的乱码说明参考后篇记录)
通信结果2:
最大数据只能达到52字节:(但在修改程序过程中,达到过83字节;等修改完整后只有52字节了):
测试 SampleApp_SPI_SendData()中的osal_mem_free( databuf ).如果不把databuf释放,则数据只能发送几次就停止.下面是添加osal_mem_free( databuf )后两边串口自动发送,测试时间>5分钟,收发一直保持稳定.
实物连接:
****************************************************************************************
****************************************************************************************
说明:
1、本文为个人学习笔记,程序均由个人编写(除十六进制转字符函数由青竹提供外).仅供参考,随时更新.
2、欢迎交流,转载请注明出处,谢谢!
这是本人看到的转载!!!
Zigbee2006, 2007, pro各个版本的区别
ZigBee是ZigBee联盟建立的技术标准,它是一种工作在900MHZ和2.4GHZ频段的新兴无线网络技术,具有中等通讯距离(10米到数百米),比较灵活经济的通讯速率(40Kbps到250Kbps),并且有星状,网状(MESH),树状等多种网络拓扑,低的功耗等特点,所以在当今无线通讯技术和无线网络技术领域中占有比较重要的地位。
第一个ZigBee协议栈规范于2004年12月正式生效,称为ZigBee 1.0或ZigBee 2004。
第二个ZigBee协议栈规范于2006年12月发布,称为ZigBee 2006规范,主要是用“群组库(cluster library)”替换了ZigBee 2004中的MSG/KVP结构。最为重要的新的ZigBee 2006协议栈将不兼容原来的ZigBee 2004技术规范,对于已经投入ZigBee 2004的厂商而言,这是一个大悲剧。例如Jennic公司将ZigBee2004协议栈固化在ROM中(JN5121/JN5139)。将无法和ZigBee 2006以后的协议栈兼容。ZigBee 2006协议栈,将是ZigBee兼容的一个战略分水岭,从这里开始,ZigBee将实现完全向后兼容性。
2007年10月发布了ZigBee 2007规范,ZigBee 2007规范定于了两套高级的功能指令集(feature set):分别是ZigBee功能命令集和ZigBee Pro功能命令集。(ZigBee 2004和2006都不兼容这两套新的命令集)。ZigBee 2007包含两个协议栈模板(profile),一个是ZigBee协议栈模板(Stack Profile 1),它是2006年发布的,目标是消费电子产品和灯光商业应用环境,设计简单,使用在少于300个节点的网络中。另一个是ZigBee Pro协议栈模板 (Stack Profile 2),它是在2007年发布,目标是商业和工业环境,支持大型网络,1000个以上网络节点,相应更好的安全性。ZigBee Pro提供了更多的特性,比如:多播、多对一路由和SKKE(Symmetric-key key establishment)高安全,但ZigBee(协议栈模板1)在内存和flash中提供了一个比较小的区域。两者都提供了全网状网络与所有的ZigBee应用模板工作。
ZigBee 2007 是向后完全兼容ZigBee 2006设备。ZigBee 2007设备可以加入一个ZigBee 2006网络,并能再ZigBee 2006网络中运行,反之亦然。
由于路由选择不同,ZigBee Pro设备必须变成非路由ZigBee End-Devices(ZEDs)设备才可加入ZigBee 2006或ZigBee 2007网络。同样ZigBee 2006或ZigBee 2007设备必须变成ZEDs才可加入ZigBee Pro网络。在这些设备上的应用程序工作是相同的,它们不管在这些设备上的协议栈模板。
下面的图表从高层次进行比较,列出2004、2006及2007/PRO ZigBee规范之间的异同。
比较图:
2004 | 2006 | 2007 | PRO | ||
Interference avoidance 避免干扰 | Support for ongoing interference detection under operational conditions and wholesale adoption of a new operating RF channel and/or Network ID. 在可操作条件下一个新的RF信道或/和网络ID被大量采用时,支持持续干扰检测。 | √ | √ | ||
Automated/distributed address management 自动/分配地址管理 | Device addresses automatically assigned using a hierarchical, distributed scheme. 使用分层、分布式的方案自动分配设备地址。 | √ | √ | √ | |
Device addresses automatically assigned using a stochastic scheme.使用随机方案自动为设备分配地址。 | √ | ||||
Group addressing 组寻址 | Devices can be assigned to groups, and whole groups can be addressed with a single frame; thereby reducing network traffic for packets destined for groups.将设备分配到组,组中的各设备可以接收同一个帧,这样送往组的数据包减少,从而减少了网络流量。 | √ | √ | √ | |
Centralized data collection 集中式数据收集 | Many-to-one routing allows the whole network to discover the aggregator in one pass.多对一路由允许整个网络在一个通道上发现汇集器。 | √ | |||
Source routing allows the aggregator to respond to all senders in an economical manner. 源路由允许汇集器以一种经济的方式对所有发件人回应。 | √ | ||||
Security 安全 | Trust Center can run on any device in the network.信托中心可以在网络中的任何设备上运行。 | √ | |||
"High Security" mode available, which is selectable by Trust Center policy, and requires Application Layer Link keys; peer-entity authentication; and peer-to-peer key establishment using Master Keys.可用“高安全”模式,它是信托中心可选择的策略,并且需要应用层链接键;身份验证对等实体;使用主键建立端到端的键。 | √ | ||||
Network scalability网络的可扩展性 | Network scales up to the limits of the addressing algorithm. Typically, networks with tens to hundreds of devices are supported.网络的规模受寻址算法的限制。通常,网络支持几十甚至几百个设备。 | √ | √ | √ | |
An addressing algorithm that relaxes the limits on network size. Networks with hundreds to thousands of devices are supported. 放松网络规模限制的寻址算法,可支持成百成千的设备。 | √ | ||||
Message size 消息尺寸 | < 100 bytes. Exact size depends on services employed, such as security.小于100B。确切的大小取决于服务,比如安全的服务。 | √ | √ | ||
Large messages, up to the buffer capacity of the sending and receiving devices, are supported using Fragmentation and Reassembly.应用分割和重组可支持大量消息(大到发送设备和接收设备的缓冲区容量)的收发。 | √ | √ | |||
Standardized commissioning 标准化调试 | Standardized startup procedure and attributes support the use of commissioning tools in a multi-vendor environment.标准化的启动过程和属性支持调试工具在多厂商环境中的使用。 | √ | √ | √ | |
Robust mesh networking 健壮的mesh网络 | Every device keeps track of its "neighborhood"; thereby further improving reliability and robustness. 每个设备保持跟踪它的邻居,从而进一步提高了可靠性和健壮性。 | √ | |||
Cluster Library support 簇库的支持 | The ZigBee Cluster Library, as an adjunct to the stack, standardizes application behavior across profiles and provides an invaluable resource for profile developers. 作为附属物加到堆栈中的 ZigBee簇库,通过profile标准化应用程序行为,并为profile开发人员提供一个无价的资源。 | √ | √ | √ |
各ZigBee版本功能比较
版本 | ZigBee04 | ZigBee06 | ZigBee07 | |
指令集 | 无 | 无 | ZigBee | ZigBee PRO |
无线射频标准 | 802.15.4 | 802.15.4 | 802.15.4 | 802.15.4 |
地址分配 | CSKIP | CSKIP | 随机 | |
拓扑 | 星状 | 树状、网状 | 树状、网状 | 网状 |
大网络 | 不支持 | 不支持 | 不支持 | 支持 |
自动跳频 | 是,3个信道 | 否 | 否 | 是 |
PAN ID冲突解决 | 支持 | 否 | 可选 | 支持 |
数据分割 | 支持 | 否 | 可选 | 可选 |
多对一路由 | 否 | 否 | 否 | 支持 |
高安全 | 支持 | 支持,1密钥 | 支持,1密钥 | 支持,多密钥 |
应用领域 | 消费电子(少量节点) | 住宅(300个节点以下) | 住宅(300个节点以下) | 商业(1000个节点以上) |
ZigBee2007两功能指令集比较
指令集 | ZigBee2007 | |
ZigBee | ZigBee PRO | |
地址分配 | 树状 | 随机 |
拓扑 | 树状、网状 | 网状 |
路由算法改进 | 无(同ZigBee06) | 多对一,一对多,源路由等 |
非对称链路处理 | 否 | 支持 |
自动跳频 | 可选 | 支持 |
PAN ID冲突解决 | 否 | 支持 |
安全 | 住宅级别 | 标准 |
应用层安全 | 可选 | 可选 |
高安全模式 | 否 | 可选 |
调试指令集 | 支持 | 支持 |
安全调试 | 否 | 支持 |
多集团广播 | 支持 | 支持 |
分割传输 | 否 | 支持 |
ZigBee Pro在应用层功能部分,新增分割传输(Fragmented Transmission)功能,就是当超过有效载荷资料(Payload)长度的限制时,可以使用分割组装(Fragment & Assemble)的功能传送长度较长的数据。这里的分割处理方式类似传输控制协议(TCP)的分割方式,先设定区块(Block)数,接着系统便会按照的Blocks数分段传送封包,直到完整的封包送完为止。
http://wjf88223.blog.163.com/blog/static/35168001201041294613991/
我从中把查询法和中断发的大概流程截取了下,看起来没那么复杂, 没我的什么见解哦。
1,查询法函数调用流程如下:
HalKeyConfig()配置一定时器为轮询按键作准备——>时间一到触发系统任务事件调用Hal_ProcessEvent()—— >调用HalKeyPoll()得到按键值——>调用OnBoard_KeyCallback()进一步处理——调用OnBoard_SendKeys()构造消息包,准备触发应用按键事件【注意这个应用之前必须通过RegisterForKeys()注册接收按键事件的任务ID】——>调用osal_msg_send()向系统发送消息——>调用osal_set_event()设置事件发生标志——>调用SampleApp_ProcessEvent()处理事件——>最终调用SampleApp_HandleKeys()处理具体按键事件
2,中断法函数调用流程如下:
HalKeyConfig()进行按键中断配置——>按键引起中断进入中断函数HAL_ISR_FUNCTION()(该函数在hal_key.c中)——>调用halProcessKeyInterrupt()对按键中断进行下一步处理:清除中断标志,启动一定时器——>时间一到触发系统任务事件调用Hal_ProcessEvent()——> 调用HalKeyPoll()得到按键值——>调用OnBoard_KeyCallback()进一步处理——>调用OnBoard_SendKeys()构造消息包,准备触发应用按键事件【注意这个应用之前必须通过RegisterForKeys()注册接收按键事件的任务ID】——>调用osal_msg_send()向系统发送消息——>调用osal_set_event()设置事件发生标志——>调用SampleApp_ProcessEvent()处理事件——>最终调用SampleApp_HandleKeys()处理具体按键事件
3,两种方法相似之处:
都是经过了两次触发系统事件(第一次通过osal_start_timerEx()启动一个软定时器触发,第二次通过osal_msg_send()发送系统消息触发),分别调用相应任务事件处理函数,第一次是HAL层的Hal_ProcessEvent()来查询按键得到键值,一系列处理,第二次是APP层的SampleApp_ProcessEvent()把传送过来的按键事件进行最终处理
osal_start_timerEx()------触发------> Hal_ProcessEvent()
osal_msg_send()--------触发--------->SampleApp_ProcessEvent()
4,两种方法不同之处:
从上面对按键查询法和中断法的总结,可以看到中断法就比查询法大致多出两步:进入中断函数HAL_ISR_FUNCTION()与调用调用halProcessKeyInterrupt(),后面都是开始开启一软定时器触发相同事件HAL_KEY_EVENT,然后……………………。
5,自己添加应用:
如果硬件配置按协议栈默认来配置,那基本只要完成这两件事情:(1)、在自己的应用中注册按键:RegisterForKeys(MyAPP_TaskID),这样用户应用就得接收到所有按键事件(2)、在自己的应用任务事件处理函数中配置具体处理函数:比如SampleApp中按SW1,发送闪烁信息给组1,按SW2,进/退组1.(该操作是在SampleApp_HandleKeys()函数中)
afStatus_t ZDP_PowerDescMsg( byte TranSeq, zAddrType_t *dstAddr, byte Status,
int16 nwkAddr, NodePowerDescriptorFormat_t *pPowerDesc,
byte SecuritySuite );
回应电源描述请求。
Status - SUCCESS 0
DEVICE_NOT_FOUND 1
pPowerDesc -指向电源描述的指针(定义在AF.h中)
afStatus_t ZDP_SimpleDescReq( zAddrType_t *dstAddr, uint16 nwkAddr, byte epIntf, byte SecuritySuite );
构建和发送简单描述请求。
epIntf -希望的应用终端/接口
afStatus_t ZDP_SimpleDescRsp( byte TranSeq, zAddrType_t *dstAddr,
byte Status, SimpleDescriptionFormat_t *pSimpleDesc,
byte SecuritySuite );
回应简单描述请求。
Status
SUCCESS 0
INVALID_EP 1
NOT_ACTIVE 2
DEVICE_NOT_FOUND 3
afStatus_t ZDP_ComplexDescReq( zAddrType_t *dstAddr, uint16 nwkAddr, byte SecuritySuite );
构建和发送复杂描述请求。实际是调用宏ZDP_NWKAddrOfInterestReq()
zigbee设备以描述项数据结构刻画自己,包含在描述项中的具体数据在描述中定义,有5种描述:节点,电源,简化,复杂和用户。
afStatus_t ZDP_ActiveEPIFReq( zAddrType_t *dstAddr, uint16 NWKAddrOfInterest,byte SecuritySuite );
构建和发送活动终端/接口请求,实际是调用宏ZDP_NWKAddrOfInterestReq(),用来请求远端设备上所有活动的终端/接口
NWKAddrOfInterest - 搜寻的16位短地址
afStatus_t ZDP_ActiveEPIFRsp( byte TranSeq, zAddrType_t *dstAddr,
byte Status, uint16 nwkAddr, byte Count, byte *pEPIntfList,
byte SecuritySuite );
回应发送的活动终端/接口请求,实际是调用宏ZDP_EPIFRsp()
Status
SUCCESS 0
DEVICE_NOT_FOUND 1
Count – pEPIntfList中活动终端(endpoint)/接口数目
pEPIntfList – 包含器件上终端(endpoint)/接口的数组
afStatus_t ZDP_MatchDescReq( zAddrType_t *dstAddr, uint16 nwkAddr, uint16 ProfileID,
byte NumInClusters, byte *InClusterList,
byte NumOutClusters, byte *OutClusterList,
byte SecuritySuite );
构建并发送匹配描述请求,用来搜寻符合应用列表中某些输入输出簇得器件/接口
ProfileID - cluster ID相关的ProfileID
NumInClusters - 输入簇中的cluster ID数目
InClusterList - 输入cluster IDs的数组
afStatus_t ZDP_MatchDescRsp( byte TranSeq, zAddrType_t *dstAddr, byte Status,
uint16 nwkAddr, byte Count, byte *pEPIntfList, byte SecuritySuite );
Status - SUCCESS 0
DEVICE_NOT_FOUND 1
Count – pEPIntfList中活动终端(endpoint)/接口数目
pEPIntfList – 包含器件上终端(endpoint)/接口的数组
afStatus_t ZDP_UserDescSet( zAddrType_t *dstAddr,uint16 nwkAddr,
UserDescriptorFormat_t *UserDescriptor,
byte SecurityEnable );
构建并发送User_Desc_set消息来设置远端设备的用户描述,这个请求单播到包含有发现信息的远端设备。远端设备需要定义NV_RESTORE来使能整个函数。
UserDescriptor –配制的用户描述,包含最多16个字符的ASCII字符串,若不足16个字符,则用空字符(0x20)填充到16个
afStatus_t ZDP_UserDescConf( byte TranSeq, zAddrType_t *dstAddr, byte Status, byte SecurityEnable );
调用这个函数来回应User_Desc_Conf
Status - SUCCESS 0x00
INV_REQUESTTYPE 0x80
DEVICE_NOT_FOUND 0x81
NOT_SUPPORTED 0x84
afStatus_t ZDP_UserDescReq( zAddrType_t *dstAddr, uint16 nwkAddr, byte SecurityEnable );
构建并发送User_Desc_Req
ZStatus_t ZDP_UserDescRsp( byte TransSeq, zAddrType_t *dstAddr,
uint16 nwkAddrOfInterest, UserDescriptorFormat_t *userDesc,
byte SecurityEnable );
userDesc -本地设备的用户描述
afStatus_t ZDP_EndDeviceAnnce( uint16 nwkAddr, byte *IEEEAddr,byte capabilities, byte SecurityEnable );
为ZigBee节点设备构建并发送End_Device_annce命令,通知其他ZigBee设备,此设备已加入或者已重新加入网络。此命令包含节点设备的新16位网络地址和64位IEEE地址,即以节点设备的功能。此消息已广播式发送。
afStatus_t ZDP_ServerDiscReq( uint16 serverMask, byte SecurityEnable );
构建并发送包含16位服务器掩码的System_Server_Discovery_req请求消息,以发现特殊系统服务器的位置或者服务器掩码指示的服务器。消息包含RxOnWhenIdle,以广播式发送。
ZStatus_t ZDP_ServerDiscRsp( byte transID, zAddrType_t *dstAddr, byte status,
uint16 aoi, uint16 serverMask, byte SecurityEnable );