Copyright (c) Hyperion Entertainment and contributors.

Timer Device

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

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.

Compatibility Warning

AmigaOS code prior to version 50 of the operating system used struct timeval which conflicts with POSIX.

The define __USE_OLD_TIMEVAL__ in the header file devices/timer.h is provided to aid in migrating old AmigaOS code to the new struct TimeVal which should always be preferred.

Timer Device Commands and Functions

Device Commands
Command 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_READENTROPY Obtain entropy data. (V51)
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.
GetUpTime() Find out how much time has passed since the system started up.
MicroDelay() Wait for a very short time. (V51)
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.
SubTime() Subtract one TimeVal structure from another. The result is placed in the first TimeVal structure.

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 Exec Device I/O 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 Request;
    struct TimeVal 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
{
    uint32 Seconds;
    uint32 Microseconds;
};
 
struct EClockVal
{
    uint32 ev_hi;   /* Upper longword of E-Clock time */
    uint32 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

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
UNIT_ENTROPY Read Entropy Data (V51.10)
  • 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.
  • The ENTROPY timer unit does not support any timing. Instead, it supplies data gathered by the entropy collector.
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.

Opening the Timer Device

Three primary steps are required to open the timer device:

  • Create a message port using AllocSysObject(ASOT_PORT). Reply messages from the device must be directed to a message port.
	struct MsgPort *TimerMP;      // Message port pointer
	struct Timerequest *TimerIO;  // I/O structure pointer
 
	// Create port for timer device communications
	if (!(TimerMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END)))
		cleanexit(" Error: Can't create port\n", RETURN_FAIL);
  • Create an I/O request structure of type TimeRequest using AllocSysObject(ASOT_IOREQUEST).
	// Create message block for device IO
	TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
		ASOIOR_Size, sizeof(struct TimeRequest),
		ASOIOR_ReplyPort, TimerMP,
		TAG_END);
  • Open the timer device with one of the six timer device units (listed above). Call OpenDevice() passing a pointer to the TimeRequest.
	// Open the timer device with UNIT_MICROHZ
	if (error = IExec->OpenDevice(TIMERNAME, UNIT_MICROHZ, TimerIO, 0))
		cleanexit(" Error: Can't open Timer.device\n", RETURN_FAIL);

Naturally, each one of the above steps needs to include code to check the preceding step suceeded. Please see the complete example program below.

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

  • Declare the timer device interface variable ITimer in the global data area.
  • Allocate memory for a TimeRequest structure using AllocVecTags().
  • Call OpenDevice(), passing the allocated TimeRequest structure.
  • Obtain the timer device interface pointer with GetInterface() using the base library pointer stored in io_Device.
struct TimerIFace *ITimer   /* global interface pointer */
 
/* Allocate memory for TimeRequest and TimeVal structures */
struct TimeRequest *TimerIO = IExec->AllocVecTags(sizeof(struct TimeRequest),
    AVT_ClearWithValue, 0,
    TAG_END);
 
if (TimerIO == NULL)
    cleanexit(" Error: Can't allocate memory for I/O structures\n", RETURN_FAIL);
 
if (error = IExec->OpenDevice(TIMERNAME, UNIT_MICROHZ, TimerIO, 0))
    cleanexit(" Error: Can't open Timer.device\n", RETURN_FAIL);
 
/* Set up pointers for timer functions */
struct Library *TimerBase = (struct Library *)TimerIO->Request.io_Device;
ITimer = IExec->GetInterface(TimerBase, "main", 1, NULL);

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 (!(IExec->CheckIO(TimerIO)))
{
    IExec->AbortIO(TimerIO);      /* Ask device to abort any pending requests */
}
 
IExec->WaitIO(TimerIO);          /* Clean up */
 
IExec->CloseDevice((struct IORequest *)TimerIO);  /* Close Timer device */


Simple_Wait Device Example

The following is a simple CLI example program that waits for a 10 second request from the Timer Device. As you will see it opens a message port and the timer.device to accomplish this. After receiving the response message from the device, it closes and deallocates all resources used and quits.

/*	Simple_Wait.c
 *	
 *	This program simply creates a 10 second timer request,
 *	waits for it to return and quits.
 *
 *	Run from CLI only.
 *
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <devices/timer.h>
 
#include <proto/dos.h>
#include <proto/exec.h>
 
 
int main(void)
{
	uint32 error, seconds = 10, microseconds = 0;
 
	// create message port
	struct MsgPort *TimerMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
	if (TimerMP != NULL)
	{
		struct TimeRequest *TimerIO;
		struct Message *TimerMSG;
 
		// allocate IORequest struct for TimeRequest
		TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
			ASOIOR_Size, sizeof(struct TimeRequest),
			ASOIOR_ReplyPort, TimerMP,
			TAG_END);      
 
		// test if we got our IORequest
		if (TimerIO != NULL)
		{
			// Open the timer.device
			if (!(error = IExec->OpenDevice( TIMERNAME, UNIT_VBLANK,
				(struct IORequest *) TimerIO, 0L)))
			{
				// timer.dev opened - Initialize other IORequest fields
				IDOS->Printf("Initializing & sending IORequest...\n");
				TimerIO->Request.io_Command = TR_ADDREQUEST;
				TimerIO->Time.Seconds       = seconds;
				TimerIO->Time.Microseconds  = microseconds;
 
				// Send IORequest
				IExec->SendIO((struct IORequest *)TimerIO);
				IDOS->Printf("Timer.device IORequest sent.\n");
 
				// There might be other processing done here
 
				// Now go to sleep with WaitPort() waiting for the request to return
				IDOS->Printf("Now to wait for the rest of %ld secs...\n",seconds);
				IExec->WaitPort(TimerMP);
 
				// Get the reply message
				TimerMSG = IExec->GetMsg(TimerMP);
 
				if (TimerMSG == (struct Message *)TimerIO)
				IDOS->Printf("Request returned.  Closing device and quitting...\n");
 
				// close timer.device
				IExec->CloseDevice((struct IORequest *) TimerIO);
			}
			else
				IDOS->Printf("\nError: Could not OpenDevice.\n");
 
			// dispose of IORequest
			IExec->FreeSysObject(ASOT_IOREQUEST, TimerIO);
		}
		else
			IDOS->Printf("Error: could not allocate IORequest.\n");
 
		// Close and dispose of message port.
		IExec->FreeSysObject(ASOT_PORT, TimerMP);
	}
	else
		IDOS->Printf("\nError: Could not create message port.\n");
 
	return 0;
}

Usage From Interrupts

The Timer Device allows software to invoke commands from both software and hardware interrupts. This is a special privilege that very few devices support. It is always best to invoke I/O from the main Task or Process and avoid doing too much work within an interrupt.

An example of using a timer from within a software interrupt can be found in Exec Software Interrupts.

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 Utility Library 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
 *
 * Get system time example
 *
 * Run from CLI only
 */
 
#include <exec/types.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <devices/timer.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
 
int main(void)
{
  int32 error;
  uint32 days,hrs,secs,mins,mics;
 
  struct MsgPort *TimerMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
  if (TimerMP != NULL)
  {
    struct TimeRequest *TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
      ASOIOR_Size, sizeof(struct TimeRequest),
      ASOIOR_ReplyPort, TimerMP,
      TAG_END);
 
    if (TimerIO != NULL)
    {
        /* Open with UNIT_VBLANK, but any unit can be used */
        if (!(error = IExec->OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)TimerIO,0L)))
        {
            /* Issue the command and wait for it to finish, then get the reply */
            TimerIO->tr_node.io_Command = TR_GETSYSTIME;
            IExec->DoIO((struct IORequest *) TimerIO);
 
            /* Get the results and close the timer device */
            mics = TimerIO->tr_time.tv_micro;
            secs = TimerIO->tr_time.tv_secs;
 
            /* Compute days, hours, etc. */
            mins = secs / 60;
            hrs  = mins / 60;
            days = hrs / 24;
            secs = secs % 60;
            mins = mins % 60;
            hrs  = hrs % 24;
 
            /* Display the time */
            IDOS->Printf("\nSystem Time (measured from Jan.1,1978)\n");
            IDOS->Printf("  Days   Hours  Minutes Seconds Microseconds\n");
            IDOS->Printf("%6ld %6ld %6ld %6ld %10ld\n", days, hrs, mins, secs, mics);
 
            /* Close the timer device */
            IExec->CloseDevice((struct IORequest *) TimerIO);
        }
        else
            IDOS->Printf("\nError: Could not open timer device\n");
 
        /* Delete the IORequest structure */
        IExec->FreeSysObject(ASOT_IOREQUEST, TimerIO);
    }
    else
        IDOS->Printf("\nError: Could not create I/O structure\n");
 
    /* Delete the port */
    IExec->FreeSysObject(ASOT_PORT, TimerMP);
  }
  else
    IDOS->Printf("\nError: Could not create port\n");
 
  return 0;
}

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 Seconds and Microseconds.

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 Seconds and Microseconds 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 ReplyMsg(). 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 Seconds and 500,000 in Microseconds.

TimerIO->Request.io_Command = TR_ADDREQUEST;
TimerIO->Time.Seconds       = 60;      /* Delay a minute */
TimerIO->Time.Microseconds  = 500000;  /* and a half     */
IExec->DoIO(TimerIO);

Time alarms are used with the UNIT_WAITUNTIL and UNIT_WAITECLOCK units. The Seconds and Microseconds 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 Seconds. 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;
 
ITimer->GetSysTime(systime);   /* Get current system time */
 
TimerIO->Request.io_Command = TR_ADDREQUEST;
TimerIO->Time.Seconds       = systime.Seconds + (SECSPERHOUR*3); /* Alarm in 3 hours */
TimerIO->Time.Microseconds  = systime.Microseconds;
IExec->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 the section “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.

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 TimeRequests you need, call OpenDevice() with one of them, and then copy the initialized fields into all the other TimeRequests.

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
 *
 *  This program is designed to do multiple (3) time requests using one
 *  OpenDevice.  It creates a message port - TimerMP, creates an
 *  extended I/O structure of type TimeRequest named TimerIO[0] and
 *  then uses that to open the device.  The other two time request
 *  structures - TimerIO[1] and TimerIO[2] - are created using AllocSysObject
 *  and duplicating them from TimerIO[0].  The Seconds field of each
 *  structure is set and then three SendIOs are done with the requests.
 *  The program then goes into a while loop until all messages are received.
 *
 * Run from CLI only
 */
 
#include <exec/types.h>
#include <exec/memory.h>
#include <devices/timer.h>
 
#include <proto/dos.h>
#include <proto/exec.h>
 
int main(void)
{
	uint32 error, x, seconds[3] = {4,1,2}, microseconds[3] = {0,0,0};
	int32 allin = 3;
 
	const char *position[] = {"last", "second", "first"};
 
	struct MsgPort *TimerMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
	if (TimerMP != NULL)
	{
		struct TimeRequest *TimerIO[3];
		struct Message *TimerMSG;
 
		// allocate IORequest struct for TimeRequest
		TimerIO[0] = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
			ASOIOR_Size, sizeof(struct TimeRequest),
			ASOIOR_ReplyPort, TimerMP,
			TAG_END);      
 
		// test if we got our IORequest
		if (TimerIO[0] != NULL)
		{
			// Open the timer.device (once!)
			if (!(error = IExec->OpenDevice( TIMERNAME, UNIT_VBLANK,
				(struct IORequest *) TimerIO[0], 0L)))
			{
				// timer.dev opened, allocate additional timer IORequests
				TimerIO[1] = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
					ASOIOR_Duplicate, TimerIO[0],
					TAG_END);      
 
				TimerIO[2] = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
					ASOIOR_Duplicate, TimerIO[0],
					TAG_END);      
 
				// test if we got the 2nd & 3rd IORequests
				if (TimerIO[1] != NULL && TimerIO[2] != NULL)
				{
					// Initialize other IORequest fields
					for (x = 0; x < 3; x++)
					{
						TimerIO[x]->Request.io_Command = TR_ADDREQUEST;
						TimerIO[x]->Time.Seconds       = seconds[x];
						TimerIO[x]->Time.Microseconds  = microseconds[x];
						IDOS->Printf("\nInitializing TimerIO[%d]",x);
					}
 
					IDOS->Printf("\n\nSending multiple requests\n\n");
 
					/* Send multiple requests asynchronously */
					/* Do not got to sleep yet...            */
					IExec->SendIO((struct IORequest *)TimerIO[0]);
					IExec->SendIO((struct IORequest *)TimerIO[1]);
					IExec->SendIO((struct IORequest *)TimerIO[2]);
 
					/* There might be other processing done here */
 
					/* Now go to sleep with WaitPort() waiting for the requests */
					while (allin)
					{
						IExec->WaitPort(TimerMP);
						/* Get the reply message */
						TimerMSG = IExec->GetMsg(TimerMP);
						for (x = 0; x < 3; x++)
						{
							if (TimerMSG == (struct Message *)TimerIO[x])
							IDOS->Printf("Request %ld finished %s\n", x, position[--allin]);
						}
					}
 
					IExec->FreeSysObject(ASOT_IOREQUEST, TimerIO[2]);
					IExec->FreeSysObject(ASOT_IOREQUEST, TimerIO[1]);
				}
				else
				IDOS->Printf("Error: could not allocate TimerIO[1] & TimerIO[2]\n");
 
				IExec->CloseDevice((struct IORequest *) TimerIO[0]);
			}
			else
				IDOS->Printf("\nError: Could not OpenDevice\n");
 
			IExec->FreeSysObject(ASOT_IOREQUEST, TimerIO[0]);
		}
		else
			IDOS->Printf("Error: could not allocate IORequest\n");
 
		IExec->FreeSysObject(ASOT_PORT, TimerMP);
	}
	else
		IDOS->Printf("\nError: Could not CreatePort\n");
 
	return 0;
}

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

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 the Library base which can then be used to obtain the interface pointer.

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

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 TimeRequest* TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
  ASOIOR_Size, sizeof(struct TimeRequest),
  ASOIOR_ReplyPort, port,
  TAG_END);
 
struct EClockVal *time1 = (struct EClockVal *)AllocVecTags(sizeof(struct EClockVal ),
  AVT_ClearWithValue, 0,
  TAG_END);
 
struct EClockVal *time2 = (struct EClockVal *)AllocVecTags(sizeof(struct EClockVal ),
  AVT_ClearWithValue, 0,
  TAG_END);
 
int32 error;
if (!(error = IExec->OpenDevice(TIMERNAME,UNIT_ECLOCK,(struct IORequest *)TimerIO,0L)) )
{
  TimerBase = (struct Library *)TimerIO->Request.io_Device;
  ITimer = IExec->GetInterface(TimerBase, "main", 1, NULL);
 
  uint32 E_Freq;
  E_Freq = ITimer->ReadEClock((struct EClockVal *) time1);   /* Get initial reading */
 
    /*  place operation to be measured here */
 
  E_Freq =  ITimer->ReadEClock((struct EClockVal *) time2);   /* Get second reading */
 
  IDOS->Printf("\nThe operation took: %f fractions of a second\n",
            (time2->ev_lo-time1->ev_lo)/(double)E_Freq);
 
  IExec->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.

Example Timer Program

Here is an example program showing how to use the Timer Device.

/* Simple_Timer.c
 *
 * A simple example of using the Timer Device.
 *
 * Run from CLI only
 */
 
#include <exec/types.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <devices/timer.h>
 
#include <proto/exec.h>
#include <proto/dos.h>
 
/* Our timer sub-routines */
void delete_timer  (struct TimeRequest *);
int32 get_sys_time  (struct TimeVal *);
int32 set_new_time  (int32);
void wait_for_timer(struct TimeRequest *, struct TimeVal *);
int32 time_delay    ( struct TimeVal *, int32 );
struct timerequest *create_timer( uint32 );
void show_time     (uint32);
 
/* manifest constants -- "will never change" */
#define   SECSPERMIN   (60)
#define   SECSPERHOUR  (60*60)
#define   SECSPERDAY   (60*60*24)
 
 
int main(void)
{
  int32 seconds;
  struct TimeVal oldtimeval;          /* timevals to store times     */
  struct TimeVal mytimeval;
  struct TimeVal currentval;
 
  IDOS->Printf("\nTimer test\n");
 
  /* sleep for two seconds */
  currentval.tv_secs = 2;
  currentval.tv_micro = 0;
  time_delay( &currentval, UNIT_VBLANK );
  IDOS->Printf( "After 2 seconds delay\n" );
 
  /* sleep for four seconds */
  currentval.tv_secs = 4;
  currentval.tv_micro = 0;
  time_delay( &currentval, UNIT_VBLANK );
  IDOS->Printf( "After 4 seconds delay\n" );
 
  /* sleep for 500,000 micro-seconds = 1/2 second */
  currentval.tv_secs = 0;
  currentval.tv_micro = 500000;
  time_delay( &currentval, UNIT_MICROHZ );
  IDOS->Printf( "After 1/2 second delay\n" );
 
  IDOS->Printf( "DOS Date command shows: " );
  (void) IDOS->Execute( "date", 0, 0 );
 
  /* save what system thinks is the time... we'll advance it temporarily */
  get_sys_time( &oldtimeval );
  IDOS->Printf("Original system time is:\n");
  show_time(oldtimeval.tv_secs );
 
  IDOS->Printf("Setting a new system time\n");
 
  seconds = 1000 * SECSPERDAY + oldtimeval.tv_secs;
 
  set_new_time( seconds );
 
  /* (if user executes the AmigaDOS DATE command now, he will*/
  /* see that the time has advanced something over 1000 days */
  IDOS->Printf( "DOS Date command now shows: " );
  (void) IDOS->Execute( "date", 0, 0 );
 
  get_sys_time( &mytimeval );
  IDOS->Printf( "Current system time is:\n");
  show_time(mytimeval.tv_secs);
 
  /* Added the microseconds part to show that time keeps */
  /* increasing even though you ask many times in a row  */
  IDOS->Printf("Now do three TR_GETSYSTIMEs in a row (notice how the microseconds increase)\n\n");
  get_sys_time( &mytimeval );
  IDOS->Printf("First TR_GETSYSTIME \t%ld.%ld\n",mytimeval.tv_secs, mytimeval.tv_micro);
  get_sys_time( &mytimeval );
  IDOS->Printf("Second TR_GETSYSTIME \t%ld.%ld\n",mytimeval.tv_secs, mytimeval.tv_micro);
  get_sys_time( &mytimeval );
  IDOS->Printf("Third TR_GETSYSTIME \t%ld.%ld\n",mytimeval.tv_secs, mytimeval.tv_micro);
 
  IDOS->Printf( "\nResetting to former time\n" );
  set_new_time( oldtimeval.tv_secs );
 
  get_sys_time( &mytimeval );
  IDOS->Printf( "Current system time is:\n");
  show_time(mytimeval.tv_secs);
 
  return 0;
}
 
 
 
struct TimeRequest *create_timer(uint32 unit)
{
  /* return a pointer to a timer request.  If any problem, return NULL */
  int32 error;
  struct MsgPort *timerport = NULL;
  struct TimeRequest *TimerIO = NULL;
 
  timerport = IExec->AllocSysObjectTags(ASOT_PORT, 0);
  if (timerport == NULL )
    return NULL;
 
  TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
    ASOIOR_Size, sizeof(struct TimeRequest),
    ASOIOR_ReplyPort, timerport,
    TAG_END);
 
  if (TimerIO == NULL )
  {
    IExec->FreeSysObject(ASOT_PORT, timerport);   /* Delete message port */
    return NULL;
  }
 
  error = IExec->OpenDevice( TIMERNAME, unit,(struct IORequest *) TimerIO, 0L );
  if (error != 0 )
  {
    delete_timer( TimerIO );
    return NULL;
  }
 
  return TimerIO;
}
 
 
 
/* more precise timer than AmigaDOS Delay() */
int32 time_delay(struct TimeVal *tv, int32 unit)
{
  /* get a pointer to an initialized timer request block */
  struct TimeRequest *tr = create_timer( unit );
 
  /* any nonzero return says timedelay routine didn't work. */
  if (tr == NULL )
    return -1L;
 
  wait_for_timer( tr, tv );
 
  /* deallocate temporary structures */
  delete_timer( tr );
 
  return 0L;
}
 
 
 
void wait_for_timer(struct TimeRequest *tr, struct TimeVal *tv)
{
  tr->Request.io_Command = TR_ADDREQUEST; /* add a new timer request */
 
  /* structure assignment */
  tr->Time = *tv;
 
  /* post request to the timer -- will go to sleep till done */
  IExec->DoIO((struct IORequest *) tr );
}
 
 
 
int32 set_new_time(int32 secs)
{
  struct TimeRequest *tr = create_timer( UNIT_MICROHZ );
 
  /* non zero return says error */
  if (tr == 0 )
    return -1L;
 
  tr->Time.Seconds = secs;
  tr->Time.Microseconds = 0;
  tr->Request.io_Command = TR_SETSYSTIME;
  IExec->DoIO((struct IORequest *) tr );
 
  delete_timer(tr);
 
  return 0L;
}
 
 
 
int32 get_sys_time(struct TimeVal *tv)
{
  struct TimeRequest *tr = create_timer( UNIT_MICROHZ );
 
  /* non zero return says error */
  if (tr == 0 )
    return -1L;
 
  tr->Request.io_Command = TR_GETSYSTIME;
  IExec->DoIO((struct IORequest *) tr );
 
  /* structure assignment */
  *tv = tr->Time;
 
  delete_timer( tr );
 
  return 0L;
}
 
 
 
void delete_timer(struct TimeRequest *tr )
{
  struct MsgPort *tp = NULL;
 
  if (tr != 0 )
  {
    tp = tr->Request.io_Message.mn_ReplyPort;
 
    IExec->FreeSysObject(ASOT_PORT, tp);
 
    IExec->CloseDevice( (struct IORequest *) tr );
    IExec->FreeSysObject(ASOT_IOREQUEST, tr);
  }
}
 
 
 
void show_time(uint32 secs)
{
  uint32 days, hrs, mins;
 
  /* Compute days, hours, etc. */
  mins = secs / 60;
  hrs  = mins / 60;
  days = hrs / 24;
  secs = secs % 60;
  mins = mins % 60;
  hrs  = hrs % 24;
 
  /* Display the time */
  IDOS->Printf("*   Hour Minute Second  (Days since Jan.1,1978)\n");
  IDOS->Printf("*%5ld:%5ld:%5ld      (%6ld )\n\n",hrs,mins,secs,days);
}

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 Autodocs section.

Timer Device Information
Includes devices/timer.h
utility/date.h
AutoDocs timer.doc
utility.doc