物联网之Zigbee系统开发三(无线传感器网络)

协议栈入门

内容概要

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:发送数据的大小

函数查找方法如下:

 • Go to到MT_UartInit定义之处:

   修改串口波特率为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)的概念,簇是一个应用领域下的一个特定对象,例如:智能家居中有这个调光器就需要一些命令,如开灯,关灯,变亮,变暗等,实现这些操作需要不同的命令,多个操作命令的集合叫做簇.

• 在设备简单描述符中需要填充输入簇和输出簇.填充时需要注意:

• 消息发送方需把命令放在输出簇里,那么消息接受方需要把同样的命令放在输入簇里,之前的设备间第一次通话,由于输入簇和输出簇是一样的,所以协调器和终端设备间也能正常通信.

猜你喜欢

转载自blog.csdn.net/weixin_39148042/article/details/81437042
今日推荐