Amiga® RKM Devices: 13 Timer Device

The Amiga timer device provides a general interface to the Amiga's
internal clocks.  Through the timer device, time intervals can be
measured, time delays can be effected, system time can be set and
retrieved, and arithmetic operations can be performed on time values.

                  NEW TIMER FEATURES FOR VERSION 2.0

               Feature                 Description
               --------                -----------
               UNIT_ECLOCK             New timer device unit
               UNIT_WAITUNTIL          New timer device unit
               UNIT_WAITECLOCK         New timer device unit
               ReadEClock()            New function

   Compatibility Warning:
   ----------------------
   The new features for 2.0 are not backwards compatible.

 Timer Device Commands and Functions 
 Device Interface 
 System Time 
 Adding a Time Request 
 Using the Time Arithmetic Functions 
 E-Clock Time and Its Relationship to Actual Time 
 Example Timer Program 
 Additional Information on the Timer Device 


13 Timer Device / Timer Device Commands and Functions

Command        Operation
-------        ---------
TR_ADDREQUEST  Request that the timer device wait a specified period of
               time before replying to the request.

TR_GETSYSTIME  Get system time and place in a timeval structure.

TR_SETSYSTIME  Set the system time from the value in a  timeval structure.


Device Functions
----------------
AddTime()      Add one  timeval structure to another.  The result is placed
               in the first timeval structure.

CmpTime()      Compare one  timeval structure to another.  The result is
               returned as a longword.

GetSysTime()   Get system time and place in a  timeval structure.

ReadEClock()   Read the current 64 bit value of the E-Clock into an
               EClockVal structure.  The count rate of the E-Clock is also
               returned. (V36)

SubTime()      Subtract one timerequest structure from another. The result
               is placed in the first timerequest structure.


Exec Functions as Used in This Chapter
--------------------------------------
AbortIO()      Abort a command to the timer device.

CheckIO()      Return the status of an I/O request.

CloseDevice()  Relinquish use of the timer device. All requests must be
               complete before closing.

DoIO()         Initiate a command and wait for completion (synchronous
               request).

OpenDevice()   Obtain use of the timer device.  The timer device may be
               opened multiple times.

SendIO()       Initiate a command and return immediately (asynchronous
               request).


Exec Support Functions as Used in This Chapter
----------------------------------------------
CreateExtIO()  Create an extended I/O request structure of type
               timerequest.  This structure will be used to communicate
               commands to the timer device.

CreatePort()   Create a signal message port for reply messages from the
               timer device.  Exec will signal a task when a message
               arrives at the reply port.

DeleteExtIO()  Delete the timerequest extended I/O request structure
               created by CreateExtIO().

DeletePort()   Delete the message port created by CreatePort().


13 Timer Device / Device Interface

The timer device operates in a similar manner to the other Amiga devices.
To use it, you must first open it, then send I/O requests to it, and then
close it when finished. See the Introduction to Amiga System Devices
chapter for general information on device usage.

The timer device also provides timer functions in addition to the usual
I/O request protocol.  These functions still require the device to be
opened with the proper timer device unit, but do not require a message
port. However, the base address of the timer library must be obtained in
order to use the timer functions.

The two modes of timer device operation are not mutually exclusive.  You
may use them both within the same application.

The I/O request used by the timer device is called timerequest.

   struct timerequest
   {
       struct IORequest tr_node;
       struct timeval tr_time;
   };

The timer device functions are passed a time structure, either timeval for
non E-Clock units or EClockVal for E-Clock units.

   struct timeval
   {
        ULONG tv_secs;   /* seconds */
        ULONG tv_micro;  /* microseconds */
   };

   struct EClockVal
   {
       ULONG ev_hi;   /* Upper longword of E-Clock time */
       ULONG ev_lo;   /* Lower longword of E-Clock time */
   };

See the include file devices/timer.h for the complete structure
definitions. Time requests fall into three categories:

   *  Time delay - wait a specified period of time.  A time delay causes an
      application to wait a certain length of time. When a time delay is
      requested, the number of seconds and microseconds to delay are
      specified in the I/O request.

   *  Time measure - how long something takes to complete.  A time measure
      is a three-step procedure where the system or E-Clock time is
      retrieved, an operation or series of operations is performed, and
      then another time retrieval is done.  The difference between the two
      time values is the measure of the duration of the operation.

   *  Time alarm - wait till a specific time.  A time alarm is a request to
      be notified when a specific time value has occurred.  It is similar
      to a time delay except that the absolute time value is specified in
      the I/O request.

   What is an E-Clock?
   -------------------
   The E-Clock is the clock used by the Motorola 68000 processor family
   to communicate with other Motorola 8-bit chips.  The E-Clock returns
   two distinct values - the E-Clock value in the form of two longwords
   and the count rate (tics/second) of the E-Clock.  The count rate is
   related to the master frequency of the machine and is different
   between PAL and NTSC machines.

 Timer Device Units 
 Opening The Timer Device 
 Closing The Timer Device 


13 / Device Interface / Timer Device Units

There are five units in the timer device.

                           TIMER DEVICE UNITS

                   Unit                        Use
                ------------            ---------------
                UNIT_MICROHZ            Interval Timing
                UNIT_VBLANK             Interval Timing
                UNIT_ECLOCK             Interval Timing
                UNIT_WAITUNTIL          Time Event Occurrence
                UNIT_WAITECLOCK         Time Event Occurrence


   *  The VBLANK timer unit is very stable and has a granularity comparable
      to the vertical blanking time.  When you make a timing request, such
      as "signal me in 21 seconds," the reply will come at the next
      vertical blank after 21 seconds have elapsed.  This timer has very
      low overhead and may be more accurate then the MICROHZ and ECLOCK
      units for long time periods. Keep in mind that the vertical blanking
      time varies depending on the display mode.

   *  The MICROHZ timer unit uses the built-in precision hardware timers to
      create the timing interval you request. It accepts the same type of
      command - "signal me in so many seconds and microseconds." The
      microhertz timer has the advantage of greater resolution than the
      vertical blank timer, but it may have less accuracy over long periods
      of time.  The microhertz timer also has much more system overhead,
      which means accuracy is reduced as the system load increases. It is
      primarily useful for short-burst timing for which critical accuracy
      is not required.

   *  The ECLOCK timer unit uses the Amiga E-Clock to measure the time
      interval you request.  This is the most precise time measure
      available through the timer device.

   *  The WAITUNTIL timer unit acts as an alarm clock for time requests.
      It will signal the task when systime is greater than or equal to a
      specified time value.  It has the same granularity as the VBLANK
      timer unit.

   *  The WAITECLOCK timer unit acts as an alarm clock for time requests.
      It will signal the task when the E-Clock value is greater than or
      equal to a specified time value.  It has the same granularity as the
      ECLOCK timer unit.

   Granularity vs. Accuracy.
   -------------------------
   Granularity is the sampling frequency used to check the timers.
   Accuracy is the precision of a measured time interval with respect to
   the same time interval in real-time.  We speak only of granularity
   because the sampling frequency directly affects how accurate the
   timers appear to be.


13 / Device Interface / Opening The Timer Device

Three primary steps are required to open the timer device:

   *  Create a message port using CreatePort(). Reply messages from the
      device must be directed to a message port.

   *  Create an I/O request structure of type timerequest using
      CreateExtIO().

   *  Open the timer device with one of the five timer device units. Call
      OpenDevice() passing a pointer to the  timerequest.

   struct MsgPort *TimerMP;   /* Message port pointer */
   struct timerequest *TimerIO;  /* I/O structure pointer */

     /* Create port for timer device communications */
   if (!(TimerMP = CreatePort(0,0)))
       cleanexit(" Error: Can't create port\n",RETURN_FAIL);

     /* Create message block for device IO */
   if (!(TimerIO = (struct timerequest *)
                   CreateExtIO(TimerMP)(sizeof timerequest)) )
       cleanexit(" Error: Can't create IO request\n",RETURN_FAIL);

     /* Open the timer device with UNIT_MICROHZ */
   if (error=OpenDevice(TIMERNAME,UNIT_MICROHZ,TimerIO,0))
       cleanexit(" Error: Can't open Timer.device\n",RETURN_FAIL);

The procedure for applications which only use the timer device functions
is slightly different:

   *  Declare the timer device base address variable TimerBase in the
      global data area.

   *  Allocate memory for a timerequest structure and a  timeval structure
      using AllocMem().

   *  Call OpenDevice(), passing the allocated timerequest structure.

   *  Set the timer device base address variable to point to the timer
      device base.

   struct Library *TimerBase;  /* global library pointer */

   struct timerequest *TimerIO;
   struct timeval *time1;

     /* Allocate memory for timerequest and timeval structures */
   TimerIO=(struct timerequest *)AllocMem(sizeof(struct timerequest),
                                  MEMF_PUBLIC | MEMF_CLEAR);
   time1=(struct timeval *)AllocMem(sizeof(struct timeval),
                                  MEMF_PUBLIC | MEMF_CLEAR);
   if (!TimerIO | !time1)
       cleanexit(" Error: Can't allocate memory for I/O structures\n",
                          RETURN_FAIL);

   if (error=OpenDevice(TIMERNAME,UNIT_MICROHZ,TimerIO,0))
       cleanexit(" Error: Can't open Timer.device\n",RETURN_FAIL);

     /* Set up pointer for timer functions */
   TimerBase = (struct Library *)TimerIO->tr_node.io_Device;


13 / Device Interface / Closing The Timer Device

Each OpenDevice() must eventually be matched by a call to CloseDevice().

All I/O requests must be complete before CloseDevice().  If any requests
are still pending, abort them with AbortIO().

   if (!(CheckIO(TimerIO)))
       {
       AbortIO(TimerIO);    /* Ask device to abort any pending requests */
       }
   WaitIO(TimerIO);         /* Clean up */
   CloseDevice((struct IORequest *)TimerIO);  /* Close Timer device */


13 Timer Device / System Time

The Amiga has a system time feature provided for the convenience of the
developer.  It is a monotonically increasing time base which should be the
same as real time. The timer device provides two commands to use with the
system time.  In addition,  there are utility functions in utility.library
which are very useful with system time.   See the "Utility Library"
chapter of the  Amiga ROM Kernel Reference Manual: Libraries for more
information.

The command TR_SETSYSTIME sets the system's idea of what time it is. The
system starts out at time "zero" so it is safe to set it forward to the
"real" time. However, care should be taken when setting the time
backwards.

The command TR_GETSYSTIME is used to get the system time. The timer device
does not interpret system time to any physical value. By convention, it
tells how many seconds have passed since midnight, January 1, 1978.  Your
program must calculate the time from this value.

The function GetSysTime() can also be used to get the system time. It
returns the same value as TR_GETSYSTIME, but uses less overhead.

Whenever someone asks what time it is using TR_GETSYSTIME, the return
value of the system time is guaranteed to be unique and unrepeating so
that it can be used by applications as a unique identifier.

   System time at boot time.
   -------------------------
   The timer device sets system time to zero at boot time. AmigaDOS will
   then reset the system time to the value specified on the boot disk.
   If the AmigaDOS C:SetClock command is given, this also resets system
   time.

Here is a program that can be used to determine the system time.
The command is executed by the timer device and, on return, the caller can
find the data in his request block.

     Get_Systime.c 


13 Timer Device / Adding a Time Request

Time delays and time alarms are done by opening the timer device with the
proper unit and submitting a timerequest to the device with TR_ADDREQUEST
set in io_Command and the appropriate values set in tv_secs and tv_micro.

Time delays are used with the UNIT_MICROHZ, UNIT_VBLANK, and
UNIT_ECLOCK units. The time specified in a time delay timerequest is a
relative measure from the time the request is posted.  This means that the
tv_secs and tv_micro fields should be set to the amount of delay required.

When the specified amount of time has elapsed, the driver will send the
timerequest back via .  You must fill in the ReplyPort pointer
of the timerequest structure if you wish to be signaled. Also, the number
of microseconds must be normalized; it should be a value less than one
million.

For a minute and a half delay, set 60 in tv_secs and 500,000 in tv_micro.

    TimerIO->tr_node.io_Command = TR_ADDREQUEST;
    TimerIO->tr_time.tv_secs  = 60;        /* Delay a minute */
    TimerIO->tr_time.tv_micro = 500000;    /* and a half     */
    DoIO(TimerIO);

Time alarms are used with the UNIT_WAITUNTIL and UNIT_WAITECLOCK units.
The tv_secs and tv_micro fields should be set to the absolute time value
of the alarm.  For an alarm at 10:30 tonight, the number of seconds from
midnight, January 1, 1978 till 10:30 tonight should be set in tv_secs.
The timer device will not return until the time is greater than or equal
to the absolute time value.

For our purposes, we will set an alarm for three hours from now by getting
the current system time and adding three hours of seconds to it.

   #define SECSPERHOUR (60*60)
   struct timeval *systime;

   GetSysTime(systime);   /* Get current system time */

   TimerIO->tr_node.io_Command = TR_ADDREQUEST;
   TimerIO->tr_time.tv_secs  = systime.tv_secs+(SECSPERHOUR*3);
   TimerIO->tr_time.tv_micro = systime.tv_micro;
   DoIO(TimerIO);

   Time requests with the E-Clock Units.
   -------------------------------------
   Time requests with the E-Clock units - UNIT_ECLOCK and
   UNIT_WAITECLOCK - work the same as the other units except that the
   values specified in their I/O requests are compared against the value
   of the E-Clock.  See "E-Clock Time and Its Relationship to Actual Time"
   below.

Remember, you must never reuse a timerequest until the timer device has
replied to it.  When you submit a timer request, the driver destroys the
values you have provided in the  timeval structure. This means that you
must reinitialize the time specification before reposting a timerequest.

Keep in mind that the timer device provides a general time-delay
capability. It can signal you when at least a certain amount of time has
passed. The timer device is very accurate under normal system loads, but
because the Amiga is a multitasking system, the timer device cannot
guarantee that exactly the specified amount of time has
elapsed - processing overhead increases as more tasks are run.
High-performance applications (such as MIDI time-stamping) may want to
take over the 16-bit counters of the CIA B timer resource instead of using
the timer device.

   Problems with small time requests in V1.3 and earlier versions.
   ---------------------------------------------------------------
   You must also take care  to avoid posting a timerequest of less than
   2 microseconds with the UNIT_MICROHZ timer device if you are using
   V1.3 or earlier versions of the system software.  In V1.3 and earlier
   versions of the Amiga system software, sending a timerequest for 0 or
   1 microseconds can cause a system crash.  Make sure all your timer
   requests are for 2 microseconds or more when you use the UNIT_MICROHZ
   timer with those versions.

 Multiple Timer Requests 


13 / Adding a Time Request / Multiple Timer Requests

Multiple requests may be posted to the timer driver. For example, you can
make three timer requests in a row:

    Signal me in 20 seconds (request 1)
    Signal me in 30 seconds (request 2)
    Signal me in 10 seconds (request 3)

As the timer queues these requests, it changes the time values and sorts
the timer requests to service each request at the desired interval,
resulting effectively in the following order:

    (request 3) in now+10 seconds
    (request 1) 10 seconds after request 3 is satisfied
    (request 2) 10 seconds after request 1 is satisfied

If you wish to send out multiple timer requests, you have to create
multiple request blocks. You can do this by allocating memory for each
timerequest you need and filling in the appropriate fields with command
data.  Some fields are initialized by the call to the OpenDevice()
function.  So, for convenience, you may allocate memory for the
timerequest you need, call OpenDevice() with one of them, and then copy
the initialized fields into all the other timerequest.

It is also permissible to open the timer device multiple times.  In some
cases this may be easier than opening it once and using multiple requests.
When multiple requests are given, SendIO() should be used to transmit each
one to the timer.

     Multiple_Timers.c 

If all goes according to plan, TimerIO[1] will finish first, TimerIO[2]
will finish next, and TimerIO[0] will finish last.


13 Timer Device / Using the Time Arithmetic Functions

As indicated above, the time arithmetic functions are accessed in the
timer device structure as if they were a routine library. To use them, you
create an IORequest block and open the timer.  In the IORequest block is a
pointer to the device's base address. This address is needed to access
each routine as an offset - for example, _LVOAddTime, _LVOSubTime,
_LVOCmpTime - from that base address.

     Timer_Arithmetic.c 

 Why Use Time Arithmetic? 


13 / Using the Time Arithmetic Functions / Why Use Time Arithmetic?

As mentioned earlier in this section, because of the multitasking
capability of the Amiga, the timer device can provide timings that are at
least as long as the specified amount of time. If you need more precision
than this, using the system timer along with the time arithmetic routines
can at least, in the long run, let you synchronize your software with this
precision timer after a selected period of time.

Say, for example, that you select timer intervals so that you get 161
signals within each 3-minute span. Therefore, the  timeval you would have
selected would be 180/161, which comes out to 1 second and 118,012
microseconds per interval.  Considering the time it takes to set up a call
to set the timer and delays due to task-switching (especially if the
system is very busy), it is possible that after 161 timing intervals, you
may be somewhat beyond the 3-minute time. Here is a method you can use to
keep in sync with system time:

   1. Begin.

   2. Read system time; save it.

   3. Perform your loop however many times in your selected interval.

   4. Read system time again, and compare it to the old value you saved.
      (For this example, it will be more or less than 3 minutes as a total
      time elapsed.)

   5. Calculate a new value for the time interval ( timeval); that is, one
      that (if precise) would put you exactly in sync with system time the
      next time around. Timeval will be a lower value if the loops took too
      long, and a higher value if the loops didn't take long enough.

   6. Repeat the cycle.

Over the long run, then, your average number of operations within a
specified period of time can become precisely what you have designed.

   You Can't Do 1+1 on E-Clock Values.
   -----------------------------------
   The arithmetic functions are not designed to operate on EClockVal
31}s.


13 Timer Device / E-Clock Time and Its Relationship to Actual Time

Unlike GetSysTime(), the two values returned by ReadEClock() - tics/sec
and the EClockVal structure - have no direct relationship to actual time.
The tics/sec is the E-Clock count rate, a value which is related to the
system master clock.  The EClockVal structure is simply the upper longword
and lower longword of the E-Clock 64 bit register.

However, when two EClockVal structures are subtracted from each other and
divided by the tics/sec (which remains constant), the result does have a
relationship to actual time.  The value of this calculation is a measure
of fractions of a second that passed between the two readings.

/* E-Clock Fractions of a second fragment
 *
 * This fragment reads the E-Clock twice and subtracts the two ev_lo values
 *         time2->ev_lo  - time1->ev_lo
 * and divides the result by the E-Clock tics/secs returned by ReadEClock()
 * to get the fractions of a second
 */

struct EClockVal *time1,*time2;
ULONG E_Freq;
LONG error;
struct timerequest *TimerIO;

TimerIO  = (struct timerequest *)AllocMem(sizeof(struct timerequest ),
            MEMF_CLEAR | MEMF_PUBLIC);

time1 = (struct EClockVal *)AllocMem(sizeof(struct EClockVal ),
         MEMF_CLEAR | MEMF_PUBLIC);

time2 = (struct EClockVal *)AllocMem(sizeof(struct EClockVal ),
         MEMF_CLEAR | MEMF_PUBLIC);

if (!(error = OpenDevice(TIMERNAME,UNIT_ECLOCK,
              (struct IORequest *)TimerIO,0L)) )
    {
    TimerBase = (struct Library *)TimerIO->tr_node.io_Device;
    E_Freq =  ReadEClock((struct EClockVal *) time1); /* Get initial */
                                                      /*   reading   */

       /*  place operation to be measured here */

    E_Freq =  ReadEClock((struct EClockVal *) time2); /* Get 2nd reading */
    printf("\nThe operation took: %f fractions of a second\n",
            (time2->ev_lo-time1->ev_lo)/(double)E_Freq);

    CloseDevice( (struct IORequest *) TimerIO );
    }

   The Code Takes Some Liberties.
   ------------------------------
   The above fragment only uses the lower longword of the EClockVal
   structures in calculating the fractions of a second that passed.
   This was done to simplify the fragment.  Naturally, you would have to
   at least check the values of the upper longwords if not use them to
   get an accurate measure.


13 Timer Device / Additional Information on the Timer Device

Additional programming information on the timer device and the utilities
library can be found in their include files and Autodocs.  All are
contained in the Amiga ROM Kernel Reference Manual: Includes and Autodocs.

                       Timer Device Information
                    -------------------------------
                    INCLUDES        devices/timer.h
                                    devices/timer.i
                                    utility/date.h
                                    utility/date.i

                    AUTODOCS        timer.doc
                                    utility.doc


Converted on 22 Apr 2000 with RexxDoesAmigaGuide2HTML 2.1 by Michael Ranner.