============================ Timers ============================ :TEP: 102 :Group: Core Working Group :Type: Documentary :Status: Draft :TinyOS-Version: 2.x :Author: Cory Sharp, Martin Turon, David Gay :Draft-Created: 22-Sep-2004 :Draft-Version: $Revision$ :Draft-Modified: $Date$ :Draft-Discuss: TinyOS Developer List .. 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 TEP proposes a Timer design that supports common timing requirements both in precision and width across common hardware configurations. This TEP focuses on aligning the Timer abstraction with the three-layer Hardware Abstraction Architecture (HAA). 1. Introduction ==================================================================== Most microcontrollers offer a rich timer system, with features like: * several counters, possibly of different widths, with multiple clocking options * one or more compare registers for each counter, which can trigger interrupts, changes to output pins and changes to the counter value * capture of the time of input pin changes The interested reader can refer to Appendix A for a brief overview of the timer hardware on some current TinyOS platforms. TinyOS does not attempt to capture all this diversity in a platform-independent fashion. Instead, following the principles of the HAA[_tep2], each microcontroller should expose all this functionality via components and interfaces at the HPL and, where appropriate, HAL levels. However, two aspects of timers are sufficiently common and important that they should be made available in a well-defined way: measuring time, and triggering (possibly repeating) events at specific times. The rest of this TEP specifies: * a set of platform-independent interfaces for counting time and triggering events (`2. Interfaces`_) * guidelines on how each microcontroller's HAL SHOULD expose its timer hardware in terms of the above interfaces (`3. HAL guidelines`_) * what components a microcontroller's timer HIL MUST implement (`4. HIL requirements`_) * a set of utility components whose use simplifies building the components specified by the HAL guidelines and HIL requirements (`5. Utility components`_) This TEP ends with appendices documenting, as an example, the mica2 timer subsystem implementation. 2. Interfaces ==================================================================== Before presenting the interfaces (2.2), we start with a general discussion of the issues of precision, width and accuracy in timer interfaces (2.1). 2.1 Precision, Width and Accuracy. -------------------------------------------------------------------- Three fundamental properties of timers are *precision*, *width* and *accuracy*. Examples of precision are millisecond, a cycle of a 32kHz clock, and microseconds. All precisions are in "binary" units with respect to one second. That is, one second contains 1024 binary milliseconds, 32768 32kHz ticks, or 1048576 microseconds. This TEP emphasizes millisecond and 32kHz tick precisions while reasonably accommodating other precisions. Examples of widths are 8-bit, 16-bit, 32-bit, and 64-bit. The width for timer interfaces and components SHOULD be 32-bits. That is, for lack of a good reason, timer interfaces should expose a 32-bit interface. In a number of circumstances there are good reasons not to expose a 32-bit interface. This TEP emphasizes 32-bit widths while reasonably accommodating other widths. Accuracy reflects how closely a component conforms to the precision it claims to provide. Accuracy is affected by issues such as clock drift (much higher for internal vs crystal oscillators) and hardware limitations. As an example of hardware limitations, a mica2 clocked at 7.37MHz cannot offer an exact microsecond timer -- the closest it can come is 7.37MHz/8. Rather than introduce a plethora of precisions, we believe it is often best to pick the existing precision closest to what can be provided, along with appropriate documentation. However, the accuracy MUST remain reasonable: for instance, it would be inappropriate to claim that a millisecond timer is a 32kHz timer. This TEP parameterizes all interfaces by precision and some interfaces by width. This intentionally makes similar timer interfaces with different precision or width mutually incompatible. It also allows user code to clearly express and understand the precision and width for a given timer interface. Accuracy is not reflected in the interface type. Precision is expressed as an empty type -- TMilli, T32khz, and TMicro -- written in the standard Timer.h header like this:: typedef struct { } TMilli; typedef struct { } T32khz; typedef struct { } TMicro; Note that the precision names are expressed as either frequency or period, whichever is convenient. 2.2 Timer interfaces -------------------------------------------------------------------- This TEP proposes these timer interfaces:: interface Counter< precision_tag, size_type > interface Alarm< precision_tag, size_type > interface BusyWait< precision_tag, size_type > interface LocalTime< precision_tag > interface Timer< precision_tag > The LocalTime and Timer interfaces are used primarily by user applications and use a fixed width of 32-bits. The Alarm, BusyWait, and Counter interfaces are used by the TinyOS timer system and advanced user components. Counter -------------------------------------------------------------------- A Counter component will increase the width of a low-level hardware timer by wrapping the overflow event and incrementing its higher order bits. These higher order bits are considered extra state over the HPL register layer, and therefore qualify all Counters as HAL components. The Counter interface returns the current time and provides commands and an event for managing overflow conditions. These overflow commands and events are necessary for properly deriving larger width Counters from smaller widths. :: interface Counter { async command size_type get(); async command bool isOverflowPending(); async command void clearOverflow(); async event void overflow(); } get() return the current time. isOverflowPending() return TRUE if an overflow interrupt will occur after the outermost atomic block is exits. FALSE otherwise. clearOverflow() cancel the pending overflow interrupt. overflow() signals that an overflow in the current time. That is, the current time has wrapped around from its maximum value to zero. Alarm -------------------------------------------------------------------- Alarm components are extensions of Counters that signal an event when their Compare register detects the alarm time has been hit. All commands and events of the Alarm interface are asynchronous (or in "interrupt context"). The Alarm interface provides a set of "basic" commands for common usage and provides a set of "extended" commands for advanced use. :: interface Alarm { // basic interface async command void start( size_type dt ); async command void stop(); async event void fired(); // extended interface async command bool isRunning(); async command void startAt( size_type t0, size_type dt ); async command size_type getNow(); async command size_type getAlarm(); } start(dt) cancel any previously running alarm and set to fire in dt time units from the time of invocation. The alarm will only fire once then stop. stop() cancel any previously running alarm. fired() signals that the alarm has occurred. isRunning() return TRUE if the alarm has been started and has not been cancelled or has not yet fired. FALSE is returned otherwise. startAt(t0,dt) cancel any previously running alarm and set to fire at time t1 = t0+dt. This form allows a delay to be anchored to some time t0 taken before the invocation of start. This is also the form used internally in the timer subsystem to allow the use of the full width of an alarm while being able to detect if the alarm time for a short alarm prematurely elapsed. getNow() return the current time in the precision and width of the alarm. getAlarm() return the time the currently running alarm will fire or the time that the previously running alarm was set to fire. BusyWait -------------------------------------------------------------------- The BusyWait interface replaces the TOSH_uwait macro from TinyOS 1.x. :: interface BusyWait { async command void wait( size_type dt ); } wait(dt) block for no less than the specified amount of time. LocalTime -------------------------------------------------------------------- The LocalTime interface exposes a 32-bit counter without overflow utilities. This is primarily for application code that does not care about overflow conditions. :: interface LocalTime { async command uint32_t get(); } get() return the current time. Timer -------------------------------------------------------------------- All commands and events of the Timer interface are synchronous (or in "task context"). The Timer interface provides a set of "basic" commands for common usage and provides a set of "extended" commands for advanced use. The Timer interface allows for periodic events. :: interface Timer { // basic interface command void startPeriodic( uint32_t dt ); command void startOneShot( uint32_t dt ); command void stop(); event void fired(); // extended interface command bool isRunning(); command bool isOneShot(); command void startPeriodicAt( uint32_t t0, uint32_t dt ); command void startOneShotAt( uint32_t t0, uint32_t dt ); command uint32_t getNow(); command uint32_t gett0(); command uint32_t getdt(); } startPeriodic(dt) cancel any previously running timer and set to fire in dt time units from the time of invocation. The timer will fire periodically every dt time units until stopped. startOneShot(dt) cancel any previously running timer and set to fire in dt time units from the time of invocation. The timer will only fire once then stop. stop() cancel any previously running timer. fired() signals that the timer has occurred. isRunning() return TRUE if the timer has been started and has not been cancelled and has not fired for the case of one-shot timers. One a periodic timer is started, isRunning will return TRUE until it is cancelled. isOneShot() return TRUE if the timer is a one-shot timer. Return FALSE otherwise if the timer is a periodic timer. startPeriodicAt(t0,dt) cancel any previously running timer and set to fire at time t1 = t0+dt. The timer will fire periodically every dt time units until stopped. startOneShotAt(t0,dt) cancel any previously running timer and set to fire at time t1 = t0+dt. The timer will fire once then stop. getNow() return the current time in the precision and width of the timer. gett0() return the time anchor for the previously started timer or the time of the previous event for periodic timers. getdt() return the delay or period for the previously started timer. 3. HAL guidelines ==================================================================== Platforms typically select a clocking option for each of their hardware counters, based on their hardware design (e.g., the mica family of motes all run their hardware timer 0 at 32kHz, and the micaz mote runs its timer 1 at cpu frequency/256). Platforms SHOULD expose the timing functionality of these timers using the Alarm and Counter interfaces, in the fashion described below. Platforms MAY expose the same hardware timer with different frequencies - use of conflicting frequences in the same program SHOULD produce compile-time errors. A hardware timer with precision *P* and width *W* SHOULD be exposed as a several components:: configuration CounterPWC { provides interface Counter; } ... generic configuration AlarmPWC { provides interface Alarm; } ... and, except if *W* is 32:: configuration CounterP32C { provides interface Counter; } ... generic configuration AlarmP32C { provides interface Alarm; } ... Instantiating the Alarm... components provides a new Alarm independent of all prior instantiations. Instantiating such a component "consumes" a compare register from the corresponding hardware timer; when no more compare registers are available, instantiation SHOULD produce a compile-time error (see Appendix B for an example of how to achieve this). For example, the micaz platform includes an AlarmMilli8C and AlarmMilli32C components for timer 0 (one instantiation allowed), and Alarm32kHz16C and Alarm32kHz32C for timer 1 (three instantiations allowed). 4. HIL requirements ==================================================================== The following component MUST be provided on all platforms:: TimerMilliC BusyWaitMicroC TimerMilliC -------------------------------------------------------------------- :: #define TIMERMILLIC_SERVICE ... configuration TimerMilliC { provides interface Init; provides interface Timer[uint8_t num]; provides interface LocalTime; } A timer is allocated using unique(TIMERMILLIC_SERVICE) to obtain a new unique timer number. This timer number is used to index the TimerMilli parameterised interface. BusyWaitMicroC -------------------------------------------------------------------- :: configuration BusyWaitMicroC { provides interface BusyWait; } BusyWaitMicroC allows applications to busy-wait for a number of microseconds. It's use should be restricted to situations where the delay is small and setting a timer or alarm would be impractical, inefficient or insufficiently precise. 5. Utility components ==================================================================== A number of platform independent generic components are provided to help implementers and advanced users of the TinyOS timer system: * AlarmToTimerC * BusyWaitCounterC * CounterToLocalTimeC * TransformAlarmC * TransformCounterC * VirtualizeTimerC Appendices B and C show how these can be used to help implement the timer HAL and HIL. AlarmToTimerC -------------------------------------------------------------------- AlarmToTimerC converts a 32-bit Alarm to a Timer. :: generic component AlarmToTimerC( typedef precision_tag ) { provides interface Timer; uses interface Alarm; } BusyWaitCounterC -------------------------------------------------------------------- BusyWaitCounterC uses a Counter to block until a specified amount of time elapses. :: generic component BusyWaitC( typedef precision_tag, typedef size_type @integer() ) { provides interface BusyWait; uses interface Counter; } CounterToLocalTimeC -------------------------------------------------------------------- CounterToLocalTimeC converts from a 32-bit Counter to LocalTime. :: generic component CounterToLocalTimeC( precision_tag ) { provides interface LocalTime; uses interface Counter; } TransformAlarmC -------------------------------------------------------------------- TransformAlarmC decreases precision and/or widens an Alarm. An already widened Counter component is used to help. :: generic component TransformAlarmC( typedef to_precision_tag, typedef to_size_type @integer(), typedef from_precision_tag, typedef from_size_type @integer(), uint8_t bit_shift_right ) { provides interface Alarm as Alarm; uses interface Counter as Counter; uses interface Alarm as AlarmFrom; } to_precision_tag and to_size_type describe the final precision and final width for the provided Alarm. from_precision_tag and from_size_type describe the precision and width for the source AlarmFrom. bit_shift_right describes the bit-shift necessary to convert from the used precision to the provided precision. For instance to convert from an Alarm to an Alarm, the following TransformAlarmC would be created:: new TransformAlarmC( TMilli, uint32_t, T32khz, uint16_t, 5 ) TransformCounterC -------------------------------------------------------------------- TransformCounterC decreases precision and/or widens a Counter. :: generic component TransformCounterC( typedef to_precision_tag, typedef to_size_type @integer(), typedef from_precision_tag, typedef from_size_type @integer(), uint8_t bit_shift_right, typedef upper_count_type @integer() ) { provides interface Counter as Counter; uses interface Counter as CounterFrom; } to_precision_tag and to_size_type describe the final precision and final width for the provided Counter. from_precision_tag and from_size_type describe the precision and width for the source AlarmFrom. bit_shift_right describes the bit-shift necessary to convert from the used precision to the provided precision. upper_count_type describes the numeric type used to store the additional counter bits. upper_count_type MUST be a type with width greater than or equal to the additional bits in to_size_type plus bit_shift_right. For instance to convert from a Counter to a Counter, the following TransformCounterC would be created:: new TransformCounterC( TMilli, uint32_t, T32khz, uint16_t, 5, uint32_t ) VirtualizeTimerC -------------------------------------------------------------------- VirtualizeTimerC uses a single Timer to create up to 255 virtual timers. :: generic component VirtualizeTimerC( typedef precision_tag, int max_timers ) { provides interface Init; provides interface Timer as Timer[ uint8_t num ]; uses interface Timer as TimerFrom; } Appendix A: Timer hardware on various microcontrollers ==================================================================== a. Atmega128 i. Two 8-bit timers, each allowing * 7 prescaler values (division by different powers of 2) * Timer 0 can use an external 32768Hz crystal * One compare register, with many compare actions (change output pin, clear counter, generate interrupt, etc) ii. Two 16-bit timers, each with * 5 prescaler values * External and software clocking options * Three compare registers (again with many actions) * Input capture b. MSP430 i. Two 16-bit timers with * One with three compare registers * One with eight compare registers * Each from distinct clock source * Each with limited prescalers c. Intel PXA27x i. One fixed rate (3.25MHz) 32-bit timer with * 4 compare registers * Watchdog functionality ii. 8 variable rate 32-bit timers with * 1 associated compare register each * Individually selectable rates: 1/32768s, 1ms, 1s, 1us * Individually selectable sources: (32.768 external osc, 13 Mhz internal clock) iii. Periodic & one-shot capability iv. Two external sync events Appendix B: a microcontroller: Atmega 128 timer subsystem ==================================================================== The Atmega128 exposes its four timers through a common set of interfaces: * HplTimer - get/set current time, overflow event, control, init * HplCompare - get/set compare time, fired event, control * HplCapture - get/set capture time, captured event, control, config Parameterising these interfaces by width allows reusing the same interfaces for the 8 and 16-bit timers. This simplifies building reusable higher level components which are independent of timer width. :: interface HplAtm128Timer { /// Timer value register: Direct access async command timer_size get(); async command void set( timer_size t ); /// Interrupt signals async event void overflow(); // { /// Compare value register: Direct access async command size_type get(); async command void set(size_type t); /// Interrupt signals async event void fired(); // { /// Capture value register: Direct access async command size_type get(); async command void set(size_type t); /// Interrupt signals async event void captured(size_type t); // as Alarm; uses interface HplTimer; uses interface HplCompare; } ... generic module Atm128CounterC(typedef frequency_tag, typedef timer_size @integer()) { provides interface Counter as Counter; uses interface HplTimer as Timer; } ... Appendix C: a mote: Mica family timer subsystem ==================================================================== Members of the mica family (mica2, mica2dot, micaz) use the Atmega128 microprocessor and have external crystals at 4 or 7.37MHz. Additionally, they can be run from an internal oscillator at 1, 2, 4, or 8 MHz. The internal oscillator is less precise, but allows for much faster startup from power-down and power-save modes (6 clocks vs 16000 clocks). Finally, power consumption is lower at the lower frequencies. The mica family members support operation at all these frequencies via a ``MHZ`` preprocessor symbol, which can be defined to 1, 2, 4, or 8. If undefined, it defaults to a platform-dependent value (4 for mica2dot, 8 for mica2 and micaz). The mica family configures its four timers in part based on the value of this MHZ symbol: * Timer 0: divides the external 32768Hz crystal by 32 to build AlarmMilli8C and AlarmMilli32C (see Section 3). As timer 0 has a single compare register, these can only be instantiated once. Timing accuracy is as good as the external crystal. * Timer 1: the 16-bit hardware timer 1 is set to run at 1MHz if possible. However, the set of dividers for timer 1 is limited to 1, 8, 64, 256 and 1024. So, when clocked at 2 or 4MHz, a divider of 1 is selected and timer 1 runs at 2 or 4MHz. To reflect this fact, the HAL components exposing timer 1 are named ``CounterOne16C`` and ``AlarmOne16C`` (rather than the ``CounterMicro16C`` ``AlarmMicro16C`` as suggested in Section 3). When building the 32-bit counter and 32-bit alarms, the rate of timer 1 is adjusted in software to 1MHz. Thus the 32-bit HAL components for timer *are* named ``CounterMicro32C`` and ``AlarmMicro32C``. Three compare registers are available on timer1, so up to three instances of ``AlarmOne16C`` and/or ``AlarmMicro32C`` can be created. The timing accuracy depends on how the mote is clocked: - internal clock: depends on how well the clock is calibrated - external 7.37MHz crystal: times will be off by ~8.6% - external 4MHz crystal: times will be as accurate as the crystal * Timer 2: this timer is not currently exposed by the HAL. * Timer 3: the 16-bit hardware timer 3 is set to run at a rate close to 32768Hz, if possible. As with timer 1, the limited set of dividers makes this impossible at some clock frequencies, so the 16-bit timer 3 HAL components are named ``CounterThree16C`` and ``AlarmThree16C``. As with timer 1, the rate of timer 3 is adjusted in software when building the 32-bit counter and 32-bit alarms, giving components ``Counter32khz32C`` and ``Alarm32khz32C``. As with timer 1, three compare registers, hence up to three instances of ``Alarm32khz32C`` and/or ``AlarmThree16C`` are available. At 1, 2, 4 and 8MHz, ``Counter32khz32C`` and ``Alarm32khz32C`` run at 31.25kHz (plus clock rate inaccuracy). At 7.37MHz, they run at ~28.8kHz. When an Atmega128 is in any power-saving mode, hardware timers 1, 2 and 3 stop counting. The default Atmega128 power management *will* enter these power-saving modes even when timers 1 and 3 are enabled, so time as measured by timers 1 and 3 does *not* represent real time. However, if any alarms built on timers 1 or 3 are active, the Atmega128 power management will not enter power-saving modes. The mica family HIL components are built as follows: * TimerMilliC: built using AlarmMilli32C (consuming its single compare register) * BusyWaitMicroC: implemented using a simple software busy-wait loop which waits for ``MHZ`` cycles per requested microsecond. Accuracy is the same as Timer 1. Finally, the mica family motes measure their clock rate at boot time, based on the external 32768Hz crystal. The results of this clock rate measurement are made available via the ``cyclesPerJiffy`` command of the ``Atm128Calibrate`` interface of the ``MeasureClockC`` component. This command reports the number of cycles per 1/32768s. Please see this interface definition for other useful commands for more accurate timing.