当前位置:文档之家› TinyOS学习笔记讲解

TinyOS学习笔记讲解

第一篇基础知识TinyOS体系结构(1) 组件模型module & configurationTinyOS 是基于构件的微操作系统,采用事件驱动模型,有效的提高了系统的运行效率以及能源合理利用。

TinyOS 采用nesC 语言编写,其应用程序由一个或多个组件连接而成,而组件可以提供和使用接口,组件必须实现其所提供的command 接口,并且必须实现其连接组件中申明的事件event 接口。

接口是程序的实体,实现程序的各功能模块,分为command 和event ,command 接口由组件本身实现,而event 接口则由调用者实现,值得注意的是,接口是双向的,调用command 接口时必须实现其event 接口。

组件又可以细分为模块module 和配件。

模块亦可分为2个部分,其一,首先申明提供以及使用的接口,如module BlinkC { } 其二,在implementation 中模块包含各接口所提供的行为(方法),也包含仅供本模块内部使用的函数,以及申明本模块所具有的事件signal ,以及实现其连接或使用的event 。

implementation{uint8_t counter = 0;void ledctl() {call Leds.set(counter);}event void Boot.booted() {} event void Timer0.fired(){ledctl();}}配件configuration 也可以分为两个部分,和module 一样,第一部分是申明可以提供以及使用的接口。

第二部分implementation 中首先列出与其相连接模块的名称,使用components 标注连接的模块,然后对本配件提供的以及与其相对应模块使用以及提供的接口进行配线,如下例:{}implementation{BlinkC -> MainC.Boot; /////或者写作BlinkC.Boot -> MainC.Boot;BlinkC.Timer<TMilli> ->TimerMilliC;BlinkC.Leds -> LedsC;}在TinyOS 中存在很多中间配件,这些配件的特点是没有与之相对应的模块,其作用就是根据不同的条件将上层的连接转接到不同的模块上,如下例所示generic configuration AMSenderC(am_id_t AMId) { provides {interface AMSend; interface Packet;interface AMPacket;interface PacketAcknowledgements as Acks;}}implementation {#if defined(LOW_POWER_LISTENING)#else #endifAMSend = SenderC;Packet = SenderC;AMPacket = SenderC;Acks = SenderC;}接口文件相当于C 程序中头文件对函数的声明,接口文件一般放置于提供该接口的模块的同一目录下的interface 文件夹中,也可以放在TinyOS 根目录下的interface 目录中,其命名必须与模块中所提供接口名字相同,注意不是接口的实例化名称或nickname 。

如下例}注意:带有参数的接口在interface 中申明时不需要写出其接口参数,比如,上例中CC2420Registe 接口是带有参数的,而在模块中实现是则写成CC2420Registe [uint8_t addr]。

(2)接口interface接口是TinyOS功能实体,通过调用接口提供的方法完成某个具体任务。

在TinyOS中接口可以带有参数,用[]引人参数,接口也可以带有类型标志,用<>引入。

TinyOS中可能有多个组件提供同一个接口,比如Init接口,那么这样就会带来一个问题:当一个组件有多个用户使用时,他们分别都调用了同一个接口,当这个接口事件触发fire时,那么,应该由哪个组件来响应这个信号呢?TinyOS采用两种策略来解决这个问题:其一,多次实例化一个组件接口,这种方法在I/O上应用的最多,如下面代码configuration PlatformLedsC {provides {interface GeneralIO as Led0;interface GeneralIO as Led1;interface GeneralIO as Led2;}接口GeneralIO被实例化了3次,这样就可以分别操这3个接口了。

其二,TinyOS采用带参数的接口,允许一个组件提供多个接口实例。

比如module CC2420SpiP @safe() {provides {interface ChipSpiResource;interface Resource[ uint8_t id ];interface CC2420Fifo as Fifo[ uint8_t id ];interface CC2420Ram as Ram[ uint16_t id ];interface CC2420Register as Reg[ uint8_t id ];interface CC2420Strobe as Strobe[ uint8_t id ];}组件提供接口并实例化为带参数的接口,那么其他组件连接时,可以提供不同的id多次使用同一接口,比如SNOP = Spi.Strobe [ CC2420_INS_SNOP ];SIBUFEX = Spi.Strobe [ CC2420_INS_SIBUFEX ];SSAMPLECCA =Spi.Strobe [ CC2420_INS_SSAMPLECCA ];SXOSCON = Spi.Strobe [ CC2420_INS_SXOSCON ];其中Spi就是连接到上例组件的。

这样用起来也不是很方便,所以TinyOS由给出了可以产生唯一ID的常量函数函数。

nesC 现在有二种常量函数:unsigned int unique(char *identifier)返回值:如果程序包含n个有相同标示字符串的对unique的调用,每个调用返回一个0—n-1之间的无符号整数。

unsigned int uniqueCount(char *identifier)返回值:如果程序包含n个有相同标示字符串的对uniqueCount的调用,每个调用都返回n使用实例说明:HilTimerMilliC组件中声明了这一句:provides interface Timer[uint8_t id];表明它可以提供256 个Timer 接口的不同实例,每一个实例对应一个uint8_t 值!AppOneC.Timer -> HilTimerMilliC.Timer[unique("Timer")]; // 实例1AppTwoC.Timer -> HilTimerMilliC.Timer[unique("Timer")]; //实例2参数化接口允许一个组件通过赋予运行时或编译时参数从而提供一个接口的多个实例。

本例中,希望TinyOS 应用程序创建和使用多个定时器,且每个定时器都被独立管理。

例如,某个应用程序组件可能需要一个定时器以特定的频率(如每秒一次)来触发事件以收集传感器数据;同时另外一个组件需要另一个定时器以不同的频率来管理无线传输。

这些组件中每个Timer 接口分别与HilTimerMilliC中提供的Timer 接口的不同实例绑定起来,这样每个组件就可以有效地获取它自己“私有”的定时器了。

当然括号内的标识符一定要相同,否则不能保证调用多个unique()时得到的数是不重复的。

比如unique(“TIME”)和unique(“TIME”)会得到两个不同的随机数,但如果是unique(“TIME”)和unique(“TIME2”)就不能保证得到独一无二的数了。

uniqueCount()主要用于得到范围的上界。

比如uniqueCount(CLIENT),如果CLIENT为8位,则该次调用的返回值为255,如果将CLIENT改为16位,则返回65535。

(3)分裂相split-phase操作硬件一般都是分相型操作而非阻塞型,即采用应答或回调信号表示一个请求的完成。

比如,当使用ADC进行模数转换时,程序首先向控制寄存器写配置命令并启动转换,当ADC转换完成时,硬件触发一个中断,这时程序开始读取转换结果。

在一般的操作系统中,如果程序请求一次ADC转换,操作系统会建立一个请求,并把这个请求线程加入到等待队列中,开始转换,并调度另一个线程继续运行。

当转换完成中断发生时,唤醒等待队列中的线程,并加入到就绪队列,调度执行后返回。

但是,在嵌入式操作系统中,这样的线程会占用一定的宝贵资源,如RAM,即使该线程在等待队列中,其占用的资源也无法释放,以便于唤醒时继续执行。

TinyOS并没有采用使用线程模型来同步所有,而是采用在硬件和软件中都采用分相操作模型。

这也就意味着许多普通的操作如传感器采样、发送数据包等都是分裂相操作。

分裂相操作最大的特点在于它是双向的:有一个向下downcall开启一个操作,还有一个向上upcall触发事件完成。

在nesC语言中,downcall一般就是command语句,而upcall则是events。

下面以Send接口为例说明:Arrayinterface Send {command error_t cancel(message_t* msg);command uint8_t maxPayloadLength();command void* getPayload(message_t* msg, uint8_t len);}(4)任务task在分裂相操作中,需要触发一个callback,而这个事件的触发只能从这个command中signal。

但是,在命令command中signal此command对应的事件是危险的,因为这样可能会造成call loops,消耗存储空间并使系统瘫痪。

如下面这段代码:Command error_t Read. read (){Signal Read.readDone (SUCCESS, filterVal);}当以高频率call Read. read时,压栈的速率远高于出栈时,就会造成堆栈枯竭,因为在有限的RAM中开辟的stack也是非常有限的。

为了解决这样的问题,TinyOS采用task任务模型,通过系统调度执行,因为调度执行并不是立即就会执行,所以在上述频繁访问时就不会产生上述问题。

相关主题