ZStack中的编程技巧

1. 像函数一样使用的宏

//这个宏,用来被其他宏使用,构造一个正确有效的表达式。这个适合于一些离散语句的组合,不适合函数的重新命名

#define st(x)      do { x } while (__LINE__ == -1)

例如:#define aps_GroupsRemaingCapacity() ( APS_MAX_GROUPS - aps_CountAllGroups() )

上述的这个宏,调用的其他函数来实现其功能,因此,不适合使用st()宏。

使用场景:  aps_GroupsRemaingCapacity(); 或者   aps_GroupsRemaingCapacity() 当做函数的参数。

例如:#define AES_SET_MODE(mode)      st( ENCCS &= ~0x70; ENCCS |= mode; )

上述这个宏,使用st宏来嵌套,功能为设置AES加密模式。

使用场景: 向个函数一样调用 AES_SET_MODE(AES_MODE_CTR); 即可。

2. 消息队列中的存储分配机制

在ZStack中,每个任务都在OSAL的大循环中轮训处理,任务与任务之间,任务与OSAL之间,是通过消息来传递信息的。每一个任务都对于一系列事件,系统发给每个任务的事件为UINT16 events,它采用独热码的机制,2个字节,一共可以处理16个事件。

#define SYS_EVENT_MSG             0x8000    //系统任务事件
    #define ZDO_CB_MSG                0xD3      //系统任务事件里面的ZDO消息
    #define AF_DATA_CONFIRM_CMD       0xFD      //系统任务事件里面的接收数据指示消息
    #define AF_INCOMING_MSG_CMD       0x1A      //系统任务事件里面的接收数据消息
    #define ZDO_STATE_CHANGE          0xD1      //系统任务事件里面的ZDO状态改变消息
    #define KEY_CHANGE                0xC0      //系统任务事件里面的按键消息

其他事件为用户自定义事件,可以使用的值为0x4000,0x2000,0x1000,0x8000...依次类推,供有15个自定义事件,可以通过
    osal_start_timerEx( ZDAppTaskID, ZDO_NETWORK_INIT, delay );      //延迟发起事件
    osal_set_event( ZDAppTaskID, ZDO_NETWORK_INIT );                 //立刻发起事件
来进行通知。

2.1 消息分配

函数原型: uint8 * osal_msg_allocate( uint16 len )

使用场景:任务调用此函数用于分配指定长度的消息缓冲。实际上,分配的长度为len加上消息头的长度

消息头结构体如下:

typedef struct
{
  void   *next;              //指向下一个消息
  uint16 len;
  uint8  dest_id;           //指向当前的任务ID,分配消息时,初始化为TASK_NO_TASK
} osal_msg_hdr_t;           //消息头结构体

typedef struct
{
  uint8  event;
  uint8  status;
} osal_event_hdr_t;        //事件结构体

osal_msg_allocate实际返回的指针为指向len长度的地址空间。 实际使用时,返回值经常被强制类型转换,以下面的函数为例子说明:

/*********************************************************************
 * @fn          ZDO_SendMsgCBs
 *
 * @brief       This function sends messages to registered tasks.
 *              Local to ZDO and shouldn‘t be called outside of ZDO.
 *
 * @param       inMsg - incoming message
 *
 * @return      TRUE if sent to at least 1 task, FALSE if not
 */
uint8 ZDO_SendMsgCBs( zdoIncomingMsg_t *inMsg )
{
  uint8 ret = FALSE;
  ZDO_MsgCB_t *pList = zdoMsgCBs;
  while ( pList )
  {
    if ( pList->clusterID == inMsg->clusterID )                                                        //对比接收到数据中的簇ID和ZDO中注册的簇ID
    {
      zdoIncomingMsg_t *msgPtr; 

      // Send the address to the task
      msgPtr = (zdoIncomingMsg_t *)osal_msg_allocate( sizeof( zdoIncomingMsg_t ) + inMsg->asduLen );   //分配消息缓存,包含消息头和数据长度
      if ( msgPtr )
      {
        // copy struct
        osal_memcpy( msgPtr, inMsg, sizeof( zdoIncomingMsg_t ));                                       //分配成功,拷贝相关内容到消息中

        if ( inMsg->asduLen )
        {
          msgPtr->asdu = (byte*)(((byte*)msgPtr) + sizeof( zdoIncomingMsg_t ));
          osal_memcpy( msgPtr->asdu, inMsg->asdu, inMsg->asduLen );
        }

        msgPtr->hdr.event = ZDO_CB_MSG;                                                                //声明此消息的事件类型
        osal_msg_send( pList->taskID, (uint8 *)msgPtr );                                                //将此消息发送给指定的任务ID
        ret = TRUE;
      }
    }
    pList = (ZDO_MsgCB_t *)pList->next;                                                                // 与当前ZDO中的簇ID不符,指向下一个ZDO的注册消息
  }
  return ( ret );
}

在osal_msg_send中,用到了几个宏,

//获得msg_ptr所指向的下一个消息

#define OSAL_MSG_NEXT(msg_ptr)      ((osal_msg_hdr_t *) (msg_ptr) - 1)->next

//获得msg_ptr所指向的任务(消息分配时,初始化为TASK_NO_TASK)

#define OSAL_MSG_ID(msg_ptr)      ((osal_msg_hdr_t *) (msg_ptr) - 1)->dest_id

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 );
}
/*********************************************************************
 * @fn      osal_set_event
 *
 * @brief
 *
 *    This function is called to set the event flags for a task. The
 *    event passed in is OR‘d into the task‘s event variable.
 *
 * @param   uint8 task_id - receiving tasks ID
 * @param   uint8 event_flag - what event to set
 *
 * @return  SUCCESS, INVALID_TASK
 */
uint8 osal_set_event( uint8 task_id, uint16 event_flag )
{
  if ( task_id < tasksCnt )
  {
    halIntState_t   intState;
    HAL_ENTER_CRITICAL_SECTION(intState);    // Hold off interrupts
    tasksEvents[task_id] |= event_flag;      // Stuff the event bit(s) 给当前的task赋予事件值,以便于在OSAL大循环中可以处理
    HAL_EXIT_CRITICAL_SECTION(intState);       // Release interrupts
    return ( SUCCESS );
  }
   else
  {
    return ( INVALID_TASK );
  }
}

在对于任务的事件处理中,需要用到osal_msg_receive(task_id)函数,从指定事件下获取对于的消息。然后循环取消息,循环处理,直到SYS_EVENT_MSG中的消息都处理完了才会退出。

系统事件下的消息类型结构体定义,在开始的部分都采用添加了事件头,便于后级循环处理。

OSAL事件头  事件内部的消息头结构,不同的消息有不同的数据内容,但有一点是相同的,就是消息头结构体的第一个元素一定是OSAL事件头。

以上大概就是OSAL的消息机制。网上有很多其他资料整理的很好,但是,总觉的不是自己的,自己写出来,才能真实的消化掉。

以按键消息为例子:OSAL的消息头(上)+OSAL的事件头(中)+事件内部的数据头(下)

,结合上图和以上的理解,是不是一下子很清楚啦。

消息入队:

3.定义变量的技巧

enum
     {
      ZDO_SRC_RTG_IND_CBID,                         // 0
      ZDO_CONCENTRATOR_IND_CBID,                // 1
      ZDO_NWK_DISCOVERY_CNF_CBID,              // 2
      ZDO_BEACON_NOTIFY_IND_CBID,               // 3
      ZDO_JOIN_CNF_CBID,                               // 4
      ZDO_LEAVE_CNF_CBID,                             // 5
      ZDO_LEAVE_IND_CBID,                             // 6
      MAX_ZDO_CB_FUNC                                 // 7(枚举变量个数,数组索引0~6)

// Must be at the bottom of the list
    };

上述枚举变量,前面都是名称的定义,取值从0开始,依次递增,后续,需要增加一个入口的话,直接在倒数第一个之前添加就可以了,其他的都不用改。

定义通用函数指针时,入口参数和出口参数最好都设置成void*类型,方便后续类型转换。

函数指针声明: 

/* ZDO Indication Callback Registration */
      typedef void* (*pfnZdoCb)( void *param );

函数指针数组定义:

pfnZdoCb zdoCBFunc[MAX_ZDO_CB_FUNC];

函数指针数组初始化

void ZDApp_InitZdoCBFunc( void )
{
  uint8 i;

  for ( i=0; i< MAX_ZDO_CB_FUNC; i++ )
  {
    zdoCBFunc[i] = NULL;
  }
}

自定义结构体:

一律采用如下的定义方式:

typedef struct                     //不需要结构体名
{
  osal_event_hdr_t hdr;
  uint8 endpoint;
  uint8 transID;
} afDataConfirm_t;           //指定重新定义的结构体名,后面加上_t,表示是一个自定义类型

4.宏定义

对于一些较长的宏定义,建议加上分行符来写,格式规范,便于检查。注意,最后一行,不能有分行符

#define SystemReset()             \
       {                                        \
       HAL_DISABLE_INTERRUPTS();   \
       HAL_SYSTEM_RESET();           \ 
       }

5. 在编译中查看宏值

采用如下的宏,可以在编译时,打印出想要查看的宏的内容。这个技巧,在之前的博文里面提到过,觉的很好用,再次再提一次。

查看已定义宏内容:

输出内容:

查看未定义宏内容:

输出内容:

一般编译器都支持message伪指令,因此,这个小技巧很好用。

如果需要对一些延迟做较为精确的计算或者对时序要求比较严格的操作,在函数上面加上禁止优化的伪指令

#pragma optimize=none    //指示不对A函数进行优化
void A()
{
}

ZStack中的编程技巧,布布扣,bubuko.com

时间: 2024-08-24 23:47:01

ZStack中的编程技巧的相关文章

在WEB工程的web层中的编程技巧

本篇以看传智播客方立勋老师的<JDBC入门>之<实现客户关系管理案例>视频有感,从中提取方老师在设计管理系统的简单案例中对自己比较有用的部分,以便日后在开发过程中希望能有所帮助. 在这个视频中,该客户关系管理系统的界面由简单的“添加客户”和“查看客户”两个超链接构成.其中“查看客户”的功能涉及使用数据库来进行网页显示的分页效果,该功能的实现请看相关博客. 在“添加客户”的功能中,方老师很灵巧的将一个Servlet的doGet方法和doPost方法一起使用,这样减少了类的定义,优化了

【VC编程技巧】窗体?3.5对单文档或者多文档程序制作启动画面

(一)概要: 文章描述了怎样通过Visual C++ 2012或者Visual C++ .NET,为单文档或者多文档程序制作启动画面.在Microsoft Visual Studio 6.0中对于单文档程序(SDI)我们可以很方便利用微软提供的组件Visual C++ Component (Splash Screen).因为在Microsoft Visual Studio 6.0以后的版本或者Visual C++ .NET没有提供这个组件,我们可以通过自定义对话框来实现Splash Screen

单片机应用编程技巧问答

1. C语言和汇编语言在开发单片机时各有哪些优缺点? 答:汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言.其主要优点是占用资源少.程序执行效率高.但是不同的CPU,其汇编语言可能有所差异,所以不易移植. C语言是一种结构化的高级语言.其优点是可读性好,移植容易,是普遍使用的一种计算机语言.缺点是占用资源较多,执行效率没有汇编高. 对于目前普遍使用的RISC架构的8bit MCU来说,其内部ROM.RAM.STACK等资源都有限,如果使用C语言编写,一条C语言指令编译

编程技巧之表格驱动编程

/* Image format-dependent operations. */ typedef struct { jas_image_t *(*decode)(jas_stream_t *in, char *opts); /* Decode image data from a stream. */ int (*encode)(jas_image_t *image, jas_stream_t *out, char *opts); /* Encode image data to a stream.

Phaser 编程技巧

2015年2月6日 欢迎! 在连续几周讨论了平台跳跃游戏的机制之后,我们也应该休息一下了.因此,本周的教程将基于论坛上经常谈论的特性:网格运动,或者可以说得更加明确一点:如何像Pacman(译者注:在红白机上叫做吃豆子,或者小精灵)那样在网格中移动. 我们本周讨论的代码可以让玩家优雅地在瓦片地图中移动,在很小的空间中转弯.我们还将构建Pacman游戏的核心代码. 获取源代码 在这里我只会着重高亮最重要的部分代码.所以请先浏览一下代码.如果你对一些代码有疑问,你可以去论坛询问. 运行/编辑汽车游戏

java命名规范和编程技巧

一个好的java程序首先命名要规范. 命名规范 定义这个规范的目的是让项目中所有的文档都看起来像一个人写的,增加可读性,方便维护等作用 Package 的命名 Package 的名字应该都是由一个小写单词组成. Class 的命名 Class 的名字必须由大写字母开头而其他字母都小写的单词组成 Class 变量的命名 变量的名字必须用一个小写字母开头,后面的单词用大写字母开头. Static Final 变量的命名  Static Final 变量的名字应该都大写,并且指出完整含义. 参数的命名

jQuery中的编程范式

浏览器前端编程的面貌自2005年以来已经发生了深刻的变化,这并不简单的意味着出现了大量功能丰富的基础库,使得我们可以更加方便的编写业务代码,更重要的是我们看待前端技术的观念发生了重大转变,明确意识到了如何以前端特有的方式释放程序员的生产力.本文将结合jQuery源码的实现原理,对javascript中涌现出的编程范式和常用技巧作一简单介绍.    1. AJAX: 状态驻留,异步更新      首先来看一点历史. A. 1995年Netscape公司的Brendan Eich开发了javacri

分享25个实用的博客,有助你提高编程技巧

编程是一个不断变化的领域,一旦你选择了它作为你的职业,你就不可能停下学习的脚步了.因为科技的发展月新日异,要跟得上它发展的脚步,你必须不断地努力学习.在Forbes.com最近发表的一篇文章(25 Practical Blogs To Sharpen Your Coding Skills)中,作者Tomas Laurinavicius 推荐了25个他认为很适合程序员或学习编程的人在工作或学习中阅读的博客. 1. Scott Hanselman 在微软担任网络平台开发的Scott Hanselma

Python高效编程技巧

下面我挑选出的这几个技巧常常会被人们忽略,但它们在日常编程中能真正的给我们带来不少帮助. 1. 字典推导(Dictionary comprehensions)和集合推导(Set comprehensions) 大多数的Python程序员都知道且使用过列表推导(list comprehensions).如果你对list comprehensions概念不是很熟悉——一个list comprehension就是一个更简短.简洁的创建一个list的方法. >>> some_list = [1,