内容概要:
TI Z-Stack协议栈安装
串口通信
TI Z-Stack协议栈安装:
• 我们用到的ZStack协议栈的版本为 2.3.0-1.4.0,找到相应安装软件并安装。
• 安装完成之后在C:\Texas Instruments下找到ZStack-CC2530-2.3.0-1.4.0文件夹,打开文件夹.可以看到文件的目录结构如下:
文件目录介绍:
• Components:存放的是Z-Stack开源的主要程序代码,包括硬件接口层(hal),MAC层,操作系统(OSAL)代码等。
• Documents:TI提供的开发文档的,主要是对协议栈的和API的讲解。
• Projects:该目录存放的是TI公司提供的协议栈例程和编程模板。(在例程的基础上来开发实现自己的相关功能)
• Tools: 该目录存放的是几个zigbee相关的实用工具。
工程目录:(复制Projects下的一个例程点击.eww文件打开)
• App:应用层目录,这是用户创建各种不同工程的区域,在这个目录中包含了应用层的内容和这个项目的主要内容。(一般在这个目录下编写和修改代码,其他目录不做改动)
• HAL:硬件层目录,包含有与硬件相关的配置和驱动及操作函数。
• MAC:MAC 层目录,包含了 MAC 层的参数配置文件及其 MAC 的 LIB 库的函数接口文 件。
• MT:实现通过串口可控制各层,并与各层进行直接交互
• NWK:网络层目录,包含网络层配置参数文件网络层库的函数接口文件及 APS 层库的函数接口。
• OSAL:协议栈的操作系统。
• Profile: Application framework 应用框架层目录,包含AF层处理函数文件。应用框架层是应用程序和 APS层的无线数据接口。
• Security:安全层目录,包含安全层处理函数,比如加密函数等
• Services:地址处理函数目录,包括地址 模式的定义及地址处理函数。
• Tools:工程配置目录,包括空间划分及 Z-Stack 相关配置信息。
• ZDO:ZDO 目录
• ZMac:MAC 层目录,包括 MAC 层参数配置 及 MAC 层 LIB 库函数回调处理函数。
• ZMain:主函数目录,包括入口函数及硬件配置文件。
• Output:输出文件目录,由 IAR IDE 自动生成。
串口发送:
• 打开C:\Texas Instruments\ZStack-CC2530-2.3.0-1.4.0\Projects\zstack\Samples并复制SampleApp为SampleApp_copy,用IAR打开此工程,在SampleApp.c里加入与串口通信相关的头文件(在MT目录下查找)。
#include "MT_UART.h"
#include "string.h"
在SampleApp_Init里添加如下代码:
MT_UartInit(); //串口初始化,需要到这个函数里面做修改
HalUARTWrite(0,"hello world\n", strlen("hello world\n"));//串口发送,函数详情如下:
extern uint16 HalUARTWrite ( uint8 port, uint8 *pBuffer, uint16 length ); 函数功能:串口数据发送。
参数1:对应的是几号串口(如果是0号串口就填写0,如果是1号串口就填写1...)
参数2:发送的数据
参数3:发送数据的大小
函数查找方法如下:
修改串口波特率为115200以及关闭硬件流控制(修改对应的宏定义)。
• 在工程预编译选项 MT_TASK、MT_SYS_FUNC、MT_ZDO_FUNC、LCD_SUPPORTE
D=DEBUG 前加上 x,加上 x 代表不编译对应的功能代码。
串口发送实验结果:
• 编译工程并下载至开发板,打开串口助手,找到对应的端口,选择波特率为115200,按下复位键可看到如下效果。
内容概要:
初识OSAL
协议栈运行机制
初识OSAL(事件轮询操作系统):(每个任务都对应一个任务处理函数,每个任务处理函数中可以处理多个事件)
• Zigbee协议栈的实时操作系统
• 操作系统抽象层-Operating System Abstraction Layer
• 作用:任务调度,资源分配和管理
• 关键词:任务与事件
• taskCnt—任务个数
• taskEvents—指向事件表首地址的指针(在初始化完成之后,数组的每一项都是0,如果有事件发生了,相应的位置显示一个16进制的数)
• taskArry—数组,数组中的每一个元素都是指向事件处理函数的指针
协议栈运行机制:
• 入口在Zmain文件下,Zmain.c的main函数中,其中包含了各种硬件和协议栈的初始化,直到调用osal_start_system()函数,协议栈才开始真正运行起来(此时进入死循环,开始事件轮询)。
int main( void )
{
…
…
osal_start_system(); // No Return from here
return 0; // Shouldn't get here.
}
• osal_start_system()的原形:
void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop 死循环
#endif
{
uint8 idx = 0; //索引事件表的
osalTimeUpdate(); //更新系统时钟用的
Hal_ProcessPoll(); /* This replaces MT_SerialPoll() and osal_check_timer().
查看硬件方面是否有事件发生(如 串口是否收到数据,是否有按键按下等)*/
do {
if (tasksEvents[idx]) /* Task is highest priority that is ready.
轮询事件表的每一项,如果有事件发生,对应项就会产生一个16进制数
(这个数是所有产生的事件对应16进制的数(事件表中的每一个事件都用
一个唯一的16进制数标识)相或得到的),则跳出循环*/
{
break;
}
} while (++idx < tasksCnt);//idx不超过任务总数,否则循环结束
if (idx < tasksCnt) /*判断idx是否不超过任务总数(如果不超过,则有事件产生,
做进一步处理)*/
{
uint16 events;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState);//保存中断状态,暂停中断
events = tasksEvents[idx];//用events临时保存一下事件
tasksEvents[idx] = 0; /* Clear the Events for this task.
将事件表中的这一项再清除为0*/
HAL_EXIT_CRITICAL_SECTION(intState);//恢复先前的中断状态
events = (tasksArr[idx])( idx, events );/*调用事件处理函数
(tasksArr[idx]:对应事件处理的函数指针)。该函数
返回值为0(表示没有其他需要处理的事件)或者为一个
16进制数(该16进制数是剩下没处理的事件相或得到的
(件表中的每一个事件都用一个唯一的16进制数标识。具体
见下文实例))*/
HAL_ENTER_CRITICAL_SECTION(intState);//保存中断状态,暂停中断
tasksEvents[idx] |= events; /* Add back unprocessed events to
the current task.把未处理的事件返回给事件表
(用于多个事件发生,都需要进行事件处理。)*/
HAL_EXIT_CRITICAL_SECTION(intState);//恢复先前的中断状态
}
#if defined( POWER_SAVING )
else // Complete pass through all task events with no activity?
{
osal_pwrmgr_powerconserve(); // Put the processor/system into sleep
}
#endif
}
}
• 首先是一个for(;;)死循环,内部Do-while用来判断是否有事件发生,有则跳出本次循环,去处理事件,事件处理完成再次进入for(;;)循环.
• 事件处理过程
• 1:先关闭中断
• 2:临时保存当前事件
• 3:将该事件从事件表中清除
• 4:调用事件处理函数表中对应的事件处理函数.
• 5.一个任务可能对应多个事件,将未处理事件返回给事件表,等待下一次轮训处理
事件处理函数表在OSAL_SampleApp.c里:
const pTaskEventHandlerFn tasksArr[] = {
macEventLoop,
nwk_event_loop,
Hal_ProcessEvent,
#if defined( MT_TASK )
MT_ProcessEvent,
#endif
APS_event_loop,
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_ProcessEvent,
#endif
ZDApp_event_loop,
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
ZDNwkMgr_event_loop,
#endif
SampleApp_ProcessEvent,
};
如何返回未处理的事件呢.以消息处理函数SampleApp_ProcessEvent()为例:
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
……
if ( events & SYS_EVENT_MSG )
{
……
return (events ^ SYS_EVENT_MSG);
}
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
{
……
return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
}
return 0;
}
• #define SYS_EVENT_MSG 0x8000
• #define SAMPLEAPP_SEND_PERIODIC_MSG_EVT 0x0001
• 如果这两个事件同时发生,则events=0x8001
• 异或运算,同为0,异为1.
• events ^ SYS_EVENT_MSG=0x0001(则只剩下后一个事件)
• events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT=0x8000(则只剩下前一个事件)
内容概要:
功能要求
实现过程
功能要求:(实现点对点的数据传输)
1:协调器组建 PAN 网络。
2:协调器组网成功后会周期性广播“I am coordinator device! ” (周期为 5s) 。
3:终端设备节点加入该 PAN 网络,加入网络成功后周期性广播字符串“I am endpoint device!” (周期为 5s) 。
实现过程:
仍然拷贝一个样板例程并重命名.
协调器编程:
1:按照串口通信例子在SampleApp.c文件里添加头文件。
#include "MT_UART.h"
#include "string.h"
2:在SampleApp_Init()里添加串口初始化代码和串口数据写入代码.(注意在串口初始化函数中修改波特率和流控制),并且将关于组播的代码注释掉。
MT_UartInit(); //串口初始化
HalUARTWrite ( 0, "uart0 is ok\n", strlen("uart0 is ok\n") );//向串口发送数据
3:设置地址模式为广播模式,网络地址设为0xffff。
Ø 对于事件处理函数SampleApp_ProcessEvent().
1.SYS_EVENT_MSG代表的是系统消息事件,是一个强制事件.
2.通过osal_msg_receive()从消息队列里提取消息.
Ø 当某一个事件发生时,通常会伴随一些附加事件和数据的产生,例如SYS_EVENT_MSG事件发生时常伴有以下以下几个常用事件,其他不常用的事件不一一列举.
(1) AF_INCOMING_MSG_CMD 表示收到了一个新的无线数据
(2) ZDO_STATE_CHANGE 表示网络状态发生了改变
(3) KEY_CHANGE 表示有按键按下
Ø 然后协议栈把这些附加的事件和数据封装成一个消息结构体,保存在消息队列里.然后再通过osal_msg_receive()从消息队列里将消息结构体提取出来.
• 如果当前zigbee设备网络状态发生了改变(初始状态为DEV_INIT).则通过定时器功能触发SAMPLEAPP_SEND_PERIODIC_MSG_EV事件.
• 当协议栈检测到SAMPLEAPP_SEND_PERIODIC_MSG_EV事件发生,则调用SampleApp_SendPeriodicMessage()周期性的把数据发送出去.
• 时间间隔周期通过调用osal_start_timerEx()函数来实现的(见上图)。
以上分析过程的完整代码如下:
SampleApp.c
void SampleApp_Init( uint8 task_id )
{
SampleApp_TaskID = task_id; //任务的优先级
SampleApp_NwkState = DEV_INIT; //当前设备网络的状态(此时赋的初始状态)
SampleApp_TransID = 0; /*发送数据包的序列号,初始值为0。以后ZigBee每发送
一个数据,这个序列号会自动加1.通过这个序列号查看当前设备发
送的数据量与接收方接收到的实际数据量做比较,算出丢包率*/
MT_UartInit(); //串口初始化
HalUARTWrite ( 0, "uart0 is ok\n", strlen("uart0 is ok\n") );//向串口发送数据
// Device hardware initialization can be added here or in main() (Zmain.c).
// If the hardware is application specific - add it here.
// If the hardware is other parts of the device add it in main().
#if defined ( BUILD_ALL_DEVICES )
// The "Demo" target is setup to have BUILD_ALL_DEVICES and HOLD_AUTO_START
// We are looking at a jumper (defined in SampleAppHw.c) to be jumpered
// together - if they are - we will start up a coordinator. Otherwise,
// the device will start as a router.
if ( readCoordinatorJumper() )
zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR;
else
zgDeviceLogicalType = ZG_DEVICETYPE_ROUTER;
#endif // BUILD_ALL_DEVICES
#if defined ( HOLD_AUTO_START )
// HOLD_AUTO_START is a compile option that will surpress ZDApp
// from starting the device and wait for the application to
// start the device.
ZDOInitDevice(0);
#endif
// Setup for the periodic message's destination address
// Broadcast to everyone
//下面三行代码为广播的相关代码,一般不需要做改动
SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;//地址模式设置,AddrBroadcast:广播模式
SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;//设置地址,广播地址设置为0xFFFF
// 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;*/
// 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;
// Register the endpoint description with the AF
afRegister( &SampleApp_epDesc );
// Register for all key events - This app will handle all key events
RegisterForKeys( SampleApp_TaskID );
// 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 );*/
#if defined ( LCD_SUPPORTED )
HalLcdWriteString( "SampleApp", HAL_LCD_LINE_1 );
#endif
}
/*协议栈运行起来之后一定会调用这个函数(事件轮询)*/
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
afIncomingMSGPacket_t *MSGpkt;
(void)task_id; // Intentionally unreferenced parameter
if ( events & SYS_EVENT_MSG )/*判断是否产生了SYS_EVENT_MSG事件。
SYS_EVENT_MSG:系统消息事件,同时也是一个强制事件
(只要协议栈运行起来之后,这个事件就会被触发,所以
只要协议栈正常运行,if里面的代码一定会执行)*/
{
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
/*一般情况下,一个事件产生的同事会额外附加产生一些事件和一些数据,协议栈
把这些额外附加的事件和数据打包成消息结构体,并把这些消息结构体一次添加到
消息队列里面去。osal_msg_receive( SampleApp_TaskID )功能:提取当前
任务的消息结构体*/
while ( MSGpkt )//对当前任务的消息结构体进行拆包
{
switch ( MSGpkt->hdr.event )//产生的一些额外的事件
{
// Received when a key is pressed
case KEY_CHANGE: //第一个额外事件:判断是否有按键按下
SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
break;
// Received when a messages is received (OTA) for this endpoint
case AF_INCOMING_MSG_CMD://第二个额外事件:判断是否有新的无线消息到达了
SampleApp_MessageMSGCB( MSGpkt );
break;
// Received whenever the device changes state in the network
case ZDO_STATE_CHANGE:/*第三个额外事件:判断当前设备网络状态
是否改变了。(如果协调器在创建网络成功的时候,
他的网络状态会由初始值变为协调器状态,所以协
调器在创建网络成功的时候,一定会触发这个事件)*/
SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
if ( (SampleApp_NwkState == DEV_ZB_COORD) //DEV_ZB_COORD:代表协调器
|| (SampleApp_NwkState == DEV_ROUTER) //DEV_ROUTER:代表路由器
|| (SampleApp_NwkState == DEV_END_DEVICE) ) //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_EVT,
/*每隔一段时间(根据参数三可知这里是5s)就会
产生SAMPLEAPP_SEND_PERIODIC_MSG_EVT这个
事件*/
SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );
/*SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT:
宏定义5000,即网络状态发生改变之后调用该
函数:每隔5000ms触发一次(SAMPLEAPP_SEND_PERIODIC_MSG_EVT)
事件,然后在事件产生之后,下次轮询的时候会检测到
SAMPLEAPP_SEND_PERIODIC_MSG_EVT(参数二)事件
触发了,然后执行事件发生之后要处理的函数(根据下文
代码可知,该事件产生之后,会调用一个发送函数:
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
{SampleApp_SendPeriodicMessage();...}
从而可以周期性的发送消息)*/
}
else
{
// Device is no longer in the network
}
break;
default:
break;
}
// Release the memory
osal_msg_deallocate( (uint8 *)MSGpkt );
// Next - if one is available
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
// Send a message out - This event is generated by a timer
// (setup in SampleApp_Init()).
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )/*当检测到
SAMPLEAPP_SEND_PERIODIC_MSG_EVT事件的发生时就会执行
SampleApp_SendPeriodicMessage函数发送消息(配合
osal_start_timerEx 函数就可以周期性的发送消息了)*/
{
// Send the periodic message
SampleApp_SendPeriodicMessage();//发送消息,后文有具体分析过程
// Setup to send message again in normal period (+ a little jitter)
/*定时器操作完成周期性的触发SAMPLEAPP_SEND_PERIODIC_MSG_EVT事件,
进而实现周期性的发送消息*/
osal_start_timerEx( SampleApp_TaskID, SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
(SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT + (osal_rand() & 0x00FF)) );
// return unprocessed events
return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
}
// Discard unknown events
return 0;
}
• 在SampleApp_SendPeriodicMessage()里首先判断当前zigbee设备的逻辑类型,是终端的话则向缓存区写入一段话,如果是协调器则向缓存区写入另外一句话,再调用AF_DataRequest()将数据广播出去.
AF_DataRequest( //发送数据的实现函数
afAddrType_t *dstAddr,//发送方式和发送网络地址(目的地址)
endPointDesc_t *srcEP,//端口描述,一般不做改动
uint16 cID,//命令号,需要与接收方协议栈轮询时的命令号保持一致,一般不做改动
uint16 len, //数据长度
uint8 *buf, //数据
uint8 *transID,//指向发送数据的序号,可以用来计算丢包率,一般不做改动
uint8 options, //默认选择AF_DISCV_ROUTE,一般不做改动
uint8 radius ) //默认选择AF_DEFAULT_RADIUS,一般不做改动
重新编写发送数据函数SampleApp_SendPeriodicMessage(),实现数据发送:
void SampleApp_SendPeriodicMessage( void )
{
uint8* buf = NULL;/*因为下面的AF_DataRequest函数中发送数据的数据类型
要求是uint8型,所以这里定义一个uint8类型的指针作为发
送数据缓存首地址*/
if(zgDeviceLogicalType == ZG_DEVICETYPE_COORDINATOR)/*如果是协调
器则广播如下字符串(通过一个全局变量zgDeviceLogicalType
来判断该设备为协调器还是终端设备)。
ZG_DEVICETYPE_COORDINATOR:宏定义,表示协调器。*/
{
buf = "I am coordinator device !\n";
}
if(zgDeviceLogicalType == ZG_DEVICETYPE_ENDDEVICE)/*如果是终端设
备则广播如下字符串(通过一个全局变量zgDeviceLogicalType
来判断该设备为协调器还是终端设备)。
ZG_DEVICETYPE_ENDDEVICE:宏定义,表示终端。*/
{
buf = "I am endpoint device !\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.
}
}
// Device types definitions ( from ZGlobals.h file )
#define ZG_DEVICETYPE_COORDINATOR 0x00 //协调器
#define ZG_DEVICETYPE_ROUTER 0x01 //路由器
#define ZG_DEVICETYPE_ENDDEVICE 0x02 //终端
如果有新的无线消息到来这个事件发生,则调用SampleApp_MessageMSGCB()进行处理。
在SampleApp_MessageMSGCB()函数里首先对消息结构体内的命令号进行判断。
如果收到的是SAMPLEAPP_PERIODIC_CLUSTERID,则把消息结构体中的数据通过串口打印出来。
重新改写SampleApp_MessageMSGCB()函数代码,实现接收到的数据通过串口打印到电脑上:
//将接收到的数据通过串口打印到电脑上
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
uint16 flashTime;
switch ( pkt->clusterId )//对消息结构体中的命令号进行判断
{
case SAMPLEAPP_PERIODIC_CLUSTERID:/*如果是这个命令号,则将接收到
的数据通过串口打印出来。因为对方使用AF_DataRequest函数发送
数据时填写的命令号为SAMPLEAPP_PERIODIC_CLUSTERID,故接收
数据时也是这个命令号*/
//向串口发送数据,打印到电脑上来。下面对pkt(afIncomingMSGPacket_t类型)有一个详细讲解
HalUARTWrite ( 0, pkt->cmd.Data, pkt->cmd.DataLength );
break;
case SAMPLEAPP_FLASH_CLUSTERID:
flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
break;
}
}
afIncomingMSGPacket_t *pkt类型详解:
typedef struct
{
osal_event_hdr_t hdr; /* OSAL Message header */
uint16 groupId; /* Message's group ID - 0 if not set */
uint16 clusterId; /* Message's cluster ID */
afAddrType_t srcAddr; /* Source Address, if endpoint is STUBAPS_INTER_PAN_EP,
it's an InterPAN message */
uint16 macDestAddr; /* MAC header destination short address */
uint8 endPoint; /* destination endpoint */
uint8 wasBroadcast; /* TRUE if network destination was a broadcast address */
uint8 LinkQuality; /* The link quality of the received data frame */
uint8 correlation; /* The raw correlation value of the received data frame */
int8 rssi; /* The received RF power in units dBm */
uint8 SecurityUse; /* deprecated */
uint32 timestamp; /* receipt timestamp from MAC */
afMSGCommandFormat_t cmd; /* Application Data 应用程序的数据,也是一个结构体,还可以追入*/
} afIncomingMSGPacket_t;
// Generalized MSG Command Format
typedef struct
{
byte TransSeqNumber; //传送的序列号
uint16 DataLength; // Number of bytes in TransData。数据的长度
byte *Data; //数据
} afMSGCommandFormat_t;
编译:
烧写程序到协调器:
烧写程序到终端:
实验现象:
内容概要:
流程回顾
函数接口+数据结构讲解
流程回顾:(其中协调器的建立网络和终端的加入网络是协议栈帮助完成的,我们不需要考虑)
发送数据函数:
AF_DataRequest(
afAddrType_t *dstAddr,//目标设备地址
endPointDesc_t *srcEP,//端口描述符
uint16 cID,//命令号
uint16 len,//数据长度
uint8 *buf,//数据
uint8 *transID,//发送数据包序列号,用来计算丢包率
uint8 options, //有效位掩码的发送选项,一般设为AF_DISCV_ROUTE.
uint8 radius //传送跳数或半径,一般设为AF_DEFAULT_RADIUS)
发送数据函数中的地址结构体:
typedef struct
{
union
{
uint16 shortAddr;//网络地址,该地址是设备在加入网络时由协议栈分配的.
ZLongAddr_t extAddr;//64位扩展地址,全球唯一
} addr;
afAddrMode_t addrMode;//地址模式(广播,组播,单播)
byte endPoint;//端口号,可供范围1~240
uint16 panId; //一个无线网络的的网络号.
} afAddrType_t;
地址结构体中的发送模式结构体:
typedef enum
{
afAddrNotPresent = AddrNotPresent,//当前地址不存在
afAddr16Bit = Addr16Bit,//用于单播
afAddr64Bit = Addr64Bit, ,//用于单播
afAddrGroup = AddrGroup,//用于组播
afAddrBroadcast = AddrBroadcast//用于广播
} afAddrMode_t;
端口描述符:
typedef struct
{
byte endPoint;//端口号
byte *task_id; //指定哪一个任务
SimpleDescriptionFormat_t *simpleDesc;//设备简单描述符
afNetworkLatencyReq_t latencyReq;//延时请求
} endPointDesc_t;
设备简单描述符:
typedef struct
{
byte EndPoint;//端口号
uint16 AppProfId;//规范号,一个规范定义一个应用领域,如智能家居,工业厂房监控,商业楼宇自动化等
uint16 AppDeviceId;//设备类型ID
byte AppDevVer:4;//应用设备版本号
byte Reserved:4;//保留位
byte AppNumInClusters;//输入簇的个数
cId_t *pAppInClusterList;//输入簇的列表
byte AppNumOutClusters;//输出簇的个数
cId_t *pAppOutClusterList;//输出簇的列表
} SimpleDescriptionFormat_t;
规范:
• 规范:在zigbee网络中进行数据收发都是建立在应用规范基础上, 每个应用规范都有一个ID来标识,应用规范又可以分为公共规范和制造商特定规范,公共规范ID的范围是0x0000~0x7FFF,制造商特定规范ID的范围0xbF00~0xFFFF
簇:
• 在一个规范下,又提出了簇(cluster)的概念,簇是一个应用领域下的一个特定对象,例如:智能家居中有这个调光器就需要一些命令,如开灯,关灯,变亮,变暗等,实现这些操作需要不同的命令,多个操作命令的集合叫做簇.
• 在设备简单描述符中需要填充输入簇和输出簇.填充时需要注意:
• 消息发送方需把命令放在输出簇里,那么消息接受方需要把同样的命令放在输入簇里,之前的设备间第一次通话,由于输入簇和输出簇是一样的,所以协调器和终端设备间也能正常通信.