Lesson 3 : Mote-mote radio communication
Active Message Interfaces
Since it is very common to have multiple services using the same radio to communicate, TinyOS provides the Active Message (AM) layer to multiplex access to the radio. The term "AM type" refers to the field used for multiplexing. AM types are similar in function to the Ethernet frame type field, IP protocol field, and the UDP port in that all of them are used to multiplex access to a communication service. AM packets also includes a destination field, which stores an "AM address" to address packets to particular motes. Additional interfaces, also located in the tos/interfaces
directory, were introduced to support the AM services:
AMPacket
- Similar toPacket
, provides the basic AM accessors for themessage_t
abstract data type. This interface provides commands for getting a node's AM address, an AM packet's destination, and an AM packet's type. Commands are also provides for setting an AM packet's destination and type, and checking whether the destination is the local node.AMSend
- Similar toSend
, provides the basic Active Message sending interface. The key difference betweenAMSend
andSend
is thatAMSend
takes a destination AM address in itssend
command.
The AM address of a node can be set at installation time, using the make install.n
or make reinstall.n
commands. It can be changed at runtime using the ActiveMessageAddressC
component.
Sending a Message over the Radio
Reimplementing Blink
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 = 0; event void Boot.booted() { call Timer0.startPeriodic(TIMER_PERIOD_MILLI); } event void Timer0.fired() { counter++; call Leds.set(counter); } }
BlinkToRadio.h header file
#ifndef BLINKTORADIO_H #define BLINKTORADIO_H enum { TIMER_PERIOD_MILLI = 250 }; #endif
the use of the ifndef(if not define)
, define
, and endif
directives. These directives are used to ensure that the definitions in each header file is not included multiple times because the compiler would complain about multiply-defined objects. By convention, the literal used for these directives is an all-caps version of the filename with any periods converted to underscores.
the use of an enum
declaration for defining the constant TIMER_PERIOD_MILLI
. Using enum
for defining constants is preferred over using define
because enum
does not indiscriminantly replace every occurence of the define
d literal, regardless of where it appears in the source. As a result, enum
s provide better scoping as well.
BlinkToRadioAppC.nc (用来连接use interface 和提供了它的实现的组件)to wire the interfaces that the implementation uses
to the actual components which provide
these interfaces
#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; }
最后建立Makefiel
COMPONENT=BlinkToRadioAppC include $(MAKERULES)
The first line tells the TinyOS make system that the top-level application component is BlinkToRadioAppC.
The second line loads in the TinyOS build system, which has all of the rules for building and installing on different platforms.
Defining a Message Structure
message will send both the node id and the counter value over the radio. Rather than directly writing and reading the payload area of the message_t
with this data, we will use a structure to hold them and then use structure assignment to copy the data into the message payload area.
用structure来读写payload更便利
Using a structure allows reading and writing the message payload more conveniently when your message has multiple fields or multi-byte fields (like uint16_t or uint32_t) because you can avoid reading and writing bytes from/to the payload using indices and then shifting and adding (e.g. uint16_t x = data[0] << 8 + data[1]
).
Even for a message with a single field, you should get used to using a structure because if you ever add more fields to your message or move any of the fields around, you will need to manually update all of the payload position indices if you read and write the payload at a byte level. Using structures is straightforward.
typedef nx_struct BlinkToRadioMsg { nx_uint16_t nodeid; nx_uint16_t counter; } BlinkToRadioMsg;
The nx_
prefix is specific to the nesC language and signifies that the struct
and uint16_t
are external types.
External types have the same representation on all platforms. The nesC compiler generates code that transparently reorders access to nx_
data types and eliminates the need to manually address endianness and alignment (extra padding in structs present on some platforms) issues.
the nx_
prefix on a type (e.g. nx_uint16_t
) indicates the field is to serialized in big endian format. In contrast, the nxle_
prefix signifies that the field is serialized in little endian format.
Sending a Message
- First, we need to identify the interfaces (and components) that provide access to the radio and allow us to manipulate the
message_t
type. - Second, we must update the
module
block in theBlinkToRadioC.nc
by addinguses
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 implement any events specified in the interfaces we plan on using.
- Sixth, the
implementation
block of the application configuration file,BlinkToRadioApp.c
, must be updated by adding acomponents
statement for each component we use that provides one of the interfaces we chose earlier. - Finally, we need to wire the interfaces used by the application to the components which provide those interfaces.
Note using the as
keyword to rename an interface. 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. Second, interfaces are sometimes renamed to something more meaningful.
components ActiveMessageC; components new AMSenderC(AM_BLINKTORADIO);
ActiveMessageC
is a singleton component that is defined once for each type of hardware platform. AMSenderC
is a generic, parameterized component. The new
keyword indicates that a new instance of AMSenderC
will be created. The AM_BLINKTORADIO
parameter indicates the AM type of the AMSenderC
. the value of AM_BLINKTORADIO 定义在BlinkToRadio.h header file 中的enum.
Receiving a Message over the Radio
和上面一样的过程去添加代码。
Note that we can safely manipulate the counter
variable outside of an atomic section. The reason is that receive event executes in task context rather than interrupt context (events that have the async
keyword can execute in interrupt context). Since the TinyOS execution model allows only one task to execute at a time, if all accesses to a variable occur in task context, then no race conditions will occur for that variable. Since all accesses to counter
occur in task context, no critical sections are needed when accessing it.
The AM_BLINKTORADIO
parameter indicates the AM type of the AMReceiverC
and is chosen to be the same as that used for the AMSenderC
used earlier, which ensures that the same AM type is being used for both transmissions and receptions.