Tuesday, October 20, 2009

Message_t Memo by Philip Lewis for tinyos 2.x

message_t

TEP:111
Group:Core Working Group
Type:Documentary
Status: Final
TinyOS-Version:2.x
Author: Philip Levis

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 covers the TinyOS 2.x message buffer abstraction, message_t. It describes the message buffer design considerations, how and where message_t is specified, and how data link layers should access it. The major goal of message_t is to allow datagrams to be passed between different link layers as a contiguous region of memory with zero copies.

1. Introduction

In TinyOS 1.x, a message buffer is a TOS_Msg. A buffer contains an active message (AM) packet as well as packet metadata, such as timestamps, acknowledgement bits, and signal strength if the packet was received. TOS_Msg is a fixed size structure whose size is defined by the maximum AM payload length (the default is 29 bytes). Fixed sized buffers allows TinyOS 1.x to have zero-copy semantics: when a component receives a buffer, rather than copy out the contents it can return a pointer to a new buffer for the underlying layer to use for the next received packet.

One issue that arises is what defines the TOS_Msg structure, as different link layers may require different layouts. For example, 802.15.4 radio hardware (such as the CC2420, used in the Telos and micaZ platforms) may require 802.15.4 headers, while a software stack built on top of byte radios (such as the CC1000, used in the mica2 platform) can specify its own packet format. This means that TOS_Msg may be different on different platforms.

The solution to this problem in TinyOS 1.x is for there to be a standard definition of TOS_Msg, which a platform (e.g., the micaZ) can redefine to match its radio. For example, a mica2 mote uses the standard definition, which is:

typedef struct TOS_Msg {   // The following fields are transmitted/received on the radio.   uint16_t addr;   uint8_t type;   uint8_t group;   uint8_t length;   int8_t data[TOSH_DATA_LENGTH];   uint16_t crc;    // The following fields are not actually transmitted or received   // on the radio! They are used for internal accounting only.   // The reason they are in this structure is that the AM interface   // requires them to be part of the TOS_Msg that is passed to   // send/receive operations.   uint16_t strength;   uint8_t ack;   uint16_t time;   uint8_t sendSecurityMode;   uint8_t receiveSecurityMode; } TOS_Msg; 

while on a mote with a CC2420 radio (e.g., micaZ), TOS_Msg is defined as:

typedef struct TOS_Msg {   // The following fields are transmitted/received on the radio.   uint8_t length;   uint8_t fcfhi;   uint8_t fcflo;   uint8_t dsn;   uint16_t destpan;   uint16_t addr;   uint8_t type;   uint8_t group;   int8_t data[TOSH_DATA_LENGTH];    // The following fields are not actually transmitted or received   // on the radio! They are used for internal accounting only.   // The reason they are in this structure is that the AM interface   // requires them to be part of the TOS_Msg that is passed to   // send/receive operations.    uint8_t strength;   uint8_t lqi;   bool crc;   uint8_t ack;   uint16_t time;  } TOS_Msg; 

There are two basic problems with this approach. First, exposing all of the link layer fields leads components to directly access the packet structure. This introduces dependencies between higher level components and the structure layout. For example, many network services built on top of data link layers care whether sent packets are acknowledged. They therefore check the ack field of TOS_Msg. If a link layer does not provide acknowledgements, it must still include the ack field and always set it to 0, wasting a byte of RAM per buffer.

Second, this model does not easily support multiple data link layers. Radio chip implementations assume that the fields they require are defined in the structure and directly access them. If a platform has two different link layers (e.g., a CC1000 and a CC2420 radio), then a TOS_Msg needs to allocate the right amount of space for both of their headers while allowing implementations to directly access header fields. This is very difficult to do in C.

The data payload is especially problematic. Many components refer to this field, so it must be at a fixed offset from the beginning of the structure. Depending on the underlying link layer, the header fields preceding it might have different lengths, and packet-level radios often require packets to be contiguous memory regions. Overall, these complexities make specifying the format of TOS_Msg very difficult.

TinyOS has traditionally used statically sized packet buffers, rather than more dynamic approaches, such as scatter-gather I/O in UNIX sockets (see the man page for recv(2) for details). TinyOS 2.x continues this approach.

2. message_t

In TinyOS 2.x, the standard message buffer is message_t. The message_t structure is defined in tos/types/message.h:

typedef nx_struct message_t {   nx_uint8_t header[sizeof(message_header_t)];   nx_uint8_t data[TOSH_DATA_LENGTH];   nx_uint8_t footer[sizeof(message_footer_t)];   nx_uint8_t metadata[sizeof(message_metadata_t)]; } message_t; 

This format keeps data at a fixed offset on a platform, which is important when passing a message buffer between two different link layers. If the data payload were at different offsets for different link layers, then passing a packet between two link layers would require a memmove(3) operation (essentially, a copy). Unlike in TinyOS 1.x, where TOS_Msg as explicitly an active messaging packet, message_t is a more general data-link buffer. In practice, most data-link layers in TinyOS 2.x provide active messaging, but it is possible for a non-AM stack to share message_t with AM stacks.

The header, footer, and metadata formats are all opaque. Source code cannot access fields directly. Instead, data-link layers provide access to fields through nesC interfaces. Section 3 discusses this in greater depth.

Every link layer defines its header, footer, and metadata structures. These structures MUST be external structs (nx_struct), and all of their fields MUST be external types (nx_*), for two reasons. First, external types ensure cross-platform compatibility [1]. Second, it forces structures to be aligned on byte boundaries, circumventing issues with the alignment of packet buffers and field offsets within them. Metadata fields must be nx_structs for when complete packets are forwarded to the serial port in order to log traffic. For example, the CC1000 radio implementation defines its structures in CC1000Msg.h:

typedef nx_struct cc1000_header {   nx_am_addr_t addr;   nx_uint8_t length;   nx_am_group_t group;   nx_am_id_t type; } cc1000_header_t;  typedef nx_struct cc1000_footer {   nxle_uint16_t crc; } cc1000_footer_t;  typedef nx_struct cc1000_metadata {   nx_uint16_t strength;   nx_uint8_t ack;   nx_uint16_t time;   nx_uint8_t sendSecurityMode;   nx_uint8_t receiveSecurityMode; } cc1000_metadata_t; 

Each link layer defines its structures, but a platform is responsible for defining message_header_t, message_footer_t, and message_metadata_t. This is because a platform may have multiple link layers, and so only it can resolve which structures are needed. These definitions MUST be in a file in a platform directory named platform_message.h. The mica2 platform, for example, has two data link layers: the CC1000 radio and the TinyOS serial stack [2]. The serial packet format does not have a footer or metadata section. The platform_message.h of the mica2 looks like this:

typedef union message_header {   cc1000_header_t cc1k;   serial_header_t serial; } message_header_t;  typedef union message_footer {   cc1000_footer_t cc1k; } message_footer_t;  typedef union message_metadata {   cc1000_metadata cc1k; } message_metadata_t; 

For a more complex example, consider a fictional platform named 'megamica' that has both a CC1000 and a CC2420 radio. Its platform_message.h would look like this:

typedef union mega_mica_header {   cc1000_header_t cc1k;   cc2420_header_t cc2420;   serial_header_t serial; } message_header_t;  typedef union mega_mica_footer {   cc1000_footer_t cc1k;   cc2420_footer_t cc2420; } message_footer_t;  typedef union mega_mica_metadata {   cc1000_metadata_t cc1k;   cc2420_metadata_t cc2420; } message__metadata_t; 

If a platform has more than one link layer, it SHOULD define each of the message_t fields to be a union of the underlying link layer structures. This ensures that enough space is allocated for all underlying link layers.

3. The message_t fields

TinyOS 2.x components treat packets as abstract data types (ADTs), rather than C structures, obtaining all of the traditional benefits of this approach. First and foremost, clients of a packet layer do not depend on particular field names or locations, allowing the implementations to choose packet formats and make a variety of optimizations.

Components above the basic data-link layer MUST always access packet fields through interfaces. A component that introduces new packet fields SHOULD provide an interface to those that are of interest to other components. These interfaces SHOULD take the form of get/set operations that take and return values, rather than offsts into the structure.

For example, active messages have an interface named AMPacket which provides access commands to AM fields. In TinyOS 1.x, a component would directly access TOS_Msg.addr; in TinyOS 2.x, a component calls AMPacket.getAddress(msg). The most basic of these interfaces is Packet, which provides access to a packet payload. TEP 116 describes common TinyOS packet ADT interfaces [3].

Link layer components MAY access packet fields differently than other components, as they are aware of the actual packet format. They can therefore implement the interfaces that provide access to the fields for other components.

3.1 Headers

The message_t header field is an array of bytes whose size is the size of a platform's union of data-link headers. Because radio stacks often prefer packets to be stored contiguously, the layout of a packet in memory does not necessarily reflect the layout of its nesC structure.

A packet header MAY start somewhere besides the beginning of the message_t. For example, consider the Telos platform:

typedef union message_header {   cc2420_header_t cc2420;   serial_header_t serial; } message_header_t; 

The CC2420 header is 11 bytes long, while the serial header is 5 bytes long. The serial header ends at the beginning of the data payload, and so six padding bytes precede it. This figure shows the layout of message_t, a 12-byte CC2420 packet, and a 12-byte serial packet on the Telos platform:

            11 bytes         TOSH_DATA_LENGTH        7 bytes           +-----------+-----------------------------+-------+ message_t |  header   |          data               | meta  |           +-----------+-----------------------------+-------+            +-----------+------------+                +-------+ CC2420    |  header   |   data     |                | meta  |           +-----------+------------+                +-------+                  +-----+------------+ Serial          | hdr |   data     |                 +-----+------------+ 

Neither the CC2420 nor the serial stack has packet footers, and the serial stack does not have any metadata.

The packet for a link layer does not necessarily start at the beginning of the message_t. Instead, it starts at a negative offset from the data field. When a link layer component needs to read or write protocol header fields, it MUST compute the location of the header as a negative offset from the data field. For example, the serial stack header has active message fields, such as the AM type. The command that returns the AM type, AMPacket.type(), looks like this:

serial_header_t* getHeader(message_t* msg) {   return (serial_header_t*)(msg->data - sizeof(serial_header_t)); } command am_id_t AMPacket.type(message_t* msg) {   serial_header_t* hdr = getheader(msg);   return hdr->type; } 

Because calculating the negative offset is a little bit unwieldy, the serial stack uses the internal helper function getHeader(). Many single-hop stacks follow this approach, as it is very likely that nesC will inline the function, eliminating the possible overhead. In most cases, the C compiler also compiles the call into a simple memory offset load.

The following code is incorrect, as it directly casts the header field. It is an example of what components MUST NOT do:

serial_header_t* getHeader(message_t* msg) {   return (serial_header_t*)(msg->header); } 

In the case of Telos, for example, this would result in a packet layout that looks like this:

            11 bytes         TOSH_DATA_LENGTH           7 bytes           +-----------+-----------------------------+-------+ message_t |  header   |          data               | meta  |           +-----------+-----------------------------+-------+            +-----+     +------------+ Serial    | hdr |     |   data     |           +-----+     +------------+ 

3.2 Data

The data field of message_t stores the single-hop packet payload. It is TOSH_DATA_LENGTH bytes long. The default size is 28 bytes. A TinyOS application can redefine TOSH_DATA_LENGTH at compile time with a command-line option to ncc: -DTOSH_DATA_LENGTH=x. Because this value can be reconfigured, it is possible that two different versions of an application can have different MTU sizes. If a packet layer receives a packet whose payload size is longer than TOSH_DATA_LENGTH, it MUST discard the packet. As headers are right justified to the beginning of the data payload, the data payloads of all link layers on a platform start at the same fixed offset from the beginning of the message buffer.

3.3 Footer

The message_footer_t field ensures that message_t has enough space to store the footers for all underlying link layers when there are MTU-sized packets. Like headers, footers are not necessarily stored where the C structs indicate they are: instead, their placement is implementation dependent. A single-hop layer MAY store the footer contiguously with the data region. For short packets, this can mean that the footer will actually be stored in the data field.

3.4 Metadata

The metadata field of message_t stores data that a single-hop stack uses or collects does not transmit. This mechanism allows packet layers to store per-packet information such as RSSI or timestamps. For example, this is the CC2420 metadata structure:

typedef nx_struct cc2420_metadata_t {   nx_uint8_t tx_power;   nx_uint8_t rssi;   nx_uint8_t lqi;   nx_bool crc;   nx_bool ack;   nx_uint16_t time; } cc2420_metadata_t; 

3.5 Variable Sized Structures

The message_t structure is optimized for packets with fixed-size headers and footers. Variable-sized footers are generally easy to implement. Variable-sized headers are a bit more difficult. There are three general approaches that can be used.

If the underlying link hardware is byte-based, the header can just be stored at the beginning of the message_t, giving it a known offset. There may be padding between the header and the data region, but assuming a byte-based send path this merely requires adjusting the index.

If the underlying link hardware is packet-based, then the protocol stack can either include metadata (e.g., in the metadata structure) stating where the header begins or it can place the header at a fixed position and use memmove(3) on reception and transmit. In this latter case, on reception the packet is continguously read into the message_t beginning at the offset of the header structure. Once the packet is completely received, the header can be decoded, its length calculated, and the data region of the packet can be moved to the data field. On transmission, the opposite occurs: the data region (and footer if need be) are moved to be contiguous with the header. Note that on completion of transmission, they need to be moved back. Alternatively, the radio stack can institute a single copy at the botttom layer.

4. Implementation

The definition of message_t can be found in tinyos-2.x/tos/types/message.h.

The definition of the CC2420 message format can be found in tinyos-2.x/tos/chips/cc2420/CC2420.h.

The defintion of the CC1000 message format can be found in tinyos-2.x/tos/chips/cc1000/CC1000Msg.h.

The definition of the standard serial stack packet format can be found in tinyos-2.x/tos/lib/serial/Serial.h

The definition of the telos family packet format can be found in tinyos-2.x/tos/platform/telosa/platform_message.h and the micaz format can be found in tinyos-2.x/tos/platforms/micaz/platform_message.h.

5. Author's Address

Philip Levis
358 Gates Hall
Computer Science Laboratory
Stanford University
Stanford, CA 94305

phone - +1 650 725 9046

6. Citations

[1]nesC: A Programming Language for Deeply Embedded Networks.
[2]TEP 113: Serial Communication.
[3]TEP 116: Packet Protocols.

FYI for tinyos 2.x

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).

Packet Format to decipher previous post

  • Destination address (2 bytes)
  • Link source address (2 bytes)
  • Message length (1 byte)
  • Group ID (1 byte)
  • Active Message handler type (1 byte)
  • Payload (up to 28 bytes):
    • source mote ID (2 bytes)
    • sample counter (2 bytes)

So we can interpret the data packet as follows:

dest addr link source addr msg len groupID handlerID source addr counter
ff ff 00 00 04 22 06 00 02 00 0B

HEX output of TinyOS

This is the output of the trial of the new code version for multiple channels

Hex code clearly shows multiple channel data in a single packet
-------------------------------
                                                                                                                                                                                        7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6D 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6F 00 02 02 E6 01 B1 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6D 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6D 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6F 00 02 02 E7 01 B1 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6C 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6F 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FE 01 F1 01 6C 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FE 01 F1 01 6E 00 02 02 E6 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6E 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6C 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FE 01 F1 01 6F 00 03 02 E6 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FE 01 F1 01 6C 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6E 00 02 02 E7 01 B0 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FE 01 F1 01 6D 00 02 02 E7 01 B1 00 B0 00  7E 00 00 88 14 84 01 01 00 86 01 FD 01 F1 01 6C 00 02 02 E7 01 B0 00 B0 00 

TEP 101 ADC guide for tinyos 2.x

For Reference in case of shift to Tinyos 2.x
-------------------

Analog-to-Digital Converters (ADCs)

TEP:101
Group:Core Working Group
Type:Documentary
Status: Final
TinyOS-Version:2.x
Author: Jan-Hinrich Hauer, Philip Levis, Vlado Handziski, David Gay

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 [TEP1].

Abstract

This TEP proposes a hardware abstraction for analog-to-digital converters (ADCs) in TinyOS 2.x, which is aligned to the three-layer Hardware Abstraction Architecture (HAA) specified in [TEP2]. It describes some design principles and documents the set of hardware-independent interfaces to an ADC.

1. Introduction

Analog-to-digital converters (ADCs) are devices that convert analog input signals to discrete digital output signals, typically voltage to a binary number. The interested reader can refer to Appendix A for a brief overview of the ADC hardware on some current TinyOS platforms. In earlier versions of TinyOS, the distinction between a sensor and an ADC were blurred: this led components that had nothing to do with an ADC to still resemble one programatically, even though the semantics and forms of operation were completely different. To compensate for the difference non-ADC sensors introduced additional interfaces, such as ADCError, that were tightly bound to sensor acquisition but separate in wiring. The separation between the ADC and ADCError interface is bug prone and problematic, as is the equation of a sensor and an ADC. TinyOS 2.x separates the structure and interfaces of an ADC from those of sensor drivers (which may be on top of an ADC stack, but this fact is hidden from higher level components). This TEP presents how TinyOS 2.x structures ADC software. [TEP109] (Sensor Boards) shows how a platform can present actual named sensors.

As can be seen in Appendix A the ADC hardware used on TinyOS platforms differ in many respects, which makes it difficult to find a chip independent representation for an ADC. Even if there were such a representation, the configuration details of an ADC would still depend on the actual device producing the input signal (sensor). Neither a platform independent application nor the ADC hardware stack itself has access to this information, as it can only be determined on a platform or sensorboard level. For example, determining which ADC port a sensor is attached to and how conversion results need to be interpreted is a platform specific determination. Although the actual configuration details may be different the procedure of configuring an ADC can be unified on all ADCs with the help of hardware independent interfaces: in a similar way as the Read interface definition does not predefine the type or semantics of the exchanged data (see [TEP114]), a configuration interface definition can abstract from the data type and semantics of the involved configuration settings. For example, like a component can provide a Read or Read interface, it can also provide a AdcConfigure or AdcConfigure interface depending on what ADC it represents. This TEP proposes the (typed) AdcConfigure interface as the standard interface for configuring an ADC in TinyOS 2.x.

In spite of their hardware differences, one aspect represents a common denominator of ADCs: they all produce conversion results. To facilitate sensor software development conversion results are returned by the ADC stack through the interfaces Read, ReadStream and ReadNow (see 2. Interfaces and [TEP114]). Conversion results are returned as uninterpreted values and translating them to engineering units can only be done with the configuration knowledge of the respective platform, for example, the reference voltage or the resistance of a reference resistor in ratiometric measurements. Translating uninterpreted values to engineering units may be performed by components located on top of the ADC stack and is out of the scope of this TEP.

The top layer of abstraction of an ADC - the Hardware Interface Layer (HIL) - thus provides the interfaces Read, ReadNow and ReadStream and uses the AdcConfigure interface for hardware configuration (why it uses and does not provide AdcConfigure is explained below). Since the type and semantics of the parameters passed through these interfaces is dependent on the actual ADC implementation, it is only a "weak" HIL (see [TEP2]).

Following the principles of the HAA [TEP2] the Hardware Adaptation Layer (HAL, which resides below the HIL) of an ADC should expose all the chip-specific capabilities of the chip. For example, the ADC12 on the MSP430 MCU supports a "Repeat-Sequence-of-Channels Mode" and therefore this function should be accessible on the HAL of the MSP430 ADC12 hardware abstraction. Other ADCs might not exhibit such functionality and might therefore - on the level of HAL - provide only an interface to perform single conversions. Since all ADCs have the same HIL representation it may be necessary to perform some degree of software emulation in the HIL implementation. For example, a ReadStream command can be emulated by multiple single conversion commands. Below the HAL resides the Hardware Presentation Layer (HPL), a stateless component that provides access to the hardware registers (see [TEP2]). The general structure (without virtualization) of the ADC stack is as follows

       ^                     |        |                     |        |                   Read,  AdcConfigure              ReadNow (+ Resource),        |                   ReadStream        |                     |        |                     V  +----------------------------------+  |  Hardware Interface Layer (HIL)  |  |  (chip-specific implementation)  |  +----------------------------------+                   |                   |    chip-specific interface(s) + Resource (e.g. Msp430Adc12SingleChannel + Resource)                   |                   V  +----------------------------------+  |  Hardware Adaptation Layer (HAL) |  |  (chip-specific implementation)  |  +----------------------------------+                   |                   |         chip-specific interface(s)             (e.g. HplAdc12)                   |                   V  +----------------------------------+  | Hardware Presentation Layer (HPL)|  | (chip-specific implementation)   |  +----------------------------------+ 

The rest of this TEP specifies:

This TEP ends with appendices documenting, as an example, the ADC implementation for the TI MSP430 MCU.

2. Interfaces

This TEP proposes the AdcConfigure interface for ADC hardware configuration and the Read, ReadStream and ReadNow interfaces to acquire conversion results. The Read and ReadStream interfaces are documented in [TEP114] and the ReadNow interface is documented in this TEP. A Read[Now|Stream] interface is always provided in conjunction with a AdcConfigure interface.

Interface for configuring the ADC hardware

The AdcConfigure interface is defined as follows:

interface AdcConfigure<> {   async command config_type getConfiguration(); } 

This interface is used by the ADC stack to retrieve the hardware configuration of an ADC HIL client. config_type is a chip-specific data type (simple or structured) that contains all information necessary to configure the respective ADC hardware. For example, on the ADC12 of the MSP430 the AdcConfigure interface will be instantiated with the const msp430adc12_channel_config_t* data type. A client MUST always return the same configuration through a AdcConfigure interface and, if configuration data is passed as a pointer, the HIL component (see 4. HIL requirements) MUST NOT reference it after the return of the getConfiguration() command. If a client wants to use the ADC with different configurations it must provide multiple instances of the AdcConfigure interface.

Note that the AdcConfigure interface is provided by an ADC HIL client and it is used by the ADC HIL implementation. Therefore an ADC HIL client cannot initiate the configuration of the ADC hardware itself. Instead it is the ADC HIL implementation that can "pull" the client's ADC configuration just before it initates a conversion based on the respective client's configuration. The rationale is that the ADC HIL implementation does not have to store an ADC configuration per client - instead the ADC client can, for example, store its configuration in program memory.

Interfaces for acquiring conversion results

This TEP proposes to adopt the following two source-independent data collection interfaces from [TEP114] for the collection of ADC conversion results on the level of HIL:

interface Read<> interface ReadStream<> 

In addition it proposes the following data collection interface for low-latency reading of conversion results:

interface ReadNow<> 

Every data collection interface is associated with an AdcConfigure interface (how this association is realized is explained in Section 4. HIL requirements). As the resolution of conversion results is chip-specific, the size_type parameter reflects an upper bound for the chip-specific resolution of the conversion results - the actual resolution may be smaller (e.g. uint16_t for a 12-bit ADC).

Read

The Read interface can be used to sample an ADC channel once and return a single conversion result as an uninterpreted value. The Read interface is documented in [TEP114].

ReadStream

The ReadStream interface can be used to sample an ADC channel multiple times with a specified sampling period. The ReadStream interface is documented in [TEP114] .

ReadNow

The ReadNow interface is intended for split-phase low-latency reading of small values:

interface ReadNow {   async command error_t read();   async event void readDone( error_t result, val_t val ); } 

This interface is similar to the Read interface, but works in asynchronous context. A successful call to ReadNow.read() means that the ADC hardware has started the sampling process and that ReadNow.readDone() will be signalled once it has finished (note that the asynchronous ReadNow.readDone() might be signalled even before the call to ReadNow.read() has returned). Due to its timing constraints the ReadNow interface is always provided in conjunction with an instance of the Resource interface and a client must reserve the ADC through the Resource interface before the client may call ReadNow.read(). Please refer to [TEP108] on how the Resource interface should be used by a client component.

3. HAL guidelines

As explained in 1. Introduction the HAL exposes the full capabilities of the ADC hardware. Therefore only chip- and platform-dependent clients can wire to the HAL. Although the HAL is chip-specific, both, in terms of implementation and representation, its design should follow the guidelines described in this section to facilitate the mapping to the HIL representation. Appendix B shows the signature of the HAL for the MSP430.

Resource reservation

As the ADC hardware is a shared resource that is usually multiplexed between several clients some form of access arbitration is necessary. The HAL should therefore provide a parameterized Resource interface, instantiate a standard arbiter component and connect the Resource interface to the arbiter as described in [TEP108]. To ensure fair and uniform arbitration on all platforms the standard round robin arbiter is recommended. Resource arbiters and the Resource interface are the topic of [TEP108].

Configuration and sampling

As the ADC hardware is a shared resource the HAL should support hardware configuration and sampling per client (although per-port configuration is possible, it is not recommended, because it forces all clients to use the same configuration for a given port). Therefore the HAL should provide sampling interfaces parameterized by a client identifier. A HAL client can use its instance of the sampling interface to configure the ADC hardware, start the sampling process and acquire conversion results. It wires to a sampling interface using a unique client identifier (this may be hidden by a virtualization component). All commands and events in the sampling interface should be 'async' to reflect the potential timing requirements of clients on the level of HAL. A HAL may provide multiple different parameterized sampling interfaces, depending on the hardware capabilities. This allows to differentiate/group ADC functionality, for example single vs. repeated sampling, single channel vs. multiple channels or low-frequency vs. high-frequency sampling. Every sampling interface should allow the client to individually configure the ADC hardware, for example by including the configuration data as parameters in the sampling commands. However, if configuration data is passed as a pointer, the HAL component MUST NOT reference it after the return of the respective command. Appendix B shows the HAL interfaces for the MSP430.

HAL virtualization

In order to hide wiring complexities and/or export only a subset of all ADC functions generic ADC wrapper components may be provided on the level of HAL. Such components can also be used to ensure that a sampling interface is always provided with a Resource interface and both are instantiated with the same client ID if this is required by the HAL implementation.

4. HIL requirements

The following generic components MUST be provided on all platforms that have an ADC:

AdcReadClientC AdcReadNowClientC AdcReadStreamClientC 

These components provide virtualized access to the HIL of an ADC. They are instantiated by an ADC client and provide/use the four interfaces described in Section 2. Interfaces. An ADC client may instantiate multiple such components. The following paragraphs describe their signatures. Note that this TEP does not address the issue of how to deal with multiple ADCs on the same platform (the question of how to deal with multiple devices of the same class is a general one in TinyOS 2.x). Appendix C shows the AdcReadClientC for the MSP430.

AdcReadClientC

generic configuration AdcReadClientC() {   provides {     interface Read<>;   }   uses {     interface AdcConfigure<>;   } } 

The AdcReadClientC component provides a Read interface for acquiring single conversion results. The associated ADC channel (port) and further configuration details are returned by the AdcConfigure.getConfiguration() command. It is the task of the client to wire this interface to a component that provides the client's ADC configuration. The HIL implementation will use the AdcConfigure interface to dynamically "pull" the client's ADC settings when it translates the Read.read() command to a chip-specific sampling command. Note that both, size_type and config_type, are only placeholders and will be instantiated by the respective HIL implementation (for an example, see the AdcReadClientC for the MSP430 in Appendix C).

AdcReadNowClientC

generic configuration AdcReadNowClientC() {   provides {     interface Resource;     interface ReadNow<>;   }   uses {     interface AdcConfigure<>;   } } 

The AdcReadNowClientC component provides a ReadNow interface for acquiring single conversion results. In contrast to Read.read() when a call to ReadNow.read() succeeds, the ADC starts to sample the channel immediately (a successful Read.read() command may not have this implication, see [TEP114] and 2. Interfaces). A client MUST reserve the ADC through the Resource interface before the client may call ReadNow.read() and it MUST release the ADC through the Resource interface when it no longer needs to access it (for more details on how to use the Resource interface please refer to [TEP108]). The associated ADC channel (port) and further configuration details are returned by the AdcConfigure.getConfiguration() command. It is the task of the client to wire this interface to a component that provides the client's ADC configuration. The HIL implementation will use the AdcConfigure interface to dynamically "pull" the client's ADC settings when it translates the ReadNow.read() command to a chip-specific sampling command. Note that both, size_type and config_type, are only placeholders and will be instantiated by the respective HIL implementation (for an example how this is done for the AdcReadClientC see Appendix C).

AdcReadStreamClientC

generic configuration AdcReadStreamClientC() {   provides {     interface ReadStream<>;   }   uses {     interface AdcConfigure<>;   } } 

The AdcReadStreamClientC component provides a ReadStream interface for acquiring multiple conversion results at once. The ReadStream interface is explained in [TEP114] and 2. Interfaces. The AdcConfigure interface is used in the same way as described in the section on the AdcReadClientC. Note that both, size_type and config_type, are only placeholders and will be instantiated by the respective HIL implementation (for an example how this is done for the AdcReadClientC see Appendix C).

5. HIL guidelines

The HIL implementation of an ADC stack has two main tasks: it translates a Read, ReadNow or ReadStream request to a chip-specific HAL sampling command and it abstracts from the Resource interface (the latter only for the AdcReadClientC and AdcReadStreamClientC). The first task is solved with the help of the AdcConfigure interface which is used by the HIL implementation to retrieve a client's ADC configuration. The second task MAY be performed by the following library components: ArbitratedReadC, and ArbitratedReadStreamC (in tinyos-2.x/tos/system) - please refer to the Atmel Atmega 128 HAL implementation (in tinyos-2.x/tos/chips/atm128/adc) for an example. Note that since the ReadNow interface is always provided in conjunction with a Resource interface the HIL implementation does not have to perform the ADC resource reservation for an AdcReadNowClientC, but may simply forward an instance of the Resource interface from the HAL to the AdcReadNowClientC.

The typical sequence of events is as follows: when a client requests data through the Read or ReadStream interface the HIL will request access to the HAL using the Resource interface. After the HIL has been granted access, it will "pull" the client's ADC configuration using the AdcConfigure interface and translate the client's Read or ReadStream command to a chip-specific HAL command. Once the HIL is signalled the conversion result(s) from the HAL it releases the ADC through the Resource interface and signals the conversion result(s) to the client though the Read or ReadStream interface. When a client requests data through the ReadNow interface the HIL translates the client's command to the chip-specific HAL command without using the Resource interface (it may check ownership of the client through the ArbiterInfo interface - this check can also be done in the HAL implementation). Once the HIL is signalled the conversion result(s) it forwards it to the respective ReadNow client.

6. Implementation

TestAdc application

An ADC HIL test application can be found in tinyos-2.x/apps/tests/TestAdc. Note that this application instantiates generic DemoSensorC, DemoSensorStreamC and DemoSensorNowC components (see [TEP114]) and assumes that these components are actually wired to an ADC HIL. Please refer to tinyos-2.x/apps/tests/TestAdc/README.txt for more information.

HAA on the MSP430 and Atmega 128

The implementation of the ADC12 stack on the MSP430 can be found in tinyos-2.x/tos/chips/msp430/adc12:

  • HplAdc12P.nc is the HPL implementation
  • Msp430Adc12P.nc is the HAL implementation
  • AdcP.nc is the HIL implementation
  • AdcReadClientC.nc, AdcReadNowClientC.nc and AdcReadStreamClientC.nc provide virtualized access to the HIL
  • the use of DMA or the reference voltage generator and the HAL virtualization components are explained in README.txt

The Atmel Atmega 128 ADC implementation can be found in tinyos-2.x/tos/chips/atm128/adc:

  • HplAtm128AdcC.nc is the HPL implementation
  • Atm128AdcP.nc is the HAL implementation
  • AdcP.nc, WireAdcP.nc and the library components for arbitrating 'Read', 'ReadNow' and 'ReadStream', ArbitratedReadC and ArbitratedReadStreamC (in tinyos-2.x/tos/system), realize the HIL
  • AdcReadClientC.nc, AdcReadNowClientC.nc and AdcReadStreamClientC.nc provide virtualized access to the HIL

Appendix A: Hardware differences between platforms

The following table compares the characteristics of two microcontrollers commonly used in TinyOS platforms:

Atmel Atmega 128 TI MSP430 ADC12
Resolution 10-bit 12-bit
channels
  • 8 multiplexed external channels
  • 16 differential voltage input combinations
  • 2 differential inputs with gain amplification
  • 8 individually configurable external channels
  • internal channels (AVcc, temperature, reference voltages)
internal reference voltage 2.56V 1.5V or 2.5V
conversion reference
  • positive terminal: AVcc or 2.56V or AREF (external)
  • negative terminal: GND
individually selectable per channel:
  • AVcc and AVss
  • Vref+ and AVss
  • Veref+ and AVss
  • AVcc and (Vref- or Veref-)
  • AVref+ and (Vref- or Veref-)
  • Veref+ and (Vref- or Veref-)
conversion modes
  • single channel conversion mode
  • free running mode (channels and reference voltages can be switched between samples)
  • single conversion mode
  • repeat single conversion mode
  • sequence mode (sequence <= 16 channels)
  • repeat sequence mode
conversion clock source clkADC with prescaler ACLK, MCLK, SMCLK or ADC-oscillator (5MHz) with prescaler respectively
sample-hold-time 1.5 clock cycles (fixed) selectable values from 4 to 1024 clock cycles
conversion triggering by software by software or timers
conversion during sleep mode possible yes yes
interrupts after each conversion after single or sequence conversion

Appendix B: a HAL representation: MSP430 ADC12

This section shows the HAL signature for the ADC12 of the TI MSP430 MCU. It reflects the four MSP430 ADC12 conversion modes as it lets a client sample an ADC channel once ("Single-channel-single-conversion") or repeatedly ("Repeat-single-channel"), multiple times ("Sequence-of-channels") or multiple times repeatedly ("Repeat-sequence-of-channels"). In contrast to the single channel conversion modes the sequence conversion modes trigger a single interrupt after multiple samples and thus enable high-frequency sampling. The DMAExtension interface is used to reset the state machine when the DMA is responsible for data transfer (managed in an exterior component):

configuration Msp430Adc12P {   provides {     interface Resource[uint8_t id];     interface Msp430Adc12SingleChannel as SingleChannel[uint8_t id];     interface AsyncStdControl as DMAExtension[uint8_t id];   } }  interface Msp430Adc12SingleChannel {   async command error_t configureSingle(const msp430adc12_channel_config_t *config);   async command error_t configureSingleRepeat(const msp430adc12_channel_config_t *config, uint16_t jiffies);   async command error_t configureMultiple( const msp430adc12_channel_config_t *config, uint16_t buffer[], uint16_t numSamples, uint16_t jiffies);   async command error_t configureMultipleRepeat(const msp430adc12_channel_config_t *config, uint16_t buffer[], uint8_t numSamples, uint16_t jiffies);   async command error_t getData();   async event error_t singleDataReady(uint16_t data);   async event uint16_t* multipleDataReady(uint16_t buffer[], uint16_t numSamples); }  typedef struct {   unsigned int inch: 4;            // input channel   unsigned int sref: 3;            // reference voltage   unsigned int ref2_5v: 1;         // reference voltage level   unsigned int adc12ssel: 2;       // clock source sample-hold-time   unsigned int adc12div: 3;        // clock divider sample-hold-time   unsigned int sht: 4;             // sample-hold-time   unsigned int sampcon_ssel: 2;    // clock source sampcon signal   unsigned int sampcon_id: 2;      // clock divider sampcon signal } msp430adc12_channel_config_t; 

Appendix C: a HIL representation: MSP430 ADC12

The signature of the AdcReadClientC component for the MSP430 ADC12 is as follows:

generic configuration AdcReadClientC() {   provides interface Read;   uses interface AdcConfigure; } 
[TEP1]TEP 1: TEP Structure and Keywords.
[TEP2](1, 2, 3, 4) TEP 2: Hardware Abstraction Architecture.
[TEP108](1, 2, 3, 4) TEP 108: Resource Arbitration.
[TEP109]TEP 109: Sensor Boards.
[TEP114](1, 2, 3, 4, 5, 6, 7, 8, 9) TEP 114: SIDs: Source and Sink Independent Drivers.

OscillscopeM.nc


includes OscopeMsg;

/**
* This module implements the OscilloscopeM component, which
* periodically takes sensor readings and sends a group of readings
* over the UART. BUFFER_SIZE defines the number of readings sent
* in a single packet. The Yellow LED is toggled whenever a new
* packet is sent, and the red LED is turned on when the sensor
* reading is above some constant value.
*/
module OscilloscopeM
{
provides interface StdControl;
uses {
interface Timer;
interface Leds;
interface StdControl as SensorControl;
interface ADC;
interface StdControl as CommControl;
interface SendMsg as DataMsg;
interface ReceiveMsg as ResetCounterMsg;
}
}
implementation
{
uint8_t packetReadingNumber;
uint16_t readingNumber;
TOS_Msg msg[2];
uint8_t currentMsg;

/**
* Used to initialize this component.
*/
command result_t StdControl.init() {
call Leds.init();
call Leds.yellowOff(); call Leds.redOff(); call Leds.greenOff();

//turn on the sensors so that they can be read.
call SensorControl.init();

call CommControl.init();
atomic {
currentMsg = 0;
packetReadingNumber = 0;
readingNumber = 0;
}
dbg(DBG_BOOT, "OSCOPE initialized\n");
return SUCCESS;
}

/**
* Starts the SensorControl and CommControl components.
* @return Always returns SUCCESS.
*/
command result_t StdControl.start() {
call SensorControl.start();
call Timer.start(TIMER_REPEAT, 125);
call CommControl.start();
return SUCCESS;
}

/**
* Stops the SensorControl and CommControl components.
* @return Always returns SUCCESS.
*/
command result_t StdControl.stop() {
call SensorControl.stop();
call Timer.stop();
call CommControl.stop();
return SUCCESS;
}

task void dataTask() {
struct OscopeMsg *pack;
atomic {
pack = (struct OscopeMsg *)msg[currentMsg].data;
packetReadingNumber = 0;
pack->lastSampleNumber = readingNumber;
}

pack->channel = 1;
pack->sourceMoteID = TOS_LOCAL_ADDRESS;
/* Try to send the packet. Note that this will return
* failure immediately if the packet could not be queued for
* transmission.
*/
if (call DataMsg.send(TOS_BCAST_ADDR, sizeof(struct OscopeMsg),
&msg[currentMsg]))
{
atomic {
currentMsg ^= 0x1;
}
call Leds.yellowToggle();
}
}
/**
* Signalled when data is ready from the ADC. Stuffs the sensor
* reading into the current packet, and sends off the packet when
* BUFFER_SIZE readings have been taken.
* @return Always returns SUCCESS.
*/
async event result_t ADC.dataReady(uint16_t data) {
struct OscopeMsg *pack;
atomic {
pack = (struct OscopeMsg *)msg[currentMsg].data;
pack->data[packetReadingNumber++] = data;
readingNumber++;
dbg(DBG_USR1, "data_event\n");
if (packetReadingNumber == BUFFER_SIZE) {
post dataTask();
}
}
if (data > 0x0300)
call Leds.redOn();
else
call Leds.redOff();

return SUCCESS;
}

/**
* Signalled when the previous packet has been sent.
* @return Always returns SUCCESS.
*/
event result_t DataMsg.sendDone(TOS_MsgPtr sent, result_t success) {
return SUCCESS;
}

/**
* Signalled when the clock ticks.
* @return The result of calling ADC.getData().
*/
event result_t Timer.fired() {
return call ADC.getData();
}

/**
* Signalled when the reset message counter AM is received.
* @return The free TOS_MsgPtr.
*/
event TOS_MsgPtr ResetCounterMsg.receive(TOS_MsgPtr m) {
atomic {
readingNumber = 0;
}
return m;
}
}

current Oscilloscope.nc

Single Channel code

-----------------------------------
configuration Oscilloscope { }
implementation
{
components Main, OscilloscopeM
, TimerC
, LedsC
, DemoSensorC as Sensor
, GenericComm as Comm;

Main.StdControl -> OscilloscopeM;
Main.StdControl -> TimerC;
OscilloscopeM.Timer -> TimerC.Timer[unique("Timer")];
OscilloscopeM.Leds -> LedsC;
OscilloscopeM.SensorControl -> Sensor;
OscilloscopeM.ADC -> Sensor;
OscilloscopeM.CommControl -> Comm;
OscilloscopeM.ResetCounterMsg -> Comm.ReceiveMsg[AM_OSCOPERESETMSG];
OscilloscopeM.DataMsg -> Comm.SendMsg[AM_OSCOPEMSG];
}