第一篇 基础知识
TinyOS调度 MainC组件 应用程序接口 (应用程序提供的接口,仅供本程序使用) 应用程序模块 *C.nc 应用层配件 *App.nc 库组件 在/tos/lib目录下 TinyOS 接口 存放在/tos/interface目录下 TinyOS系统组件 在/tos/system目录下,为系统抽象出的最高层组件 硬件适配层/抽象层 该层一般位于/tos/chips或相应的平台/platforms下 具体硬件 TinyOS体系结构
Sleep Task1 Task2 提交task3 提交task1 COMP1 提交task2 事件2 Task3 Task3 Task1运行 事件1 COMP2 Task2 Task3 中断服务向量 TinyOS任务调度 硬件中断 (1) 组件模型module & configuration
TinyOS是基于构件的微操作系统,采用事件驱动模型,有效的提高了系统的运行效率以及能源合理利用。TinyOS采用nesC语言编写,其应用程序由一个或多个组件连接而成,而组件可以提供和使用接口,组件必须实现其所提供的command接口,并且必须实现其连接组件中申明的事件event接口。接口是程序的实体,实现程序的各功能模块,分为command和event,command接口由组件本身实现,而event接口则由调用者实现,值得注意的是,接口是双向的,调用command接口时必须实现其event接口。
组件又可以细分为模块module和配件。模块亦可分为2个部分,其一,首先申明提供以及使用的接口,如 <>表示可变类型argument type
只用做配线时类型匹配检测 申明使用的接口。As 表明生成这个接口的一个实例。这是标准写法,其实下面这条语句的全写就是 uses module BlinkC {
uses interface Timer
interface Leds as Leds; 其二,在implementation中模块包含各接口所提供的行为(方法),也包含仅供本模块内部使用的函数,以及申明本模块所具有的事件signal,以及实现其连接或使用的event。
implementation {
uint8_t counter = 0; void ledctl() 实现Boot接口中的事件 { call Leds.set(counter); }
event void Boot.booted() 使用接口提供的方法 {
call Timer0.startPeriodic( 250 ); } 实现调用Timer0的事件 event void Timer0.fired() {
counter++; 调用模块内部函数 ledctl(); } }
配件configuration也可以分为两个部分,和module一样,第一部分是申明可以提供以及使用的接口。第二部分implementation 中首先列出与其相连接模块的名称,使用components标注连接的模块,然后对本配件提供的以及与其相对应模块使用以及提供的接口进行配线,如下例:
configuration BlinkAppC { BlinkC模块中使用的Boot接口连接到MainC模块上 BlinkC作为该配件对应的模块 }
implementation
与其连接的模块申明 {
components MainC, BlinkC, LedsC,TimerMilliC; BlinkC -> MainC.Boot; /////或者写作BlinkC.Boot -> MainC.Boot; BlinkC.Timer
在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)
components new LplAMSenderC(AMId) as SenderC; #else ()带参数的模块 components new DirectAMSenderC(AMId) as SenderC; #endif 为该配件提供的接口配线 AMSend = SenderC; Packet = SenderC; AMPacket = SenderC; Acks = SenderC; }
接口文件相当于C程序中头文件对函数的声明,接口文件一般放置于提供该接口的模块的同一目录下的interface文件夹中,也可以放在TinyOS根目录下的interface目录中,其命名必须与模块中所提供接口名字相同,注意不是接口的实例化名称或nickname。如下例
interface CC2420Register { 当在中断中调用时必须加此声明
async command cc2420_status_t read( uint8_t* data ,uint8_t len);
async command cc2420_status_t write( uint8_t *data ,uint8_t len); 作为模块命令,使用call访问 }
注意:带有参数的接口在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(\ // 实例1 AppTwoC.Timer -> HilTimerMilliC.Timer[unique(\ //实例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转换完成时,硬件触发一个中断,这时程