TinyOS Programming
By: R Jayampathi Sampath
nesC (network embedded system C)
• necC is a component-based C dialect.
• nesC application consists of one or more
components wired.
• Use a purely local namespace.
• There are two types of components:
– modules
• provide the implementation of one or more
interfaces.
– configurations
• used to wire other components together.
Contd.
• Components define two scopes.
– Specification (signature)
• names of interfaces it provides (implements) and names of
interfaces it uses (calls).
– Implementation
• implementation of commands and events.
module configuration
module
{
…..//provide and uses
interfaces
…
}
implementation{
…..//executable code
…
}
configuration
{
…..//provide and uses
interfaces
…
}
implementation{
…..//wire components
…
}
Modules and State
• Modules:
– are executable codes.
– must implement every command of interfaces it provides and
every event of interfaces it uses.
– can declare state variables. Any state of a components is
private.
– Ex: uint8_t counter = 0;
8 bits 16 bits 32 bits 64 bits
signed int8_t int16_t int32_t int64_t
unsigned uint8_t uint16_t uint32_t uint64_t
BilinkC.nc
module BilnkC
{
uses interface Timer<TMilli> as Timer0;
uses interface Timer<TMilli> as Timer1;
uses interface Timer<TMilli> as Timer2;
uses interface Leds;
uses interface Boot;
}
Implementation
{
event void Boot.booted()
{
call Timer0.startPeriodic(250);
call Timer1.startPeriodic(500);
call Timer1.startPeriodic(1000);
}
event void Timer0.fired(){
call Leds.led0Toggle();
}
event void Timer1.fired(){
call Leds.led1Toggle();
}
event void Timer2.fired()
{
call Leds.led2Toggle();
}
}
BlinkAppC.nc
configuration BlinkC
{
}
Implementation
{
components MainC, BlinkC, LedsC;
components new TimerMillic() as Timer0;
components new TimerMillic() as Timer0;
components new TimerMillic() as Timer0;
BlinkC ->MainC.Boot;
BlinkC.Timer0 -> Timer0;
BlinkC.Timer1 -> Timer1;
BlinkC.Timer2 -> Timer2;
BlinkC.Leds -> LedsC;
}
nesC uses arrows to bind interfaces to one another.
The right arrow (A->B) as "A wires to B"
Interfaces, Commands and Events
• if a component uses an interface, it can call the interface's
commands and must implement handlers for its events.
• Invoking an interface command requires the call keyword, and
invoking an interface event requires the signal keyword.
• Ex
Timer.nc:
interface Timer
{
// basic interface
command void startPeriodic( uint32_t dt );
command void startOneShot( uint32_t dt );
command void stop();
event void fired();
// extended interface omitted (all commands)
}
Internal
Functions
• Component's private
function for its own
internal use.
• Similar to C function.
• Can’t invoke directly.
• Can freely call
commands or signal
events.
module BlinkC {
uses interface Timer<TMilli> as Timer0;
uses interface Timer<TMilli> as Timer1;
uses interface Timer<TMilli> as Timer2;
uses interface Leds;
uses interface Boot;
}
implementation {
void startTimers() {
call Timer0.startPeriodic( 250 );
call Timer1.startPeriodic( 500 );
call Timer2.startPeriodic( 1000 );
}
event void Boot.booted() {
startTimers();
}
.
.
.
}
•Ex:
Split-Phase Operations
• Hardware is almost always split-phase (non blocking/asynchronous).
• In split-phase completion of a request is a callback.
– ADC
• synchronous operation:
– Magnetometer.
– samples periodically.
– when queried gives a cached value. (it can return the result immediately)
• make everything synchronous through threads is not possible.
• Solution :
– Operations that are split-phase in hardware are split-phase in software.
– Introduce interfaces that are bidirectional
ADC
S/W
Start Sample
Interrupts
when
complete
Read the
value
Split-Phase Operations
(Contd.)
ADC
Down call
Start the operation
Up call
signals the operation is complete
• Down call - command
• Up call - event
Split-Phase Operations
(Contd.)
• Split-phase interfaces enable a TinyOS component to easily start several
operations at once and have them execute in parallel.
• split-phase operations can save memory.
• Ex: The command Timer.startOneShot is an example of a split-phase call.
The user of the Timer inteface calls the command, which returns
immediately. Some time later (specified by the argument), the component
providing Timer signals Timer.fired. In a system with blocking calls, a
program might use sleep():
Interfaces with Arguments
• Interfaces can take types as arguments.
• wiring providers and users of interfaces that have type
arguments, they types must match.
• used to enforce type checking.
Module Implementation
Tasks
• Consider magnetometer/ADC example:
– depending on split-phase operations means that the
magnetometer driver has to issue a callback.
– it could just signal the event from within the call.
– signaling an event from within a command is
generally a bad idea.
• might cause a very long call loop.
• corrupt memory and crash the program.
– needs a way to schedule a function to be called later
(like an interrupt).
– can do this is with a task.
Tasks (Contd.)
• Task
– A module can post a task to the TinyOS scheduler.
– doesn’t take any parameters.
– A component posts a task to the TinyOS scheduler with the
post keyword: post
– only one task runs at any time. and TinyOS doesn’t interrupt
one task to run another.
– Implies that tasks should usually be reasonably short.
Tasks (Contd.)
• Ex:
event void Timer.fired() {
call Read.read();
}
event void RawRead.readDone(error_t err, uint16_t val) {
if (err == SUCCESS) {
lastVal = val;
filterVal *= 9;
filterVal /= 10;
filterVal += lastVal / 10;
}
}
command error_t Read.read() {
post readDoneTask();
return SUCCESS;
}
task void readDoneTask() {
signal Read.readDone(SUCCESS, filterVal);
}
• When Read.read is called, posts readDoneTask and returns immediately.
At some point later, TinyOS runs the task, which signals Read.readDone.
Tasks (Contd.)
• Why?
– Synchronous code runs in a single execution context
and does not have any kind of pre-emption.
– sync code runs for a long time.
– A component needs to be able to split a large
computation into smaller parts, which can be
executed one at a time.
• Task
– A task is function which a component tells TinyOS to
run later, rather than now.
Concurrency
• Tasks allow software components to emulate the split-
phase behavior of hardware.
• They also provide a mechanism to manage pre-emption
in the system.
• In nesC and TinyOS, functions that can run
preemptively labeled with the async keyword.
• async function can’t call a command or event that isn’t
async.
• By default, commands and events are sync.
• A task post is an async operation, while a task running
is sync.
Concurrency (Contd.)
• Can’t make everything
async because of race
condition.
• Ex: consider the
command, toggle, which
flips the state bit and
returns the new one:
• Solutions:
– Keep code synchronous
when you can.
– functionality through
atomic statements.
Concurrency (Contd.)
• The atomic block
promises that these
variables can be read
and written atomically
• this does not promise
that the atomic block
won’t be preempted
– with atomic blocks, two
code segments that do not
share any of the same
variables can preempt one
another
Concurrency (Contd.)
• An atomic block involves some kind of
execution (e.g.. disabling an interrupt), so
unnecessary atomics are a waste of CPU
cycles.
• an atomic block does have a CPU cost,
so you want to minimize how many you
have.
• shorter atomic blocks delay interrupts less
and so improve system concurrency.
Allocation
• the only way that components can share state is
through function calls.
• two basic ways that components can pass
parameters: by value and by reference (pointer).
• every pointer should have a clear owner, and
only the owner can modify the corresponding
memory.
• abstract data types (ADTs) in TinyOS are
usually represented one of two ways:
– Generic modules
– Through an interface with by-reference commands.
Allocation (An Example)
• Generic module, Ex
– many TinyOS components needs to maintain bit vectors, and so in tos/system there’s a
generic module BitVectorC that takes the number of bits as a parameter:
– This component allocates the bit vector internally and provides the BitVector
interface to access it:
Allocation (Contd.)
– in BitVector, it’s possible that, after a bit has been
fetched for get() but before it returns, an interrupt
fires whose handler calls set() on that same bit.
– you should call get() from within an atomic section.
• passing a parameter by reference,
– this is easy, as all of its commands are synchronous:
no code that can preempt the call (async).
References
• TinyOS Documentation Wiki (URL:
http://docs.tinyos.net/index.php/Main_
).
• TinyOS Programing (URL:
http://www.tinyos.net/tinyos
2.x/doc/pdf/tinyos
programming.pdf)

0903 1

  • 1.
    TinyOS Programming By: RJayampathi Sampath
  • 2.
    nesC (network embeddedsystem C) • necC is a component-based C dialect. • nesC application consists of one or more components wired. • Use a purely local namespace. • There are two types of components: – modules • provide the implementation of one or more interfaces. – configurations • used to wire other components together.
  • 3.
    Contd. • Components definetwo scopes. – Specification (signature) • names of interfaces it provides (implements) and names of interfaces it uses (calls). – Implementation • implementation of commands and events. module configuration module { …..//provide and uses interfaces … } implementation{ …..//executable code … } configuration { …..//provide and uses interfaces … } implementation{ …..//wire components … }
  • 4.
    Modules and State •Modules: – are executable codes. – must implement every command of interfaces it provides and every event of interfaces it uses. – can declare state variables. Any state of a components is private. – Ex: uint8_t counter = 0; 8 bits 16 bits 32 bits 64 bits signed int8_t int16_t int32_t int64_t unsigned uint8_t uint16_t uint32_t uint64_t
  • 5.
    BilinkC.nc module BilnkC { uses interfaceTimer<TMilli> as Timer0; uses interface Timer<TMilli> as Timer1; uses interface Timer<TMilli> as Timer2; uses interface Leds; uses interface Boot; } Implementation { event void Boot.booted() { call Timer0.startPeriodic(250); call Timer1.startPeriodic(500); call Timer1.startPeriodic(1000); } event void Timer0.fired(){ call Leds.led0Toggle(); } event void Timer1.fired(){ call Leds.led1Toggle(); } event void Timer2.fired() { call Leds.led2Toggle(); } } BlinkAppC.nc configuration BlinkC { } Implementation { components MainC, BlinkC, LedsC; components new TimerMillic() as Timer0; components new TimerMillic() as Timer0; components new TimerMillic() as Timer0; BlinkC ->MainC.Boot; BlinkC.Timer0 -> Timer0; BlinkC.Timer1 -> Timer1; BlinkC.Timer2 -> Timer2; BlinkC.Leds -> LedsC; } nesC uses arrows to bind interfaces to one another. The right arrow (A->B) as "A wires to B"
  • 6.
    Interfaces, Commands andEvents • if a component uses an interface, it can call the interface's commands and must implement handlers for its events. • Invoking an interface command requires the call keyword, and invoking an interface event requires the signal keyword. • Ex Timer.nc: interface Timer { // basic interface command void startPeriodic( uint32_t dt ); command void startOneShot( uint32_t dt ); command void stop(); event void fired(); // extended interface omitted (all commands) }
  • 7.
    Internal Functions • Component's private functionfor its own internal use. • Similar to C function. • Can’t invoke directly. • Can freely call commands or signal events. module BlinkC { uses interface Timer<TMilli> as Timer0; uses interface Timer<TMilli> as Timer1; uses interface Timer<TMilli> as Timer2; uses interface Leds; uses interface Boot; } implementation { void startTimers() { call Timer0.startPeriodic( 250 ); call Timer1.startPeriodic( 500 ); call Timer2.startPeriodic( 1000 ); } event void Boot.booted() { startTimers(); } . . . } •Ex:
  • 8.
    Split-Phase Operations • Hardwareis almost always split-phase (non blocking/asynchronous). • In split-phase completion of a request is a callback. – ADC • synchronous operation: – Magnetometer. – samples periodically. – when queried gives a cached value. (it can return the result immediately) • make everything synchronous through threads is not possible. • Solution : – Operations that are split-phase in hardware are split-phase in software. – Introduce interfaces that are bidirectional ADC S/W Start Sample Interrupts when complete Read the value
  • 9.
    Split-Phase Operations (Contd.) ADC Down call Startthe operation Up call signals the operation is complete • Down call - command • Up call - event
  • 10.
    Split-Phase Operations (Contd.) • Split-phaseinterfaces enable a TinyOS component to easily start several operations at once and have them execute in parallel. • split-phase operations can save memory. • Ex: The command Timer.startOneShot is an example of a split-phase call. The user of the Timer inteface calls the command, which returns immediately. Some time later (specified by the argument), the component providing Timer signals Timer.fired. In a system with blocking calls, a program might use sleep():
  • 11.
    Interfaces with Arguments •Interfaces can take types as arguments. • wiring providers and users of interfaces that have type arguments, they types must match. • used to enforce type checking.
  • 12.
  • 13.
    Tasks • Consider magnetometer/ADCexample: – depending on split-phase operations means that the magnetometer driver has to issue a callback. – it could just signal the event from within the call. – signaling an event from within a command is generally a bad idea. • might cause a very long call loop. • corrupt memory and crash the program. – needs a way to schedule a function to be called later (like an interrupt). – can do this is with a task.
  • 14.
    Tasks (Contd.) • Task –A module can post a task to the TinyOS scheduler. – doesn’t take any parameters. – A component posts a task to the TinyOS scheduler with the post keyword: post – only one task runs at any time. and TinyOS doesn’t interrupt one task to run another. – Implies that tasks should usually be reasonably short.
  • 15.
    Tasks (Contd.) • Ex: eventvoid Timer.fired() { call Read.read(); } event void RawRead.readDone(error_t err, uint16_t val) { if (err == SUCCESS) { lastVal = val; filterVal *= 9; filterVal /= 10; filterVal += lastVal / 10; } } command error_t Read.read() { post readDoneTask(); return SUCCESS; } task void readDoneTask() { signal Read.readDone(SUCCESS, filterVal); } • When Read.read is called, posts readDoneTask and returns immediately. At some point later, TinyOS runs the task, which signals Read.readDone.
  • 16.
    Tasks (Contd.) • Why? –Synchronous code runs in a single execution context and does not have any kind of pre-emption. – sync code runs for a long time. – A component needs to be able to split a large computation into smaller parts, which can be executed one at a time. • Task – A task is function which a component tells TinyOS to run later, rather than now.
  • 17.
    Concurrency • Tasks allowsoftware components to emulate the split- phase behavior of hardware. • They also provide a mechanism to manage pre-emption in the system. • In nesC and TinyOS, functions that can run preemptively labeled with the async keyword. • async function can’t call a command or event that isn’t async. • By default, commands and events are sync. • A task post is an async operation, while a task running is sync.
  • 18.
    Concurrency (Contd.) • Can’tmake everything async because of race condition. • Ex: consider the command, toggle, which flips the state bit and returns the new one: • Solutions: – Keep code synchronous when you can. – functionality through atomic statements.
  • 19.
    Concurrency (Contd.) • Theatomic block promises that these variables can be read and written atomically • this does not promise that the atomic block won’t be preempted – with atomic blocks, two code segments that do not share any of the same variables can preempt one another
  • 20.
    Concurrency (Contd.) • Anatomic block involves some kind of execution (e.g.. disabling an interrupt), so unnecessary atomics are a waste of CPU cycles. • an atomic block does have a CPU cost, so you want to minimize how many you have. • shorter atomic blocks delay interrupts less and so improve system concurrency.
  • 21.
    Allocation • the onlyway that components can share state is through function calls. • two basic ways that components can pass parameters: by value and by reference (pointer). • every pointer should have a clear owner, and only the owner can modify the corresponding memory. • abstract data types (ADTs) in TinyOS are usually represented one of two ways: – Generic modules – Through an interface with by-reference commands.
  • 22.
    Allocation (An Example) •Generic module, Ex – many TinyOS components needs to maintain bit vectors, and so in tos/system there’s a generic module BitVectorC that takes the number of bits as a parameter: – This component allocates the bit vector internally and provides the BitVector interface to access it:
  • 23.
    Allocation (Contd.) – inBitVector, it’s possible that, after a bit has been fetched for get() but before it returns, an interrupt fires whose handler calls set() on that same bit. – you should call get() from within an atomic section. • passing a parameter by reference, – this is easy, as all of its commands are synchronous: no code that can preempt the call (async).
  • 24.
    References • TinyOS DocumentationWiki (URL: http://docs.tinyos.net/index.php/Main_ ). • TinyOS Programing (URL: http://www.tinyos.net/tinyos 2.x/doc/pdf/tinyos programming.pdf)