Copyright (c) Hyperion Entertainment and contributors.
Timer Device
Contents
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.
Timer Device Commands and Functions
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. |
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 { ULONG Seconds; ULONG 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
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.
- Create an I/O request structure of type TimeRequest using AllocSysObject(ASOT_IOREQUEST).
- 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 = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END))) cleanexit(" Error: Can't create port\n", RETURN_FAIL); /* Create message block for device IO */ TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST, ASOIOR_Size, sizeof(struct TimeRequest), ASOIOR_ReplyPort, TimerMP, TAG_END); if (TimerIO == NULL) cleanexit(" Error: Can't create IO request\n", RETURN_FAIL); /* 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);
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 */
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> void main() { 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() { uint32 error, x, seconds[3] = {4,1,2}, microseconds[3] = {0,0,0}; int32 allin = 3; char *position[] = {"last", "second", "first"}; struct MsgPort *TimerMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END); if (TimerMP != NULL) { struct TimeRequest *TimerIO[3]; TimerIO[0] = IExec->AllocSysObjectTags(ASOT_IOREQUEST, ASOIOR_Size, sizeof(struct TimeRequest), ASOIOR_ReplyPort, TimerMP, TAG_END); if (TimerIO[0] != NULL) { /* Open the device once */ if (!(error = IExec->OpenDevice( TIMERNAME, UNIT_VBLANK, (struct IORequest *) TimerIO[0], 0L))) { TimerIO[1] = IExec->AllocSysObjectTags(ASOT_IOREQUEST, ASOIOR_Duplicate, TimerIO[0], TAG_END); TimerIO[2] = IExec->AllocSysObjectTags(ASOT_IOREQUEST, ASOIOR_Duplicate, TimerIO[0], TAG_END); if (TimerIO[1] != NULL && TimerIO[2] != NULL) { /* Initialize other fields */ for (x = 0; x < 3; x++) { TimerIO[x]->tr_node.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 create 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:
- Begin.
- Read system time; save it.
- Perform your loop however many times in your selected interval.
- 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.)
- 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.
- 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() { int32 seconds; struct TimeRequest *tr; /* IO block for timer commands */ 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( ¤tval, UNIT_VBLANK ); IDOS->Printf( "After 2 seconds delay\n" ); /* sleep for four seconds */ currentval.tv_secs = 4; currentval.tv_micro = 0; time_delay( ¤tval, 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( ¤tval, 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( ULONG unit ) { /* return a pointer to a timer request. If any problem, return NULL */ int32 error; struct MsgPort *timerport; struct TimeRequest *TimerIO; 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 ) { struct TimeRequest *tr; /* get a pointer to an initialized timer request block */ 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; tr = create_timer( UNIT_MICROHZ ); /* non zero return says error */ if (tr == 0 ) return( -1 ); tr->Time.Seconds = secs; tr->Time.Microseconds = 0; tr->Request.io_Command = TR_SETSYSTIME; IExec->DoIO((struct IORequest *) tr ); delete_timer(tr); return(0); } int32 get_sys_time(struct TimeVal *tv) { struct TimeRequest *tr; tr = create_timer( UNIT_MICROHZ ); /* non zero return says error */ if (tr == 0 ) return( -1 ); tr->Request.io_Command = TR_GETSYSTIME; IExec->DoIO((struct IORequest *) tr ); /* structure assignment */ *tv = tr->Time; delete_timer( tr ); return( 0 ); } void delete_timer(struct TimeRequest *tr ) { struct MsgPort *tp; 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(ULONG 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 |