📄 mote-mote radio communication - tinyos documentation wiki.htm
字号:
<P>First, we need to identify the interfaces (and components) that provide
access to the radio and allow us to manipulate the <CODE>message_t</CODE> type.
Second, we must update the <CODE>module</CODE> block in the
<CODE>BlinkToRadioC.nc</CODE> by adding <CODE>uses</CODE> statements for the
interfaces we need. Third, we need to declare new variables and add any
initialization and start/stop code that is needed by the interfaces and
components. Fourth, we must add any calls to the component interfaces we need
for our application. Fifth, we need to implmement any events specified in the
interfaces we plan on using. Sixth, the <CODE>implementation</CODE> block of the
application configuration file, <CODE>BlinkToRadioApp.c</CODE>, must be updated
by adding a <CODE>components</CODE> statement for each component we use that
provides one of the interfaces we chose earlier. Finally, we need to wire the
the interfaces used by the application to the components which provide those
interfaces. </P>
<P>Let's walk through the steps, one-by-one: </P>
<OL>
<LI><B>Identify the interfaces (and components) that provide access to the
radio and allow us to manipulate the <CODE>message_t</CODE> type.</B> <BR>We
will use the <CODE>AMSend</CODE> interface to send packets as well as the
<CODE>Packet</CODE> and <CODE>AMPacket</CODE> interfaces to access the
<CODE>message_t</CODE> abstract data type. Although it is possible to wire
directly to the <CODE>ActiveMessageC</CODE> component, we will instead use the
<CODE>AMSenderC</CODE> component. However, we still need to start the radio
using the <CODE>ActiveMessageC.SplitControl</CODE> interface.The reason for
using <CODE>AMSenderC</CODE> is because it provides a virtualized abstraction.
Earlier versions of TinyOS did not virtualize access to the radio, so it was
possible for two components that were sharing the radio to interfere with each
other. It was not at all uncommon for one component to discover the radio was
busy because some other component, unknown to the first component, was
accessing the active message layer. Radio virtualization was introduced in
TinyOS 2.0 to address this interference and <CODE>AMSenderC</CODE> was written
to provide this virtualization. Every user of <CODE>AMSenderC</CODE> is
provided with a 1-deep queue and the queues of all users are serviced in a
fair manner.
<LI><B>Update the <CODE>module</CODE> block in the
<CODE>BlinkToRadioC.nc</CODE> by adding <CODE>uses</CODE> statements for the
interfaces we need:</B> <PRE>module BlinkToRadioC {
...
uses interface Packet;
uses interface AMPacket;
uses interface AMSend;
uses interface SplitControl as AMControl;
}
</PRE>
<P>Note that <CODE>SplitControl</CODE> has been renamed to
<CODE>AMControl</CODE> using the <CODE>as</CODE> keyword. nesC allows
interfaces to be renamed in this way for several reasons. First, it often
happens that two or more components that are needed in the same module provide
the same interface. The <CODE>as</CODE> keyword allows one or more such names
to be changed to distinct names so that they can each be addressed
individually. Second, interfaces are sometimes renamed to something more
meaningful. In our case, <CODE>SplitControl</CODE> is a general interface used
for starting and stopping components, but the name <CODE>AMControl</CODE> is a
mnemonic to remind us that the particular instance of
<CODE>SplitControl</CODE> is used to control the <CODE>ActiveMessageC</CODE>
component. </P>
<LI><B>Declare any new variables and add any needed initialization code.</B>
<BR>First, we need to declare some new module-scope variables. We need a
<CODE>message_t</CODE> to hold our data for transmission. We also need a flag
to keep track of when the radio is busy sending. These declarations need to be
added in the <CODE>implementation</CODE> block of
<CODE>BlinkToRadioC.nc</CODE>: <PRE>implementation {
bool busy = FALSE;
message_t pkt;
...
}
</PRE>
<P>Next, we need to handle the initialization of the radio. The radio needs to
be started when the system is booted so we must call
<CODE>AMControl.start</CODE> inside <CODE>Boot.booted</CODE>. The only
complication is that in our current implementation, we start a timer inside
<CODE>Boot.booted</CODE> and we are planning to use this timer to send
messages over the radio but the radio can't be used until it has completed
starting up. The radio signals that it has completed starting through the
<CODE>AMControl.startDone</CODE> event. To ensure that we do not start using
the radio before it is ready, we need to postpone starting the timer until
after the radio has completed starting. We can accomplish this by moving the
call to start the timer, which is now inside <CODE>Boot.booted</CODE>, to
<CODE>AMControl.startDone</CODE>, giving us a new <CODE>Boot.booted</CODE>
with the following body: </P><PRE> event void Boot.booted() {
call AMControl.start();
}
</PRE>
<P>We also need to implement the <CODE>AMControl.startDone</CODE> and
<CODE>AMControl.stopDone</CODE> event handlers, which have the following
bodies: </P><PRE> 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) {
}
</PRE>
<P>If the radio is started successfully, <CODE>AMControl.startDone</CODE> will
be called with the <CODE>error_t</CODE> parameter set to a value of
<CODE>SUCCESS</CODE>. If the radio starts successfully, then it is appropriate
to start the timer. If, however, the radio does not start successfully, then
it obviously cannot be used so we try again to start it. This process
continues until the radio starts, and ensures that the node software doesn't
run until the key components have started successfully. If the radio doesn't
start at all, a human operator might notice that the LEDs are not blinking as
they are supposed to, and might try to debug the problem. </P>
<LI><B>Add any program logic and calls to the used interfaces we need for our
application.</B> <BR>Since we want to transmit the node's id and counter value
every time the timer fires, we need to add some code to the
<CODE>Timer0.fired</CODE> event handler: <PRE>event void Timer0.fired() {
...
if (!busy) {
BlinkToRadioMsg* btrpkt = (BlinkToRadioMsg*)(call Packet.getPayload(&pkt, NULL));
btrpkt->nodeid = TOS_NODE_ID;
btrpkt->counter = counter;
if (call AMSend.send(AM_BROADCAST_ADDR, &pkt, sizeof(BlinkToRadioMsg)) == SUCCESS) {
busy = TRUE;
}
}
}
</PRE>
<P>This code performs several operations. First, it ensures that a message
transmission is not in progress by checking the busy flag. Then it gets the
packet's payload portion and casts it to a pointer to the previously declared
<CODE>BlinkToRadioMsg</CODE> external type. It can now use this pointer to
initialise the packet's fields, and then send the packet by calling
<CODE>AMSend.send</CODE>. The packet is sent to all nodes in radio range by
specyfing <CODE>AM_BROADCAST_ADDR</CODE> as the destination address. Finally,
the test against SUCCESS verifies that the AM layer accepted the message for
transmission. If so, the busy flag is set to true. For the duration of the
send attempt, the packet is owned by the radio, and user code must not access
it. Note that we could have avoided using the <CODE>Packet</CODE> interface,
as it's <CODE>getPayload</CODE> command is repeated within
<CODE>AMSend</CODE>. </P>
<LI><B>Implmement any (non-initialization) events specified in the interfaces
we plan on using.</B> <BR>Looking through the <CODE>Packet</CODE>,
<CODE>AMPacket</CODE>, and <CODE>AMSend</CODE> interfaces, we see that there
is only one <CODE>event</CODE> we need to worry about,
<CODE>AMSend.sendDone</CODE>: <PRE> /**
* Signaled in response to an accepted send request. msg is
* the message buffer sent, and error indicates whether
* the send was successful.
*
* @param msg the packet which was submitted as a send request
* @param error SUCCESS if it was sent successfully, FAIL if it was not,
* ECANCEL if it was cancelled
* @see send
* @see cancel
*/
event void sendDone(message_t* msg, error_t error);
</PRE>
<P>This event is signaled after a message transmission attempt. In addition to
signaling whether the message was transmitted successfully or not, the event
also returns ownership of <CODE>msg</CODE> from <CODE>AMSend</CODE> back to
the component that originally called the <CODE>AMSend.send</CODE> command.
Therefore <CODE>sendDone</CODE> handler needs to clear the <CODE>busy</CODE>
flag to indicate that the message buffer can be reused: </P><PRE> event void AMSend.sendDone(message_t* msg, error_t error) {
if (&pkt == msg) {
busy = FALSE;
}
}
</PRE>
<P>Note the check to ensure the message buffer that was signaled is the same
as the local message buffer. This test is needed because if two components
wire to the same <CODE>AMSend</CODE>, <I>both</I> will receive a
<CODE>sendDone</CODE> event after <I>either</I> component issues a
<CODE>send</CODE> command. Since a component writer has no way to enforce that
her component will not be used in this manner, a defensive style of
programming that verifies that the sent message is the same one that is being
signaled is required. </P>
<LI><B>Update the <CODE>implementation</CODE> block of the application
configuration file by adding a <CODE>components</CODE> statement for each
component used that provides one of the interfaces chosen earlier.</B> <BR>The
following lines can be added just below the existing <CODE>components</CODE>
declarations in the <CODE>implementation</CODE> block of
<CODE>BlinkToRadioAppC.nc</CODE>: <PRE>implementation {
...
components ActiveMessageC;
components new AMSenderC(AM_BLINKTORADIO);
...
}
</PRE>
<P>These statements indicate that two components, <CODE>ActiveMessageC</CODE>
and <CODE>AMSenderC</CODE>, will provide the needed interfaces. However, note
the slight difference in their syntax. <CODE>ActiveMessageC</CODE> is a
singleton component that is defined once for each type of hardware platform.
<CODE>AMSenderC</CODE> is a generic, parameterized component. The
<CODE>new</CODE> keyword indicates that a new instance of
<CODE>AMSenderC</CODE> will be created. The <CODE>AM_BLINKTORADIO</CODE>
parameter indicates the AM type of the <CODE>AMSenderC</CODE>. We can extend
the <CODE>enum</CODE> in the <CODE>BlinkToRadio.h</CODE> header file to
incorporate the value of <CODE>AM_BLINKTORADIO</CODE>: </P><PRE>...
enum {
AM_BLINKTORADIO = 6,
TIMER_PERIOD_MILLI = 250
};
...
</PRE>
<LI><B>Wire the the interfaces used by the application to the components which
provide those interfaces.</B> <BR>The following lines will wire the used
interfaces to the providing components. These lines should be added to the
bottom of the <CODE>implementation</CODE> block of
<CODE>BlinkToRadioAppC.nc</CODE>: <PRE>implementation {
...
App.Packet -> AMSenderC;
App.AMPacket -> AMSenderC;
App.AMSend -> AMSenderC;
App.AMControl -> ActiveMessageC;
}
</PRE></LI></OL><A name=Receiving_a_Message_over_the_Radio></A>
<H1><SPAN class=mw-headline>Receiving a Message over the Radio</SPAN></H1>
<P>Now that we have an application that is transmitting messages, we can add
some code to receive and process the messages. Let's write code that, upon
receiving a message, sets the LEDs to the three least significant bits of the
counter in the message. To make this application interesting, we will want to
remove the line <CODE><STRIKE>call Leds.set(counter);</STRIKE></CODE> from the
<CODE>Timer0.fired</CODE> event handler. Otherwise, both the timer events and
packet receptions will update the LEDs and the resulting effect will be bizarre.
</P>
<P>If two motes are programmed with our modified application, then each will
display the other mote's counter value. If the motes go out of radio range, then
the LEDs will stop changing. You can even investigate link asymmetry by trying
to get one mote's LEDs to keep blinking while the other mote's LEDs stop
blinking. This would indicate that the link from the non-blinking mote to
blinking mote was available but that the reverse channel was no longer
available. </P>
<OL>
<LI><B>Identify the interfaces (and components) that provide access to the
radio and allow us to manipulate the <CODE>message_t</CODE> type.</B> <BR>We
will use the <CODE>Receive</CODE> interface to receive packets.
<LI><B>Update the module block in the BlinkToRadioC.nc by adding uses
statements for the interfaces we need:</B> <PRE>module BlinkToRadioC {
...
uses interface Receive;
}
</PRE>
<LI><B>Declare any new variables and add any needed initialization code.</B>
<BR>We will not require any new variables to receive and process messages from
the radio.
<LI><B>Add any program logic and calls to the used interfaces we need for our
application.</B> <BR>Message reception is an event-driven process so we do not
need to call any commands on the <CODE>Receive</CODE>.
<LI><B>Implemement any (non-initialization) events specified in the interfaces
we plan on using.</B> <BR>We need to implement the
<CODE>Receive.receive</CODE> event handler: <PRE>event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len) {
if (len == sizeof(BlinkToRadioMsg)) {
BlinkToRadioMsg* btrpkt = (BlinkToRadioMsg*)payload;
call Leds.set(btrpkt->counter);
}
return msg;
}
</PRE>
<P>The <CODE>receive</CODE> event handler performs some simple operations.
First, we need to ensure that the length of the message is what is expected.
Then, the message payload is cast to a structure pointer of type
<CODE>BlinkToRadioMsg*</CODE> and assigned to a local variable. Then, the
counter value in the message is used to set the states of the three LEDs.Note
that we can safely manipulate the <CODE>counter</CODE> variable outside of an
atomic section. The reason is that receive event executes in task context
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -