第三章 TinyOS编程语言nesC

第四节 模块

    例3.10:BlinkTaskC组件

    #include “Timer.h”

    module BlinkTaskC{

    uses interface  Timer<TMilli> as Timer0;

    uses interface  Leds;

    uses interface  Boot;

    }

    implementation{

    task void toggle(){

    call Leds.led0Toggle();

        }

    event void Boot.booted(){

    call Timer0.startPeriodic(1000);

        }

    event void Timer0.fired(){

    post toggle();

        }

    }

    BlinkTaskC组件使用了第二种定义和提交任务的方式。

    此任务是以1000ms为周期来触发Led0灯,切换其亮灭状态。在模块的实现部分,定义了一个名为toggle的任务,此任务调用Leds接口中的命令led0Toggle()来切换Led0的亮灭状态。在事件Boot.booted()中,设定定时器Timer0的值为1000ms,当定时器Timer0的时间到达1000ms时,重新初始化,使定时器重新开始计时。在最后事件Timer0.fired()中,当定时器Timer0被触发时,提交一个任务。

    3.4.3  模块数据

    1.模块变量

    一般来说,nesC中不提倡使用malloc或者其他C库函数来动态分配内存。这是因为大多数嵌入式微控制器没有内存保护,动态分配内存会带来一定的风险。合适的做法是在模块中通过变量来静态分配内存。模块变量定义位于模块实现代码的开始部分。

    例:模块PeriodicReaderC的工作是周期性采集传感器数据。在模块PeriodicReaderC的实现部分,除了定义StdControl接口中的命令和Timer、Read接口中的事件外,在开始位置还定义一个类型为uint_16 的变量lastVal,用于存放传感器数据。uint_16的关键字,后面会详细说明。

    对于模块变量有三点说明。

    1)模块变量定义位于模块实现部分的开始位置。模块变量定义与C变量是一致的,在定义的同时可以初始化(如下面第一行),也可以将定义与初始化分开(如下面第二行和第三行)。

    uint8_t count =1;

    message_t packet;

    message_t* packetPtr =&packet;

    2)模块变量的命名范围是组件。本模块文件范围内可以引用该变量,而其他组件不能直接引用该变量,其他组件引用lastVal的唯一方式是通过接口函数。在这个例子中,其他组件要引用lastVal,可以调用函数Read.readDone( )来返回该变量的值。

    3)模块变量是静态存储变量,类似于C中使用关键字static标识的变量。

    除了在模块实现代码的开始位置定义模块变量外,在各函数内部也可以根据需要定义内部变量,与C函数内部变量的用法一致,这里不再赘述。

    2.平台独立数据

    TinyOS具有支持多种硬件平台的能力。微控制器和无线通信芯片是硬件平台的主要组成部件。无线芯片与微控制器对数据的布局(字节顺序big-endian/litttle-endian)可能有所不同。不同微控制器上处理器的地址总线宽度也可能不同,比如MSP430的地址总线是16位,Atmag128的地址总线是8位,这往往使得同一个数据结构在有的平台上能够对准边界,有的平台上却不能对准边界。而没有边界对准的字是不能加载使用的。比如TelosB平台上,针对无线通信芯片CC2420的通信分组控制头的数据结构如下所示,该数据结构的字节长度是11,为奇数。

    typedef struct cc2420_header_t{

    uint8_t length;

      uint16_t fcf;

      uint8_t dsn;

      uint16_t destpan;

      uint16_t dest;

      uint16_t src;

      uint8_t type;

    }cc2420_header_t;

    该平台的微控制器采用的是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等的宏转换。