一文理解ZigBee通信全过程(基于ZStack-CC2530-2.5.1a协议栈)

自己在word中整理的笔记,基本上详细的分析了Zigbee的原理,比较透彻,分享给大家交流参考!

目录

0概述... 2

1协议栈整体运行流程... 3

1.1. 操作系统初始化流程... 5

1.2操作系统启动... 9

2相关数据结构与函数调用... 13

3 ZigBee协议栈OSAL理解... 14

3.1 OSAL存在的目的:... 14

3.2 OSAL提供的主要功能:... 14

3.3 OSAL消息队列:... 15

3.4 OSAL添加任务:... 15

3.5 OSAL应用接口编程:... 15

4其它相关问题... 15

 

0概述

1协议栈整体运行流程

OSAL作为操作系统抽象层,是整个Z-Stack运行的基础,用户自己建立的任务和应用程序都必须在此基础上运行,那我们知道整个Z-Stack协议就是用C语言编写的,既然使用C语言编写的,那程序的入口点就是main()函数,而且整个Z-Stack都只有一个main()函数入口,那我们的入口点也是main()函数,由int main()函数开始分析:

int main( void )

{

  // Turn off interrupts

 //关闭中断

 osal_int_disable( INTS_ALL );

 

  //初始化硬件

 // Initialization for board related stuff such as LEDs

 HAL_BOARD_INIT();

 

  // Make sure supply voltage ishigh enough to run

  //电压检测,确保芯片能正常工作的电压

 zmain_vdd_check();

 

  // Initialize board I/O

 //初始化板载I/O

 InitBoard( OB_COLD );

 

  // Initialze HAL drivers

 //初始化硬件驱动

 HalDriverInit();

 

  // Initialize NV System

 //初始化NV系统

 osal_nv_init( NULL );

 

  // Initialize the MAC

 //初始化MAC

 ZMacInit();

 

 // Determine the extended address

 //确定扩展地址(64IEEE/物理地址)

 zmain_ext_addr();

 

#if defined ZCL_KEY_ESTABLISH

  // Initialize the Certicom certificate information.

 zmain_cert_init();

#endif

 

  // Initialize basic NVitems

 //初始化基本NV条目

 zgInit();

 

#ifndef NONWK

  //Since the AF isn't a task, call it's initialization routine

 afInit();

#endif

 

 // Initialize the operating system

 //初始化操作系统

  osal_init_system();

 

  // Allow interrupts

 //使能中断

 osal_int_enable( INTS_ALL );

 

  // Final board initialization

 //最终板载初始化

 InitBoard( OB_READY );

 

  // Display informationabout this device

 //显示设备信息

 zmain_dev_info();

 

  /*Display the device info on the LCD */

#ifdef LCD_SUPPORTED

 zmain_lcd_init();

#endif

 

#ifdef WDT_IN_PM1

  /*If WDT is used, this is a good place to enable it. */

 WatchDogEnable( WDTIMX );

#endif

 

  osal_start_system(); // No Return from here没有返回,即进入操作系统

 

 return 0;  // Shouldn't get here.//不会运行到这

}

 

 

1.1. 操作系统初始化流程

 在这个函数中初始化了操作系统用到的重要信息,比如:内存,堆栈等,但我们重点关注的是初始化系统任务函数osalInitTasks();

uint8 osal_init_system( void )

{

  // Initialize the Memory Allocation System

 //初始化内存分配系统

 osal_mem_init();

 

  // Initialize the message queue

 //初始化消息队列  任务之间的通信靠的就是消息队列

 osal_qHead = NULL;

 

  // Initialize the timers

 //初始化定时器

 osalTimerInit();

 

  // Initialize the Power Management System

 //初始化电源管理系统

 osal_pwrmgr_init();

 

  // Initialize the system tasks.

 //初始化系统任务,重点关注

 osalInitTasks();

 

  // Setup efficient search for the first free block of heap.

 //设置有效的查找堆上的第一个空闲块

 osal_mem_kick();

 

 return ( SUCCESS );

}

 

 

任务初始化函数-------osalInitTasks();

void osalInitTasks( void )

{

 uint8 taskID = 0;

  //osal_mem_alloc()该函数是OSAL中的内存管理函数,是一个存储分配函数,返回指向一个缓存的指针,参数是被分配缓存的大小,其tasksCnt的定义如下const uint8tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] );tasksEvents指向被分配的内存空间,这里注意tasksArr[]函数指针数组的联系是一一对应的。tasksEvents就是指向第一个被分配的任务的内存空间

 tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);

 

  //把申请的内存空间全部设置为0tasksCnt任务数 * 单个任务占的内存空间(4byte

 osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));

 

  //下面就是Z-Stack协议栈中,从MAC层到ZDO层的初始化函数,其中的参数都是任务的ID,不过ID号是依次递增的

  macTaskInit(taskID++ ); //mac_ID = 0

 nwk_init( taskID++ ); //nwk_ID = 1

 Hal_Init( taskID++ ); //Hal_ID = 2

 

#if defined( MT_TASK )

 MT_TaskInit( taskID++ );//mt_ID = 3

#endif

 APS_Init( taskID++ ); //APS_ID =4

#if defined ( ZIGBEE_FRAGMENTATION )

  APSF_Init(taskID++ ); //ZDO_ID =5

#endif

 ZDApp_Init( taskID++ ); ;//ZDO_ID =6

#if defined ( ZIGBEE_FREQ_AGILITY ) ||defined ( ZIGBEE_PANID_CONFLICT )

 ZDNwkMgr_Init( taskID++ ); //ZDO_ID =7

#endif

 GenericApp_Init( taskID ); //ZDO_ID =8

}

 

注意:任务初始化,就是为系统的各个任务分配存储空间,当然,这个空间初始化时为全0(NULL),然后为各任务分配taskID;这里的顺序要注意.系统主循环函数里tasksEvents[ idx]tasksArr[ idx]idx与这里taskID是一一对应关系。我们可以在OSAL_GenericApp.c文件中看到有下面的一个数组的定义,数组中的每个成员都是一个函数,这里的函数和上面的初始化顺序是一样的,也是和tasksEvents所指向的任务的内存地址一保持一致。在这个数组中我们仍然重点观注的是最后一个GenericApp_ProcessEvent,这个是用户应用层任务处理函数。

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_ProcessEvent

};

指针数组tasksEvents[ ]里面最终分别指向的是各任务存储空间指针数组tasksArr[]里面最终分别指向的是各任务事件处理函数这两个指针数组里面各元素的顺序要一一对应,因为后面需要相应任务调用相应事件处理函数.

      由于MAC层和NWK层是不开源的,这里面的代码我们看不到,还是重点看一个用户的任务函数吧,

 

void GenericApp_Init( uint8 task_id )

{

  //OSAL分配的任务ID,从前面可知这里是8

 GenericApp_TaskID = task_id;

 

//设备状态的初始化,DEV_INIT表示设备初始化并且没有任何的连接

 GenericApp_NwkState = DEV_INIT; GenericApp_TransID = 0;

 

     halUARTCfg_tuartConfig;//定义一个串口结构体

     uartConfig.configured       =TRUE;//使能串口结构体

     uartConfig.baudRate        =HAL_UART_BR_9600;//定义串口波特率

     uartConfig.flowControl=FALSE; //流控为FALSE,大家在与其他单片机设备串

                              //口对接时,也是要设置为FALSE的

     uartConfig.callBackFunc       =   rxCB;

     HalUARTOpen(0,&uartConfig);//打开串口0

 

  // Devicehardware initialization can be added here or in main() (Zmain.c).

  // If thehardware is application specific - add it here.

  // If the hardwareis other parts of the device add it in main().

  //发送模式:广播

 GenericApp_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;

 

  //指定的端点号EP20

 GenericApp_DstAddr.endPoint = GENERICAPP_ENDPOINT;

  //指定的网络短地址为广播地址

GenericApp_DstAddr.addr.shortAddr= 0xFFFF;

 

  // Fill out the endpoint description.

  //定义设备用来通信的APS层的端点描述符

 GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;//EP号,EP=20

 GenericApp_epDesc.task_id = &GenericApp_TaskID;//任务ID号,这里是8

 

  // GenericApp EP的简单描述符

 GenericApp_epDesc.simpleDesc

            =(SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc;  GenericApp_epDesc.latencyReq = noLatencyReqs;

 

  // Register the endpoint description with the AF

  //AF层注册EP描述符,应用程序中的每一个EP都必须使用该函数进行注册,告诉应用层有一个EP已经可以使用了

  afRegister(&GenericApp_epDesc );

 

  // Register for all key events - This app will handle all keyevents

  //注册所有按键事件

 RegisterForKeys( GenericApp_TaskID );

 

  // Update thedisplay

#if defined ( LCD_SUPPORTED )

 HalLcdWriteString( "GenericApp", HAL_LCD_LINE_1 );

#endif

}

注意:在上面的程序中有这样一句话,GenericApp_NwkState = DEV_INIT;其中, GenericApp_NwkState是下面这样一个枚举类型,这句话主要是初始化应用设备的网络类型,设备类型的改变都要产生一个事件—ZDO_STATE_CHANGE,说明ZDO状态发生了改变。所以在设备初始化的时候一定要把它初始化为DEV_INIT状态。那么它就要去检测整个环境,看是否能重新建立或者加入存在的网络。但是有一种情况例外,就是当NV_RESTORE被设置的时候(NV_RESTORE是把信息保存在非易失存储器中),那么当设备断电或者某种意外重启时,由于网络状态存储在非易失存储器中,那么此时就只需要恢复其网络状态,而不需要重新建立或者加入网络了*/

 

typedef enum

{

  DEV_HOLD,               // Initialized - not startedautomatically

  DEV_INIT,               // Initialized - not connectedto anything

 DEV_NWK_DISC,           // Discovering PAN's to join

 DEV_NWK_JOINING,        // Joininga PAN

 DEV_NWK_REJOIN,         //ReJoining a PAN, only for end devices

 DEV_END_DEVICE_UNAUTH,  // Joinedbut not yet authenticated by trust center

 DEV_END_DEVICE,         // Startedas device after authentication

 DEV_ROUTER,             // Devicejoined, authenticated and is a router

 DEV_COORD_STARTING,     // Startedas Zigbee Coordinator

 DEV_ZB_COORD,           // Startedas Zigbee Coordinator

 DEV_NWK_ORPHAN          // Devicehas lost information about its parent..

} devStates_t;

 

 

上面的代码中还有一个很重要的数据结构,GenericApp_DstAddr.addr.shortAddr= 0xFFFF;它其实就是数据发送模式和地址等参数的设置,

 

typedef struct

{

  union

  {

    uint16      shortAddr;

    ZLongAddr_textAddr;

  } addr;

  afAddrMode_taddrMode;

  uint8endPoint;

  uint16panId;  // used for the INTER_PAN feature

} afAddrType_t;

 

GenericApp的例子中,应用层提供了两中发送数据的方式,一种是周期性发送,一种是Flash发送。 用户应用任务初始化大致是:在osalInitTasks( void )函数中添加应用初始化函数SampleApp_Init( taskID )。然后在应用初始化函数中,设置本应用发送数据的方式和目的地址寻址模式,登记注册本应用所用到的端点,以及配置相关发送模式所需的参数.

   所有初始化结束以后开始进入osal_start_system(),也就是真正的开始启动操作系统.

 

 

 

 

1.2操作系统启动

 

void osal_start_system( void )

{

#if !defined ( ZBIT ) && !defined (UBIT )

     for(;;) // Forever Loop

#endif

     {

          osal_run_system();

     }

}

下面分析osal_run_system( )函数

 

void osal_run_system( void )

{

  uint8 idx = 0;

 

 osalTimeUpdate();

 Hal_ProcessPoll();

 

  do {

    if (tasksEvents[idx])  // Task is highest priority that is ready.

    {

      break;

    }

  } while (++idx < tasksCnt);

 

  if (idx < tasksCnt)

  {

    uint16 events;

    halIntState_t intState;

 

    HAL_ENTER_CRITICAL_SECTION(intState);

    events = tasksEvents[idx];

    tasksEvents[idx] = 0;  // Clear the Events for this task.

    HAL_EXIT_CRITICAL_SECTION(intState);

 

    activeTaskID = idx;

    events = (tasksArr[idx])( idx, events );

    activeTaskID = TASK_NO_TASK;

 

    HAL_ENTER_CRITICAL_SECTION(intState);

    tasksEvents[idx] |= events;  // Add back unprocessed events to the currenttask.

   HAL_EXIT_CRITICAL_SECTION(intState);

  }

#if defined( POWER_SAVING )

  else  // Complete pass through all task events withno activity?

  {

   osal_pwrmgr_powerconserve();  //Put the processor/system into sleep

  }

#endif

 

  /* Yield incase cooperative scheduling is being used. */

#if defined (configUSE_PREEMPTION) &&(configUSE_PREEMPTION == 0)

  {

   osal_task_yield();

  }

#endif

}

 

 

osal_start_system( )函数是ZigBee协议栈的灵魂,实现的方法是不断查询事件表,如果有事情发生就调用相应的事件处理函数。

events = (tasksArr[idx])( idx, events );是指调用事件处理函数去处理。这是因为

tasksArr[]是一个函数指针数组,每一个元素都是函数指针!在事件处理函数

uint16 GenericApp_ProcessEvent( uint8 task_id, uint16events )中又调用消息处理函数,比如协调器或路由器或者终端节点常用的GenericApp_MessageMSGCB( MSGpkt )接收数据处理函数函数,

 

GenericApp_SendTheMessage( )数据发送函数

 

GenericApp_HandleKeys(参数略); 按键处理函数

 

 

GenericApp_ProcessEvent函数实现的基本方法是:使用osal_msg_receive函数从消息队列接收一个消息(在该消息中包含了事件以及接收到的数据),然后使用switch-case语句判断时间类型,进而根据事件类型,调用相应的事件处理函数。

 

uint16 GenericApp_ProcessEvent( uint8 task_id, uint16events )

{

 afIncomingMSGPacket_t *MSGpkt;

  afDataConfirm_t*afDataConfirm;

 

  // DataConfirmation message fields

  byte sentEP;

  ZStatus_tsentStatus;

  bytesentTransID;       // This should matchthe value sent

 (void)task_id;  // Intentionallyunreferenced parameter

 

  if ( events& SYS_EVENT_MSG )

  {

    MSGpkt =(afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );

    while (MSGpkt )

    {

      switch (MSGpkt->hdr.event )

      {

        caseZDO_CB_MSG:

         GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );

          break;

 

        caseKEY_CHANGE:

         GenericApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t*)MSGpkt)->keys );

          break;

 

        caseAF_DATA_CONFIRM_CMD:

          //This message is received as a confirmation of a data packet sent.

          // Thestatus is of ZStatus_t type [defined in ZComDef.h]

          // Themessage fields are defined in AF.h

         afDataConfirm = (afDataConfirm_t *)MSGpkt;

          sentEP= afDataConfirm->endpoint;

         sentStatus = afDataConfirm->hdr.status;

         sentTransID = afDataConfirm->transID;

         (void)sentEP;

         (void)sentTransID;

 

          //Action taken when confirmation is received.

          if (sentStatus != ZSuccess )

          {

            //The data wasn't delivered -- Do something

          }

          break;

 

        caseAF_INCOMING_MSG_CMD:

         GenericApp_MessageMSGCB( MSGpkt );

          break;

 

        caseZDO_STATE_CHANGE:

         GenericApp_NwkState = (devStates_t)(MSGpkt->hdr.status);

          if ((GenericApp_NwkState == DEV_ZB_COORD)

              ||(GenericApp_NwkState == DEV_ROUTER)

              ||(GenericApp_NwkState == DEV_END_DEVICE) )

          {

            //Start sending "the" message in a regular interval.

            osal_start_timerEx( GenericApp_TaskID,

                               GENERICAPP_SEND_MSG_EVT,

                               GENERICAPP_SEND_MSG_TIMEOUT );

          }

          break;

 

        default:

          break;

      }

 

      // Releasethe memory

     osal_msg_deallocate( (uint8 *)MSGpkt );

 

      // Next

      MSGpkt =(afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );

    }

 

    // returnunprocessed events

    return(events ^ SYS_EVENT_MSG);

  }

 

  // Send amessage out - This event is generated by a timer

  //  (setup in GenericApp_Init()).

  if ( events& GENERICAPP_SEND_MSG_EVT )

  {

    // Send"the" message

   //GenericApp_SendTheMessage();

   GenericApp_Send_wenshidu_Message();

 

    // Setup tosend message again

   osal_start_timerEx( GenericApp_TaskID,

                       GENERICAPP_SEND_MSG_EVT,

                       GENERICAPP_SEND_MSG_TIMEOUT );

 

    // returnunprocessed events

    return(events ^ GENERICAPP_SEND_MSG_EVT);

  }

 

  // Discardunknown events

  return 0;

}

 

 

Zigbee数据通信
Zigbee发送数据时进入自定义事件GenericApp _ProcessEvent()后调用函数GenericApp _SendTheMessage(),进而此函数调用AF_DataRequest()数据发送函数


Zigbee接收到数据,则进入自定义事件GenericApp _ProcessEvent()后,会触发能接收及处理数据包的函数GenericApp _MessageMSGCB()

 

2相关数据结构与函数调用

ZigBee通信流程概述

第一步:设备建立网络

第二步:设备加入网络

第三步:数据发送

第四步:数据接收

第五步:其他的问题

一、数据传输实验

 

1、协调器编程

Coordinator.h

相应的头文件

 

Coordinator.c

 

(1)包含相应的头文件

(2)       const cId_t GenericApp_ClusterList[GENERICAPP_MAX_CLUSTERS] =

{

                         GENERICAPP_CLUSTERID

};

(3)描述一个ZigBee设备节点

         constSimpleDescriptionFormat_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,          // byte  AppNumInClusters;

                (cId_t*)GenericApp_ClusterList,  //  byte *pAppInClusterList;

                GENERICAPP_MAX_CLUSTERS,          // byte  AppNumInClusters;

                (cId_t*)GenericApp_ClusterList   //  byte *pAppInClusterList;

 

};

 

3 ZigBee协议栈OSAL理解

3.1 OSAL存在的目的:

在Zigbee协议中可以找到使用OSAL的某些“根源”。在基于Zigbee协议栈的应用程序开发中,用户只需要实现应用层的程序开发即可。因应用程序框架包含了最多240个应用程序对象,每个应用程序对象运行在不同的端口上,因此,端口的作用是区分不同的应用程序对象。可以把一个应用程序对象看成为一个任务,因此需要一个机制来实现任务的切换、同步和互斥,这就是OSAL产生的根源。

OSAL并非真正的操作系统,但实现了操作系统的某些功能,如任务切换,内存管理功能。

 

3.2 OSAL运行机理:

*********************************************************************

Zigbee协议栈中,两个重要的函数

       GenericApp_Init(uint8 task_id )是任务初始化函数

GenericApp_ProcessEvent( uint8 task_id,uint16 events )函数负责处理传递给此任务的事件,其主要功能是判断由参数传递的事件类型,然后执行相应的事件处理函数。

*********************************************************************

 

 

 

 

 

*********************************************************************

Zigbee协议栈中,事件和事件处理函数如何建立的联系

建立一个事件表,保存各个任务对应的事件,建立另外一个函数表,保存各个任务事件处理函数的地址,然后将两张表建立某种对应关系,当某一事件发生时,则查询函数表找到对应的事件处理函数即可。

*********************************************************************

 

 

 

 

 

*********************************************************************

Zigbee协议栈中,有三个变量至关重要

 

       tasksCnt----------该变量保存任务的总个数

       tasksEvent-------这是一个指针,指向了事件表的首地址

tasksArr-----------这是一个指针数组,数组的每一项都指向了一个函数指针,指向了事件处理函数。

*********************************************************************

 

 

 

 

 

*********************************************************************

OSAL工作原理总结

      通过指针访问事件表的每一项,如果有事情发生,则查找函数表找到事件处理函数进行处理,处理完成后,继续访问事件表,查看是否有事件发生,无限循环。

      事件表使用数组实现,数组额每一项对应一个任务的事件,每一位表示一个事件;函数表使用函数指针数组实现,数组的每一项是一个函数指针,指向事件处理函数。

      从这种意义上说,OSAL是一种基于事件驱动的轮询式操作系统。事件驱动是指发生事件后采取相应的事件处理函数,轮询是指不断的查看是否有事件发生。

 

 

 

 

 

 

*********************************************************************

Question1:访问共享资源

void osal_start_system( void )

{

#if !defined ( ZBIT ) && !defined (UBIT )

     for(;;) // Forever Loop

#endif

     {

          osal_run_system();

     }

}

下面分析osal_run_system( )函数

 

void osal_run_system( void )

{

  uint8 idx = 0;

 

 osalTimeUpdate();

 Hal_ProcessPoll();

 

  do {

    if (tasksEvents[idx])  // Task is highest priority that is ready.

    {

      break;

    }

  } while (++idx < tasksCnt);

 

  if (idx < tasksCnt)

  {

    uint16 events;

    halIntState_t intState;

 

    HAL_ENTER_CRITICAL_SECTION(intState);

    events = tasksEvents[idx];

    tasksEvents[idx] = 0;  // Clear the Events for this task.

    HAL_EXIT_CRITICAL_SECTION(intState);

 

    activeTaskID = idx;

    events = (tasksArr[idx])( idx, events );

    activeTaskID = TASK_NO_TASK;

 

    HAL_ENTER_CRITICAL_SECTION(intState);

    tasksEvents[idx] |= events;  // Add back unprocessed events to the currenttask.

   HAL_EXIT_CRITICAL_SECTION(intState);

  }

#if defined( POWER_SAVING )

  else  // Complete pass through all task events withno activity?

  {

   osal_pwrmgr_powerconserve();  //Put the processor/system into sleep

  }

#endif

 

  /* Yield incase cooperative scheduling is being used. */

#if defined (configUSE_PREEMPTION) &&(configUSE_PREEMPTION == 0)

  {

   osal_task_yield();

  }

#endif

}

HAL_ENTER_CRITICAL_SECTION(intState);

HAL_EXIT_CRITICAL_SECTION(intState);

在访问共享资源时,需要保证该变量不被其它任务同时访问,此处是采用关中断的方法。

events= (tasksArr[idx])( idx, events );是调用事件处理函数。

*********************************************************************

 

 

 

 

*********************************************************************

Question2:如何在事件处理函数中,返回未处理的事件:

    GenericApp_ProcessEvent(uint8 task_id, uint16 events )函数的返回值 语句是return(events ^ GENERICAPP_SEND_MSG_EVT);

 

在void osal_start_system( void )函数中,tasksEvents[idx]|= events;

所以当同时发生了串口接收和读取温度事件时,events=0b00000101,即0x5。假设处理完串口接收,则应将第0位清零,即events^0x01=0x5^0x1=0x4.

事件的表示:

                     串口接收新数据;0x01

                     接收到无线数据;0x02

                     读取温度数据 ;0x04

*********************************************************************

 

3.3 OSAL消息队列:

消息和事件的区别

       事件是驱动任务去执行某些操作的的条件,当系统中产生一个事件,OSAL将这个事件传递给相应的任务后,任务才能执行一个相应的操作(调用事件处理函数去处理)。因事件发生时常伴随某些信息,比如数据,所以将事件和数据封装成一个消息,然后再在事件处理函数中使用osal_msg_receive()函数接收消息,并作相应的消息判断,比如AF_INCOMING_MSG_CMD消息,然后调用消息处理函数,比如

caseAF_INCOMING_MSG_CMD:

                         GenericApp_MessageMSGCB( MSGpkt);

                         break;

OSAL维护了一个消息队列,每个消息都会被放到这个消息队列中去,当任务接收到事件后,可以从消息队列中获取属于自己的消息,然后调用相应的消息处理函数即可。

 

注意区分:事件表、函数表、消息队列

Zstack中任务,事件,消息之间的关系

 

  Zstack是Zigbee协议的具体实现,在实现的过程中为了能够更好的对各个模块和功能进行管理,所以加入了OSAL(OperatingSystem Abstraction Layer 操作系统抽象层)(以上为个人见解)。

 

  在OSAL中,提出了任务,事件,消息三个概念。它们之间的关系如下:

 

    任务是处理事件的一个功能集合,一般由一个事件处理函数实现(ProcessEvents)

 

    事件是当前系统中被触发的动作,例如有按键按下,接收到其他节点发送过来的消息等等

 

    消息则是事件编号和其对应事件内容的集合,发生事件并将其打包到消息队列,供事件处理函数进行处理

 

    大致的流程如下:

 

    事件发生后-->被打包为消息-->存放到消息队列-->事件处理函数取出消息并进行相应操作。

 

3.4 OSAL添加任务:

添加新任务,只需编写两个函数:

 

新任务的初始化函数;GenericApp_Init( uint8 task_id )

新任务的事件处理函数;GenericApp_ProcessEvent( uint8 task_id, uint16 events)

将任务初始化函数添加在osal_Init_Tasks函数的最后;

将事件处理函数地址加入tasksArr[]

3.5 OSAL应用接口编程:

         OSAL提供了8个方面的共计数十个API函数:

                                                        消息管理

                                                        任务管理

                                                        时间管理

                                                        中断管理

                                                        任务管理

                                                        内存管理

                                                        电源管理

osal_msg_receive()就属于消息管理。

时间管理中的API可实现定时功能。

Main函数中就调用了绝大多数API函数,比如任务管理的osal_init_system()和osal_start_system()等等。

4其它相关问题

回调函数和事件处理函数的区别:

当在PC与协调器进行串口通讯时,PC向串口发送数据,这是会触发回调函数。

而无线数据的收发以及串口向PC发送数据都不会触发回调函数,这时调用的是事件处理函数。

串口回调函数触发条件总结

 

 

ZigBee分层与文件目录的关系:

协议栈的文件包层次结构:

App:应用层目录,这是用户创建各种不同工程的区域,在这个目录中包含了应用层的内容和这个项目的主要内容,在协议中一般是以操作系统的任务实现的。

 HAL:硬件层目录,包含有与硬件相关的配置和驱动及操作函数

 MAC:MAC层目录,包含了MAC层的参数配置文件及其MAC的LIB库的函数接口文件

 MT:实现通过串口可控制各层,并与各层进行直接交互

 NWK:网络层目录,包含网络层配置参数文件网络层库的函数接口文件及APS层库的函数接口

 OSAL:协议栈的操作系统

 Profile:AF(Applicationframework应用框架)层目录

 

 Security:安全层目录,包含安全层处理函数,比如加密函数等

 Services:地址处理函数目录,包括地址模式的定义及地址处理函数

 Tools: 工程配置目录,包括空间划分及Z-Stack相关配置信息

 ZDO:ZDO目录

 ZMac:MAC层目录,包括MAC层参数配置及MAC层LIB库函数回调处理函数

 ZMain:主函数目录,包括入口函数及硬件配置文件

 Output:输出文件目录,由IAR IDE自动生成

 

端点(EndPoint):是协议栈应用层的入口,即入口地址,也可以理解应用对象(

Application Object)存在的地方,它是为实现一个设备描述而定义的一组群集

端点0 :用于整个ZigBee设备的配置和管理,附属在端点0的对象被称为ZigBee设备对象(ZD0)

端点255:用于向所有的端点进行广播,端点241~254:保留端点

其他端点:映射应用对象,并使得应用程序可以跟ZigBee堆栈其他层进行通信。

簇(Cluster):一个具体的应用(例如智能家居系统)有大量细节上的小规范(例如

电灯的控制:开灯、关灯等),这个规范即成为簇(cluster)

 

协调器:协调器是整个网络的核心,它最主要的作用是启动网络,其方法是其方法是选择一个相对空闲的信道,形成一个PANID。

路由器:路由器的主要功能是提供接力作用,能扩展信号的传输范围,因此一般情况下应该一直处于活动状态,不应休眠。终端设备可以睡眠也可以唤醒,因此可以用电池来供电。

信道:  2.4GHz的射频频段被分为16个独立的信道。每一个设备都有一个默认的信道集(DEFAULT_CHANLIST)。协调器扫描自己的默认信道并选择噪声最小的信道作为自己所建的网络信道。设备节点和路由器也要扫描默认信道集并选择信道上已经存在的网络加入。

PANID: PANID指网络编号,用于区分不同的网络设备,PANID值与ZDAPP_CONFIG_PAN_ID的值设定有关。如果协调器的ZDAPP_CONFIG_PAN_ID设置为0xFFFF,则协调器将产生一个随机的PANID,如果路由器和终端节点的ZDAPP_CONFIG_PAN_ID设置为0xFFFF,路由器和终端节点将会在自己默认信道上随机的选择一个网络加入,网络协调器的PANID即为自己的PANID。如果协调器的ZDAPP_CONFIG_PAN_ID设置为非0xFFFF值,则协调器根据自己的网络长地址(IEEE地址)或ZDAPP_CONFIG_PAN_ID随机产生PANID的值。不同的是如果路由器和终端节点的ZDAPP_CONFIG_PAN_ID 的值设置为非0xFFFF,则会以ZDAPP_CONFIG_PAN_ID值作为PANID。如果协调器的值设为小于等于0x3FFF的有效值,协调器就会以这个特定的PANID值建立网络,但是如果在默认信道上已经有了该PANID值的网络存在,则协调器会继续搜寻其它的PANID,直到找到不冲突的网络为止,这样就可能产生一个问题如果协调器在默认信道上发生PANID冲突而更换PANID,终端节点并不知道协调器已经更换了PANID,还会继续加入到PANID为ZDAPP_CONFIG_PAN_ID值的网络中。

 

路由器和终端节点的区别:

 

 

用户自己添加的应用任务程序在Zstack中的调用过程是:

(1).main() 执行(ZMain.c)

main() --->osal_init_system()

(2). osal_init_system()调用osalInitTasks(), (OSAL.c)

osal_init_system()---> osalInitTasks()

(3). osalInitTasks()调用SampleApp_Init() , (OSAL_SampleApp.c)

osalInitTasks()---> SampleApp_Init()

osalInitTasks()中实现了多个任务初始化的设置,其中macTaskInit( taskID++ )ZDApp_Init( taskID++ )的几行代码表示对于几个系统运行初始化任务的调用,而用户自己实现的SampleApp_Init()在最后,这里taskID随着任务的增加也随之递增.所以用户自己实现的任务的初始化操作应该在osalInitTasks()中增加.

 

你可能感兴趣的:(★Linux系统及应用开发)