TinyOS Tutorial
Communication Networks I
Wenyuan Xu
Fall 2006
Lecture Overview
1.
Hardware Primer
2.
Introduction to TinyOS
3.
Programming TinyOS
4.
Network Communication
UC Berkeley Family of Motes
Mica2 and Mica2Dot
ATmega128 CPU
Self-programming
128KB Instruction EEPROM
4KB Data EEPROM
Chipcon CC1000
Manchester encoding
Tunable frequency
38K or 19K baud
Lower power consumption
315, 433 or 900MHz
2 AA batteries
Expansion
51 pin I/O Connector
1 inch
MTS300CA Sensor Board
Programming Board (MIB510)
Hardware Setup Overview
Lecture Overview
1.
Hardware Primer
2.
Introduction to TinyOS
3.
Programming TinyOS
4.
Network Communication
What is TinyOS?
An operation system
An open-source development environment
Not an operation system for general purpose, it is
designed for wireless embedded sensor network.
Official website: http://www.tinyos.net/
Programming language: NesC (an extension of C)
It features a component-based architecture.
Supported platforms include Linux, Windows 2000/XP
with Cygwin.
Install TinyOS and the ‘make’
Download
http://www.tinyos.net/download.html
From within the application’s
directory:
Directory Structure
/apps
make (re)install.<node id>
<platform>
<node id> is an integer between 0
and 255
<platform> may be mica2,
mica2dot, or all
Example: make install.0 mica2
/Blink
/Forwarder
/contrib
/doc
/tools
/java
/tos
/interfaces
/lib
/platform
/mica
/mica2
/mica2dot
/sensorboard
/micasb
/system
/types
make pc
Generates an executable that can
be run a pc for
Build Tool Chain
Convert NesC into C
and compile to exec
Modify exec with
platform-specific
options
Set the mote ID
Reprogram the
mote
Lecture Overview
1.
Hardware Primer
2.
Introduction to TinyOS
3.
Programming TinyOS
4.
Network Communication
Characteristics of Network Sensors
Small physical size and low power consumption
Concurrency-intensive operation
multiple
Limited Physical Parallelism and Controller
Hierarchy
primitive
flows, not wait-command-respond
direct-to-device interface
Diversity in Design and Usage
application
specific, not general purpose
huge device variation
actuators
=> efficient modularity
=> migration across HW/SW boundary
sensors
Robust Operation
numerous,
unattended, critical
=> narrow interfaces
network
storage
A Operating System for Tiny Devices?
Main Concept
HURRY UP AND SLEEP!!
provide framework for concurrency and modularity
Sleep as often as possible to save power
Commands, events, tasks
interleaving flows, events - never poll, never block
Separation of construction and composition
Programs are built out of components
Libraries and components are written in nesC.
Applications are too -- just additional components composed with the OS
components
Each component is specified by an interface
Provides “hooks” for wiring components together
Components are statically wired together based on their interfaces
Increases runtime efficiency
Programming TinyOs
A component provides and uses interfaces.
A interface defines a logically related set of commands and events.
Components implement the events they use and the commands they
provide:
Component
Commands
Events
Use
Can call
Must implement
Provide
Must implement
Can signal
There are two types of components in nesC:
Modules. It implements application code.
Configurations. It assemble other components together, called wiring
A component does not care if another component is a module or
configuration
A component may be composed of other components via configurations
Component Syntax - Module
A component specifies a set of interfaces by which it is connected to other
components
provides a set of interfaces to others
uses a set of interfaces provided by others
module ForwarderM {
provides {
interface StdControl;
}
uses {
interface StdControl as CommControl;
interface ReceiveMsg;
interface SendMsg;
interface Leds;
}
}
implementation {
…// code implementing all provided commands
and used events
}
uses
provides
CommControl
ReceiveMsg
StdControl
ForwarderM
SendMsg
Leds
Component Syntax - Configuration
Component
Selection
configuration Forwarder { }
implementation
{
components Main, LedsC;
components GenericComm as Comm;
components ForwarderM;
Main.StdControl -> ForwarderM.StdControl;
ForwarderM.CommControl -> Comm;
ForwarderM.SendMsg -> Comm.SendMsg[AM_INTMSG];
ForwarderM.ReceiveMsg -> Comm.ReceiveMsg[AM_INTMSG];
ForwarderM.Leds -> LedsC;
Wiring the
Components
together
}
Forwarder
uses
provides
StdControl
CommControl
Main
StdControl
ReceiveMsg
StdControl
ForwarderM
ReceiveMsg
SendMsg
GenericComm
SendMsg
Leds
Leds
LedsC
Configuration Wires
A configuration can bind an interface user to a
provider using -> or <
Bounce responsibilities using =
User.interface -> Provider.interface
Provider.interface <- User.interface
User1.interface = User2.interface
Provider1.interface = Provider2.interface
The interface may be implicit if there is no
ambiguity
e.g., User.interface -> Provider
User.interface -> Provider.interface
Interface Syntax- interface StdControl
Look in <tos>/tos/interfaces/StdControl.nc
interface StdControl {
// Initialize the component and its subcomponents.
command result_t init();
// Start the component and its subcomponents.
command result_t start();
// Stop the component and pertinent subcomponents
command result_t stop();
}
Multiple components may provide and use this interface
Every component should provide this interface
This is good programming technique, it is not a language specification
Interface Syntax- interface SendMsg
Look in <tos>/tos/interfaces/SendMsg.nc
includes AM; // includes AM.h located in <tos>\tos\types\
interface SendMsg {
// send a message
command result_t send(uint16_t address, uint8_t length,
TOS_MsgPtr msg);
// an event indicating the previous message was sent
event result_t sendDone(TOS_MsgPtr msg, result_t success);
}
Includes both command and event.
Split the task of sending a message into two parts, send
and sendDone.
Component implementation
Command
implementation
(interface provided)
module ForwarderM {
//interface declaration
}
implementation {
command result_t StdControl.init()
{
call CommControl.init();
call Leds.init();
return SUCCESS;
}
command result_t StdControl.start() {…}
command result_t StdControl.stop() {…}
event TOS_MsgPtr ReceiveMsg.receive(TOS_MsgPtr m)
{
call Leds.yellowToggle();
call SendMsg.send(TOS_BCAST_ADDR, sizeof(IntMsg), m);
return m;
}
Event
implementation
(interface used)
event result_t SendMsg.sendDone(TOS_MsgPtr msg, bool success)
{
call Leds.greenToggle();
return success;
}
}
TinyOS Commands and Events
{
...
status = call CmdName(args)
...
}
event EvtName)(args) {
...
return status;
}
command CmdName(args) {
...
return status;
}
{
...
status = signal EvtName(args)
...
}
TinyOs Concurrency Model
TinyOS executes only one program consisting of a set of
components.
Two type threads:
Task
Hardware event handler
Tasks:
Time flexible
Longer background processing jobs
Atomic with respect to other tasks (single threaded)
Preempted by event
Hardware event handlers
Time critical
Shorter duration (hand off to task if need be)
Interrupts task and other hardware handler.
Last-in first-out semantics (no priority among events)
executed in response to a hardware interrupt
Tasks
Provide concurrency internal to a component
longer running operations
Scheduling:
Currently simple FIFO scheduler
Bounded number of pending tasks
When idle, shuts down node except clock
Uses non-blocking task queue data structure
Simple event-driven structure + control over complete application/system graph
instead of complex task priorities and IPC
{
...
post TaskName();
...
}
task void TaskName {
...
}
TinyOS Execution Contexts
events
Tasks
commands
Interrupts
Hardware
Events generated by interrupts preempt
tasks
Tasks do not preempt tasks
Both essential process state transitions
Event-Driven Sensor Access Pattern
command result_t StdControl.start() {
return call Timer.start(TIMER_REPEAT, 200);
SENSE
}
event result_t Timer.fired() {
return call sensor.getData();
Timer
}
Photo
event result_t sensor.dataReady(uint16_t data) {
display(data)
return SUCCESS;
}
clock event handler initiates data collection
sensor signals data ready event
data event handler calls output command
device sleeps or handles other activity while waiting
conservative send/ack at component boundary
LED
Lecture Overview
1.
Hardware Primer
2.
Introduction to TinyOS
3.
NesC Syntax
4.
Network Communication
Inter-Node Communication
General idea:
Sender:
Fill message
buffer with data
Specify
Recipients
Pass buffer
to OS
Determine when
message buffer
can be reused
Receiver:
OS Buffers
incoming message
in a free buffer
Signal
application with
new message
OS obtains free
buffer to store
next message
TOS Active Messages
Message is “active” because it
contains the destination
address, group ID, and type.
‘group’: group IDs create a
virtual network
an 8 bit value specified in
<tos>/apps/Makelocal
The address is a 16-bit value
specified by “make”
– make install.<id> mica2
“length” specifies the size of
the message .
“crc” is the check sum
Preamble Sync Header (5)
typedef struct TOS_Msg {
// the following are transmitted
uint16_t addr;
uint8_t type;
uint8_t group;
uint8_t length;
int8_t data[TOSH_DATA_LENGTH];
uint16_t crc;
// the following are not transmitted
uint16_t strength;
uint8_t ack;
uint16_t time;
uint8_t sendSecurityMode;
uint8_t receiveSecurityMode;
} TOS_Msg;
Payload (29)
CRC (2)
TOS Active Messages (continue)
Sending a message
Define the message
format
Define a unique
active message
number
How does TOS
know the AM
number?
includes Int16Msg;
module ForwarderM {
//interface declaration
}
implementation {
event TOS_MsgPtr ReceiveMsg.receive(TOS_MsgPtr m)
{
call Leds.yellowToggle();
call SendMsg.send(TOS_BCAST_ADDR,
sizeof(IntMsg), m);
return m;
}
destination
length
event result_t SendMsg.sendDone(TOS_MsgPtr msg, bool success)
{
call Leds.greenToggle();
return success;
}
File: Int16Msg.h
struct Int16Msg {
uint16_t val;
};
enum {
AM_INTMSG = 47
};
}
configuration Forwarder { }
implementation
{ …
ForwarderM.SendMsg -> Comm.SendMsg[AM_INTMSG];
ForwarderM.ReceiveMsg -> Comm.ReceiveMsg[AM_INTMSG];
}
Receiving a message
Define the message
format
Define a unique
active message
number
How does TOS
know the AM
number?
includes Int16Msg;
module ForwarderM {
Message received
//interface declaration
}
implementation {
event TOS_MsgPtr ReceiveMsg.receive(TOS_MsgPtr m)
{
call Leds.yellowToggle();
call SendMsg.send(TOS_BCAST_ADDR,
sizeof(IntMsg), m);
return m;
}
event result_t SendMsg.sendDone(TOS_MsgPtr msg, bool success)
{
call Leds.greenToggle();
return success;
}
File: Int16Msg.h
struct Int16Msg {
uint16_t val;
};
enum {
AM_INTMSG = 47
};
}
configuration Forwarder { }
implementation
{ …
ForwarderM.SendMsg -> Comm.SendMsg[AM_INTMSG];
ForwarderM.ReceiveMsg -> Comm.ReceiveMsg[AM_INTMSG];
}
Where exactly is the radio stuff?
Mica2
StdControl
StdControl
CC1000RadioC
BareSendMsg
ReceiveMsg
BareSendMsg
ReceiveMsg
CC1000RadioIntM
Spi bus interrupt handler
Connection between Chipcon CC1000 radio and the
ATmega128 processor: SPI bus.
Spibus interrupt handler: SpiByteFifo.dataReady()
SpiByteFifo.dataReady() will be called every 8 ticks.
file:CC1000RadioIntM.nc
async event result_t SpiByteFifo.dataReady(uint8_t data_in) {
…
switch (RadioState) {
case RX_STATE: {...}
case DISABLED_STATE: {…}
case IDLE_STATE: {...}
case PRETX_STATE: {...}
case SYNC_STATE: {...}
case RX_STATE: {...}
return SUCCESS;
}
Preamble Sync Header (5)
Payload (29)
CRC (2)
Receiving a message (1)
IDLE_STATE SYNC_STATE
Listen to the preamble, if the enough bytes of preamble are received, entering
SYCN_STATE
file:CC1000RadioIntM.nc
async event result_t SpiByteFifo.dataReady(uint8_t data_in) {
…
switch (RadioState) {
…
case IDLE_STATE:
{ if (((data_in == (0xaa)) || (data_in == (0x55)))) {
PreambleCount++;
if (PreambleCount > CC1K_ValidPrecursor) {
PreambleCount = SOFCount = 0;
RxBitOffset = RxByteCnt = 0;
usRunningCRC = 0;
rxlength = MSG_DATA_SIZE-2;
RadioState = SYNC_STATE;
}
}
}…
}
Preamble Sync Header (5)
Payload (29)
CRC (2)
Receiving a message (2)
SYNC_STATE RX_STATE
look for a SYNC_WORD (0x33cc).
Save the last received byte and current received byte
Use a bit shift compare to find the byte boundary for the sync byte
Retain the shift value and use it to collect all of the packet data
SYNC_STATE IDLE_STATE
didn't find the SYNC_WORD after a reasonable number of tries, so set the radio
state back to idel:
RadioState = IDLE_STATE;
file:CC1000RadioIntM.nc
async event result_t SpiByteFifo.dataReady(uint8_t data_in) {
…
switch (RadioState) {
case SYNC_STATE: …
{ if ( find SYCN_WORD) {
…
RadioState = RX_STATE;
} else if ( too many preamble) {
…
RadioState = IDLE_STATE; }
}…
}
Preamble Sync Header (5)
Payload (29)
CRC (2)
Receiving a message (3)
RX_STATE IDLE_STATE/SENDING_ACK
Keep receiving bytes and calculate CRC until the end of the packet.
The end of the packet are specified by the length in the packet header
Pass the message to the application layer, no matter whether the
message passed the CRC check
file:CC1000RadioIntM.nc
async event result_t SpiByteFifo.dataReady(uint8_t data_in) { …
switch (RadioState) {
…
case RX_STATE:
{ …RxByteCnt++;
if (RxByteCnt <= rxlength) {
usRunningCRC = crcByte(usRunningCRC,Byte);
if (RxByteCnt == HEADER_LENGTH_OFFSET) {
rxlength = rxbufptr->length;}
} else if (RxByteCnt == CRCBYTE_OFFSET) {
if (rxbufptr->crc == usRunningCRC) {
rxbufptr->crc = 1;
} else {
rxbufptr->crc = 0;
}…
RadioState = IDLE_STATE;
post PacketRcvd();
}…
Preamble Sync Header (5)
Payload (29)
CRC (2)
Error Detection – CRC
CRC – Cyclic Redundancy Check
Polynomial
cods or checksums
Procedure:
1. Let r be the degree of the code polynomial. Append r zero bits to
the end of the transmitted bit string. Call the entire bit string S(x)
2. Divide S(x) by the code polynomial using modulo 2 division.
3. Subtract the remainder from S(x) using modulo 2 subtraction.
The result is the checksummed message
Generating a CRC – example
Message: 1011
1 * x3 + 0 * x2 + 1 * x + 1= x3 + x + 1
Code Polynomial: x2 + 1 (101)
Step 1: Compute S(x)
r=2
S(x) = 101100
Step 2: Modulo 2 divide
101
1011
101100
101
001
000
010
000
100
101
01
Step 3: Modulo 2 subtract
the remainder from S(x)
101100
- 01
101101
Checksummed Message
Remainder
Decoding a CRC – example
Procedure
1. Let n be the length of the checksummed message in bits
2. Divide the checksummed message by the code polynomial using
modulo 2 division. If the remaidner is zero, there is no error detected.
Case 1:
Checksummed
message (n=6):
101101
1011
Original message
101
Case 2:
1001
101101
101
001
000
010
000
101
101
00
Remainder = 0
(No error detected)
101
1000
101001
101
000
000
000
000
001
000
01
Remainder = 1
(Error detected)
CRC in TinyOS
Calculate the CRC byte by byte
crc=0x0000;
while (more bytes) {
crc=crc^b<<8;
calculate the high byte of crc
}
file: system/crc.h
uint16_t crcByte(uint16_t crc, uint8_t b)
{
uint8_t i;
crc = crc ^ b << 8;
i = 8;
do
if (crc & 0x8000)
crc = crc << 1 ^ 0x1021;
else
crc = crc << 1;
while (--i);
return crc;
Code Polynomial: CRC-CCITT
0x1021 = 0001 0000 0010 0001
x16 + x12 + x5 + 1
}
Example:
10001 0000 0010 0001
10…
10110110110110110110110110110100
10001000000100001
01111101100101111
00000000000000000
11111011001011111
…
Further Reading
Go through the on-line tutorial:
Search the help archive:
http://www.tinyos.net/tinyos-1.x/doc/nesc/ref.pdf
Getting started guide
http://www.tinyos.net/search.html
NesC language reference manual:
http://www.tinyos.net/tinyos-1.x/doc/tutorial/index.html
http://www.xbow.com/Support/Support_pdf_files/Getting_Started_Guide
.pdf
Hardware manual:
http://www.xbow.com/Support/Support_pdf_files/MPRMIB_Series_Users_Manual.pdf
Reference
“Programming TinyOS”, David Culler, Phil Levis,
Rob Szewczyk, Joe Polastre University of California,
BerkeleyIntel Research Berkeley
“TinyOS Tutorial”, Chien-Liang Fok,
http://www.princeton.edu/~wolf/EECS579/imotes/tos_tut
orial.pdf
“Computer Networks”, Badri Nath
http://www.cs.rutgers.edu/dataman/552dir/notes/
week2-one.pdf
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )