浅谈ZigBee消息机制

本章简单的介绍一下ZigBee的消息机制,如果说的有问题还请多多指正。

通俗的说,ZigBee的消息机制就好比这有一排抽屉,当我要向某个任务或者事件发送消息时,就把该消息和对应的数据放到抽屉中,在任务轮训时,某个任务轮训按个查看抽屉中是否有自己需要的消息,有就取出并且处理,没有就结束,等待下一次轮训。

ZigBee消息链表如下图所示:(注:消息机制实现代码在osal.c中)

浅谈ZigBee消息机制_第1张图片

图1 ZigBee消息队列

值得注意的是,消息队列中每条消息都有一个系统消息头,结构体为osal_msg_hdr_t,包括三个数据类型,包括下一条消息的指针、消息长度和任务ID。消息头下有一个事件消息头,结构体为osal_event_hdr_t,该消息头包括事件的掩码和状态。使用过程中特别注意,通过系统函数获取的消息指针为事件消息头(osal_event_hdr_t)对应的地址,具体可看图1。

 

下面以串口接收到数据解包后将数据发到应用层为例,来介绍一下ZigBee的消息机制是怎么实现的。

【MT_UART.c】串口接收到数据后,首先要申请该消息对应的空间内存,申请多少要看数据有多少。

App_pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof (mtOSALSerialData_t) + MT_RPC_FRAME_HDR_SZ + App_LEN_Token );

浅谈ZigBee消息机制_第2张图片

其中 [MT_RPC_FRAME_HDR_SZ + App_LEN_Token]为串口数据长度,MT_RPC_FRAME_HDR_SZ包括数据长度、命令码0、命令码1三个数据,App_LEN_Token表示应用层数据,在此不再赘述。申请的该指针返回的为mtOSALSerialData_t,对应的图2中event的地址。

 

再看osal_msg_allocate( uint16 len )这个函数。


uint8 * osal_msg_allocate( uint16 len )
{
  osal_msg_hdr_t *hdr;

  if ( len == 0 )
    return ( NULL );

  hdr = (osal_msg_hdr_t *) osal_mem_alloc( (short)(len + 							                                           sizeof( osal_msg_hdr_t )) );
  if ( hdr )
  {
    hdr->next = NULL;
    hdr->len = len;
    hdr->dest_id = TASK_NO_TASK;
    return ( (uint8 *) (hdr + 1) );
  }
  else
    return ( NULL );
}

该函数在申请内存时,添加了sizeof( osal_msg_hdr_t )长度,该长度及图1中消息时间头,并且赋予了该消息头的next指针,长度len和dest_id任务ID。函数返回的指针为hdr+1,从我们正常的角度看,即申请了一片内存,返回内存指针,在内存指针前加上了任务消息头。因此这就是在处理任务消息头时都要-1的原因。

浅谈ZigBee消息机制_第3张图片

图3 任务消息处理函数

 

这样就生成一条消息。但是该工作并没有完成,需要将该消息放入消息对垒并且通知对应的任务ID取出该消息。

 

通过查看串口代码,可发现当数据校验正确后,要调用该函数。

osal_msg_send( App_TaskID, (byte *)App_pMsg );

该函数代码如下:

uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr )
{
  if ( msg_ptr == NULL )
    return ( INVALID_MSG_POINTER );

  if ( destination_task >= tasksCnt )
  {
    osal_msg_deallocate( msg_ptr );
    return ( INVALID_TASK );
  }

  // Check the message header
  if ( OSAL_MSG_NEXT( msg_ptr ) != NULL ||
       OSAL_MSG_ID( msg_ptr ) != TASK_NO_TASK )
  {
    osal_msg_deallocate( msg_ptr );
    return ( INVALID_MSG_POINTER );
  }

  OSAL_MSG_ID( msg_ptr ) = destination_task;

  // queue message
  osal_msg_enqueue( &osal_qHead, msg_ptr );

  // Signal the task that a message is waiting
  osal_set_event( destination_task, SYS_EVENT_MSG );

  return ( SUCCESS );
}

除去一些判断信息,该函数最重要的操作有三个,赋值任务ID、消息入队列和设置任务事件。

赋值任务ID不在赘述,即将 该条消息中的dest_id赋值为要通知的任务ID。

消息入队列的操作也比较简单,结合图1,通过查找链表的链尾,将该条消息的event地址赋值给链尾消息的*next即可。

设置任务事件即置位相应的任务ID和事件掩码。

 

 

将消息如队列后,对应的取消息即为该过程的逆操作。以应用层取串口数据为例。

【sampleApp.c】应用层调用函数

MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );

该函数代码如下:

uint8 *osal_msg_receive( uint8 task_id )
{
  osal_msg_hdr_t *listHdr;
  osal_msg_hdr_t *prevHdr = NULL;
  osal_msg_hdr_t *foundHdr = NULL;
  halIntState_t   intState;

  // Hold off interrupts
  HAL_ENTER_CRITICAL_SECTION(intState);

  // Point to the top of the queue
  listHdr = osal_qHead;

  // Look through the queue for a message that belongs to the asking task
  while ( listHdr != NULL )
  {
    if ( (listHdr - 1)->dest_id == task_id )
    {
      if ( foundHdr == NULL )
      {
        // Save the first one
        foundHdr = listHdr;
      }
      else
      {
        // Second msg found, stop looking
        break;
      }
    }
    if ( foundHdr == NULL )
    {
      prevHdr = listHdr;
    }
    listHdr = OSAL_MSG_NEXT( listHdr );
  }

  // Is there more than one?
  if ( listHdr != NULL )
  {
    // Yes, Signal the task that a message is waiting
    osal_set_event( task_id, SYS_EVENT_MSG );
  }
  else
  {
    // No more
    osal_clear_event( task_id, SYS_EVENT_MSG );
  }

  // Did we find a message?
  if ( foundHdr != NULL )
  {
    // Take out of the link list
    osal_msg_extract( &osal_qHead, foundHdr, prevHdr );
  }

  // Release interrupts
  HAL_EXIT_CRITICAL_SECTION(intState);

  return ( (uint8*) foundHdr );
}

该函数就不在一一翻译了,大体意思为:

查找消息链表,链表头为osal_qHead,查看链表中消息的任务消息头,如果消息头为查找的任务ID,则跳出循环,并且将该条消息 从链表中剔除,如果没有找到,返回NULL。找到该条消息后下一步就是进行处理了。

 

到此ZigBee的消息机制基本就结束了,其中还有一些消息处理的其它函数,也比较简单,就不在一一描述了,有需要的可以自行查看。


你可能感兴趣的:(ZigBee系列)