Zigbee通讯之开发篇(基于TI 的Z-Stack)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhanglianpin/article/details/46907349

1.Zigbee协议和Z-Stack

  Zigbee协议和Z-Stack是什么关系?这可能是初学Zigbee同学想知道的问题。给大家举个例子吧,我们生活中使用的插排是要符合一定的标准的,现在国家标准是GB2099.3-2008,里面规定了好多插排的电气、机械等要求。不同厂家生产的插排,要在中国国内销售的话,必须符合这个标准。但是生产这个插排的厂家多了去了,像 公牛、philips等等(排除做广告的嫌疑)。其实Zigbee协议栈规范和Z-Stack的关系也差不多,Z-Stack就是符合Zigbee协议栈规范的一个硬件和软件平台,是Zigbee协议栈的一个具体实现。当然,还有其他的具体实现,freakz协议栈和contiki操作系统、TinyOS等等。大家要注意,Z-Stack是TI公司提供的协议栈,它是个半开源的协议栈,有些核心代码是以库的形式提供的,所以要想深入了解协议栈或者想进一步提升编程能力的同学还是找一个全开源的协议栈玩玩吧。比如,freakz协议栈。

2.IAR和Z-Stack

 Z-Stack的整个开发环境IDE使用的是IAR(IAR的版本需要参考Z-Stack Home Sample Application User's Guide。此文档在TI提供的资料包里\Documents内)。从来没有使用过IAR的同学请参考文章最后附件:IAR入门。这只是个简单的入门指导,想详细了解IAR或者使用过程中遇到什么问题请使用IAR的help选项。

 我们知道Zigbee设备的分为Coordinator、Router、Enddevice三种角色,这三种角色在IAR里怎么修改呐?我们打开一个TI HA(TI提供的关于智能家居的解决方案),下载地址见下载平台:http://down.51cto.com/data/2067778

wKioL1Wk53LxIEsoAAFyv-dkGSI060.jpg

从图中标示的位置可以修改这三种设备角色,其实这里的选项是修改IAR project配置的地方,这里TI提供的project里已经配置好了这三钟Zigbee设备角色的配置文件。我们这里只选择就可以了。

Z-Stack软件结构有一个很大的特点:使用宏定义来区分是否编译某一模块/功能/函数。

wKioL1Wk7nWQmoxpAAGhZuoMECk541.jpg

wKiom1Wk7QPiEbzKAAEYILvI9E8551.jpg

全是根据宏定义来决定是否编译这个功能,有的是根据是否定义这个宏,有的是根据定义的这个宏的值来决定其什么作用。这也可以理解,因为TI提供的是一个通用的基础开发平台,需要考虑兼容性,易用性,并且硬件资源有限,只能使用宏定义的方式在程序预编译阶段根据宏定义就可以知道需要哪些功能了。

那么关键的问题来了,这些宏定义在IAR里是怎么定义的类?在IAR中有两种定义方式:

第一种:

wKiom1Wk7tHBhrJaAAG4NBIk_mE550.jpg

在打开的TI project工程目录的Tools下面,有***.cfg文件,这些是IAR Compiler command-line options。可以在这里定义宏。

wKiom1Wk71aBvXOYAAIr1o9BUYo509.jpg

定义的方法是“-D 加上你要定义的宏”,例如上面的 -DMAC_CFG_TX_MAX,其实定义的宏就是MAC_CFG_TX_MAX,你搜索整个工程就可以找到在哪里使用了这个宏。取消定义可以在定义前加上“//”。例如//-DMAC_CFG_TX_DATA_MAX=5。

你可能想知道IAR读取这些cfg文件需要不需要配置?自己新增加配置文件怎么办?

打开菜单栏 project---option

wKioL1Wk8w7TzqMGAAH4PJsKoOM737.jpg

找到C/C++Complier 选项,然后选择Extra Options,在这里写你自己需要引用的cfg文件就可以了。

第二种:

打开菜单栏 project---option 然后找到preprocessor选项。

wKioL1Wk9GSQ33xeAAJl6zDtaeg698.jpg

在这里也可以定义宏,直接书写宏的名字即可,例如:“ZTOOL_P1”。取消宏定义可以在前面添加“x”,例如“xZTOOL_P1”。当然也可以定义有值的宏,“LCD_SUPPORTED=DEBUG”。

另外,还要说一下上面截图中的上面的部分:是添加头文件包含路径的,这样在源代码包含头文件时你就不用书写好长的路径名称了,直接写头文件名称就行。

 

关于这两种定义宏有什么区别?要是两个地方都定义了相同的宏但值不同(有可能是你马虎定义重复了),这种情况以那个为准?

关于区别,第一种定义方式其实就是将要定义的某一类功能的宏都放到一个文件中,方便修改、查找。这样所有的工程都可以通过指定文件的方式来使用这些宏定义或者宏定义的值。比较方便一些,例如:TI工程里的f8wCoord.cfg这个宏定义配置文件,是针对所有Coordinator的定义,好多不同project想编译成Coordinator角色的都可以引用这个command-line options文件。

第二种方式定义只是针对具体某个project的,通过第二种方式设置的内容都会存储在***.ewp文件中,这个就是具体某个project的具体配置。

还有一点需要说明:

wKioL1Wlx1fQmKbFAACoarni4NA973.jpg

上面第一处是IAR关于某一个project的一个配置文件,选中不同的配置文件,IAR就会根据不同的配置进行编译、链接等等一系列动作。这里的配置主要包括:菜单栏里 roject---option里面所有的设置,还有选择是否编译某一个具体的文件。

wKioL1WlyIXg5kgVAAHZkcjuFi4900.jpg

显示为暗灰色的X号的文件不参与此project的编译,设置方法为在project具体某个文件上右键--option--Exclude from buid。如上图所示。

IAR会单独建立文件夹用于保存不同peoject配置的编译、链接生成的文件。就相当于利用不同的配置实现不同的功能,最明显的你想编译一个release版本,编译一个debug版本,release版本不包括调试信息。你就可以设置两个配置。新增配置的方法是菜单栏--project---edit configration里面add即可。

wKioL1WlyeWyQEVkAACjfSXP9fQ752.jpg

这里是选择同一个工作空间里不同的项目的。即IAR管理思路是这样的:一个workspace里可以包含好多的project,而一个project又可以存在好多种的配置。具体参考菜单栏---help---IDE Project Management and Building Guide。

 

wKiom1WlyHOzxGHAAAB3cEnhh_s461.jpg

这是IAR的链接器使用的链接脚本,使用这个文件制定不同变量或者存储区域的最终链接地址,还有其他一些功能,具体参考菜单栏---help---Linker and Library Reference Guide。

3.Z-Stack软件结构简介

 关于Z-Stack结构比较详细的资料需要看这两个TI的官方资料:Z-Stack Home Developer's Guide.pdf

Z-Stack Home Sample Application User's Guide.pdf。是比较全面的资料。关于TI Z-Stack的project各个文件夹的作用网络上已经有大量的资料,这里就不一一赘述。大家可以到网络搜索资料学习。我这里只是简单说一下基于Z-Stack协议栈开发application的思路和方法。Z-Stack project不仅仅提供了Zigbee协议栈的各层API,还提供了一个基于轮询调度的OS(osal),还提供了一些硬件资源驱动API。各个API使用说明见 TI安装包 Document---API里面有各个API的使用说明。

下面我们重点说说这个OSAL,因为它是一个简易的轮询式操作系统,Z-Stack协议使用它作为简单的任务管理、调度、任务间通通讯,使用它使其软件结构更清晰。另外,我们基于Z-Stack协议的Application设计也要基于此软件结构。关于这个东东的讲解可以参考:http://bbs.feibit.com/thread-16-1-1.html。我这里举个例子让大家好理解这个轮询操作系统。如果大家有嵌入式实时操作系统的知识,那这个OSAL就比较好理解。其实OSAL并不是实际意义上的操作系统,它只是一个轮询系统。大家可以想象一个部门有好几个雇员,只有一个办公电脑,这个办公电脑同一时间只能有一位雇员使用,雇员使用办公电脑需要部门领导审批。部门领导根据雇员年龄的大小进行排序,年龄小的先使用,年龄大的后使用。部门领导负责通知各个雇员在使用办公电脑时都需要做哪些具体的工作,若某一雇员在得到办公电脑的使用权时没有任何事情需要做,那他就将办公电脑让给下一个等待的雇员使用。部门领导会根据发生的事件(包括外部事件、雇员之间需要沟通)记录在一个工作安排薄里,每一个雇员得到办公电脑都需要查询工作安排薄来看自己有哪些工作,工作做完了,再接着查询,直到没有自己的工作了,就让下一个等待的雇员使用办公电脑。

这下大家可能明白了,若是那个年龄最小的雇员老是有任务做,那其他雇员就没有机会使用办公电脑了。所以这个OSAL只是一个简单的轮询外部事件的简单调度系统。想了解嵌入式实时操作系统的相关知识,可以学习一下UCOS的相关资料。

另外,我们还需要了解这样一种软件设计思路:Z-Stack作为一个基础软件开发包,为了易于维护软件结构设计时是分层的,那各层之间如何通讯?上层需要调用下层的服务时,直接调用下层提供的API接口即可,那下层有一些紧急事件或者有些变化是上层关心的,如何通知到上层呐?上层接收到这样的消息时有可能需要做不同的操作,这一般怎么实现呐?这种下层事件发生需要通知上层的情况,需要使用回调函数,下层事件发生,会调用用户高层注册的函数来处理下层事件,这就实现了下层到上层的通讯。

所以一般我们需要注册回调函数,然后底层事件发生会调用我们注册的函数,注册函数可以根据传递过来的参数做相应处理。

4.两个基于TI CC2530和Z-Stack平台的设备Zigbee通讯

 一个Coordinator一个Enddevice,它们之间通讯,我们在Z-Stack提供的project---SimpleLight的基础上进行修改源代码做我们的实验。实现现象如下:Coordinator建立网络后,Enddevice设备加入网络,然后Coordinator通过广播的方式发送字符串“Coordinator send!”,EndDevice收到此字符串后控制LED灯闪烁,并且向Coordinator发送“EndDevice received!\r\n”。Coordinator收到后,通过串口打印出来。使用的硬件平台为battery board。

我们选择CoordinatorEB配置选项。

wKiom1WmDf2y2l_iAACvLl5s4Ks966.jpg

我们移除project中App文件夹中的zcl_samplelight.c、zcl_samplelight.h、zcl_samplelight_data.c,添加Coordinator.c、Coordinator.h这两个文件,OSAL_GenericApp.c这个文件是OSAL层和Application层之间的接口文件。这个文件主要负责OSAL的task的初始化,添加task event处理函数。

先看Coordinator的代码:

我们新添加一个task,在Z-Stack中新添加task一般需要以下两步:1,添加task初始化函数:GenericApp_Init。

wKioL1WmBUXwI8H9AANH8n0uEuA443.jpg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*********************************************************************
 * @fn      osalInitTasks
 *
 * @brief   This function invokes the initialization function for each task.
 *
 * @param   void
 *
 * @return  none
 */
void osalInitTasks( void )
{
  uint8 taskID = 0;
 
  tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
  osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
 
  macTaskInit( taskID++ );
  nwk_init( taskID++ );
  Hal_Init( taskID++ );
#if defined( MT_TASK )
  MT_TaskInit( taskID++ );
#endif
  APS_Init( taskID++ );
#if defined ( ZIGBEE_FRAGMENTATION )
  APSF_Init( taskID++ );
#endif
  ZDApp_Init( taskID++ );
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
  ZDNwkMgr_Init( taskID++ );
#endif
  GenericApp_Init( taskID );
}

这个初始化函数要在OSAL_GenericApp.c文件里的void osalInitTasks( void )函数里添加调用。如上图所示。

2,在OSAL_GenericApp.c文件里的pTaskEventHandlerFn tasksArr[]数组里添加用于task event处理的函数GenericApp_event_loop。

wKioL1WmBevRH21rAANIhS5fKSI783.jpg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// The order in this table must be identical to the task initialization calls below in osalInitTask.
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
  GenericApp_event_loop
};

注意初始化task和处理task event的函数顺序要一致。

我们先来看void GenericApp_Init(byte task_id)函数都做了些什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void GenericApp_Init(byte task_id)
{
    GenericApp_TaskID           = task_id;
    GenericApp_TransID          = 0;
     
    GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;
    GenericApp_epDesc.task_id  = &GenericApp_TaskID;
    GenericApp_epDesc.simpleDesc =(SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc ;
    GenericApp_epDesc.latencyReq = noLatencyReqs;
     
     
    afRegister( &GenericApp_epDesc ); 
     
     
     
    //UART configuration
      halUARTCfg_t uartConfig;
      uartConfig.configured           = TRUE;
      uartConfig.baudRate             = HAL_UART_BR_115200;
      uartConfig.flowControl          = FALSE;
      uartConfig.callBackFunc         = NULL;
      HalUARTOpen (0, &uartConfig);
       
       
      HalLedSet(HAL_LED_ALL,HAL_LED_MODE_OFF);
      
}

1,初始化了task的ID GenericApp_TaskID的值。

2,定义了一个endPointDesc_t端点描述符。

3,初始化了串口,用于串口输出。

4,初始化所有LED为OFF状态。

我们再来看uint16 GenericApp_event_loop( uint8 task_id, uint16 events )函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
uint16 GenericApp_event_loop( uint8 task_id, uint16 events )
{
  afIncomingMSGPacket_t *MSGpkt;
 
  (void)task_id;  // Intentionally unreferenced parameter
 
  if ( events & SYS_EVENT_MSG )
  {
    while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID )) )
    {
      switch ( MSGpkt->hdr.event )
      {
        case AF_INCOMING_MSG_CMD:
        GenericApp_MessageMSGCB( MSGpkt );    
        break
        case ZDO_STATE_CHANGE:
             GenericApp_NwkState = (devStates_t) (MSGpkt->hdr.status);
             if(GenericApp_NwkState == DEV_ZB_COORD)
             {
                osal_start_timerEx(GenericApp_TaskID,SEND_BROADCAST_MESSAGE,2000);
             }
             break
        default:
          break;
      }
      
      // Release the memory
      osal_msg_deallocate( (uint8 *)MSGpkt );
    }
 
    // return unprocessed events
            
    return (events ^ SYS_EVENT_MSG);
  }
   
  //if need send brodcast message
  if(events & SEND_BROADCAST_MESSAGE )
  {
      GenericApp_SendTheMessage( );
      osal_start_timerEx(GenericApp_TaskID,SEND_BROADCAST_MESSAGE,5000);
      return (events ^ SEND_BROADCAST_MESSAGE);
  }
 
  // Discard unknown events
  return 0;
}

这个函数里主要处理了两个系统event,一个是ZDO_STATE_CHANGE事件,当Zigbee网络发生变化时(有新设备加入)产生此事件,在此事件里我们启动了一个定时事件SEND_BROADCAST_MESSAGE,用于广播Zigbee信息。另外还有一个AF_INCOMING_MSG_CMD事件,当接收到Zigbee信息包时,会产生此事件,在此事件处理函数中将Coordinator接收到的信息通过串口打印出来。

所需要的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void GenericApp_MessageMSGCB(afIncomingMSGPacket_t *pkt)
{
    unsigned char buffer[20] ;
     
    unsigned char frame_end[2] = {'\r','\n'};
     
    switch(pkt->clusterId)
    {
        case GENERICAPP_CLUSTERID:
             osal_memcpy(buffer,pkt->cmd.Data,20);
             HalUARTWrite(0, buffer, 20);
             HalUARTWrite(0, frame_end, 2);
             
             break;
       
    }
}
 
void GenericApp_SendTheMessage(void)
{
    unsigned char *theMessageData = "Coordinator send!";
    afAddrType_t my_DstAddr;
    my_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;
    my_DstAddr.endPoint = GENERICAPP_ENDPOINT;
    my_DstAddr.addr.shortAddr =0xFFFF ;
     
    AF_DataRequest(&my_DstAddr,&GenericApp_epDesc,GENERICAPP_CLUSTERID,osal_strlen((char *)theMessageData)+1,theMessageData,&GenericApp_TransID,AF_DISCV_ROUTE,AF_DEFAULT_RADIUS);
}

Enddevice的代码和这个基本上差不多。

我们在CoordinatorEB配置下添加Enddevice.c,并将其设置为不参与buid。方法是在Enddevice.c文件上右键----option---Exclude from buid。然后切换配置到EnddeviceEB配置选项,然后将Coordinator.c设置成不参与buid状态。

task初始化函数:void GenericApp_Init(byte task_id)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void GenericApp_Init(byte task_id)
{
    GenericApp_TaskID           = task_id;
    GenericApp_TransID          = 0;
     
    GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;
    GenericApp_epDesc.task_id  = &GenericApp_TaskID;
    GenericApp_epDesc.simpleDesc =(SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc ;
    GenericApp_epDesc.latencyReq = noLatencyReqs;
     
     
    afRegister( &GenericApp_epDesc ); 
     
     
   HalLedSet(HAL_LED_ALL,HAL_LED_MODE_OFF);
}
 
uint16 GenericApp_event_loop( uint8 task_id, uint16 events )
{
  afIncomingMSGPacket_t *MSGpkt;
 
  (void)task_id;  // Intentionally unreferenced parameter
 
  if ( events & SYS_EVENT_MSG )
  {
    while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID )) )
    {
      switch ( MSGpkt->hdr.event )
      {
        case AF_INCOMING_MSG_CMD:
        GenericApp_MessageMSGCB( MSGpkt );    
        break
         
        default:
          break;
      }
      
      // Release the memory
      osal_msg_deallocate( (uint8 *)MSGpkt );
    }
 
    // return unprocessed events
            
    return (events ^ SYS_EVENT_MSG);
  }
   
 
  // Discard unknown events
  return 0;
}
void GenericApp_SendTheMessage(void)
{
    unsigned char *theMessageData = "EndDevice received!\r\n";
     
    afAddrType_t my_DstAddr;
    my_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
    my_DstAddr.endPoint = GENERICAPP_ENDPOINT;
    my_DstAddr.addr.shortAddr =0x0000 ;
     
    AF_DataRequest(&my_DstAddr,&GenericApp_epDesc,GENERICAPP_CLUSTERID,osal_strlen((char *)theMessageData)+1,theMessageData,&GenericApp_TransID,AF_DISCV_ROUTE,AF_DEFAULT_RADIUS);
}
 
 
static unsigned char led_state = 1;
void GenericApp_MessageMSGCB(afIncomingMSGPacket_t *pkt)
{
    unsigned char buffer[20] ;
    
     
    switch(pkt->clusterId)
    {
        case GENERICAPP_CLUSTERID:
             osal_memcpy(buffer,pkt->cmd.Data,osal_strlen("Coordinator send!")+1);
              
             if(osal_memcmp(buffer,"Coordinator send!",osal_strlen("Coordinator send!")+1))
             {
                 
                if(led_state)
                {   
                    led_state = !led_state ;
                    HalLedSet(HAL_LED_4,HAL_LED_MODE_ON);
                }
                else
                {
                    led_state = !led_state ;
                    HalLedSet(HAL_LED_4,HAL_LED_MODE_OFF);
                }
                 
                 
                GenericApp_SendTheMessage();
             }
             
             break;
       
    }
}

想使用串口,还需要定义宏:

wKiom1WmD2mSUnOaAAJdpwfVFnI871.jpg

将上述代码编译后分别下载到两个Zigbee开发板上,Enddevice板上的LED灯会闪烁,Coordinator的串口会输出:“EndDevice received!\r\n”。说明程序正常运行。

通过上面的简单实验,你可能对TI的Z-Stack有了一定的感性认识,但是对代码和原理还不是特别清楚。

没关系,这是第一步,有了感性认识,再结合TI提供的开发文档和源代码,我们对原理也会有一定的认识的。

 

下面我们来看一个重要的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*********************************************************************
 * @fn      AF_DataRequest
 *
 * @brief   Common functionality for invoking APSDE_DataReq() for both
 *          SendMulti and MSG-Send.
 *
 * input parameters
 *
 * @param  *dstAddr - Full ZB destination address: Nwk Addr + End Point.
 * @param  *srcEP - Origination (i.e. respond to or ack to) End Point Descr.
 * @param   cID - A valid cluster ID as specified by the Profile.
 * @param   len - Number of bytes of data pointed to by next param.
 * @param  *buf - A pointer to the data bytes to send.
 * @param  *transID - A pointer to a byte which can be modified and which will
 *                    be used as the transaction sequence number of the msg.
 * @param   options - Valid bit mask of Tx options.
 * @param   radius - Normally set to AF_DEFAULT_RADIUS.
 *
 * output parameters
 *
 * @param  *transID - Incremented by one if the return value is success.
 *
 * @return  afStatus_t - See previous definition of afStatus_... types.
 */
uint8 AF_DataRequestDiscoverRoute = DISC_ROUTE_NETWORK;
afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP,
                           uint16 cID, uint16 len, uint8 *buf, uint8 *transID,
                           uint8 options, uint8 radius )

这个函数是用来发送Zigbee无线数据的。我们看看这个函数都需要哪些参数?

dstAddr:包括网络地址和端点号。

 

srcEP: 端点描述符

cID:  指定一个ClusterID。

len:  发送数据的长度。

*buf:  需要发送的数据内容。

 

我们知道Zigbee要发送信息,需要知道对方的短地址,端点号,还需要指定一个Cluster ID,这些信息和程序是怎么关联起来的呐?光说这些名词感觉有点抽象。

好吧,我们就一起分析一下。

我们在程序里定义了一个endPointDesc_t GenericApp_epDesc;

这个结构体变量中都有哪些内容呐?

GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;

GenericApp_epDesc.task_id  = &GenericApp_TaskID;

GenericApp_epDesc.simpleDesc =(SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc ;

有端点号,taskID,还有一个简单描述符(这个后面再说)。

你看,通过这个端点描述符把端点号和taskID联系在了一起。

接着,我们使用afRegister( &GenericApp_epDesc );向AF层注册了这个端点描述符。也就是说AF层收到发给相应端点号的数据时,会通过OSAL注册一个系统event,OSAL轮询调度会调用与之关联的taskID,这里就是GenericApp_event_loop。在这个函数里我们看到有如下代码:

wKioL1WmHLDRhBqJAAEZRP8vv5s108.jpg判断这个系统event如果是AF_INCOMING_MSG_CMD,也就是收到了AF层发来的信息,就调用GenericApp_MessageMSGCB( MSGpkt );在这个函数里:

wKiom1WmG0XC6RYgAAD08lzWupA515.jpg

判断是哪个cluster ID,根据不同的cluster ID做不同的动作。

总结一下就是:短地址用于确定发送给哪个Zigbee设备,端点号用于确定你发给这个Zigbee设备的哪个端口,在发送给这个端口中使用Cluster ID来区分不同的功能。

简单描述符:

1
2
3
4
5
6
7
8
9
10
11
12
const SimpleDescriptionFormat_t GenericApp_SimpleDesc =
{
  GENERICAPP_ENDPOINT,               //  int Endpoint; 
  GENERICAPP_PROFID,                //  uint16 AppProfId[2];
  GENERICAPP_DEVICEID,              //  uint16 AppDeviceId[2];
  GENERICAPP_DEVICE_VERSION,        //  int   AppDevVer:4;
  GENERICAPP_FLAGS,                 //  int   AppFlags:4;
  GENERICAPP_MAX_CLUSTERS,          //  uint8  AppNumInClusters;
  (cId_t *)GenericApp_ClusterList,  //  uint8 *pAppInClusterList;
  0,                                //  uint8  AppNumInClusters;
  (cId_t *)NULL                     //  uint8 *pAppOutClusterList;
};

这里面包含了端点号、profileID、deviceID等内容,这些是和profile的规范有关,zclSampleLight_InClusterList 和zclSampleLight_OutClusterList这些和binding有关系。就是建立绑定关系时,需要相应的Cluster一致。

5.ZCL和HA是什么?

 

ZCL的全称是Zigbee Cluster Library。

ZCL在zigbee中相当于Cluster功能仓库的作用。开发者开发新的Profile就必须加入相应的ZCL簇功能函数到该Profile中。

也就是说ZCL相当于一个存储命令集合的仓库。节点与节点之间,利用ZCL的命令来进行通信。

wKioL1WmI57wn6gfAAHarqd0wRs375.jpg

在Zigbee中,一个簇群就是一个容器,在容器中以命令结构体包含了一个或多个属于某个应用剖面的属性/消息,不管应用剖面如何,相同的设备(比如开关)拥有相同的定义和功能。属性是设备的变量或特性,能够设置或获得。比如设置自动调温器的加热点。ZCL提供了一种机制,利用这种机制设备能够将变化异步地报告给属性(attribute),比如当空气变热时自动控温器服务器就将室温改变报告给他的客户端,这个过程不需要客户端发起请求。

ZCL采用客户端/服务器模块的模式,一般储存簇属性的作为服务器,影响或操作属性的作为客户端。然而如果需要,属性也可以呈现在客户端上。例如,设备通过读写属性的命令来操作属性,这些命令从客户端设备发送到服务器设备;对这些命令的应答从服务器设备发送到客户端设备;但是报告属性命令是从服务器发送到客户端。cluster ID是每个簇的标志,由剖面分配,在内部使用的是逻辑簇ID,所以还有一个Cluster ID转换表。

 

Z-Stack中这一部分的代码在project的Profile文件夹中,使用Z-Stack的ZCL API请参阅TI提供的资料Document/api/Z-Stack ZCL API.pdf.

HA的全称是home Automation,家庭自动化。这个是Zigbee联盟专门为智能家居领域指定的一个规范,这个是基于Zigbee协议栈的一个应用层的规范。这个规范主要规定了智能家居产品的属性和动作,以及收到相应数据后应该做的动作。不同厂家生产的基于Zigbee的智能家居产品都可以兼容,只要他们做的产品符合HA的规范。

上个图:

上述部分说的比较简略,详细内容请参阅:文章后面的附件:ZigBee_Cluster_Library_Public.pdf和ZigBee Home AutomationPublicApplicationProfile.pdf。

6.Zigbee抓包工具(SmartRF Packet Sniffer)的使用

 Zigbee Radio层采用2.4G无线传输信息,我们希望利用工具抓到空中数据包用于学习、分析无线数据包的格式。另外,抓包还可以分析实际遇到的问题。

TI给我们提供了空中抓包方案,需要一个硬件CC2531 USB Dongle,还需要安装一个SmartRF Packet Sniffer的抓包软件。具体抓包软件使用方法请参见 SmartRF Packet Sniffer软件菜单栏---help---User’s Manual。这里不再赘述,也可以到网上搜索相关资料。

 

wKiom1WmJiziunKIAA8hK8O-T_g554.jpg上面是一张我使用上述工具抓到的数据包,可以分析Zigbee协议各个层的数据内容,协议内容。结合Zigbee Specification可以加深对Zigbee协议的理解,同样,也可以分析实际应用中遇到的问题。

 

7.总结

 Zigbee定义了从物理介质传输、网络层、应用层还有不同领域的应用规范。可以说Zigbee协议栈是一个比较大的通讯协议集合。针对Zigbee的认识和学习,我的建议是首先根据你的应用目的利用TI平台提供的各种API实现想要的功能。慢慢加深对Zigbee的认识。针对Zigbee的ZCL和HA详细的内容我也在学习当中,上述内容纯属个人见解,如有错误,欢迎指正。

文章部分附件请到:http://1801179.blog.51cto.com/1791179/1674160下载(文章最后有“附件下载”)


没有更多推荐了,返回首页