Lesson 2:
Event-Driven Sensor Acquisition
Last updated 9 September
2003 |
This lesson demonstrates a simple sensor application that takes light
intensity readings (from the sensor board's photo sensor) and displays those
readings on the LEDs. This is based on the Sense application, found in
apps/Sense. This is a very simple
application that reads a value from the light sensor and displays the lower 3
bits of the value on the LEDs. The application configuration is found in
Sense.nc and the implementation module in SenseM.nc.
Let's first look at the top portion of SenseM.nc:
SenseM.nc module SenseM { provides { interface StdControl; } uses { interface Timer; interface ADC; interface StdControl as ADCControl; interface Leds; } } // Implementation not shown ... |
Like BlinkM, this component provides the StdControl
interface and uses Timer and Leds. It also uses two more
interfaces: ADC, which is used to access data from the
analogue-to-digital converter, and StdControl, which is used to
initialize the ADC component.
This application uses a new component, TimerC, in place of
SingleTimer. The reason is that TimerC allows multiple
instance of timers, while SingleTimer only provides a single one that
only one component can use. We'll discuss more on Timer a bit later.
Note the line
interface StdControl as ADCControl;
This is saying that this
component uses the
StdControl interface but gives the
interface
instance the name
ADCControl. In this way a component can require
multiple instances of the same interface, but give them different names. For
example, a component might need to use the
StdControl interface to
control both the ADC and the sounder components. In this case we might say:
interface StdControl as ADCControl;
interface StdControl as SounderControl;
The configuration that uses this
module will be responsible for wiring each interface instance to some actual
implementation.
One related note: if you don't provide an interface instance name then the
instance is named the same as the interface. That is, the line
interface ADC;
is just shorthand for
interface ADC as ADC;
Take a look at the
StdControl and
ADC interfaces (both in tos/interfaces). You'll see that
StdControl is used to
initialize and power a component (usually a piece of physical hardware) and
ADC is used to get data from
an ADC channel.
ADC signals an event,
dataReady(), when data
is ready on the channel. Notice the
async keyword is used in the ADC
interface. This is declaring the commands and events as asynchronous code, i.e.,
code which can run in response to a hardware interrupt.
Open the file apps/Sense/SenseM.nc. You'll see the
code simply calls ADC.getData() each time Timer.fired() is
signaled. Likewise, when ADC.dataReady() is signaled, the internal
function display() is invoked, which sets the LEDs to the low-order
bits of the ADC value.
Notice the use of the function rcombine() in the implementation of
StdControl.init().
return rcombine(call ADCControl.init(), call Leds.init());
The
function
rcombine() is a special nesC combining function which returns
the logical-and of two commands who result type is result_t.
The Sense.nc
configuration |
At this point you should be wondering, "how does the Sense
application know that the ADC channel should access the light sensor?" This is
exactly what the Sense.nc configuration wires up for us.
Sense.nc configuration Sense { // this module does not provide any interface } implementation { components Main, SenseM, LedsC, TimerC, Photo;
Main.StdControl -> SenseM; Main.StdControl -> TimerC;
SenseM.ADC -> Photo; SenseM.ADCControl -> Photo; SenseM.Leds -> LedsC; SenseM.Timer -> TimerC.Timer[unique("Timer")]; } |
Most of this should be familiar from Blink. We're wiring up the
Main.StdControl to SenseM.StdControl, as before. The
Leds wirings is also similar to Blink, and we'll discuss
Timer in a minute. The wiring for the ADC is:
SenseM.ADC -> Photo;
SenseM.ADCControl -> Photo;
All we're doing is wiring up the
ADC interface (used by
SenseM) to a new component called
Photo. Same goes for the
ADCControl interface, which you'll
recall is an instance of the
StdControl interface used by
SenseM.
Remember that
SenseM.ADC -> Photo;
is just shorthand for
SenseM.ADC -> Photo.ADC;
On the other hand,
SenseM.ADControl -> Photo;
is
not shorthand for
SenseM.ADC -> Photo.ADCControl;
What's going on here? If you look
at the
Photo.nc component (found in
tos/sensorboards/micasb),
you'll see that it provides both the
ADC and
StdControl
interfaces -- it doesn't have an interface called
ADCControl.
(
ADCControl was just the name that we gave to the instance of
StdControl in the
SenseM component.) Rather, the nesC compiler
is smart enough to figure out that because
SenseM.ADCControl is
an
instance of the
StdControl interface, that it needs to be wired up
to
an instance of the
StdControl interface provided by
Photo. (If
Photo were to provide two instances of
StdControl, this would be an error because the wiring would be
ambiguous.) In other words,
SenseM.ADControl -> Photo;
is shorthand for
SenseM.ADControl -> Photo.StdControl;
Whew!
Timer and parameterized
interfaces |
Let's look at the line
SenseM.Timer -> TimerC.Timer[unique("Timer")];
This is
introducing a new bit of syntax, called a
parameterized interface.
A parameterized interface allows a component to provide multiple
instances of an interface that are parameterized by a runtime or
compile-time value. Recall that it's possible for a component to provide
multiple instances of an interface and to give them different names, e.g.,
provides {
interface StdControl as fooControl;
interface StdControl as barControl;
}
This is just a generalization of the same idea. The
TimerC
component declares:
provides interface Timer[uint8_t id];
In other words, it provides
256 different instances of the
Timer interface, one for each
uint8_t value!
In this case, we want TinyOS applications to create and use multiple timers,
each of which can be managed independently. For example, an application
component might need one timer to fire at a certain rate (say, once per second)
to gather sensor readings, while another component might need the a timer to
fire at a different rate to manage radio transmission. By wiring the
Timer interface in each of these components to a separate instance of
the Timer interface provided by TimerC, each component can
effectively get its own "private" timer.
When we say TimerC.Timer[someval], we are specifying that
BlinkM.Timer should be wired to the instance of the Timer
interface specified by the value (someval) in the square brackets. This
can be any 8-bit positive number. If we were to specify a particular value here,
such as 38 or 42, we might accidentally conflict with the timer being used by
another component (if that component used the same value in the brackets). So,
we use the compile-time constant function unique(), which generates a
unique 8-bit identifier from the string given as an argument. Here,
unique("Timer")
generates a unique 8-bit number from a set
corresponding to the string
"Timer". Therefore, Every component that
uses
unique("Timer") is guaranteed to get a
different 8-bit
value as long as the string used in the argument is the same. Note that if one
component uses
unique("Timer") and another uses
unique("MyTimer"), they might get the
same 8-bit value.
Therefore it's good practice to use the name of the parameterized interface as
an argument to the
unique() function. The compile-time constant
function
uniqueCount("Timer")
will count the number of uses of unique("Timer").
Running the Sense
application |
As before, just do a make mica install in the Sense
directory to compile and install the application on your mote. You'll need a
sensor board attached to the mote with a photo sensor. The Mica sensor board,
for example, snaps into place via the 51 pin connector. The type of sensorboard
is selected by the -board command line option to ncc. On the
Mica mote, the default sensor board type is micasb. However, if you
have an older ("basic") sensor board, you might need to pass in the -board
basicsb option to ncc. To do this, edit the Makefile in the Sense
directory and add the line SENSORBOARD=basicsb before the line that
includes Makerules. The sensor boards supported by TinyOS are
represented by the directory names in tos/sensorboards .
The operation of the photo sensor is a bit unusual. The ADC yields a 10-bit
digitized sample of the photo sensor. What we would like is for the LEDs to be
off when the node is in the light, and on when the node is in the dark. Here, we
are looking at the upper three bits of this data coming from the ADC, and
inverting the value. Note the line:
display(7 - ((data >> 7) & 0x7));
in the
ADC.dataReady() function of
SenseM.
Note that when running the application you may really have to cover up the
photo sensor pretty well to get it to respond - we'll admit that this is not the
best application to really see the photo sensor at work!
Extend the capability of the application by chirping the sensor board's
sounder when it is dark! The TinyOS component for the Sounder is located in
tos/sensorboards/micasb/Sounder.nc and provides the StdControl
interface. Modify SenseM.nc to add a StdControl as
SounderControl, wiring it as appropriate in Sense.nc.
Remember to call init() to initialize the Sounder component. To make the sounder
turn on from SenseM.nc, call SounderControl.start(). To turn off the
sounder, call SounderControl.stop(). Be sure to test this new
application many times to annoy everyone sitting around you.
posted on 2005-11-28 01:06
井冈山 阅读(479)
评论(0) 编辑 收藏 引用 所属分类:
tinyOS