大家好,我是皮皮猫吖!
每文一言:一个人的价值在于贡献了什么,而不是能得到什么!
本篇文章:
主要是在协议栈的基础之上,Zigbee模块通过单播收发字符串。
正文如下:
一、无线数据传输:
1)在学习单播之前,我们需要借助官方模板,添加简单的代码,做出通信基本单播收发实验,先对单播建立一个基础的认识。
然后,在官方代码基本实验基础上,了解单播中相关的概念。掌握单播数据通信原理,再结合自己的理解,自己动手做一个个性化实验,验证我们的理解。
二、模块之间发送数据的方式
1)单播
在Zigbee网络里,模块之间要进行通信,发射模块需要非常明确知道接收模块的网络地址,发送模块使用这个地址发送数据给接收模块,这种发送数据的方式叫做单播。
2)广播
广播可以理解为,发送模块发出数据,这个网络里的所有节点模块都可以拿到这个数据。这种发送数据的方式叫做广播。
3)组播
在Zigbee网络,模块可以分组来标记,发送模块如果发送的组号和网络里标记模块的组号相对应,那么组号相对应的这些模块就可以拿到发送模块发送的这些无线数据包。这种发送数据的方式叫做组播。
4)绑定
绑定是Zigbee一种基本通信方式,有源绑定。源绑定中发送模块必须要知道接收模块(被绑定模块)的网络地址或者MAC地址,接收方的接收端点。接受方的接收簇。
三、Zigbee模块的地址特点:
1)模块在入网的时候,父节点随机分配网络地址给子节点。
2) 协调器模块在网络里的地址永远是0x0000。
四、练习一:终端向协调器发送字符:
1)终端模块【发送模块】PPMApp.c文件:
在PPMApp.c文件中编写发送函数:
在PPMApp.c文件里的PPMApp_MY_SEND_MSG_EVT事件处理函数中,当按钮1按下的时候,相关处理代码:
char theMessageData[] = {8};
//按钮3按下
LS164_BYTE(3);
//配置目的地址
PPMApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
//设置接收的地址为(目的地址):0x0000
PPMApp_DstAddr.addr.shortAddr = 0x0000;
// Take the first endpoint, Can be changed to search through endpoints
PPMApp_DstAddr.endPoint = PPMApp_ENDPOINT;
//发送数据函数
AF_DataRequest( &PPMApp_DstAddr, &PPMApp_epDesc,
PPMApp_CLUSTERID,
1,//(byte)osal_strlen( theMessageData ) + 1,
(byte *)&theMessageData,
&PPMApp_TransID,
AF_DISCV_ROUTE, AF_DEFAULT_RADIUS );
//第四个参数:发送的数据有几个字节,这里只有一个字节
//每按下一下开关,LED灯进行亮灭操作
P1SEL &= 0xfe;
P1DIR |= 0x01;
P1_0 ^= 1;
2)协调器模块【接收模块】PPMApp.c文件:
void SDApp_MessageMSGCB( afIncomingMSGPacket_t *pkt );//函数中
//添加一行显示代码,显示接收到的数据(在Break之前添加代码)
LS164_BYTE(pkt->cmd.Data[0]); //添加这么一行代码,数码管就可以显示接收到的数据
3)模块接收的过程是:
当终端模块发送数据时候,协调器模块底层任务拿到这个无线数据,给我们应用层任务发送一个AF_INCOMING_MSG_CMD消息,根据消息在系统事件函数中,找到对应的消息,进行相关操作,把终端模块发送过来的数据拿出来在数码管上进行显示。
五、练习二:终端向协调器发送字符串
1)终端模块【发送模块】PPMApp.c文件:
在PPMApp.c文件里的PPMApp_MY_SEND_MSG_EVT 事件处理中按钮1按下的相关处理代码:
char theMessageData[] = "Hello lao da";
//按钮3按下
LS164_BYTE(3);
//配置目的地址
PPMApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
//接收的地址为(目的地址):0x0000
PPMApp_DstAddr.addr.shortAddr = 0x0000;
// Take the first endpoint, Can be changed to search through endpoints
PPMApp_DstAddr.endPoint = PPMApp_ENDPOINT;
//发送数据函数
AF_DataRequest( &PPMApp_DstAddr, &PPMApp_epDesc,
PPMApp_CLUSTERID,
(byte)osal_strlen( theMessageData ) + 1,//字符串的长度包括最后的'\0'
(byte *)&theMessageData,
&PPMApp_TransID,
AF_DISCV_ROUTE, AF_DEFAULT_RADIUS );
//第四个参数:发送的数据有几个字节,这里只有一个字节
//每按下一下开关,LED灯进行亮灭操作
P1SEL &= 0xfe;
P1DIR |= 0x01;
P1_0 ^= 1;
2)协调器模块【接收模块】PPMApp.c文件
因为要接收字符串,接收到的字符串需要进行显示,通过串口显示较为清晰,选择串口显示字符串。在协议栈的基础之上移植串口模块化文件。
在协议栈上添加并使用串口模块化文件过程如下:
① 首先,将UART.C、UART.h头文件添加到工程源文件目录下
② 然后,在ZMain.c文件的中导入#include “UART.h”;
③ 在ZMain的一般函数初始化下面,初始化串口:InitUart();
④ 在ZMain中找到HalDriverInit(),函数,进入该函数定义中,我们可以看到,系统会默认初始化串口:HalUARTInit();系统的这个初始化会和我们自定义的串口初始化会发生冲突。这个时候,我们需要对HalUARTInit()上方的HAL_UART进行重定义。【注意】进入HAL_UART的定义中,改为false,即可实现取消默认初始化的操作。
⑤ 在PPMApp.c上方添加#include “UART.h”,到这里,串口初始化算完成了。
//在PPMApp_MessageMSGCB函数中添加如下代码
Uart_Send_String(pkt->cmd.Data,pkt->cmd.DataLength);
六、终端节点向协调器节点发送数据过程分析
1)端点
① 端点是一个字节编号的,是数据收和发送的基本单元。在模块之间进行通信的时候,发送模块必须指定收发双方模块的网络地址和端点。
② 端点要使用必须要和模块里的某个任务挂钩定义(该端口,只开放给某个任务使用,其他任务不可使用):
每一个端点可以看成是一个1个字节数字编号的开有一扇门的房间,数据最终的目标是进入到无线数据包指定的目的地址的目标端点房间,而取无线数据这个相关的代码在任务事件处理函数里,TI协议栈有那么多的任务事件处理函数,所以必须要指定是哪个任务事件处理函数,来取这个无线数据包里面的有用数据。
③ 一个端点只能挂钩在一个任务上,而一个任务可以挂钩多个端点,且端点对所有的任务是共用的,定义一个少一个。(该端口服务于某个任务,该端口将不会为其他任务服务)
④ 如果一个端点可以挂钩在多个任务上,那么接收模块接到无线数据时候,这个时候同一个端点有多个任务事件处理函数要去处理,这样是不合理的;但是,一个任务上可以挂多个端点(6、7端点挂在应用层任务上),发送给协调器模块的6、7端点的数据都会进入到应用层任务事件处理函数里来,仅仅需要做个判断到底是投递到6房间还是7号房间就可以了。
2)簇【ClusterID】
簇就是相当于端点房间里面的人,是最终的接收目标。这东西是2个字节编号,在射频发送的时候,必须要指定接收模块的簇,发送模块不需要指定。
3)分析终端节点向协调器节点发送的数据包
① 终端向协调器模块发送数据包
- P.nbr.:第几个发送的帧【在这个抓包软件中】
- Time(us):从抓包工具抓包开始到现在多少us拿到这个帧
- Length:表示这个帧有多少个字节:40个字节
- Dest PAN:目的网络:0x99A3【协调器创建的网络PAN】
- Dest Address:目的地址:0x0000【协调器地址】
- Source Address:源地址(自己的地址):0x【终端节点的网络地址】
- APS Dest.Endpoint:目的端点:0x0A【需要去网络地址为0x0000的0x0A端点房间】
- APS Src.Endpoint:源端点(自己的端点):0x0A【数据从终端节点的0x0A端点房间出去】
- APS Payload:传输的数据(该项目中是"hello lao da"):【终端节点向协调器节点发送的数据】
- LQI:帧的信号强度,最大255
- FCS:ok(正确)、err(错误)【帧检验序列】
- APS Cluster ID:簇ID,端点里面指定的ID:0x0001【数据是从终端节点的0x0A端点房间里面编号为0x0001的人发出的】
② 协调器向终端回复应答ACK
4)终端节点向协调器节点发送数据过程分析:
网络地址为0x0DA5的终端节点的10号端点与网络地址为0x0000的协调器节点的10号端点进行数据通信的过程:
0x0DA5【终端节点】发送无线数据包以后,由于目标地址是0x0000,协调器模块的网络地址为0x0000对,协调器可以拿到这个无线数据包。然后在底层任务中,判断10号端点房间是否已经定义并且和应用层任务挂钩,如果已经定义,也已与任务层任务挂钩,那么这个无线数据包就发送一个消息到我们应用层任务。
应用层消息:
case AF_INCOMING_MSG_CMD:
SDApp_MessageMSGCB( MSGpkt );
break;
在消息处理函数中,把发送的数据通过串口发送出去:
Uart_Send_String(pkt->cmd.Data,pkt->cmd.DataLength);
5)代码分析:
① 端点和任务挂钩代码 ( PPMApp.c的PPMApp_init() ):
void PPMApp_Init( byte task_id ) //定义了10号端点并且和这个模块的应用层任务挂钩
{
..
//任务与端点进行挂钩
//端点10与应用层任务挂钩
//端点:是一个结构体:SDApp_epDesc
// Fill out the endpoint description.
SDApp_epDesc.endPoint = 10;//SDApp_ENDPOINT; 设置端点编号为10
SDApp_epDesc.task_id = &SDApp_TaskID; //和我们应用层任务挂钩
SDApp_epDesc.simpleDesc
= (SimpleDescriptionFormat_t *)&SDApp_SimpleDesc;//更加详细的描述这个端点一些情况就像我们定义一个编号房间,描述房间里大概有多少人之类的信息。
SDApp_epDesc.latencyReq = noLatencyReqs;
// Register the endpoint description with the AF
afRegister( &SDApp_epDesc );//这个函数必须要调用才能完成端点和任务的挂钩操作
..
}
② 无线数据接收消息触发,数据接收函数【PPMApp.c文件中的PPMApp_MessageMSGCB】:
如果一个任务下,挂载多个端点,在该函数中会有多个端点进入其中,需要首先判断下目的端点和哪一个端点匹配
端点判断正确后,再继续判断簇ID是哪一个,找到指定的簇ID:
switch(目的端点){
case 端点1:
switch(目的簇ID){
case 簇ID1:
break;
case 簇ID2:
break;
...
}
break;
case 端点2:
switch(目的簇ID){
case 簇ID1:
break;
case 簇ID2:
break;
...
}
break;
...
}
③ 发送模块发送数据代码:
在发送模块里,我们用的数据发送源端点,也是10,所以我们也定义这个10端点也挂钩应用层任务【系统会自动定义这个】。原则上,外部给我们终端模块10号端点来数据,也会进入终端应用层任务事件处理函数里。而我们这个端点仅仅这里作为发送模块,但是我们要使用10号端点,必须要挂钩定义。
// This is the unique message ID (counter) SDApp.c全局变量,记录我们应用层任务发送的数据包的次数。
byte SDApp_TransID;
void SDApp_Init( byte task_id )
{
..
SDApp_TransID = 0;
..
}
//是在端点描述符里有提到,所以在无线数据包里有看到
#define SDApp_PROFID 0x0F04
//按下按键,发送数据
if(0==P1_1)
{
//发送数据字符串
char theMessageData[] = "Hello lao da";
//LED显示3
LS164_BYTE(3);
//配置目的地址
PPMApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
//接收模块的网络地址为(目的地址):0x0000
PPMApp_DstAddr.addr.shortAddr = 0x0000;
// Take the first endpoint, Can be changed to search through endpoints
//接收模块的端点房间号
PPMApp_DstAddr.endPoint = PPMApp_ENDPOINT;
//PPMApp_epDesc:结构体,端点描述符,存储着端点相关的信息,有源端点的信息
//发送帧里面:APS这些数据
//发送数据函数
AF_DataRequest( &PPMApp_DstAddr, &PPMApp_epDesc,
PPMApp_CLUSTERID,//目标端点簇,接收端点房间内的指定物品,两个字节,在射频中是0x0001
(byte)osal_strlen( theMessageData ) + 1,//发送字符串的长度包括最后的'\0',长度为1个字节,可以发送0-255字节的数据
(byte *)&theMessageData,//字符串内容数组的首地址
&PPMApp_TransID,//全局变量:记录应用层任务发送数据包的次数
AF_DISCV_ROUTE, AF_DEFAULT_RADIUS );
//每按下一下开关,LED灯进行亮灭操作
P1SEL &= 0xfe;
P1DIR |= 0x01;
P1_0 ^= 1;
}
七、单播发送数据代码模板总结
1)发送模块代码模板
//发送端代码:
//1. 修改发送端点为11(上方PPMApp_epDesc.endPoint属性)
//2. 设置目的地址为协调器地址的地址0x0000
//3. 修改目地地址端点为7
//4. 复制PPMApp_SendTheMessage函数中的发送代码
//5. 修改目的地址端点的簇ID、字节数
PPMApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
PPMApp_DstAddr.addr.shortAddr = 0x0000;
// Take the first endpoint, Can be changed to search through endpoints
//接收端点为7
PPMApp_DstAddr.endPoint = 7;
//要发送的数据
char theMessageData[] = {4};
//接收簇为0x0002
//向目标地址为0x0000的节点的7号端点中的簇为0x0002的人发送消息
AF_DataRequest( &PPMApp_DstAddr, &PPMApp_epDesc,
0x0002,
1,//(byte)osal_strlen( theMessageData ) + 1,
(byte *)&theMessageData,
&PPMApp_TransID,
AF_DISCV_ROUTE, AF_DEFAULT_RADIUS );
2)接收模块模板
① 端点和应用层任务挂钩
//-------------------------------------------
//7号端点和应用层任务挂钩
// Fill out the endpoint description.
PPMApp_epDesc.endPoint = 7;//PPMApp_ENDPOINT;
PPMApp_epDesc.task_id = &PPMApp_TaskID;
PPMApp_epDesc.simpleDesc
= (SimpleDescriptionFormat_t *)&PPMApp_SimpleDesc;
PPMApp_epDesc.latencyReq = noLatencyReqs;
// Register the endpoint description with the AF
afRegister( &PPMApp_epDesc );
//--------------------------------------------
② 接收到无线数据,响应函数
void PPMApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
// switch ( pkt->clusterId )
// {
// case PPMApp_CLUSTERID:
// // "the" message
//#if defined( LCD_SUPPORTED )
// HalLcdWriteScreen( (char*)pkt->cmd.Data, "rcvd" );
//#elif defined( WIN32 )
// WPRINTSTR( pkt->cmd.Data );
//#endif
// break;
// }
//外来数据要把数据发送到7号端点房间
if(7 == pkt->endPoint){
switch ( pkt->clusterId ){
case 0x0001:
//P1_0:LED1
P1SEL &= 0xfe;
P1DIR |= 0x01;
P1_0 ^= 1;
LS164_BYTE( pkt->cmd.Data[0]);
break;
case 0x0002:
//P0_1:LED2
P0SEL &= 0xfd;
P0DIR |= 0x02;
P0_1 ^= 1;
LS164_BYTE( pkt->cmd.Data[0]);
break;
}
}
//来数据要把数据发送到8号端点房间
if(8 == pkt->endPoint){
switch ( pkt->clusterId ){
case 0x0001:
//P0_4:LED3
P0SEL &= 0xef;
P0DIR |= 0x10;
P0_4 ^= 1;
LS164_BYTE( pkt->cmd.Data[0]);
break;
}
}
}
希望本篇文章对大家有所帮助,后续会继续分享Zigbee相关学习知识…
如果文章内容有错误的地方,请在留言处留下你的见解,方便大家共同学习。谢谢!
如有侵权或其他任何问题请联系:QQ1370922071,本文主要用于学习交流,转载请声明!
作者:皮皮猫吖