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,表示是一个自定义类型

在具体使用时, 使用 afDataConfirm_t *afDataConfirm;

4.宏定义

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

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

在宏定义中,对于一些预编译的条件判断,可以添加#error指令,来确保在编译过程中,保持条件配置的一致性。

对于一些需要使用默认配置的选项,采用如下的定义方法:

如果不做任何其他的设定,默认是开启ADC功能的。如果用户要自定义开启或者关闭,则定义

#define HAL_ADC TRUE  或者 #define HAL_ADC FALSE 就可以了。

在条件宏定义中,采用适当的缩进比例,便于检查。

看到左边的减号了吗?那是编译器提供的折叠功能,使用起来也很方便,但是,适当的缩进,还是很有必要的,便于配对检查,不多不漏。

可以在编译时,添加对过期模块的编译检查,在软件升级时,可能需要用到。

5. 在编译中查看宏值

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

查看已定义宏内容:

输出内容:

查看未定义宏内容:

输出内容:

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

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

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

6. 调试

设计一个程序,重要的是它的设计流程。

所有程序都是调试出来的,不是写出来的。如何去调试,实际上是如何却解决问题。

把问题拆开来思考,

7. 用于设置寄存器的宏

以ZStack中的hal_lcd.c中的寄存器设置为例子。

使用时,直接使用LCD_DO_WRITE就可以了,像个函数一样用它。从名字上可以看出,它是往第port的第pin个管脚写val数值。LCD_DO_WRITE宏执行的功能 就是往P0.0写1.在这里,P0_0需要有相应的定义才可以正确进行,如果移植到其他处理机,做相应的更改就可以了。

一般来说,IO线都有复用的功能,建议使用不同的宏分开设置。如下图所示:

前两个是针对普通GPIO来设置的宏,功能也是类似的,向port的pin管脚输出val值。

后两个是针对peripheral function的GPIO来设置的宏。

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

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

    本篇以看传智播客方立勋老师的<JDBC入门>之<实现客户关系管理案例>视频有感,从中提取方老师在设计管理系统的简单案例中对自己比较有用的部分,以便日后在开发过程中希望能有所帮助 ...

  2. STL中实现 iterator trail 的编程技巧

    STL中实现 iterator trail 的编程技巧 <泛型编程和 STL>笔记及思考. 这篇文章主要记录在 STL 中迭代器设计过程中出现的编程技巧,围绕的 STL 主题为 (迭代器特 ...

  3. Python 高效编程技巧实战(2-1)如何在列表,字典, 集合中根据条件筛选数据

    Python 高效编程技巧实战(2-1)如何在列表,字典, 集合中根据条件筛选数据 学习目标 1.学会使用 filter 借助 Lambda 表达式过滤列表.集合.元组中的元素: 2.学会使用列表解析 ...

  4. iOS开发中调试小技巧

    对于软件开发而言,调试是必须学会的技能,重要性不言而喻.对于调试的技能,基本上是可以迁移的,也就是说你以前在其他平台上掌握的很多调试技巧,很多也是可以用在iOS开发中.不同语言.不同IDE.不同平台的 ...

  5. EF – 2.EF数据查询基础(上)查询数据的实用编程技巧

    目录 5.4.1 查询符合条件的单条记录 EF使用SingleOrDefault()和Find()两个方法查询符合条件的单条记录. 5.4.2 Entity Framework中的内部数据缓存 DbS ...

  6. 避免Java应用中NullPointerException的技巧和最佳实践

    Java应用中抛出的空指针异常是解决空指针的最好方式,也是写出能顺利工作的健壮程序的关键.俗话说"预防胜于治疗",对于这么令人讨厌的空指针异常,这句话也是成立的.值得庆幸的是运用一 ...

  7. VC多文档编程技巧(取消一开始时打开的空白文档)

    VC多文档编程技巧(取消一开始时打开的空白文档) http://blog.csdn.net/crazyvoice/article/details/6185461 VC多文档编程技巧(取消一开始时打开的 ...

  8. java命名规范和编程技巧

    一个好的java程序首先命名要规范. 命名规范 定义这个规范的目的是让项目中所有的文档都看起来像一个人写的,增加可读性,方便维护等作用 Package 的命名 Package 的名字应该都是由一个小写 ...

  9. 无插件Vim编程技巧

    无插件Vim编程技巧 http://bbs.byr.cn/#!article/buptAUTA/59钻风 2014-03-24 09:43:46 发表于:vim  相信大家看过<简明Vim教程& ...

随机推荐

  1. HDU 1077Catching Fish(简单计算几何)

    Catching Fish Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) T ...

  2. [Bower] Bower

    //search bower search jquery bower search jquery | grep formstyler //info bower info jquery //instal ...

  3. oc-06-无参方法的调用

    // 12-[掌握]无参方法声明实现及调用 #import <Foundation/Foundation.h> //类的声明 @interface Person : NSObject { ...

  4. 描述cookie,sessionstroage,localstrage的区别

    HTML5 提供了两种在客户端存储数据的新方法(Web Storage): localStorage - 没有时间限制的数据存储 sessionStorage - 针对一个 session 的数据存储 ...

  5. Java_spark简单例子

    import org.apache.spark.{SparkContext, SparkConf} /** * Created by spark on 15-1-19. * 根据key对K-V类型的R ...

  6. [转]AFNetWorking使用笔记

    转载自:http://blog.sina.com.cn/s/blog_719d537e01017x82.html AFNetwork是一个轻量级的网络请求api类库.是以NSURLConnection ...

  7. 嵌入式 Linux 应用:概述

    转载:http://www.ibm.com/developerworks/cn/linux/embed/embl/overview/index.html   从腕表到基于群集的超级计算机 在对嵌入式 ...

  8. 小白日记35:kali渗透测试之Web渗透-手动漏洞挖掘(一)-默认安装引发的漏洞

    手动漏洞挖掘 即扫描后,如何对发现的漏洞告警进行验证. #默认安装 流传linux操作系统比windows系统安全的说法,是因为windows系统默认安装后,会开放很多服务和无用的端口,而且未经过严格 ...

  9. SelectionKey理解(总结)

    SelectKey注册了写事件,不在合适的时间去除掉,会一直触发写事件,因为写事件是代码触发的 client.register(selector, SelectionKey.OP_WRITE); 或者 ...

  10. 为什么arcgis里,鼠标的图标都变成放大镜不能用了

    做作业做到一半,鼠标的图标就只有放大镜了,不管是点箭头还是作图工具都没用,手抓的也没用,只剩下放大镜的功能和图标了,这是怎么一回事啊?种情况我碰到过几次,具体原因不清楚,但是解决方法是有的:把你的数据 ...