内容概要:
网络拓扑结构
功能要求
实现过程
网络拓扑结构:
ZigBee 网络支持星状、树(簇)状、网状三种网络拓补结构:
功能需求:(广播)
协调器周期性以广播的形式向终端节点发送数据”I am coordinator\n”
加入其网络的终端节点都会收到数据,终端节点分别单播给协调器”I am endpoint device 1 or 2\n”
实现过程:
按照套路,复制一份样板工程并且重命名。
仍然先按照串口实验把相关代码添加上。
#include "MT_UART.h"
#include "string.h"
MT_UartInit(); //串口初始化
//#define MT_UART_DEFAULT_OVERFLOW TRUE
#define MT_UART_DEFAULT_OVERFLOW FALSE
//#define MT_UART_DEFAULT_BAUDRATE HAL_UART_BR_38400
#define MT_UART_DEFAULT_BAUDRATE HAL_UART_BR_115200
在SampleApp_Init()里将SampleApp_Flash_DstAddr设置为单播模式,且地址设置为0x0000,此地址是协调器的地址。
// Broadcast to everyone广播相关设置
/*地址结构体 SampleApp_Periodic_DstAddr 作为协调器给终端发送数据的地址*/
SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;//选择广播模式
SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;//广播地址
// Setup for the flash command's destination address - Group 1
/*地址结构体 SampleApp_Flash_DstAddr 作为终端给协调器发送数据的地址*/
SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddr16Bit;//选择单播模式
SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_Flash_DstAddr.addr.shortAddr = 0x0000;//协调器的地址,单播网络中协调器的地址是0x0000
注释掉组播模式的相关设置:
当网络状态发生改变,触发SAMPLEAPP_SEND_PERIODIC_MSG_EVT事件,当检测到这个事件时,首先判断设备的逻辑类型:
1:如果是协调器则执行SampleApp_SendPeriodicMessage();
2:如果是终端则执行SampleApp_SendFlashMessage();
if(zgDeviceLogicalType == ZG_DEVICETYPE_COORDINATOR)//如果是协调器,则执行如下函数发送消息
{// Send the periodic message
SampleApp_SendPeriodicMessage();
}
else if(zgDeviceLogicalType == ZG_DEVICETYPE_ENDDEVICE)//如果是终端,则执行如下函数发送消息
{
SampleApp_SendFlashMessage( 0x0000 );//该函数的参数是一个16进制的数,因为自己改写该函数时没有用到该函数,所以可以任意填写一个16进制数
}
在SampleApp_SendPeriodicMessage()里,注意AF_DataRequest的第一个参数,必须是广播模式和广播地址,第三个参数设为SAMPLEAPP_PERIODIC_CLUSTERID。
广播发送:协调器发送数据给终端
void SampleApp_SendPeriodicMessage( void )//协调器发送数据
{
uint8 * buf = NULL;
buf = "I am coordinator\n";
if ( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc,
SAMPLEAPP_PERIODIC_CLUSTERID,
strlen(buf),//发送数据的长度
buf,//发送数据的地址
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
}
else
{
// Error occurred in request to send.
}
}
在SampleApp_SendPeriodicMessage(),注意AF_DataRequest的第一个参数,必须是单播模式和广播地址,第三个参数设为SAMPLEAPP_FLASH_CLUSTERID。
单播发送:终端发送数据给协调器
void SampleApp_SendFlashMessage( uint16 flashTime )
{
uint8* buf = "I am endpoint device 1\n";
if ( AF_DataRequest( &SampleApp_Flash_DstAddr, &SampleApp_epDesc,
SAMPLEAPP_FLASH_CLUSTERID,//注意看清楚发送的命令号
strlen(buf),
buf,
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
}
else
{
// Error occurred in request to send.
}
}
当有新的无线消息到达时,会执行SampleApp_MessageMSGCB(),首先还是对命令号进行判断:
如果是SAMPLEAPP_PERIODIC_CLUSTERID,打印出来的就是协调器发送的信息
如果是SAMPLEAPP_FLASH_CLUSTERID,打印出来的就是终端发送的信息
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
uint16 flashTime;
switch ( pkt->clusterId )
{
case SAMPLEAPP_PERIODIC_CLUSTERID: //协调器发送过来的数据
HalUARTWrite ( 0, pkt->cmd.Data, pkt->cmd.DataLength );//串口打印协调器发送过来的数据
break;
case SAMPLEAPP_FLASH_CLUSTERID://终端发送过来的数据
HalUARTWrite ( 0, pkt->cmd.Data, pkt->cmd.DataLength );//串口打印终端发送过来的数据
break;
}
}
编译和烧写:
协调器烧写
终端1烧写:
终端2烧写:
实验结果:
内容概要:
功能要求
实现过程
功能需求:
协调器创建网络,并加入一个组,并向组内成员组播数据”I am coordinator device \n”
终端1加入网络,并加入与协调器相同的组,收到协调器发送而来的数据
终端2加入网络,并加入另外一个组,不能收到协调器发来的数据
实现过程:
复制工程模板并重命名
加入串口通信代码。
#include "MT_UART.h"
#include "string.h"
MT_UartInit(); //串口初始化
HalUARTWrite ( 0, "uart0 is ok\n", strlen("uart0 is ok\n") );//向串口发送数据
将广播代码注释掉,选择组播代码,注意在发送数据时看清楚选择是组播的结构体变量。
// Setup for the flash command's destination address - Group 1
/*组播模式*/
SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddrGroup;
SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_Flash_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP;/*组播地址必须
与组的id一致,SAMPLEAPP_FLASH_GROUP:宏定义,表示0x0001*/
// By default, all devices start out in Group 1
/*组播的相关设置,组播必须有自己的id 和 组名,其中SampleApp_Group是组播的一个结构体,
下面有给出该结构体定义*/
SampleApp_Group.ID = 0x0001;//设置组ID,与组播地址是一致的
osal_memcpy( SampleApp_Group.name, "Group 1", 7 );//设置组名
aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );//使端口加入组中
//组播结构体的定义
typedef struct
{
uint16 ID;//组ID
uint8 name[APS_GROUP_NAME_LEN]; //组名,首元素是组名长度,后边是组名.
} aps_Group_t;
因为只需要协调器发送数据,所以(协议栈轮询中)判断网络状态时只需要判断协调器的状态即可。
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 );
}
在SampleApp_SendPeriodicMessage的AF_DataRequest里,第一个传参选择组播模式,地址为组ID。
void SampleApp_SendPeriodicMessage( void )
{
uint8 *buf = "I am coordinator device \n";
if ( AF_DataRequest( &SampleApp_Flash_DstAddr/*设置成组播的地址结构体*/, &SampleApp_epDesc,
SAMPLEAPP_PERIODIC_CLUSTERID,
strlen(buf),
buf,
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
}
else
{
// Error occurred in request to send.
}
}
终端在接收到新的消息时将消息通过串口打印到电脑上。
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
switch ( pkt->clusterId )
{
case SAMPLEAPP_PERIODIC_CLUSTERID://判断是否为协调器发过来的消息结构
HalUARTWrite ( 0, pkt->cmd.Data, pkt->cmd.DataLength );//向串口发送数据
break;
case SAMPLEAPP_FLASH_CLUSTERID:
break;
}
}
注意:在协议规范里有规定,休眠中断模式下不接收组播信息,SampleApp 例程中终端设备默认采用中断的工作方式,射频不是一直工作。如果一定想要接收到的话,只有将终端的接收机打开,这样就可以接收了,我们在f8wConfig.cfg 配置文件中152行将FALSE修改为TRUE即可。
将代码以协调器工程和终端工程进行编译并分别下载至zigbee硬件,这时它们是属于GROUP 1的。
烧写协调器:
烧写终端:
修改组ID=0x0002,组名为”GROUP 2”,以及目标地址为0x0002,以终端工程编译并下载至另一个zigbee硬件。
实现现象:
加入GROUP 1的终端能收到数据,加入GROUP 2的终端不能收到数据
内容概要:
功能要求
实现过程
功能需求:
协调器获取pc机通过串口发来的数据,并且广播给终端,终端收到数据后打印在串口调试工具
终端获取pc机通过串口发来的数据,并且单播给协调器,协调器收到数据后打印到串口调试器上
复制工程模板并重命名
加入串口通信代码.加入一个头文件MT.h这里有串口数据到达事件CMD_SERIAL_MSG的定义,并且注册串口处理任务。
#include "MT_UART.h"
#include "string.h"
#include "MT.h"
MT_UartInit(); //串口初始化
MT_UartRegisterTaskID( task_id );//注册串口任务,使串口产生事件
HalUARTWrite ( 0, "uart is ok\n", strlen("uart is ok\n") );//向串口发送数据
将两个地址结构体变量分别填充为广播模式和单播模式
// Broadcast to everyone
SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;//协调器设置为广播模式
SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;//协调器设置为广播地址
// Setup for the flash command's destination address - Group 1
SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddr16Bit;//终端设置为单播模式
SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_Flash_DstAddr.addr.shortAddr = 0x0000;//终端设置为协调器地址
// Fill out the endpoint description.端口描述符的相关设置,一般不需要修改
SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_epDesc.task_id = &SampleApp_TaskID;
SampleApp_epDesc.simpleDesc
= (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc;
SampleApp_epDesc.latencyReq = noLatencyReqs;
// By default, all devices start out in Group 1
/*关于组播的相关设置注释掉即可
SampleApp_Group.ID = 0x0001;
osal_memcpy( SampleApp_Group.name, "Group 1", 7 );
aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );*/
在事件轮训时,如果有串口数据到达,则调用一个自己封装的函数来处理串口数据,如:SerialData_Anlysis():
case CMD_SERIAL_MSG: //添加轮询事件,CMD_SERIAL_MSG:检测是否有串口数据到达
SerialData_Analysis((mtOSALSerialData_t*)MSGpkt);/*协议栈中并没有封装
串口数据到达时处理事件的函数,所以这里需要自己编写封装一个事件处理函数。
函数需要传入一个串口数据结构体(下面有给出该结构体的定义),而MSGpkt是
一个消息结构体,所以需要强制转换为mtOSALSerialData_t*类型*/
break;
串口数据的结构体定义(mt_uart.h文件中已经事先定义,无需我们定义,直接使用即可):
串口数据结构体:
typedef struct
{
osal_event_hdr_t hdr;//数据头
uint8 *msg;//msg指向的是数据长度,msg+1以后才是真正的数据
} mtOSALSerialData_t;
SerialData_Anlysis()里,先做设备的逻辑类型判断,如果是协调器,就以广播方式将串口数据发送出去,如果是终端,就把串口数据以单播方式发送给协调器
void SerialData_Analysis((mtOSALSerialData_t*)MSGpkt)
{
if(zgDeviceLogicalType == ZG_DEVICETYPE_COORDINATOR)//如果是协调器,则以广播的形式将串口接收的数据发送给终端
{
AF_DataRequest( &SampleApp_Periodic_DstAddr/*SampleApp_Periodic_DstAddr:宏定义,表示广播地址*/, &SampleApp_epDesc,
SAMPLEAPP_PERIODIC_CLUSTERID,//SAMPLEAPP_PERIODIC_CLUSTERID:命令号。发送数据和对方接收消息数据时的命令号要保持一致
*(MSGpkt->msg),
(MSGpkt->msg) + 1,
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS );
}
else if(zgDeviceLogicalType == ZG_DEVICETYPE_ENDDEVICE)//如果是终端,则以单播的形式将串口接收的数据发送给协调器
{
AF_DataRequest( &SampleApp_Flash_DstAddr/*SampleApp_Flash_DstAddr:宏定义,表示协调器的地址*/, &SampleApp_epDesc,
SAMPLEAPP_PERIODIC_CLUSTERID,//SAMPLEAPP_PERIODIC_CLUSTERID:命令号。发送数据和对方接收消息数据时的命令号要保持一致
*(MSGpkt->msg),
(MSGpkt->msg) + 1,
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS );
}
}
注意:这样的实现过程必须按照协议栈的格式要求来发送数据,格式不匹配,zigbee仍然识别不了pc机发过来的数据,所以需要对串口的回调函数做修改:
void MT_UartInit ()
{
halUARTCfg_t uartConfig;
/* Initialize APP ID */
App_TaskID = 0;
/* UART Configuration */
uartConfig.configured = TRUE;
uartConfig.baudRate = MT_UART_DEFAULT_BAUDRATE;
uartConfig.flowControl = MT_UART_DEFAULT_OVERFLOW;
uartConfig.flowControlThreshold = MT_UART_DEFAULT_THRESHOLD;
uartConfig.rx.maxBufSize = MT_UART_DEFAULT_MAX_RX_BUFF;
uartConfig.tx.maxBufSize = MT_UART_DEFAULT_MAX_TX_BUFF;
uartConfig.idleTimeout = MT_UART_DEFAULT_IDLE_TIMEOUT;
uartConfig.intEnable = TRUE;
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
uartConfig.callBackFunc = MT_UartProcessZToolData; /*调用一个回调函数,
才能真正触发CMD_SERIAL_MSG(是否有串口数据送达)事件,进而才能调用对应事
件处理函数。但是该回调函数要求发送过来的数据格式必须与协议栈定义的格式保持
一致,否则不会触发CMD_SERIAL_MSG(是否有串口数据送达)事件,所以我们需要
改写函数,使得该回调函数接收到任意格式的串口数据都能触发CMD_SERIAL_MSG
(是否有串口数据送达)事件。*/
#elif defined (ZAPP_P1) || defined (ZAPP_P2)
uartConfig.callBackFunc = MT_UartProcessZAppData;
#else
uartConfig.callBackFunc = NULL;
#endif
/* Start UART */
#if defined (MT_UART_DEFAULT_PORT)
HalUARTOpen (MT_UART_DEFAULT_PORT, &uartConfig);
#else
/* Silence IAR compiler warning */
(void)uartConfig;
#endif
/* Initialize for ZApp */
#if defined (ZAPP_P1) || defined (ZAPP_P2)
/* Default max bytes that ZAPP can take */
MT_UartMaxZAppBufLen = 1;
MT_UartZAppRxStatus = MT_UART_ZAPP_RX_READY;
#endif
}
void MT_UartProcessZToolData ( uint8 port, uint8 event )
{
uint8 ch, len = 0;
uint8 uartData[128];//定义一个数组暂存数据
uint8 i;
(void)event;//Intentionally unreferenced parameter
while(Hal_UART_RxBufLen(port))//只要检测到串口缓存区有数据
{
HalUARTRead (port,&ch,1);//就把数据一个一个的传给ch变量
uartData[len+1] = ch;//再把ch的数据传给数组
len ++;
}
if(len)
{
uartData[0] = len;//第一个元素呢设为数据长度
pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof(mtOSALSerialData_t ) + len + 1 );//为串口数据包分配内存
if (pMsg)
{
/* Fill up what we can */
pMsg->hdr.event = CMD_SERIAL_MSG;//设置串口数据到达事件
pMsg->msg = (uint8*)(pMsg+1);//把数据定位到结构体数据部分
for(i=0; i<=len; i++)
{
pMsg->msg[i] = uartData[i];//再把暂存区数据放入串口数据包中
}
osal_msg_send( App_TaskID, (byte *)pMsg );//将数据包发往消息队列
}
osal_msg_deallocate ((uint8 *)pMsg );//分起的内存用完后记得开除,以免内存溢出。
}
}
协议栈轮询,当有新的无线消息到达,则触发相应的事件处理函数,将接收到的消息数据通过串口打印到电脑上:
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
uint16 flashTime;
switch ( pkt->clusterId )
{
case SAMPLEAPP_PERIODIC_CLUSTERID:/*如果是这个命令号,则将接收到的数据通过串口打印出来。
因为对方使用AF_DataRequest函数发送数据时填写的命令号为SAMPLEAPP_PERIODIC_CLUSTERID,
故接收数据时也是这个命令号*/
HalUARTWrite ( 0, pkt->cmd.Data, pkt->cmd.DataLength );//向串口发送数据,打印到电脑上来。
break;
case SAMPLEAPP_FLASH_CLUSTERID:
break;
}
}
编译与烧写:
协调器:
终端:
实验现象:
内容概要:
地址分配机制
网络管理函数
地址分配机制:
Z-stack采用分布式分配机制,整个网络架构由3个值决定:
Lm:网络的最大深度(网络有几层)
Cm:每个父设备拥有的子设备数(子设备代表路由和终端)
Rm:每个父设备拥有的路由子设备数
d:父设备所处的网络深度
则父设备下的路由器子设备间的地址间隔:
Cskip(d) = 1+Cm*(Lm-d-1);当Rm=1
Cskip(d)=(1+Cm-Rm-Cm*( Rm)^(Lm-d-1))/(1-Rm). 当Rm!=1
父设备给路由器分配的网络地址:Achild =Aparent +(n-1)*Cskip(d)+1 //Aparent 表示父设备地址 Cskip(d)表示路由器之间的地址间隔 n代表的是第几个子设备(从1开始)
父设备给终端分配的网络地址:Achild = Aparent + Rm*Cskip(d)+ n
对于协调器来说:Cskip(d)= (1+Cm-Rm-Cm*( Rm)^(Lm-d-1)) /(1-Rm) = (1+5-3-5*3^(3-0-1))/(1-3) =21
路由地址=Aparent +(n-1)*Cskip(d)+1
路由1=0x0000+1=0x0001
路由2=0x0000+1+21=0x0016
路由3=0x0000+1+2*21=0x002b
对于协调器来说:Cskip(d)=21
终端地址=Aparent + Rm*Cskip(d)+ n
终端1=0x0000+3*21+1=0x0040
终端2=0x0000+3*21+2=0x0041
对于路由器1来说:Cskip(d)=(1+Cm-Rm-Cm*( Rm)^(Lm-d-1)) /(1-Rm) =(1+5-3-5*3^(3-1-1))/(1-3) =6
路由地址=Aparent +(n-1)*Cskip(d)+1
路由1=0x0001+1=0x0002
路由2=0x0001+1+6=0x0008
路由3=0x0001+1+2*6=0x000E
对于协调器来说:Cskip(d)=6
终端地址=Aparent + Rm*Cskip(d)+ n
终端1=0x0001+3*6+1=0x0014
终端2=0x0001+3*6+2=0x0015
网络管理函数:
在NLMEDE.h里有四个网络管理函数
返回指向本设备的MAC地址的指针(扩展地址)
byte *NLME_GetExtAddr( void );
返回本设备的网络地址(16位的短地址)
uint16 NLME_GetShortAddr( void );
返回父设备的网络地址函数
uint16 NLME_GetCoordShortAddr( void );
参数是指向父设备的MAC地址的指针
void NLME_GetCoordExtAddr( byte * buf);