概述
试验三:MicaZ-TinyOS2.x平台下点对点通讯试验
——BlinktoRadio实验
试验目的:本节介绍TinyOS中的无线通信。能够熟练使用TinyOS支持通信的接口和组件,并且我们可以学习到:
1).TinyOS2.0消息缓存message_t的使用;
2).通过radio发送一个message;
3).接收一个message通过radio。
本实验所需硬件平台:2个micaz节点,1块mib510板,1根串口线。
一:简要介绍
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 <message.h> interface Packet { //清空数据包,将msg的数据清空重新使用 command void clear(message_t* msg); //返回信息负载的长度 command uint8_t payloadLength(message_t* msg); //设置负载长度 command void setPayloadLength(message_t* msg, uint8_t len); //返回最大负载 command uint8_t maxPayloadLength(); //获取负载,len为需要的负载长度 command void* getPayload(message_t* msg, uint8_t len); } |
注:Packet.nc文件中包含了message.h文件,下面我们看一下message.h文件的内容
//tos/types/message.h: // platform_message.h为相应平台的目录下,例如在MicaZ中为: //tosplatformsmicaz platform_message.h //MicaZ使用的MAC层为802.15.4的MAC协议,我们无需关心MAC协议 //该message同样定义了如何用于串口通信。 #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 <TinyError.h> #include <message.h>
interface Send { //设置发送数据包的负载长度 command error_t send(message_t* msg, uint8_t len); //取消数据的传输 command error_t cancel(message_t* msg); //发送数据完成 event void sendDone(message_t* msg, error_t error); //返回通讯层允许的最大负载长度 command uint8_t maxPayloadLength(); //获取负载 command void* getPayload(message_t* msg, uint8_t len); } |
Receive:提供了基本消息的接收接口。接口提供了一个事件来接收消息。它同样提供了一些指令,可以很方便的获取消息的负载长度并获取消息负载区的指针。
//tos/interfaces/Receive.nc: #include <TinyError.h> #include <message.h>
interface Receive { //返回数据包的buffer,msg接收的数据包,数据包负载的指针,负载长度 event message_t* receive(message_t* msg, void* payload, uint8_t len); } |
PacketAcknowledgements:在每个数据包上提供了一个请求应答机制。
//tos/interfaces/PacketAcknowledgements.nc: interface PacketAcknowledgements { //告诉协议,当要发送数据包时,使用同步的ACK async command error_t requestAck( message_t* msg ); //告诉协议,当要发送数据包时,不使用同步的ACK async command error_t noAck( message_t* msg ); //判断传输的数据包是否为ACK async command bool wasAcked(message_t* msg); } |
注意:async的含义是异步的意思。
RadioTimerStamping:为无线传输和接收提供了一个时间戳信息。
//tos/interfaces/RadioTimeStamping.nc: interface RadioTimeStamping { //提供帧开始传输的时间,时间单元是 32KHz Clock async event void transmittedSFD( uint16_t time, message_t* p_msg ); //开始接收帧的时间 async event void receivedSFD( uint16_t time ); } |
三:AM ( Active Message )接口
由于通常情况下会出现多个服务利用同一个radio通信的情况,TinyOS提供了AM层来多元访问radio。对于多元访问区,我们使用AM类型:”AM type”。AM类型与以太网的帧类型,IP协议区,UDP协议区相类似,它们都是多元访问通讯服务器。AM包同样包含一个目的区域,用”AM address”来存储某一特定节点的目的地址。支持AM服务的接口同样保存在目录tos/interfaces下:
AMPacket:与Packet相似,为message_t抽象的数据类型提供基本的AM访问。接口提供用了一些指令,这些指令可以获取节点的AM地址,AM包的目的地址以及AM包的类型。指令同样可以设置节点的AM目的地址,并检测AM包的目的地址是不是本地节点地址。
//tos/interfaces/AMPacket.nc: #include <message.h> #include <AM.h>
interface AMPacket { //返回AM栈中节点的AM地址 command am_addr_t address(); //返回AM数据包的目的地址的AM地址 command am_addr_t destination(message_t* amsg); //返回AM数据包的源地址的AM地址 command am_addr_t source(message_t* amsg); //设置AM数据包的目的地址的AM地址 command void setDestination(message_t* amsg, am_addr_t addr); //设置AM数据包的源地址的AM地址 command void setSource(message_t* amsg, am_addr_t addr); //返回该信息是否为给本节点的,简单判断destination和address是否一致 //广播可能出错 command bool isForMe(message_t* amsg); //返回AM数据包的AM类型,也就是说数据包的类型 command am_id_t type(message_t* amsg); //设置包的类型 command void setType(message_t* amsg, am_id_t t); //获得AM数据包的AM组 command am_group_t group(message_t* amsg); //设置AM数据包的AM组 command void setGroup(message_t* amsg, am_group_t grp); //返回本节点的AM组 command am_group_t localGroup(); } |
AMSend:与Send相似,提供了一个基本的AM发送接口。而两者之间关键的不同点是AMSend把目的地址放在它的send指令中,而Send却没有。
//tos/interfaces/AMSend.nc: #include <TinyError.h> #include <message.h> #include <AM.h> interface AMSend { //发送负载长度为len的数据,addr为发送地址 command error_t send(am_addr_t addr, message_t* msg, uint8_t len); //取消发送数据 command error_t cancel(message_t* msg); //数据发送完成,成功返回SUCCESS,失败返回FAIL,被取消返回ECANCEL event void sendDone(message_t* msg, error_t error); //返回通信层提供的最大负载长度 command uint8_t maxPayloadLength(); //获取负载 command void* getPayload(message_t* msg, uint8_t len); } |
节点的AM地址可以在下载程序的时候进行设置,使用makeinstall,n或者makereinstall,n指令。我们可以在程序运行的时候通过ActiveMessageAddressC组件进行改变AM地址。
四:支持AM接口的组件
有一些组件可以实现基本的通信和AM接口。我们来看一下这些组件,在目录/tos/system 下。我们有必要对下面的组件进行熟悉,因为我们的应用程序都要用到下面的组件以及它们所支持的接口:
AMReceiverC:提供支持以下的接口:Receive,Packet和AMPacket。
AMSenderC:提供支持以下的接口:AMSend,Packet,AMPacket和Acks(即PacketAcknowledgements)
AMSnooperC:提供支持以下的接口:Receive,Packet和AMPacket,进行嗅探的组件功能,和组件AMReceiverC功能相同,用于接收数据。
AMSnoopingReceiverC:提供支持以下的接口:Receive,Packet和AMPacket。与AMSnooperC组件比,多了一个ActiveMessageC.Receive[AMId]
ActiveMessageAddressC:本模块提供了一些指令可以用来获取和设置节点的AM地址。这个模块不是为一般用户提供的,它容易破坏网络栈,所以如果不清楚操作的情况下要避免使用它。
五:命名的包装外壳( Naming Wrappers )
既然TinyOS支持多种平台,每一种平台又有它自己的射频实现方式。具体到某一特定的平台,被命名为ActiveMessageC的外壳包装用来桥接它们的底层硬件,和特定平台的实现。ActiveMessageC提供了大部分之前提到的通讯接口。特定平台下的ActiveMessageC版本被应用到特定的平台中:
针对eyesIFX平台,ActiveMessageC是由Tda5250ActiveMessageC来实现的;
针对intelmote2,micaz, telosa和telosb平台,ActiveMessageC是由CC2420ActiveMessageC来实现的;
针对mica2平台,ActiveMessageC是由CC1000ActiveMessageC来实现的;
六:TinyOS 2.x的消息缓存区
TinyOS 2.x 使用了一个新的消息缓存区,叫做message_t。如果是对之前较早版本的TinyOS比较熟悉的话,必须明白TOS_Msg已被message_t替换。message_t结构在tos/types/message.h中做了定义。
//tos/types/message.h: typedef nx_struct message_t { 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; |
注意:header,footer和metadata区都是不透明的,并且不可以被直接访问。这点很重要,我们要通过Packet, AMPacket和其他的接口访问message_t。这样做的理由是为了保持数据负载固定有特定的偏移量。
七:通过Radio发送一个消息
我们现在创建一个简单的应用,计数器,并通过3个LED灯显示出计数器的低3位数值大小。然后把这个计数值通过radio发送出去,我们的实现过程要用到一个timer和一个计数量,这与上一个试验中的BlinkSingle很相似。
7.1).重新来实现Blink
首先,我们要用一个timer和一个计数量来重新实现Blink。在/apps中创建一个新的目录/BlinkToRadio:
进入这个目录中,创建一个文件BlinkToRadioC.nc,其包含的源代码如下:
//app/BlinkToRadio/BlinkToRadioC.nc: #include <Timer.h> #include "BlinkToRadio.h"
module BlinkToRadioC { uses interface Boot; uses interface Leds; uses interface Timer<TMilli> 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<Time.h>:表明Timer.h位于头文件标准目录下:tos目录及其子目录。include ”BlinkToRadio.h”:告诉预编译器在本目录下寻找BlinkToRadio.h,若没有再去标准目录下寻找头文件;如果头文件在既不在本目录下,也不在标准头文件目录下,那么我们需要在Makefile里面用-I标记向编译器指明不再标准目录下的头文件,比较常见的,如一些又其他机构或用户contributed库,它的头文件都位于contrib目录下。
程序中是通过调用Leds.set来直接设置LED灯,用于显示计数器的低3位。
最后,注意到在事件Boot.booted()中,调用了程序段Timer0.startPeriodic(TIMER_PERIOD_MILLI ); TIMER_PERIOD_MILLI的值在BlinkToRadio.h头文件中做了定义:
//app/BlinkToRadio/BlinkToRadio.h: #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 <Timer.h> #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; … } |
最后,测试我们的应用程序
测试我们的应用程序很简单,首先拿两个节点,可以是mica2, micaz, telosa,telosb或者imote。这里,我们有两个micaz节点和一块MIB510底板,首先打开Cygwin,进入到apps/tutorails/BlinkToRadio目录下。
注,上电后对micaz使用motelist指令,产生错误。
现在,我们已进入apps/tutorails/BlinkToRadio目录下,输入make micazinstall,1 mib510,com1。我们可以看到:
这时候从MIB510板上移走micaz节点,给节点上电,会发现节点的3个LED灯没有任何显示。原因是没有其他节点给它发送消息。现在,换另一个micaz节点,输入make micaz reinstall,2 mib510,com1:
MIB510板上的拨动开关拨到OFF,并reset第2个节点,会发现两个节点的LED灯都开始闪烁了。如果现在关闭或复位其中的一个节点,另一个节点的LED灯就会保持住当前的状态或保持一会。说明LED灯闪烁依赖于另个节点的数据发送。
八:总结
这个试验详细介绍了基于TinyOS2.x的radio通信。
最后
以上就是要减肥蛋挞为你收集整理的试验三:MicaZ-TinyOS2.x平台下点对点通讯试验-----BlinktoRadio实验的全部内容,希望文章能够帮你解决试验三:MicaZ-TinyOS2.x平台下点对点通讯试验-----BlinktoRadio实验所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复