[Tinyos Core WG] Meeting: 11/29/06
Philip Levis
pal at cs.stanford.edu
Wed Nov 29 07:40:02 PST 2006
Wednesday, November 29, 2006 9:30 AM US Pacific Time
Bridge: 1, Passcode: 7219221
Numbers:
US: 1-916-356-2663 or 1-888-875-9370 (non-Intel)
UK: +44 1793 402663
Denmark: +45 4527 5090
David Culler will be chairing.
Agenda:
tinyos-2.x-contrib progress
Working group formation
TEP status (101, 102, 106, 107, 108)
TEP 109 (Gil) (attached)
TEP 113 (attached)
Phil
-------------- next part --------------
=========================
Sensors and Sensor Boards
=========================
:TEP: 109
:Group: Core Working Group
:Type: Documentary
:Status: Draft
:TinyOS-Version: 2.x
:Author: David Gay, Phil Levis, Wei Hong, Joe Polastre, and Gilman Tolle
:Draft-Created: 10-Jun-2006
:Draft-Discuss: TinyOS Developer List <tinyos-devel at mail.millennium.berkeley.edu>
.. Note::
This memo documents a part of TinyOS for the TinyOS Community, and
requests discussion and suggestions for improvements. Distribution
of this memo is unlimited. This memo is in full compliance with
TEP 1.
Abstract
====================================================================
This memo documents how sensor drivers are organized in TinyOS and how
sets of sensor drivers are combined into sensor boards and sensor
platforms, along with general principles followed by the components
that provide access to sensors.
1. Principles
====================================================================
This section describes the basic organization principles for sensor
drivers in TinyOS.
For background, a sensor may be attached to the microcontroller on a
TinyOS platform through a few different types of connections:
* Included within the microcontroller itself
* Connected to general-purpose IO pins for level/edge detection
* Connected to an ADC in the microcontroller for voltage sampling
* Connected to general-purpose IO pins for digital communication
* Connected through a standard digital bus protocol (1-Wire, I2C, SPI)
Physically, these connections may also be decoupled by attaching the
sensors to a `sensor board`, which can be removed from the TinyOS
platform, and may fit multiple different TinyOS platforms.
The capabilities of a physical sensor are made available to a TinyOS
application through a `sensor driver`.
According to the HAA [TEP2]_, TinyOS devices should provide both
simple hardware-independent interfaces for common-case use (HIL) and
rich hardware-dependent interfaces for special-case use (HAL). Sensor
drivers should follow this spirit as well.
TinyOS 2.x represents each sensor as an individual component. This
allows the compilation process to minimize the amount of code
included. A sensor board containing multiple sensors should be
represented as a collection of components, one for each sensor,
contained within a sensor board directory.
Sensors, being physical devices that may be shared, can benefit from
virtualization and arbitration. This document describes a design
pattern for sensor virtualization that may be followed by sensor
drivers.
The same physical sensor may be attached to multiple different TinyOS
platforms, through platform-dependent interconnections. The common
logic of sensor driver should be factored into chip-dependent,
platform-independent components, and those components should be bound
to the hardware resources on a platform by platform-dependent
components, and to the hardware resources on a sensor board by
sensorboard-dependent components.
A physical sensor has a general class and a specific set of
performance characteristics, captured by the make and model of the
sensor itself. The naming of the sensor driver components should
reflect the specifc name of the sensor, and optionally provide a
component with a generic name for application authors who only care
about the general class of the sensor.
This document takes no position on the meaning of the values returned
by sensor drivers. They may be raw uninterpreted values or they may
have some physical meaning. If a driver returns uninterpreted values,
the driver may provide additional interfaces that would allow
higher-level clients to interpret the value properly.
2. Sensor HIL Components
====================================================================
A sensor HIL component MUST provide:
- One or more SID interfaces [TEP114]_, for reading data.
A sensor HIL component MAY provide:
- One or more SID interfaces [TEP114]_, for reading or
writing calibration coefficients or control registers.
A sensor device driver SHOULD be a generic component that virtualizes
access to the sensor. A sensor device driver can provide such
virtualization for itself by defining a nesC generic client
component. When a client component is being used, a call to a
top-level SID interface should be delayed when the device is busy,
rather than failing. This virtualization may be easier to accomplish
by using one of the arbiters provided by the system.
For example::
generic configuration SensirionSht11C() {
provides interface Read<uint16_t> as Temperature;
provides interface ReadStream<uint16_t> as TemperatureStream;
provides interface Read<uint16_t> as Humidity;
provides interface ReadStream<uint16_t> as HumidityStream;
}
implementation {
// connect to the ADC HIL, GPIO HAL, or sensor's HAL
}
When a HIL component is being used, the sensor MUST initialize itself,
either by including the `MainC` component and wiring to the
`SoftwareInit` interface, or by allowing a lower-level component (like
an ADC) to initialize itself.
In addition, the HIL sensor driver MUST start the physical sensor
automatically. For sensors without a constant power draw, the sensor
MAY be started once at boot time by wiring to the `MainC.Boot`
interface. Sensors that draw appreciable power MUST be started in
response to a call to one of the top-level SID interfaces, and stopped
some time after that call completes. One of the power-management
components described in [TEP115]_ may be useful for this purpose.
Generally, simple types are made up of octets. However, sensor values
often have levels of precision besides a multiple of 8. A device MAY
specify the precision of one of its interfaces with the DeviceMetadata
interface::
interface DeviceMetadata {
command uint8_t getSignificantBits();
}
The name of the instance of DeviceMetadata SHOULD clearly indicate
which interface it corresponds to.
A value contained returned from the device through a SID interface
MAY be left shifted so that it covers as much of the type's range as
possible. For example, if a 12-bit ADC reading is presented as a
16-bit Read interface::
component DemoSensorC {
provides interface Read<uint16_t>;
}
then the driver MAY shift the 12-bit value left so that its range is
0x0000 - 0xfff0, rather than 0x0000 - 0x0fff.
Sensor driver components SHOULD be named according to the make and
model of the sensing device being presented. Using specific names
gives the developer the option to bind to a particular sensor, which
provides compile-time detection of missing sensors. However, wrapper
components using "common" names MAY also be provided by the driver
author, to support application developers who are only concerned with
the particular type of the sensor and not its make, model, or detailed
performance characteristics.
A "common" naming layer atop a HIL may look like this::
generic configuration TemperatureC() {
provides interface Read<uint16_t>;
provides interface ReadStream<uint16_t>;
}
implementation {
components new SensirionSht11C();
Read = SensirionSht11C.Temperature;
ReadStream = SensirionSht11C.TemperatureStream;
}
generic configuration HumidityC() {
provides interface Read<uint16_t>;
provides interface ReadStream<uint16_t>;
}
implementation {
components new SensirionSht11C();
Read = SensirionSht11C.Humidity;
ReadStream = SensirionSht11C.HumidityStream;
}
3. Sensor HAL Components
====================================================================
Sensors with a richer interface than would be supported by the SID
interfaces MAY provide a HAL component in addition to a HIL
component.
A sensor HAL component MUST provide:
- A SID-based interface or a specific hardware-dependent interface
with commands for sampling and controlling the sensor device.
A sensor HAL component MAY need to provide:
- A `StdControl` or `SplitControl` interface for manual power
management by the user, following the conventions described in
[TEP115]_.
- A Resource[] interface for requesting access to the device and
possibly performing automated power management.
- Any other interfaces needed to control the device.
For example::
configuration SensirionSht11DeviceC {
provides interface Resource[ uint8_t client ];
provides interface SensirionSht11[ uint8_t client ];
}
implementation {
// connect to the sensor's platform-dependent HPL here
}
4. Directory Organization Guidelines
====================================================================
Because the same physical sensor may be attached to TinyOS platforms
in many different ways, the organization of sensor drivers should
reflect the distinction between sensor and sensor interconnect.
Sensor components commonly exist at three levels:
platform-independent, sensorboard-dependent, and
platform-dependent. Factoring a sensor driver into these three pieces
allows for greater code reuse when the same sensor is attached to
different sensorboards or platforms.
Platform-independent sensor driver components for a particular sensor,
like protocol logic, when in the core TinyOS 2.x source tree, SHOULD
be placed into "tos/chips/<sensor>", where <sensor> reflects the make
and model of the sensor device being supported. When not a part of the
core source tree, this directory can be placed anywhere as long as the
nesC compiler recieves a `-I` directive pointing to the sensor's
directory. However, not all sensors have a sufficiently large amount
of platform-independent logic to justify a separate "chips"
directory. Sensor chips are more likely to be digital sensors than
analog sensors, for example.
A sensor board is a collection of sensor components with a fixed name,
intended for attachment to multiple platforms. Each sensor board MUST
have its own directory named <sensorboard>. Default TinyOS 2.x sensor
boards are placed in "tos/sensorboards/<sensorboard>", but sensor
board directories can be placed anywhere as long as the nesC compiler
receives a `-I` directive pointing to the sensor board's directory.
Both sensors and sensor boards MUST have unique names. Case is
significant, but two sensor boards MUST differ in more than case. This
is necessary to support platforms where filename case differences are
not significant.
Each sensor board directory MUST contain a `.sensor` file. This file
is a perl script which gets executed as part of the `ncc` nesC
compiler frontend. It can add or modify any compile-time options
necessary for a particular sensor board. It MAY modify the following
perl variables, and MUST NOT modify any others:
- @new_args: This is the array of arguments which will be passed to
nescc. For instance, you might add an include directive to @new_args
with push @new_args, `-Isomedir`. This could be used to include
subdirectories.
- @commonboards: This can be set to a list of sensor board names which
should be added to the include path list. These sensor boards must be
in tinyos-2.x/tos/sensorboards.
If the sensor board wishes to define any C types or constants, it
SHOULD place these in a file named <sensorboard>.h in the sensor
board's directory.
A sensor board directory MAY contain a "chips" directory, with
subdirectories for each of the sensors connected to the sensor board.
If a "chips" subdirectory is used, sensorboard-dependent driver
components needed to connect platform-independent logic to a
particular attachment for that sensor should be placed in
"<sensorboard>/chips/<sensor>".
Components needed to connect the platform-independent sensor driver
components or sensorboard-dependent components to the hardware
resources available on a particular platform SHOULD be placed in
"tos/<platform>/chips/<sensor>". In addition, components for a sensor
that only exists on a particular platform should be placed in a such a
directory.
Sensors that exist as part of a larger chip, like a MCU internal
voltage sensor, SHOULD be placed in a subdirectory of the chip's
directory. "tos/<chip>/sensors/<sensor>".
The `.platform` and `.sensor` files need to include enough `-I`
directives to locate all of the necessary components needed to support
the sensors on a platform and/or sensorboard.
All of these directory organization guidelines are only intended for
code that will enter the core source tree. In general, sensor
components may be placed anywhere as long as the nesC compiler
receives enough `-I` directives to locate all of the necessary pieces.
5. Authors' Addresses
====================================================================
| David Gay
| 2150 Shattuck Ave, Suite 1300
| Intel Research
| Berkeley, CA 94704
|
| phone - +1 510 495 3055
|
| email - david.e.gay at intel.com
|
| Wei Hong
| Arch Rock
| 657 Mission St. Suite 600
| San Francisco, CA 94105
|
| email - wei.hong at gmail.com
|
| Philip Levis
| 358 Gates Hall
| Computer Science Department
| 353 Serra Mall
| Stanford, CA 94305
|
| phone - +1 650 725 9046
|
| email - pal at cs.stanford.edu
|
| Joe Polastre
| 467 Soda Hall
| UC Berkeley
| Berkeley, CA 94720
|
| email - polastre at cs.berkeley.edu
|
| Gilman Tolle
| Arch Rock
| 657 Mission St. Suite 600
| San Francisco, CA 94105
|
| email - gtolle at archrock.com
6. Citations
====================================================================
.. [TEP2] TEP 2: Hardware Abstraction Architecture
.. [TEP114] TEP 114: SIDs: Source and Sink Indepedent Drivers
.. [TEP115] TEP 115: Power Management of Non-Virtualized Devices
Appendix A: Sensor Driver Examples
====================================================================
1. Analog ADC-Connected Sensor
------------------------------
The Analog sensor requires two components
* a component to present the sensor itself (HamamatsuS1087ParC)
* a component to select the appropriate hardware resources, such as
ADC port 4, reference voltage 1.5V, and a slow sample and hold time
(HamamatsuS1087ParP).
The AdcReadClientC component and underlying machinery handles all of
the arbitration and access to the ADC.
::
tos/platforms/telosa/chips/s1087/HamamatsuS1087ParC.nc
generic configuration HamamatsuS1087ParC() {
provides interface Read<uint16_t>;
provides interface ReadStream<uint16_t>;
}
implementation {
components new AdcReadClientC();
Read = AdcReadClientC;
components new AdcReadStreamClientC();
ReadStream = AdcReadStreamClientC;
components HamamatsuS1087ParP;
AdcReadClientC.AdcConfigure -> HamamatsuS1087ParP;
AdcReadStreamClientC.AdcConfigure -> HamamatsuS1087ParP;
}
::
tos/platforms/telosa/chips/s1087/HamamatsuS1087ParP.nc
#include "Msp430Adc12.h"
module HamamatsuS1087ParP {
provides interface AdcConfigure<const msp430adc12_channel_config_t*>;
}
implementation {
msp430adc12_channel_config_t config = {
inch: INPUT_CHANNEL_A4,
sref: REFERENCE_VREFplus_AVss,
ref2_5v: REFVOLT_LEVEL_1_5,
adc12ssel: SHT_SOURCE_ACLK,
adc12div: SHT_CLOCK_DIV_1,
sht: SAMPLE_HOLD_4_CYCLES,
sampcon_ssel: SAMPCON_SOURCE_SMCLK,
sampcon_id: SAMPCON_CLOCK_DIV_1
};
async command const msp430adc12_channel_config_t* AdcConfigure.getConfiguration() {
return &config;
}
}
2. Binary Pin-Connected Sensor
------------------------------
The Binary sensor gets a bit more complex, because it has three
components:
* one to present the sensor (UserButtonC)
* one to execute the driver logic (UserButtonLogicP)
* one to select the appropriate hardware resources, such as MSP430
Port 27 (HplUserButtonC).
Note that the presentation of this sensor is not arbitrated because
none of the operations are split-phase.
::
tos/platforms/telosa/UserButtonC.nc
configuration UserButtonC {
provides interface Get<bool>;
provides interface Notify<bool>;
}
implementation {
components UserButtonLogicP;
components HplUserButtonC;
UserButtonLogicP.GpioInterrupt -> HplUserButtonC.GpioInterrupt;
UserButtonLogicP.GeneralIO -> HplUserButtonC.GeneralIO;
Get = UserButtonLogicP;
Notify = UserButtonLogicP;
}
::
tos/platforms/telosa/UserButtonLogicP.nc
module UserButtonLogicP {
provides interface Get<bool>;
provides interface Notify<bool>;
uses interface GeneralIO;
uses interface GpioInterrupt;
}
implementation {
norace bool m_pinHigh;
task void sendEvent();
command bool Get.get() { return call GeneralIO.get(); }
command error_t Notify.enable() {
call GeneralIO.makeInput();
if ( call GeneralIO.get() ) {
m_pinHigh = TRUE;
return call GpioInterrupt.enableFallingEdge();
} else {
m_pinHigh = FALSE;
return call GpioInterrupt.enableRisingEdge();
}
}
command error_t Notify.disable() {
return call GpioInterrupt.disable();
}
async event void GpioInterrupt.fired() {
call GpioInterrupt.disable();
m_pinHigh = !m_pinHigh;
post sendEvent();
}
task void sendEvent() {
bool pinHigh;
pinHigh = m_pinHigh;
signal Notify.notify( pinHigh );
if ( pinHigh ) {
call GpioInterrupt.enableFallingEdge();
} else {
call GpioInterrupt.enableRisingEdge();
}
}
}
::
tos/platforms/telosa/HplUserButtonC.nc
configuration HplUserButtonC {
provides interface GeneralIO;
provides interface GpioInterrupt;
}
implementation {
components HplMsp430GeneralIOC as GeneralIOC;
components new Msp430GpioC() as UserButtonC;
UserButtonC -> GeneralIOC.Port27;
GeneralIO = UserButtonC;
components HplMsp430InterruptC as InterruptC;
components new Msp430InterruptC() as InterruptUserButtonC;
InterruptUserButtonC.HplInterrupt -> InterruptC.Port27;
GpioInterrupt = InterruptUserButtonC.Interrupt;
}
3. Digital Bus-Connected Sensor
-------------------------------
The Digital sensor is the most complex out of the set, and includes
six components:
* one to present the sensor (SensirionSht11C)
* one to request arbitrated access and to transform the sensor HAL
into the sensor HIL (SensirionSht11P)
* one to present the sensor HAL (HalSensirionSht11C)
* one to perform the driver logic needed to support the HAL, which
twiddles pins according to a sensor-specific protocol
(SensirionSht11LogicP).
* one to select the appropriate hardware resources, such as the clock,
data, and power pins, and to provide an arbiter for the sensor
(HplSensirionSht11C).
* one to perform the power control logic needed to support the power
manager associated with the arbiter (HplSensirionSht11P).
This bus-connected sensor is overly complex because it does not rely
on a shared framework of bus manipulation components. A sensor built
on top of the I2C or SPI bus would likely require fewer components.
::
tos/platforms/telosa/chips/sht11/SensirionSht11C.nc
generic configuration SensirionSht11C() {
provides interface Read<uint16_t> as Temperature;
provides interface Read<uint16_t> as Humidity;
}
implementation {
components new SensirionSht11ReaderP();
Temperature = SensirionSht11ReaderP.Temperature;
Humidity = SensirionSht11ReaderP.Humidity;
components HalSensirionSht11C;
enum { TEMP_KEY = unique("Sht11.Resource") };
enum { HUM_KEY = unique("Sht11.Resource") };
SensirionSht11ReaderP.TempResource -> HalSensirionSht11C.Resource[ TEMP_KEY ];
SensirionSht11ReaderP.Sht11Temp -> HalSensirionSht11C.SensirionSht11[ TEMP_KEY ];
SensirionSht11ReaderP.HumResource -> HalSensirionSht11C.Resource[ HUM_KEY ];
SensirionSht11ReaderP.Sht11Hum -> HalSensirionSht11C.SensirionSht11[ HUM_KEY ];
}
::
tos/chips/sht11/SensirionSht11ReaderP.nc
generic module SensirionSht11ReaderP() {
provides interface Read<uint16_t> as Temperature;
provides interface Read<uint16_t> as Humidity;
uses interface Resource as TempResource;
uses interface Resource as HumResource;
uses interface SensirionSht11 as Sht11Temp;
uses interface SensirionSht11 as Sht11Hum;
}
implementation {
command error_t Temperature.read() {
call TempResource.request();
return SUCCESS;
}
event void TempResource.granted() {
error_t result;
if ((result = call Sht11Temp.measureTemperature()) != SUCCESS) {
call TempResource.release();
signal Temperature.readDone( result, 0 );
}
}
event void Sht11Temp.measureTemperatureDone( error_t result, uint16_t val ) {
call TempResource.release();
signal Temperature.readDone( result, val );
}
command error_t Humidity.read() {
call HumResource.request();
return SUCCESS;
}
event void HumResource.granted() {
error_t result;
if ((result = call Sht11Hum.measureHumidity()) != SUCCESS) {
call HumResource.release();
signal Humidity.readDone( result, 0 );
}
}
event void Sht11Hum.measureHumidityDone( error_t result, uint16_t val ) {
call HumResource.release();
signal Humidity.readDone( result, val );
}
event void Sht11Temp.resetDone( error_t result ) { }
event void Sht11Temp.measureHumidityDone( error_t result, uint16_t val ) { }
event void Sht11Temp.readStatusRegDone( error_t result, uint8_t val ) { }
event void Sht11Temp.writeStatusRegDone( error_t result ) { }
event void Sht11Hum.resetDone( error_t result ) { }
event void Sht11Hum.measureTemperatureDone( error_t result, uint16_t val ) { }
event void Sht11Hum.readStatusRegDone( error_t result, uint8_t val ) { }
event void Sht11Hum.writeStatusRegDone( error_t result ) { }
default event void Temperature.readDone( error_t result, uint16_t val ) { }
default event void Humidity.readDone( error_t result, uint16_t val ) { }
}
::
tos/platforms/telosa/chips/sht11/HalSensirionSht11C.nc
configuration HalSensirionSht11C {
provides interface Resource[ uint8_t client ];
provides interface SensirionSht11[ uint8_t client ];
}
implementation {
components new SensirionSht11LogicP();
SensirionSht11 = SensirionSht11LogicP;
components HplSensirionSht11C;
Resource = HplSensirionSht11C.Resource;
SensirionSht11LogicP.DATA -> HplSensirionSht11C.DATA;
SensirionSht11LogicP.CLOCK -> HplSensirionSht11C.SCK;
SensirionSht11LogicP.InterruptDATA -> HplSensirionSht11C.InterruptDATA;
components new TimerMilliC();
SensirionSht11LogicP.Timer -> TimerMilliC;
components LedsC;
SensirionSht11LogicP.Leds -> LedsC;
}
::
tos/chips/sht11/SensirionSht11LogicP.nc
generic module SensirionSht11LogicP() {
provides interface SensirionSht11[ uint8_t client ];
uses interface GeneralIO as DATA;
uses interface GeneralIO as CLOCK;
uses interface GpioInterrupt as InterruptDATA;
uses interface Timer<TMilli>;
uses interface Leds;
}
implementation {
... bus protocol details omitted for brevity ...
}
::
tos/platforms/telosa/chips/sht11/HplSensirionSht11C.nc
configuration HplSensirionSht11C {
provides interface Resource[ uint8_t id ];
provides interface GeneralIO as DATA;
provides interface GeneralIO as SCK;
provides interface GpioInterrupt as InterruptDATA;
}
implementation {
components HplMsp430GeneralIOC;
components new Msp430GpioC() as DATAM;
DATAM -> HplMsp430GeneralIOC.Port15;
DATA = DATAM;
components new Msp430GpioC() as SCKM;
SCKM -> HplMsp430GeneralIOC.Port16;
SCK = SCKM;
components new Msp430GpioC() as PWRM;
PWRM -> HplMsp430GeneralIOC.Port17;
components HplSensirionSht11P;
HplSensirionSht11P.PWR -> PWRM;
HplSensirionSht11P.DATA -> DATAM;
HplSensirionSht11P.SCK -> SCKM;
components new TimerMilliC();
HplSensirionSht11P.Timer -> TimerMilliC;
components HplMsp430InterruptC;
components new Msp430InterruptC() as InterruptDATAC;
InterruptDATAC.HplInterrupt -> HplMsp430InterruptC.Port15;
InterruptDATA = InterruptDATAC.Interrupt;
components new FcfsArbiterC( "Sht11.Resource" ) as Arbiter;
Resource = Arbiter;
components new SplitControlPowerManagerC();
SplitControlPowerManagerC.SplitControl -> HplSensirionSht11P;
SplitControlPowerManagerC.ArbiterInit -> Arbiter.Init;
SplitControlPowerManagerC.ArbiterInfo -> Arbiter.ArbiterInfo;
SplitControlPowerManagerC.ResourceController -> Arbiter.ResourceController;
}
::
tos/platforms/telosa/chips/sht11/HplSensirionSht11P.nc
module HplSensirionSht11P {
provides interface SplitControl;
uses interface Timer<TMilli>;
uses interface GeneralIO as PWR;
uses interface GeneralIO as DATA;
uses interface GeneralIO as SCK;
}
implementation {
task void stopTask();
command error_t SplitControl.start() {
call PWR.makeOutput();
call PWR.set();
call Timer.startOneShot( 11 );
return SUCCESS;
}
event void Timer.fired() {
signal SplitControl.startDone( SUCCESS );
}
command error_t SplitControl.stop() {
call SCK.makeInput();
call SCK.clr();
call DATA.makeInput();
call DATA.clr();
call PWR.clr();
post stopTask();
return SUCCESS;
}
task void stopTask() {
signal SplitControl.stopDone( SUCCESS );
}
}
-------------- next part --------------
============================
Serial Communication
============================
:TEP: 113
:Group: Core Working Group
:Type: Documentary
:Status: Draft
:TinyOS-Version: 2.x
:Author: Ben Greenstein and Philip Levis
:Draft-Created: 11-Jul-2005
:Draft-Version: $Revision: 1.1.2.6 $
:Draft-Modified: $Date: 2006/11/07 23:14:55 $
:Draft-Discuss: TinyOS Developer List <tinyos-devel at mail.millennium.berkeley.edu>
.. Note::
This memo documents a part of TinyOS for the TinyOS Community, and
requests discussion and suggestions for improvements. Distribution
of this memo is unlimited. This memo is in full compliance with
TEP 1.
Abstract
====================================================================
This memo describes the structure and standard implementation of the
TinyOS 2.x serial communication system for mote-to-PC data
exchange. The system is broken into three levels (encoding, framing,
and dispatch) to allow easy experimentation and replacement. It can
also handle multiple packet formats: unlike 1.x, 2.x serial packets
are not bound to the mote's radio packet format. Additionally, one of
the supported packet formats is platform independent, so PC-side
applications can communicate with arbitrary motes.
1. Introduction
====================================================================
Users need to read data out of a TinyOS network. The most common
approach is to attach a mote to a PC or latop with a wired
connection. While the interface on the PC side can vary from a serial
cable to a USB device to IP, the mote generally talks to a serial port
(UART). In TinyOS 1.x, the UART packet format is platform-specific,
pushing a good deal of complexity into the protocol and PC-side tools
in order to discover and properly handle platform diversity. TinyOS
2.0 introduces the notion of packet format dispatch, so a mote can
support multiple UART packet formats simultaneously. This allows
transparent bridging (e.g., an 802.15.4 base station) to exist in
parallel with platform-independent communication, which allows
simplifies the PC toolchain. This memo documents the protocols and
structure of the TinyOS 2.x serial communication stack.
2. Serial Stack Structure
====================================================================
The TinyOS 2.x serial communication stack is broken up into four
functional components. From bottom to top, they are
o the raw UART,
o the encoder/framer,
o the protocol,
o and the dispatcher.
Structurally, they look like this:
::
_____________________
| |
| Dispatcher | Packet formatting.
|_____________________|
_____________________
| |
| Protocol | Acknowledgements, CRC computation,
|_____________________| windowing.
_____________________
| |
| Encoder/Framer | Translating raw bytes into frame
|_____________________| delimiters, escape bytes.
_____________________
| |
| Raw UART | Platform code for reading/writing
|_____________________| bytes over the serial connection.
The bottom three provide a byte-level interface: only the Dispatcher
provides a packet-level interface. The top three are all
platform-independent: only the UART is platform-specific code.
The lowest level of the stack is the raw UART. This HIL component
provides functionality for configuring the UART (speed, stop bytes,
etc.) as well as sending/receiving bytes.
The Encoder/Framer sits above the raw UART. This component translates
raw data bytes into packet bytes using a serial protocol's
encoding. The Encoder/Framer assumes that a protocol's encoding has
two kinds of bytes: delimiters and data bytes, and signals each in
separate events to the component above.
The Protocol component handles data and delimiter byte events. It is
responsible for reading in and sending all protocol control
packets. If the Protocol component starts receiving a data packet, it
signals to the Dispatcher that a packet has started and signals the
data bytes. When the data packet completes, the Protocol signals to
the Dispatcher that the packet is complete and whether it passed the
protocol-level CRC.
The Dispatcher component handles data packet bytes and delimiters. It
is responsible for reading data bytes into a message_t and signaling
packet reception to components above it. The dispatcher can support
multiple packet formats. Based on how message_t works (see TEP
111[tep111_]), this boils down to knowing where in a message_t a
particular packet format begins (based on its header size). Section
3.4 describes how the default TinyOS 2.x implementation,
``SerialDispatcherC`` does this.
3. The 2.x Serial Stack Implementation
====================================================================
Section 2 describes the basic structure of the TinyOS 2.x serial
stack structure. This section describes its actual implementation,
including SerialActiveMessageC, which sits on top of the Dispatcher.
All of the components except for UartC are part of the serial
library that lives in ``tos/lib/serial``.
3.1 Raw UART: UartC
--------------------------------------------------------------------
The UART HIL[TEP2_] is ``UartC``, which provides a byte-level
interface to the underlying serial communication. It provides the
``SerialByteComm`` interface:
::
interface SerialByteComm {
async command error_t put(uint8_t data);
async event void get(uint8_t data);
async event void putDone();
}
It also provides interfaces for configuring the serial port. *NOTE:
These are not codified yet, and so working out the UART HIL seems like
a good idea.*
3.2 Encoder/Framer: HdlcTranslateC
--------------------------------------------------------------------
HdlcTranslateC is the serial encoder/framer. It uses the
``SerialByteComm`` interface and provides the ``SerialFrameComm``
interface:
::
interface SerialFrameComm {
async command error_t putDelimiter();
async command error_t putData(uint8_t data);
async command void resetSend();
async command void resetReceive();
async event void delimiterReceived();
async event void dataReceived(uint8_t data);
async event void putDone();
}
As its name suggests, it uses the same encoding as the HDLC[HDLC_]
protocol. ``0x7e`` is reserved as a frame delimiter byte, and ``0x7d``
is reserved as an escape byte. HdlcTranslateC maintains ten bits of
state. The receive and send paths each have one bit to store whether
they are using an escape byte, and the transmit path has a byte for
when it sends an escaped byte.
When HdlcTranslateC receives a delimiter byte, it signals
delimiterReceived(). When HdlcTranslateC receives an escape byte, it
sets the receiveEscape flag to true. When it receives any other byte,
it tests to see if the receiveEscape flag is set; if so, it XORs the
data byte with ``0x20`` and clears the flag. It signals dataReceived()
with the byte. The most common use of escape byte is to transmit data
bytes corresponding to the delimiter byte or escape byte. For example,
``0x7e`` becomes ``0x7d 0x5e``.
HdlcTranslateC performs similar actions on the transmit side. When
told to transmit the delimiter or escape byte as a data byte, it sets
the transmitEscape flag to true, stores the data byte XOR ``0x20``,
and sends an escape byte. When the escape byte is sent, it sends the
stored data byte.
3.3 Protocol: SerialP
--------------------------------------------------------------------
The SerialP component implements the serial protocol using PPP/HDLC-
like framing (See RFC 1662[RFC1662_]). Type dispatch and buffer
management are left to higher layers in the serial stack. The protocol
is currently stop-and-wait in the host-to-mote direction and best
effort in the mote-to-host direction. The first performance upgrade of
this module will be to implement sliding window reliability in both
directions.
SerialP provides two byte-level interfaces to the upper layer for
sending and receiving packets, respectively called SendBytePacket and
ReceiveBytePacket.
On the sending side, SerialP is responsible for encapsulation of upper
layer packets. An upper layer component such as SerialDispatcherC
initiates the sending of a packet by calling startSend, passing the
first byte to send. SerialP collects subsequent bytes by signalling
nextByte. Within the nextByte handler or between calls to nextByte,
the upper layer should indicate the end-of-packet by calling
completeSend. If completeSend is called from within a nextByte
handler, SerialP will ignore the return of the call to nextByte.
::
interface SendBytePacket {
async command error_t startSend(uint8_t first_byte);
async command error_t completeSend();
async event uint8_t nextByte();
async event void sendCompleted(error_t error);
}
SerialP maintains a small window of bytes that have been received by
the upper layer and not yet sent to the UART. Depending on the timing
requirements of the underlying UART, the size of this window can be
changed. SerialP uses repeated calls to nextByte to keep this window
filled.
SerialP uses SerialFrameComm to send a delimiter between frames,
a serial-level type field, the bytes of the packet, and a two-byte
frame CRC. For mote-to-host gap detection and link reliability, a
sequence number may also be sent (not currently activated).
After sending an entire frame and receiving the last putDone event
from below, SerialP signals sendCompleted to indicate the success or
failure of a requested transmission.
Packet reception is also managed by SerialP and the interface
provided to the upper layer is ReceiveBytePacket:
::
interface ReceiveBytePacket {
async event error_t startPacket();
async event void byteReceived(uint8_t b);
async event void endPacket(error_t result);
}
Upon receiving an interframe delimiter and a new frame's header,
SerialP signals the upper layer indicating that a packet is
arriving. For each byte received, SerialP signals byteReceived. (Note:
SerialP signals on byte k-2 when byte k arrives, because the
implementation precludes it from knowing when it has encountered the
2-byte CRC in the frame footer until after it has received it. Lagging
behind by two bytes makes it possible to hide all frame details from
the upper layer.) Once SerialP receives the complete frame it signals
endPacket with a value of SUCCESS. If instead it loses sync during
reception it signals endPacket with FAIL.
SerialP acknowledges frames it receives. Acknowledgements have a
higher priority than data transmissions and consequently, data frames
may be slightly delayed. However, acknowledgement information is
stored in a queue separate from the data buffer, so a data packet to
be transmitted may begin spooling into SerialP while SerialP is
actively sending an acknowledgement.
3.4 Dispatcher: SerialDispatcherC
--------------------------------------------------------------------
SerialDispatcherC handles the data packets that the Protocol component
receives. It uses the SendBytePacket and ReceiveBytePacket interfaces,
and provides parameterized Send and Receive interfaces. The parameter
in the Send and Receive interfaces (``uart_id_t``) determines the
packet format contained in the message_t.
SerialDispatcherC places a one-byte header, the packet format
identifier, on the packets sent and received through SerialP.
SerialDispatcherC uses a parameterized SerialPacketInfo interface to
be able to handle various packet formats:
::
interface SerialPacketInfo {
async command uint8_t offset();
async command uint8_t dataLinkLength(message_t* msg, uint8_t upperLen);
async command uint8_t upperLength(message_t* msg, uint8_t dataLinkLen);
}
When SerialDispatcherC receives the first data byte of a packet from
SerialP, it stores it as the packet type and calls
SerialPacketInfo.offset() to determine where in a message_t that
packet format begins. It then spools data bytes in, filling them into
its message_t buffer. Similarly, on the send side, it first sends the
type byte and spools out data bytes starting from the index denoted by
the call to offset(). SerialDispatcherC uses the two length commands,
dataLinkLength and upperLength, to translate between the two notions
of packet length: above, length refers to the payload excluding
header, while below it refers to the payload plus header.
A component that provides communication over the serial port with
uart_id_t *U* MUST wire a component implementing SerialPacketInfo to
SerialDispatcherC with uart_id_t *U*. The file ``Serial.h`` contains
reserved uart_id_t's for supported packet formats. Currently, only
platform independent active messages
(``TOS_SERIAL_ACTIVE_MESSAGE_ID``, described in Section 3.5), 802.15.4
active messages (``TOS_SERIAL_802_15_4_ID``), mica2 CC1000 packets
(``TOS_SERIAL_CC1000_ID``) and the error code
``TOS_SERIAL_UNKNOWN_ID`` are reserved. New packet formats MUST NOT
reuse any reserved identifiers.
3.5 SerialActiveMessageC
--------------------------------------------------------------------
SerialActiveMessageC is a platform-independent active message layer
that operates on top of the serial communication
stack. SerialActiveMessageC is a configuration that wires
SerialActiveMessageP to SerialDispatcherC with uart_id_t
TOS_SERIAL_ACTIVE_MESSAGE_ID and wires SerialPacketInfoActiveMessageP
to SerialDispatcherC with uart_id_t TOS_SERIAL_ACTIVE_MESSAGE_ID:
::
includes Serial;``
configuration SerialActiveMessageC {
provides {
interface Init;
interface AMSend[am_id_t id];
interface Receive[am_id_t id];
interface Packet;
interface AMPacket;
}
uses interface Leds;
}
implementation {
components new SerialActiveMessageP() as AM, SerialDispatcherC;
components SerialPacketInfoActiveMessageP as Info;
Init = SerialDispatcherC;
Leds = SerialDispatcherC;
AMSend = AM;
Receive = AM;
Packet = AM;
AMPacket = AM;
AM.SubSend -> SerialDispatcherC.Send[TOS_SERIAL_ACTIVE_MESSAGE_ID];
AM.SubReceive -> SerialDispatcherC.Receive[TOS_SERIAL_ACTIVE_MESSAGE_ID];
SerialDispatcherC.SerialPacketInfo[TOS_SERIAL_ACTIVE_MESSAGE_ID] -> Info;
}
SerialActiveMessageP is a generic component so that it can be used to
sit on top of any packet-level communication layer. It does not filter
packets based on destination address or group. It assumes that if the
packet was received over the serial port, it was destined to the
node. This saves PC-side tools from having to discover or consider the
ID and group of a mote.
Platform-independent active messages do not have a CRC (they assumes
the serial stack CRC is sufficient), and have the following header:
::
typedef nx_struct SerialAMHeader {
nx_am_addr_t addr;
nx_uint8_t length;
nx_am_group_t group;
nx_am_id_t type;
} SerialAMHeader;
3.6 Packet Format
--------------------------------------------------------------------
A data packet in the TinyOS 2.x serial stack has the following format
over the wire. Each protocol field is associated with a specific component:
::
____________________________________________
| | | | | | | |
| | | | | | | |
|_|_|_|_|_______________________________|__|_|
F P S D Payload CR F
F = Framing byte, denoting start of packet: HdlcTranslateC
P = Protocol byte: SerialP
S = Sequence number byte: SerialP
D = Packet format dispatch byte: SerialDispatcherC
Payload = Data payload (stored in SerialDispatcherC): SerialDispatcherC
CR = Two-byte CRC over S to end of Payload: SerialP
F = Framing byte denoting end of packet: HdlcTranslateC
Payload is a contiguous packet that SerialDispatcherC reads in. Note
that any data bytes (P - CR) equal to 0x7e or 0x7d will be escaped to
0x7d 0x5e or 0x7d 0x5d accordingly. For example, a platform
independent AM packet of type 6, group 0x7d, and length 5 to
destination 0xbeef with a payload of 1 2 3 4 5 would look like this:
``7e 40 09 00 be ef 05 7d 5d 06 01 02 03 04 05 7e``
Note that the group 0x7d is escaped to 0x7d 0x5d. The protocol field
(P) is 0x40 (64), corresponding to ``SERIAL_PROTO_ACK`` (in Serial.h).
4. Access Abstractions
====================================================================
Two generic components: SerialAMSenderC and SerialAMReceiverC connect
to SerialActiveMessageC to provide virtualized access to the serial
stack. Each instantiation of SerialAMSenderC has its own queue of
depth one. Therefore, it does not have to contend with other
SerialAMSender instantiations for queue space. The underlying
implementation schedulers the packets in these queues using some form
of fair-share queueing. SerialAMReceiverC provides the virtualized
abstraction for reception. These abstraction are very similar to
TinyOS's radio abstractions, namely, AMSenderC and AMReceiverC. See
Section 4 of TEP 116[TEP116_] for more information. Unlike the
services in the TEP 116, the serial component virtualizations provide
no snooping capabilities.
5. Author's Address
====================================================================
| Philip Levis
| 358 Gates
| Computer Science Laboratory
| Stanford University
| Stanford, CA 94305
|
| phone - +1 650 725 9046
| email - pal at cs.stanford.edu
|
|
| Ben Greenstein
| Center for Embedded Networked Sensing
| UCLA 3563 Boelter Hall
| Los Angeles, CA 90095-1596
|
| phone - +1 310 206 3925
| email - ben at cs.ucla.edu
6. Citations
====================================================================
.. [TEP2] TEP 2: Hardware Abstraction Architecture. tinyos-2.x/doc/txt/tep2.txt
.. [TEP111] TEP 111: message_t. tinyos-2.x/doc/txt/tep111.txt
.. [TEP116] TEP 116: Packet Protocols. tinyos-2.x/doc/txt/tep116.txt
.. [HDLC] International Organization For Standardization, ISO Standard 3309-1979, "Data communication - High-level data link control procedures - Frame structure", 1979.
.. [RFC1662] PPP in HDLC-like Framing, Internet Engineering Task Force (IETF), 1994
-------------- next part --------------
More information about the Tinyos-2.0wg
mailing list