tinyos learning on day 3

一:简要介绍
TinyOS提供了一些接口来提取底层的通信服务和一些组件来实现这些接口。所有的这些接口和组件使用一个公共的消息缓存区,叫做message_t。这个message_t消息是替换了TinyOS1.x中的TOS_Msg。和TinyOS1.x不同,message_t的成员变量不透明,因此不能直接访问。而message_t是一个抽象的数据类型,它的成员变量通过accessor和mutator函数进行读写。
二:熟悉基本的通信接口
这里有一些接口和组件使用message_t作为底层的数据结构,我们看一下目录tos/interfaces中的这些接口,我们应该熟悉通信系统的通用功能。
Packet:为message_t抽象的数据类型提供基本的访问。接口提供用了一些指令,这些指令可以清除消息内容,获得消息中数据负载的长度,并获得数据负载存储区的指针。
//tos/interfaces/Packet.nc:

include

include “platform_message.h”

ifndef TOSH_DATA_LENGTH

define TOSH_DATA_LENGTH 28

endif

//定义了广播地址为为OxFFFF

ifndef TOS_BCAST_ADDR

define TOS_BCAST_ADDR 0xFFFF

endif

//nx_struct为nesC中的结构体,即可理解为C语言中的struct
typedef nx_struct message_t {
//nx_uint8_t为nesC中的无符号8为整型数据
nx_uint8_t header[sizeof(message_header_t)];
nx_uint8_t data[TOSH_DATA_LENGTH];
nx_uint8_t footer[sizeof(message_footer_t)];
nx_uint8_t metadata[sizeof(message_metadata_t)];
} message_t;

Send:提供了基本无地址消息的发送接口。接口提供了一些指令用于发送或取消等待发送的消息。这个接口提供了一个事件来相应消息是否发送成功。它同样提供了一些函数,可以很方便的获得通信层允许的最大消息负载长度,并可获得消息负载区的指针。
//tos/interfaces/Send.nc:

include

include

include

include

include

include

include

include

include

include

include “BlinkToRadio.h”

module BlinkToRadioC {
uses interface Boot;
uses interface Leds;
uses interface Timer as Timer0;
}
implementation {
uint16_t counter;
event void Boot.booted() {
call Timer0.startPeriodic( TIMER_PERIOD_MILLI );
}
event void Timer0.fired() {
counter++;
call Leds.set(counter);
}
}
我们来看一下这个程序中几行特殊的代码行。include

ifndef BLINKTORADIO_H

define BLINKTORADIO_H

enum {
TIMER_PERIOD_MILLI = 250
};

endif

     首先,要注意头文件使用了#ifndef,#define和#endif,可用于防止头文件被重复编译。然后,代码中使用了enum来定义一个常数TIMER_PERIOD_MILLI,而不是采用define,因为define不受作用域的限制,而enum有很好的区域性。
     在此,有一个总结性的东西:
     1).只是声明单一固定值,尽可能采用const;
     2).如果是一组固定值,并且互相有关联,则采用enum;
     3).不涉及条件编译,只是定义固定值的情形下,尽可能不使用#define。
   BlinkToRadioC.nc 文件提供了程序的实现逻辑,BlinkToRadio.h文件定义了一些常数和结构体。第三个文件应该把实现中使用到的接口及提供接口的执行组件连接起来。BlinkToRadioAppC.nc文件中提供了这种连接关系:

//app/BlinkToRadio/BlinkToRadioAppC.h:

include

include “BlinkToRadio.h”

configuration BlinkToRadioAppC {
}
implementation {
components MainC;
components LedsC;
components BlinkToRadioC as App;
components new TimerMilliC() as Timer0;

App.Boot -> MainC;
App.Leds -> LedsC;
App.Timer0 -> Timer0;
}
这三个程序构成了应用程序的所有东西,而另外一个很需要的东西是Makefile文件。创建一个Makefile文件。对于一个简单的应用程序,它的Makefile文件也很简短:
//app/BlinkToRadio/Makefile:
COMPONENT=BlinkToRadioAppC
include $(MAKERULES)
第一行告诉TinyOS,顶层的应用程序组件叫BlinkToRadioAppC。第二行是装载TinyOS搭建的系统环境,针对不同的应用平台要搭建不同的环境规则。
7.2).定义一下消息结构体
现在Blink已经可以通过一个timer和计数量来实现LED灯闪烁。那么我们回到我们最初的目的,即定义一个消息体,并通过radio来发送数据。我们的消息体会通过radio把节点的ID和计数量都发送出去。我们通过结构任务去复制一段数据到消息负载去,而不是简单的对message_t消息负载区的数据进行读写操作。这样避免了用户人为的在字节级去更新负载区的数值。把负载区结构化更易懂。例,在负载区中定义一些结构化的变量uint16_tnode id 和uint16_tcounter。我们把这些添加到BlinkToRadio.h文件中。
//app/BlinkToRadio/BlinkToRadioAppC.h:
typedef nx_struct BlinkToRadioMsg {
nx_uint16_t nodeid;
nx_uint16_t counter;
} BlinkToRadioMsg;
这里要注意一点,nx_前缀表示数据为外部类型的大顶端数据,小顶端前缀为nxle_,这对通信来说很重要,因为不同的CPU具有不同的Endianness格式,如果通信两端的CPU具有不同的Endianness格式,那么它们的数据传输会出现很大的错误。
7.3).发送一个消息体
现在为我们的应用程序定义一个消息类型,BlinkToRadioMsg,我们将会看到消息是如果通过radio发送的。首先,我们先来回顾一下我们这个应用程序的目的:我们想要i):在一个timer驱动的系统中添加一个计数量,ii):通过LED灯显示计数量的低3位数值,iii):通过radio传输这个节点的id和计数值。要实现这个程序,我们需要按以下的步骤来操作:
第一:我们需要找出这些能够支持radio的接口和组件,能够允许我们控制使用message_t类型;
第二:我们必须添加需要使用的接口来更新一下BlinkToRadioC.nc文件中的module区;
第三:我们需要声明一些新的变量并添加一些初始化和start/stop代码;
第四:我们必须添加一些应用程序需要的接口和组件;
第五:我们需要实现一些我们用到的接口中特定的事件
第六:我们必须把为之前选用的提供接口的组件添加到BlinkToRadioAppC.nc文件中的implementation区
最后,我们需要把应用程序中使用到的组件进行连接。

接下来,让我们按上面的步骤,一步一步的来做:
第一:我们需要找出这些能够支持radio的接口和组件,能够允许我们控制使用message_t类型;
我们需要使用AMSend接口去发送数据包;
使用Packet和AMPacket接口去使用message_t抽象数据类型;
尽管我们可以直接连接到ActiveMessageC组件,可以代替使用AMSenderC组件。但是,我们需要使用ActiveMessageC.SplitControl接口启动radio。我们使用AMSenderC的原因是,它是一个虚拟化的抽象。早期的TinyOS版本不支持虚拟化接口去访问radio,那么它可以提供了两个组件去同时分享radio。在TinyOS2.0以后的版本中,AMSenderC提供了虚拟的使用广播的方法。每个AMSender的使用者都有一个深度为1的队列,这些队列都是平等的。
第二:我们必须添加需要使用的接口来更新一下BlinkToRadioC.nc文件中的module区;
//app/BlinkToRadio/BlinkToRadioC.nc:
module BlinkToRadioC {

uses interface Packet;
uses interface AMPacket;
uses interface AMSend;
uses interface SplitControl as AMControl;
}
注意到使用关键词as把SplitControl重新命名为AMControl。nesC允许用这种方法进行重命名也是有多种理由的。第一,如果有两个或多个组件都在模块中提供了同样的接口,那么有必要使用重命名。关键词as可以使一个或多个直接命名为更明显的名字,以便于各自单独地定位。第二:接口有时候重命名更有意义。在我们的例子中,SplitControl是启动和停止组件常用的接口,但如果命名为AMControl则更容易让我们想起SplitControl组件的这个实例是用来控制ActiveMessageC组件的。
第三:我们需要声明一些新的变量并添加一些初始化和start/stop代码;
首先,我们需要声明一些模块区域的变量。我们需要一个message_t来保存我们要发送的数据。我们同样需要一个标志量去跟踪何时radio是忙于发数据呢。这些声明都需要添加在BlinkToRadioC.nc文件中的implementation区
//app/BlinkToRadio/BlinkToRadioC.nc:
implementation {
message_t pkt;
bool busy = FALSE;

}
接下来,我们需要处理radio的初始化。当系统启动的时候需要启动radio,所以必须在Boot.booted中调用AMControl.start。目前唯一的难题在于实现部分,我们在Boot.booted中启动了一个timer,并且我们打算使用这个timer去通过radio发送消息,但是radio在启动完之前是不能够使用的。如果radio启动完成之后会通过信号触发AMControl.startDone事件。为了确保我们没有在radio准备好之前启动timer,我们需要推迟启动timer,直到radio完全的启动起来之后。那我们就不能在Boot.booted中启动timer,而需要在AMControl.startDone中启动。现在我们给出一个新的Boot.booted,如下:
//app/BlinkToRadio/BlinkToRadioC.nc:
event void Boot.booted() {
call AMControl.start();
}
我们同样需要实现AMControl.startDone事件和AMControl.stopDone事件句柄。它们的程序段如下:
//app/BlinkToRadio/BlinkToRadioC.nc:
event void AMControl.startDone(error_t err) {
if (err == SUCCESS) {
call Timer0.startPeriodic(TIMER_PERIOD_MILLI);
}
else {
call AMControl.start();
}
}
event void AMControl.stopDone(error_t err) {
}
如果radio启动成功,AMControl.startDone会被触发,同时参数error_t会被置值SUCCESS。如果radio启动成功,它就会启动timer,可如果radio没有启动成功,那么我们有必要再重新启动一遍。如果radio没有完全启动的话,我们有必要通过LED灯不闪烁来通知操作者,这时可以尝试调试一下程序。
第四:我们必须添加一些应用程序需要的接口和组件;
因为我们想要在timer每次触发的时候传输节点ID和计数器,我们需要在Timer0.fired事件中添加如下代码:
//app/BlinkToRadio/BlinkToRadioC.nc:
event void Timer0.fired() {

if (!busy) {
BlinkToRadioMsg* btrpkt =
(BlinkToRadioMsg*)(call Packet.getPayload(&pkt, sizeof(BlinkToRadioMsg)));
btrpkt->nodeid = TOS_NODE_ID;
btrpkt->counter = counter;
if (call AMSend.send(AM_BROADCAST_ADDR,
&pkt, sizeof(BlinkToRadioMsg)) == SUCCESS) {
busy = TRUE;
}
}
}
这段代码实现了多个操作,首先,它确保了在发送消息的时候radio的状态不是busy。然后获得通信包中数据负载部分并把它发送到之前声明过的外部变量BlinkToRadioMsg的指针处。现在可以通过这个指针来初始化数据包区,并通过AMSend.send发送这个包。通过设定AM_BROADCAST_ADDR作为目的地址来把这个包传递到所有节点中。最后,通过busy来校验AM层是否接受了消息的传输,如果接受了,那么状态为SUCCESS。在发送阶段,数据包归radio私有,禁止用户代码访问。注意,我们应该避免使用Packet接口,因为它的getPayload指令在AMSend中重复调用。
第五:我们需要实现一些我们用到的接口中特定的事件
看一下Packet,AMPacket和AMSend接口,我们可以看到只有一个接口需要考虑:AMSend.sendDone。
//tos/interfaces/AMSend.nc and /tos/interfaces/AMSend.nc :

event void sendDone(message_t* msg, error_t error);

这个事件是在消息传输完成之后触发的。另外,它可以判断消息是否发送完成,返回从AMSend中返回msg的所有权。因此,sendDone事件中可以清除busy状态来说明消息缓存已经可以再使用。
//app/BlinkToRadio/BlinkToRadioC.nc:
event void AMSend.sendDone(message_t* msg, error_t err) {
if (&pkt == msg) {
busy = FALSE;
}
}
注意,这里要去检验消息缓存是否为本地消息缓存,这个检验是必须的,因为如果两个组件都连接了AMSend,可能会收到另一个组件sendDone事件触发信号。既然组件的作者没有强制去限制组件不能处于这种情况,那么我们就需要采用这个保守的编程风格去检验消息传递。
第六:我们必须把为之前选用的提供接口的组件添加到BlinkToRadioAppC.nc文件中的implementation区
接下来要在BlinkToRadioAppC.nc文件中的implementation区添加之前选用的提供接口的组件:
//app/BlinkToRadio/BlinkToRadioAppC.nc:
implementation {

components ActiveMessageC;
components new AMSenderC(AM_BLINKTORADIO);

}
这几句代码表示有两个组件,ActiveMessageC和AMSenderC,提供了需要的接口。可是,注意到语法上有一些细微的不同。ActiveMessageC组件是单独的,它在每个硬件平台中进行定义。而AMSenderC是通用的、参数化的组件。关键词new表明生成了一个新的AMSenderC实例。参数AM_BLINKTORADIO表明了AMSenderC的AM类型。我们可以在头文件BlinkToRadio.h中使用enum来定义AM_BLINKTORADIO的值。
//app/BlinkToRadio/BlinkToRadio.h:
enum {
AM_BLINKTORADIO = 6,
TIMER_PERIOD_MILLI = 250
};
最后,我们需要把应用程序中使用到的组件进行连接。
接下来我们把提供使用接口的组件进行连接。在BlinkToRadioAppC.nc文件中的implementation区的最后添加程序代码:
//app/BlinkToRadio/BlinkToRadioAppC.nc:
implementation {

App.Packet -> AMSenderC;
App.AMPacket -> AMSenderC;
App.AMControl -> ActiveMessageC;
App.AMSend -> AMSenderC;
}

7.4).通过radio接收一个消息体
现在我们要写一个应用程序来传输消息,应该加一些接收和处理消息的代码。现在我们来写一段代码让它可以通过LED灯来显示接收到计数器数据的低3位。为了使应用程序变的有趣,我们要先把Timer0.fired事件响应中的命令行call Leds.set(counter)删掉。否则,要写的应用程序结果显示会出错。
如果有两个节点烧写我们修改的应用程序,它们各自可以显示对方的计数值。如果节点超出radio的范围,LED灯就停止变化。可以让一个节点的LED灯停止闪烁,而另一个闪烁。这可以显示不闪烁节点和闪烁节点之间的联系。如果把通道反转,两个节点之间将不在联系。
第一:我们需要找出这些能够支持radio的接口和组件,能够允许我们控制使用message_t类型;
我们使用Receive接口来接收数据包。
第二:我们必须添加需要使用的接口来更新一下BlinkToRadioC.nc文件中的module区;
//app/BlinkToRadio/BlinkToRadioC.nc:
module BlinkToRadioC {

uses interface Receive;
}
第三:我们需要声明一些新的变量和一些需要的初始化代码;
我们不需要新的变量来接收和处理通过radio传递的消息。
第四:我们必须添加一些应用程序需要的接口和组件;
消息接收是一个事件驱动过程,所以不需要调用Receive中的任何指令。
第五:我们需要实现一些我们用到的接口中特定的事件
我们需要实现一下Receive.receive事件相应:
//app/BlinkToRadio/BlinkToRadioC.nc:
event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len){
if (len == sizeof(BlinkToRadioMsg)) {
BlinkToRadioMsg* btrpkt = (BlinkToRadioMsg*)payload;
setLeds(btrpkt->counter);
}
return msg;
}
receive事件响应中是一些简单的操作,首先,我们需要确定一些消息的长度和期望的是否一致。然后,消息负载传递一个BlinkToRadioMsg*类型的结构指针,并分配给本地变量。消息中的计数值用来设定3个LED灯的显示状态。注意到我们安全的使用counter变量在原子区之外。这个原因是接收事件在任务中的执行比使用关键词async的中断内容的优先级更高。既然TinyOS只允许同一个时间运行一个任务,如果在任务区中有多个任务去访问变量,不会产生竞争。即使所有的任务都去访问counter变量,不会有竞争危险区。
第六:我们必须把为之前选用的提供接口的组件添加到BlinkToRadioAppC.nc文件中的implementation区
//app/BlinkToRadio/BlinkToRadioAppC.nc:
implementation {

components new AMReceiverC(AM_BLINKTORADIO);

}
关键词new表明生成了一个新的AMReceiverC实例。参数AM_BLINKTORADIO表明了AMReceiverC的AM类型。我们可以在头文件BlinkToRadio.h中使用enum来定义AM_BLINKTORADIO的值,注意在configuration文件中配线时,要注意AMSenderC和AMReceiverC类型必须相同。
第七,我们需要把应用程序中使用到的组件进行连接。
接下来我们把提供使用接口的组件进行连接。在BlinkToRadioAppC.nc文件中的implementation区的最后插入程序代码:
//app/BlinkToRadio/BlinkToRadioAppC.nc:
implementation {

App.Receive -> AMReceiverC;

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值