1.模块变量
一般来说,nesC中不提倡使用malloc或者其他C库函数来动态分配内存。这是因为大多数嵌入式微控制器没有内存保护,动态分配内存会带来一定的风险。合适的做法是在模块中通过变量来静态分配内存。模块变量定义位于模块实现代码的开始部分。
例:模块PeriodicReaderC的工作是周期性采集传感器数据。在模块PeriodicReaderC的实现部分,除了定义StdControl接口中的命令和Timer、Read接口中的事件外,在开始位置还定义一个类型为uint_16的变量lastVal,用于存放传感器数据。uint_16的关键字,后面会详细说明。
对于模块变量有三点说明。
1)模块变量定义位于模块实现部分的开始位置。模块变量定义与C变量是一致的,在定义的同时可以初始化(如下面第一行),也可以将定义与初始化分开(如下面第二行和第三行)。
2)模块变量的命名范围是组件。本模块文件范围内可以引用该变量,而其他组件不能直接引用该变量,其他组件引用lastVal的唯一方式是通过接口函数。在这个例子中,其他组件要引用lastVal,可以调用函数Read.readDone()来返回该变量的值。
3)模块变量是静态存储变量,类似于C中使用关键字static标识的变量。
除了在模块实现代码的开始位置定义模块变量外,在各函数内部也可以根据需要定义内部变量,与C函数内部变量的用法一致,这里不再赘述。
2.平台独立数据
TinyOS具有支持多种硬件平台的能力。微控制器和无线通信芯片是硬件平台的主要组成部件。无线芯片与微控制器对数据的布局(字节顺序big-endian/litttle-endian)可能有所不同。不同微控制器上处理器的地址总线宽度也可能不同,比如MSP430的地址总线是16位,Atmag128的地址总线是8位,这往往使得同一个数据结构在有的平台上能够对准边界,有的平台上却不能对准边界。而没有边界对准的字是不能加载使用的。比如TelosB平台上,针对无线通信芯片CC2420的通信分组控制头的数据结构如下所示,该数据结构的字节长度是11,为奇数。
该平台的微控制器采用的是MSP430F1611(MSP430系列中的一种),由于其地址总线宽度是16位,字节的变量必须与2字节边界对准。为了解决这个问题,MSP430编译器会在后面填充一个字节,从而实现边界对齐。但这样做也带了新的问题,即为针对CC2420设计的这个数据结构在不同平台(无线通信芯片相同,微控制器不同)上的数据结构是不相容的。如何使得TinyOS程序中的数据结构独立于硬件平台,以便于能够对数据自由访问与使用呢?
一种常用的解决无线芯片与处理器之间不同字节顺序的方法是调用宏指令来转换微控制器和无线芯片的字节顺序,如UNIX中的宏指令htons,ntohl等。但是这种方法容易出错,也不能解决边界对准的问题。
在TinyOS 1.x中,一些程序试图通过使用gcc的packed属性来解决数据结构独立于硬件平台。如例3.11所示,packed告知gcc忽略平台的结构对齐要求来紧凑封装一个数据结构。这种做法使得运行于ATmega128和x86上的代码数据结构格式保持一致,然而也存在一些问题。首先,MSP430系列的gcc版本(用于Telos系列节点)不能正确处理使用packed属性标识的数据结构;其次,packed属性是gcc特有的,因此代码可移植性不好;最后,这种方法能够解决边界对准问题,但是不能解决不同的字节顺序问题,也就是说,在存在不同字节顺序的平台上,仍然需要使用htons、ntohl等的宏转换。
例3.11:
在TinyOS 2.x中的做法是使用了nesC 1.2中的平台独立类型。对于简单数据类型而言,除了在关键字之前加上nx_(表示高字节顺序布局)或者nxle_(低字节顺序布局)前缀外,平台独立类型数据的定义(声明)与正常类型数据一致。如例3.12。
例3.12:
除了简单数据类型,也有独立于平台的结构体(struct)和共用体(union),分别用nx_struct和nx_union声明。这些数据类型中的每个域也必须是平台独立类型。非位的域以字节为单位对准边界。例如,针对无线通信芯片CC2420的通信分组控制头的平台独立的结构体如下。在所有硬件平台上编译这个结构体时都使用相同的内存布局,对结构体中的各个域都使用相同的字节顺序。这就使得平台代码可以封装和解封装该结构体,而不用使用htons、ntohl等宏指令。
对大多数nesC代码,可以忽略运行时间的开销。如例3.13。(www.xing528.com)
例3.13:
上述代码需要几个处理器时钟周期将x的字节重新排序,按照本地芯片的字节顺序形成y。需要注意的是,系统处理开销与转化次数成正比。因此,在需要对某个平台独立类型进行大量计算或者多次访问时,需要先将其转化为本地数据格式来处理。例如,要对多字节数组执行计算,应在计算前把这些数组复制到本地格式,在计算结束后再将这些数组复制回平台独立的数据中。
3.常值变量
在TinyOS程序实现中往往需要一些常量,比如,重传次数或阀值。可以采用常值变量、枚举以及#define三种方式实现,这三种方式可以达到相同的效果,但是内存分配及其他方面稍有不同。例如,定义一个最大重传次数,这三种方式声明(定义)分别如下。
方式一:常值变量
方式二:MAX_RETRANSMIT 5枚举类型的常量
方式三:宏定义#define
在C中,常值变量是一种常用方式,它属于静态存储分配,编译器会为其分配一块RAM或ROM区域,即便程序运行完,这部分存储区域也不释放。使用枚举常量(枚举元素)来定义常量也可以达到相同的目的。枚举常量在节省内存方面优于前一种方式,属于动态存储变量。相应程序函数运行结束,这部分区域可以重新使用。枚举常量在某些方面也优于第三种方式宏定义,因为枚举常量存在调试符号表和应用的元数据中。然而,枚举常量也有一些局限性,只能声明整数常量,而使用宏定义还可以定义浮点和字符串常量等。
需要注意的是,由于枚举类型默认为整数宽度,使用枚举类型声明变量可能浪费内存。例如枚举类型变量state_t的定义如例3.14。
例3.14:
程序中需要定义变量state,该变量的有效值范围是0~3。我们可以采用下述两种不同的方式来定义变量state。
方式一:
方式二:
在第一种方式下,将变量state定义为枚举类型。由于该类型变量的存储单位是本地整数宽度,在微控制器上可能是2~4个字节(依据不同微控制器而不同)。在第二个方式下,由于使用类型unit8_t,因此只分配单个字节。所以当变量所需长度比较短时,应该避免使用枚举类型声明变量。
另外,模块中的数据是局部的,模块之间通过接口函数实现数据(状态)的交互。除了这些局部数据之外,nesC程序中还需要一些全局可用的数据结构、数据类型及函数,比如message_t、error_t以及ecombine(…)。为了说明方便,这些统称为全局名字。与C一致,nesC中的这些全局名字通常在头文件中定义。例如,TinyOS的error_t类型和错误常量在\opt\tinyos-2.x\tos\types\TinyError.h中定义。如例3.15。
例3.15:
需要使用相应的全局名字时,只需要在模块、接口或配件等源文件中的适当位置(通常是关键字modoule、interface和configuration之前)使用文件包含(#include)来声明即可。另外,在nesC中还可以使用文件包含,使得在模块等内部使用的是标准C库或者是用户私有C代码库。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。