在ZigBee网络中进行数据通信主要有三种类型:广播(Broadcast)、单播(Unicast)和组播(Multicast)。
广播描述的是一个节点发送的数据包,网络中的所有节点都可以收到。这类似于开会时,领导讲话,每个与会者都可以听到。如图所示:
单播描述的是网络中两个节点之间进行数据包的收发过程。这就类似于任意两个与会者之间进行的讨论。如图所示:
组播,又称为多播,描述的是一个节点发送的数据包,只有和该节点属于同一组的节点才能收到该数据包。这类似于领导开完会后,各个小组进行自由讨论,只有本小组的成员才能听到相关的讨论内容,不属于该小组的成员不需要接受相关信息。如图所示:
那么,ZigBee协议栈是如何实现上述通信方式的呢?简单来说,ZigBee协议栈将数据通信过程高度抽象,使用一个函数完成数据的发送,以不同的参数来选择数据发送方式(广播、组播还是单播)。该函数如下:
afStatus_t AF_DataRequest( afAddrType_t *dstAddr,
endPointDesc_t *srcEP,
uint16 cID,
uint16 len,
uint8 *buf,
uint8 *transID,
uint8 options,
uint8 radius );
函数的第一个参数是一个指向afAddrType_t类型的结构体的指针,该结构体的定义如下:
typedef struct
{
union
{
uint16 shortAddr;
ZLongAddr_t extAddr;
} addr;
afAddrMode_t addrMode; // 发送方式
uint8 endPoint;
uint16 panId; // used for the INTER_PAN feature
}afAddrType_t;
在afAddrType_t结构体中,有一个表示地址模式的参数类型为afAddrMode_t,它是一个枚举类型,定义为:
typedef enum
{
afAddrNotPresent = AddrNotPresent,
afAddr16Bit = Addr16Bit, // 单播方式发送数据
afAddr64Bit = Addr64Bit,
afAddrGroup = AddrGroup, // 组播方式发送数据
afAddrBroadcast = AddrBroadcast // 广播方式发送数据
} afAddrMode_t;
可见:
1、单播
通过上面所讲原理可知,不同的通信方式只需要改变addrMode就可以了。下面打开SampleApp.c 文件,仿照协议栈已经定义好的广播和组播,定义单播模式,如下:
接下来,在初始化函数中对SampleApp_Point_DstAddr的一些参数进行配置,参考广播或者组播就可以了,如下:
最后一句配置单播发送的对象时0x0000,也就是协调器的地址,节点与协调器单播通信。
接下来需要定义单播通信的发送函数,通过发送函数发送This is a unicast,依然参考广播和组播通信的发送函数,如下:
void SampleApp_SendPointMessage( void )
{
char buffer[] = "This is a unicast\n";
if ( AF_DataRequest( &SampleApp_Point_DstAddr, &SampleApp_epDesc,
SAMPLEAPP_POINT_CLUSTERID, // 自己定义的单播ID
18,
buffer,
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
}
else
{
// Error occurred in request to send.
}
}
相对于广播发送来说,我们只需要将发送函数的第一个参数改成新定义的SampleApp_Point_DstAddr就可以了,簇ID为新定义的一个宏,如下:
函数定义完成之后,还需要在文件上部进行声明,如下:
下面我们把周期发送部分的广播发送改为单播发送,发送部分就完成了,如下:
在接收方面,我们只需要在无线接收中添加对单播簇ID的判断与处理就可以了,如下:
由于协调器不允许给自己单播,故周期性单播初始化时协调器不能初始化。如下:
将终端、路由以及协调器程序下载进开发板中,并通过串口连接在PC上,可以看到只有 协调器 在一个周期内收到信息。也就是说路由器和终端均与地址为 0x0000 (协调器)的设备通信,不与其他设备通信,实现单播传输。
2、广播
广播的定义,协议栈已经全部定义好了,我们只需要直接调用就可以了,可以看到,设备通信地址配置的为0xFFFF。广播地址主要有三种类型:
接着看广播的发送函数,如下:
发送函数的第一个参数为广播方式,簇ID也为广播簇ID。周期发送时,调用了广播发送函数,将设备类型发送了出去。如下:
无线接收部分,判断为广播簇ID时,将设备地址打印出去,如下:
3、组播
组播的相关定义协议栈也已经写好了,我们在使用的时候根据需要进行修改就可以了,如下:
组播通信的组信息在哪里呢?协议栈也已经定义好了,如下:
可以看到,组的类型为结构体aps_Group_t,来看一下aps_Group_t的定义,如下:
每个定义的组有一个特定的ID,然后是组名,组名存放在name数组中。name数组的第一个元素是组名的长度,从第二个元素开始存放真正的组名字符串。
在初始化函数SampleApp_Init()中,将组信息进行了添加,默认情况下,所有设备都从组1开始。如下:
为了区分两个路由设备以及协调器,我们定义一个全局变量作为设备号来使用,如果设置为0,就为协调器,如果设置为1,就为路由设备1,如果设置为2,就为路由设备2,如下:
接下来,将组播通信的发送函数修改为如果设备号为0就发送this is a GroupMSG0,如果为1就发送this is a GroupMSG1,如果为2就this is a GroupMSG2,终端消息ID为组播的簇ID,如下:
void SampleApp_SendFlashMessage( uint16 flashTime )
{
(void) flashTime;
char *buffer = NULL;
char buffer0[] = "this is a GroupMSG0\n";
char buffer1[] = "this is a GroupMSG1\n";
char buffer2[] = "this is a GroupMSG2\n";
if(device == 0)
{
buffer = buffer0;
}
else if(device == 1)
{
buffer = buffer1;
}
else if(device == 2)
{
buffer = buffer2;
}
if ( AF_DataRequest( &SampleApp_Flash_DstAddr, &SampleApp_epDesc,
SAMPLEAPP_FLASH_CLUSTERID,
20,
buffer,
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
}
else
{
// Error occurred in request to send.
}
}
然后我们将周期广播发送改为组播发送,这样就可以实现周期组播发送了,组播发送需要传参,所以传了一个0,如下:
在接收部分,我们只需要在无线接收中修改对组播簇ID的判断与处理就可以了,如下:
将修改 后的程序分别以 1个协调器、2个路由器的方式下载到 3个设备,把协调器和路由器1组号设置成 0x0001 ,路由器设备 2 组号设成 0x0002 。连接串口,可以观察到只有组号为0x0001 的两个设备相互发送信息。如图:
最后,再补充一点知识,终端设备不参与组播,原因是 SampleAPP 例程中终端设备默认采用睡眠中断的工作方式,射频不是一直工作。这个在协议规范里面是有规定的,睡眠中断不接收组播信息,如果一定想要接收的话,只有将终端的接收机一直打开,这样就可以接收到了。
具体做法为:将 f8config.cfg 配 置 文 件 中 的 -RFD_RCVC_ALWAYS_ON=FALSE 改 为-RFD_RCVC_ALWAYS_ON=TRUE 就可以了!如图: