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