Copyright (c) Hyperion Entertainment and contributors.
Difference between revisions of "Timer Device"
Steven Solie (talk | contribs) |
Steven Solie (talk | contribs) |
||
(57 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
− | [[Category:Devices|Timer]] |
+ | [[Category:Devices|Timer]] |
== Timer Device == |
== 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. |
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 [http://en.wikipedia.org/wiki/POSIX 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 == |
== Timer Device Commands and Functions == |
||
{| class="wikitable" |
{| class="wikitable" |
||
+ | |+Device Commands |
||
! Command |
! Command |
||
! Command Operation |
! Command Operation |
||
Line 12: | Line 19: | ||
| TR_ADDREQUEST || Request that the timer device wait a specified period of time before replying to the request. |
| 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 |
+ | | 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. |
||
+ | |- |
||
+ | | TR_SETSYSTIME || Set the system time from the value in a TimeVal structure. |
||
|} |
|} |
||
− | |||
− | Device Functions: |
||
{| class="wikitable" |
{| class="wikitable" |
||
+ | |+Device Functions |
||
− | | AddTime() || Add one timeval structure to another. The result is placed in the first timeval structure. |
||
+ | | AddTime() || Add one TimeVal structure to another. The result is placed in the first TimeVal structure. |
||
|- |
|- |
||
− | | CmpTime() || Compare one |
+ | | CmpTime() || Compare one TimeVal structure to another. The result is returned as a longword. |
|- |
|- |
||
− | | GetSysTime() || Get system time and place in a |
+ | | 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. |
| 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 |
+ | | SubTime() || Subtract one TimeVal structure from another. The result is placed in the first TimeVal structure. |
|} |
|} |
||
Line 41: | Line 53: | ||
The I/O request used by the timer device is called timerequest. |
The I/O request used by the timer device is called timerequest. |
||
+ | <syntaxhighlight> |
||
− | <pre> |
||
− | struct |
+ | struct TimeRequest |
{ |
{ |
||
− | struct IORequest |
+ | struct IORequest Request; |
− | struct |
+ | struct TimeVal Time; |
}; |
}; |
||
+ | </syntaxhighlight> |
||
− | </pre> |
||
The timer device functions are passed a time structure, either timeval for non E-Clock units or EClockVal for E-Clock units. |
The timer device functions are passed a time structure, either timeval for non E-Clock units or EClockVal for E-Clock units. |
||
+ | <syntaxhighlight> |
||
− | <pre> |
||
− | struct |
+ | struct TimeVal |
{ |
{ |
||
+ | uint32 Seconds; |
||
− | ULONG tv_secs; /* seconds */ |
||
+ | uint32 Microseconds; |
||
− | ULONG tv_micro; /* microseconds */ |
||
}; |
}; |
||
struct EClockVal |
struct EClockVal |
||
{ |
{ |
||
− | + | uint32 ev_hi; /* Upper longword of E-Clock time */ |
|
− | + | uint32 ev_lo; /* Lower longword of E-Clock time */ |
|
}; |
}; |
||
+ | </syntaxhighlight> |
||
− | </pre> |
||
See the include file devices/timer.h for the complete structure definitions. |
See the include file devices/timer.h for the complete structure definitions. |
||
Line 75: | Line 87: | ||
* 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. |
* 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. |
||
+ | {{Note|title=What is an E-Clock?|text=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.}} |
||
− | {| class="wikitable" |
||
− | | ''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 === |
=== Timer Device Units === |
||
− | There are |
+ | There are several units in the timer device. |
'''Timer Device Units''' |
'''Timer Device Units''' |
||
Line 98: | Line 108: | ||
|- |
|- |
||
| UNIT_WAITECLOCK || 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.'' |
||
− | <ul> |
||
− | + | * 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. |
||
− | <p></p></li> |
||
+ | * 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. |
||
− | <li><p>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.</p> |
||
+ | * 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. |
||
− | <p></p></li> |
||
+ | * The ENTROPY timer unit does not support any timing. Instead, it supplies data gathered by the entropy collector. |
||
− | <li><p>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.</p> |
||
− | <p></p></li> |
||
− | <li><p>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.</p> |
||
− | <p></p></li> |
||
− | <li><p>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.</p></li></ul> |
||
+ | {{Note|title=Granularity vs. Accuracy|text=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.}} |
||
− | {| class="wikitable" |
||
− | | ''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 === |
=== Opening the Timer Device === |
||
Line 120: | Line 126: | ||
* Create a message port using AllocSysObject(ASOT_PORT). Reply messages from the device must be directed to a message port. |
* 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. |
||
− | |||
<syntaxhighlight> |
<syntaxhighlight> |
||
− | struct MsgPort *TimerMP; / |
+ | struct MsgPort *TimerMP; // Message port pointer |
− | struct |
+ | struct Timerequest *TimerIO; // I/O structure pointer |
− | / |
+ | // Create port for timer device communications |
if (!(TimerMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END))) |
if (!(TimerMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END))) |
||
cleanexit(" Error: Can't create port\n", RETURN_FAIL); |
cleanexit(" Error: Can't create port\n", RETURN_FAIL); |
||
+ | </syntaxhighlight> |
||
+ | * Create an I/O request structure of type TimeRequest using AllocSysObject(ASOT_IOREQUEST). |
||
− | /* Create message block for device IO */ |
||
+ | <syntaxhighlight> |
||
+ | // Create message block for device IO |
||
TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST, |
TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST, |
||
− | ASOIOR_Size, sizeof(struct TimeRequest), |
+ | ASOIOR_Size, sizeof(struct TimeRequest), |
− | ASOIOR_ReplyPort, TimerMP, |
+ | ASOIOR_ReplyPort, TimerMP, |
− | TAG_END); |
+ | TAG_END); |
+ | </syntaxhighlight> |
||
− | if (TimerIO == NULL) |
||
− | cleanexit(" Error: Can't create IO request\n", RETURN_FAIL); |
||
− | + | * Open the timer device with one of the timer device units (listed above). Call OpenDevice() passing a pointer to the TimeRequest. |
|
+ | |||
+ | <syntaxhighlight> |
||
+ | // Open the timer device with UNIT_MICROHZ |
||
if (error = IExec->OpenDevice(TIMERNAME, UNIT_MICROHZ, TimerIO, 0)) |
if (error = IExec->OpenDevice(TIMERNAME, UNIT_MICROHZ, TimerIO, 0)) |
||
cleanexit(" Error: Can't open Timer.device\n", RETURN_FAIL); |
cleanexit(" Error: Can't open Timer.device\n", RETURN_FAIL); |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
+ | |||
+ | 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: |
The procedure for applications which only use the timer device functions is slightly different: |
||
− | * Declare the timer device |
+ | * Declare the timer device interface variable ITimer in the global data area. |
− | * Allocate memory for a TimeRequest |
+ | * Allocate memory for a TimeRequest structure using AllocVecTags(). |
* Call OpenDevice(), passing the allocated TimeRequest structure. |
* Call OpenDevice(), passing the allocated TimeRequest structure. |
||
− | * |
+ | * Obtain the timer device interface pointer with GetInterface() using the base library pointer stored in io_Device. |
+ | <syntaxhighlight> |
||
− | <pre> |
||
− | struct |
+ | struct TimerIFace *ITimer /* global interface pointer */ |
+ | /* Allocate memory for TimeRequest and TimeVal structures */ |
||
− | struct timerequest *TimerIO; |
||
+ | struct TimeRequest *TimerIO = IExec->AllocVecTags(sizeof(struct TimeRequest), |
||
− | struct timeval *time1; |
||
+ | AVT_ClearWithValue, 0, |
||
+ | TAG_END); |
||
+ | if (TimerIO == NULL) |
||
− | /* Allocate memory for timerequest and timeval structures */ |
||
+ | cleanexit(" Error: Can't allocate memory for I/O structures\n", RETURN_FAIL); |
||
− | 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)) |
+ | if (error = IExec->OpenDevice(TIMERNAME, UNIT_MICROHZ, TimerIO, 0)) |
− | cleanexit( |
+ | cleanexit(" Error: Can't open Timer.device\n", RETURN_FAIL); |
− | + | /* Set up pointers for timer functions */ |
|
− | TimerBase = (struct Library *)TimerIO- |
+ | struct Library *TimerBase = (struct Library *)TimerIO->Request.io_Device; |
+ | ITimer = IExec->GetInterface(TimerBase, "main", 1, NULL); |
||
− | </pre> |
||
+ | </syntaxhighlight> |
||
=== Closing the Timer Device === |
=== Closing the Timer Device === |
||
Line 178: | Line 187: | ||
All I/O requests must be complete before CloseDevice(). If any requests are still pending, abort them with AbortIO(). |
All I/O requests must be complete before CloseDevice(). If any requests are still pending, abort them with AbortIO(). |
||
+ | <syntaxhighlight> |
||
− | <pre>if (!(CheckIO(TimerIO))) |
||
+ | if (!(IExec->CheckIO(TimerIO))) |
||
− | { |
||
+ | { |
||
− | AbortIO(TimerIO); /* Ask device to abort any pending requests */ |
||
+ | IExec->AbortIO(TimerIO); /* Ask device to abort any pending requests */ |
||
− | } |
||
+ | } |
||
− | WaitIO(TimerIO); /* Clean up */ |
||
+ | |||
− | CloseDevice((struct IORequest *)TimerIO); /* Close Timer device */</pre> |
||
+ | IExec->WaitIO(TimerIO); /* Clean up */ |
||
+ | |||
+ | IExec->CloseDevice((struct IORequest *)TimerIO); /* Close Timer device */ |
||
+ | </syntaxhighlight> |
||
+ | |||
+ | === 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. |
||
+ | |||
+ | <syntaxhighlight> |
||
+ | /* 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; |
||
+ | } |
||
+ | </syntaxhighlight> |
||
+ | |||
+ | === 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_Interrupts#Software_Interrupts|Exec Software Interrupts]]. |
||
== System Time == |
== System Time == |
||
Line 197: | Line 306: | ||
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. |
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. |
||
+ | {{Note|title=System time at boot time|text=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.}} |
||
− | {| class="wikitable" |
||
− | | ''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. |
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. |
||
+ | <syntaxhighlight> |
||
− | <pre> |
||
/* Get_Systime.c |
/* Get_Systime.c |
||
* |
* |
||
* Get system time example |
* Get system time example |
||
− | * |
||
− | * Compile with SAS C 5.10: LC -b1 -cfistq -v -y -L |
||
* |
* |
||
* Run from CLI only |
* Run from CLI only |
||
*/ |
*/ |
||
− | #include |
+ | #include <exec/types.h> |
− | #include |
+ | #include <exec/io.h> |
− | #include |
+ | #include <exec/memory.h> |
− | #include |
+ | #include <devices/timer.h> |
− | #include |
+ | #include <proto/exec.h> |
− | #include |
+ | #include <proto/dos.h> |
− | #include <clib/dos_protos.h> |
||
− | #include <clib/intuition_protos.h> |
||
− | #include <stdio.h> |
||
+ | int main(void) |
||
− | #ifdef LATTICE |
||
+ | { |
||
− | int CXBRK(void) { return(0); } /* Disable SAS CTRL/C handling */ |
||
+ | int32 error; |
||
− | int chkabort(void) { return(0); } /* really */ |
||
+ | uint32 days,hrs,secs,mins,mics; |
||
− | #endif |
||
+ | struct MsgPort *TimerMP = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END); |
||
− | struct timerequest *TimerIO; |
||
− | struct MsgPort *TimerMP; |
||
− | struct Message *TimerMSG; |
||
− | |||
− | VOID main(VOID); |
||
− | |||
− | void main() |
||
− | { |
||
− | LONG error; |
||
− | ULONG days,hrs,secs,mins,mics; |
||
− | if (TimerMP = |
+ | 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 (TimerIO = (struct timerequest *) |
||
− | + | if (!(error = IExec->OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)TimerIO,0L))) |
|
{ |
{ |
||
− | /* Open with UNIT_VBLANK, but any unit can be used */ |
||
− | if (!(error=OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)TimerIO,0L))) |
||
− | { |
||
/* Issue the command and wait for it to finish, then get the reply */ |
/* Issue the command and wait for it to finish, then get the reply */ |
||
− | TimerIO- |
+ | TimerIO->tr_node.io_Command = TR_GETSYSTIME; |
− | DoIO((struct IORequest *) TimerIO); |
+ | IExec->DoIO((struct IORequest *) TimerIO); |
/* Get the results and close the timer device */ |
/* Get the results and close the timer device */ |
||
− | mics=TimerIO- |
+ | mics = TimerIO->tr_time.tv_micro; |
− | secs=TimerIO- |
+ | secs = TimerIO->tr_time.tv_secs; |
/* Compute days, hours, etc. */ |
/* Compute days, hours, etc. */ |
||
− | mins=secs/60; |
+ | mins = secs / 60; |
− | hrs=mins/60; |
+ | hrs = mins / 60; |
− | days=hrs/24; |
+ | days = hrs / 24; |
− | secs=secs%60; |
+ | secs = secs % 60; |
− | mins=mins%60; |
+ | mins = mins % 60; |
− | hrs=hrs%24; |
+ | hrs = hrs % 24; |
/* Display the time */ |
/* 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 */ |
/* Close the timer device */ |
||
− | CloseDevice((struct IORequest *) TimerIO); |
+ | IExec->CloseDevice((struct IORequest *) TimerIO); |
− | + | } |
|
else |
else |
||
− | + | IDOS->Printf("\nError: Could not open timer device\n"); |
|
/* Delete the IORequest structure */ |
/* Delete the IORequest structure */ |
||
− | + | IExec->FreeSysObject(ASOT_IOREQUEST, TimerIO); |
|
− | + | } |
|
else |
else |
||
− | + | IDOS->Printf("\nError: Could not create I/O structure\n"); |
|
/* Delete the port */ |
/* Delete the port */ |
||
− | + | IExec->FreeSysObject(ASOT_PORT, TimerMP); |
|
− | + | } |
|
− | else |
+ | else |
− | + | IDOS->Printf("\nError: Could not create port\n"); |
|
+ | |||
+ | return 0; |
||
} |
} |
||
+ | </syntaxhighlight> |
||
− | </pre> |
||
== Adding a Time Request == |
== Adding a Time Request == |
||
− | Time delays and time alarms are done by opening the timer device with the proper unit and submitting a |
+ | 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 |
+ | 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 |
+ | 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 |
+ | For a minute and a half delay, set 60 in Seconds and 500,000 in Microseconds. |
+ | <syntaxhighlight> |
||
− | <pre> |
||
− | TimerIO- |
+ | TimerIO->Request.io_Command = TR_ADDREQUEST; |
− | TimerIO- |
+ | TimerIO->Time.Seconds = 60; /* Delay a minute */ |
− | TimerIO- |
+ | TimerIO->Time.Microseconds = 500000; /* and a half */ |
− | DoIO(TimerIO); |
+ | IExec->DoIO(TimerIO); |
+ | </syntaxhighlight> |
||
− | </pre> |
||
− | Time alarms are used with the UNIT_WAITUNTIL and UNIT_WAITECLOCK units. The |
+ | 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. |
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. |
||
+ | <syntaxhighlight> |
||
− | <pre> |
||
#define SECSPERHOUR (60*60) |
#define SECSPERHOUR (60*60) |
||
− | struct |
+ | struct TimeVal *systime; |
− | GetSysTime(systime); /* Get current system time */ |
+ | ITimer->GetSysTime(systime); /* Get current system time */ |
− | TimerIO- |
+ | TimerIO->Request.io_Command = TR_ADDREQUEST; |
− | TimerIO- |
+ | TimerIO->Time.Seconds = systime.Seconds + (SECSPERHOUR*3); /* Alarm in 3 hours */ |
− | TimerIO- |
+ | TimerIO->Time.Microseconds = systime.Microseconds; |
− | DoIO(TimerIO); |
+ | IExec->DoIO(TimerIO); |
+ | </syntaxhighlight> |
||
− | </pre> |
||
+ | {{Note|title=Time requests with the E-Clock Units|text=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.}} |
||
− | {| class="wikitable" |
||
− | | ''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 |
+ | 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. |
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. |
||
Line 353: | Line 450: | ||
|} |
|} |
||
− | 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 |
+ | 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. |
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. |
||
+ | <syntaxhighlight> |
||
− | <pre> |
||
/* Multiple_Timers.c |
/* Multiple_Timers.c |
||
* |
* |
||
* This program is designed to do multiple (3) time requests using one |
* This program is designed to do multiple (3) time requests using one |
||
* OpenDevice. It creates a message port - TimerMP, creates an |
* OpenDevice. It creates a message port - TimerMP, creates an |
||
− | * extended I/O structure of type |
+ | * extended I/O structure of type TimeRequest named TimerIO[0] and |
* then uses that to open the device. The other two time request |
* then uses that to open the device. The other two time request |
||
− | * structures - TimerIO[1] and TimerIO[2] - are created using |
+ | * structures - TimerIO[1] and TimerIO[2] - are created using AllocSysObject |
− | * and |
+ | * and duplicating them from TimerIO[0]. The Seconds field of each |
* structure is set and then three SendIOs are done with the requests. |
* 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. |
* The program then goes into a while loop until all messages are received. |
||
− | * |
||
− | * Compile with SAS C 5.10 lc -b1 -cfistq -v -y -L |
||
* |
* |
||
* Run from CLI only |
* Run from CLI only |
||
*/ |
*/ |
||
− | #include |
+ | #include <exec/types.h> |
− | #include |
+ | #include <exec/memory.h> |
− | #include |
+ | #include <devices/timer.h> |
− | #include |
+ | #include <proto/dos.h> |
− | #include |
+ | #include <proto/exec.h> |
+ | int main(void) |
||
− | #include <stdio.h> |
||
− | |||
− | #ifdef LATTICE |
||
− | int CXBRK(void) { return(0); } /* Disable Lattice CTRL/C handling */ |
||
− | int chkabort(void) { return(0); } /* really */ |
||
− | #endif |
||
− | |||
− | VOID main(VOID); |
||
− | |||
− | void main(void) |
||
{ |
{ |
||
+ | uint32 error, x, seconds[3] = {4,1,2}, microseconds[3] = {0,0,0}; |
||
− | struct timerequest *TimerIO[3]; |
||
+ | int32 allin = 3; |
||
− | struct MsgPort *TimerMP; |
||
+ | |||
− | struct Message *TimerMSG; |
||
+ | 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; |
||
+ | } |
||
+ | </syntaxhighlight> |
||
− | ULONG error,x,seconds[3]={4,1,2}, microseconds[3]={0,0,0}; |
||
− | |||
− | int allin = 3; |
||
− | char *position[]={"last","second","first"}; |
||
− | |||
− | if (TimerMP = CreatePort(0,0)) |
||
− | { |
||
− | if (TimerIO[0] = (struct timerequest *) |
||
− | CreateExtIO(TimerMP,sizeof(struct timerequest)) ) |
||
− | { |
||
− | /* Open the device once */ |
||
− | if (!(error=OpenDevice( TIMERNAME, UNIT_VBLANK, |
||
− | (struct IORequest *) TimerIO[0], 0L))) |
||
− | { |
||
− | /* Set command to TR_ADDREQUEST */ |
||
− | TimerIO[0]->tr_node.io_Command = TR_ADDREQUEST; |
||
− | |||
− | if (TimerIO[1]=(struct timerequest *) |
||
− | AllocMem(sizeof(struct timerequest),MEMF_PUBLIC | MEMF_CLEAR)) |
||
− | { |
||
− | if (TimerIO[2]=(struct timerequest *) |
||
− | AllocMem(sizeof(struct timerequest),MEMF_PUBLIC | MEMF_CLEAR)) |
||
− | { |
||
− | /* Copy fields from the request used to open the timer device */ |
||
− | *TimerIO[1] = *TimerIO[0]; |
||
− | *TimerIO[2] = *TimerIO[0]; |
||
− | |||
− | /* Initialize other fields */ |
||
− | for (x=0;x<3;x++) |
||
− | { |
||
− | TimerIO[x]->tr_time.tv_secs = seconds[x]; |
||
− | TimerIO[x]->tr_time.tv_micro = microseconds[x]; |
||
− | printf("\nInitializing TimerIO[%d]",x); |
||
− | } |
||
− | |||
− | printf("\n\nSending multiple requests\n\n"); |
||
− | |||
− | /* Send multiple requests asynchronously */ |
||
− | /* Do not got to sleep yet... */ |
||
− | SendIO((struct IORequest *)TimerIO[0]); |
||
− | SendIO((struct IORequest *)TimerIO[1]); |
||
− | SendIO((struct IORequest *)TimerIO[2]); |
||
− | |||
− | /* There might be other processing done here */ |
||
− | |||
− | /* Now go to sleep with WaitPort() waiting for the requests */ |
||
− | while (allin) |
||
− | { |
||
− | WaitPort(TimerMP); |
||
− | /* Get the reply message */ |
||
− | TimerMSG=GetMsg(TimerMP); |
||
− | for (x=0;x<3;x++) |
||
− | if (TimerMSG==(struct Message *)TimerIO[x]) |
||
− | printf("Request %ld finished %s\n",x,position[--allin]); |
||
− | } |
||
− | |||
− | FreeMem(TimerIO[2],sizeof(struct timerequest)); |
||
− | } |
||
− | |||
− | else |
||
− | printf("Error: could not allocate TimerIO[2] memory\n"); |
||
− | |||
− | FreeMem(TimerIO[1],sizeof(struct timerequest)); |
||
− | } |
||
− | |||
− | else |
||
− | printf("Error could not allocate TimerIO[1] memory\n"); |
||
− | |||
− | CloseDevice((struct IORequest *) TimerIO[0]); |
||
− | } |
||
− | |||
− | else |
||
− | printf("\nError: Could not OpenDevice\n"); |
||
− | |||
− | DeleteExtIO((struct IORequest *) TimerIO[0]); |
||
− | } |
||
− | |||
− | else |
||
− | printf("Error: could not create IORequest\n"); |
||
− | |||
− | DeletePort(TimerMP); |
||
− | } |
||
− | |||
− | else |
||
− | printf("\nError: Could not CreatePort\n"); |
||
− | } |
||
− | </pre> |
||
If all goes according to plan, TimerIO[1] will finish first, TimerIO[2] will finish next, and TimerIO[0] will finish last. |
If all goes according to plan, TimerIO[1] will finish first, TimerIO[2] will finish next, and TimerIO[0] will finish last. |
||
Line 488: | Line 577: | ||
== Using the Time Arithmetic Functions == |
== 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. |
||
− | |||
− | |||
− | 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. |
||
=== Why use Time Arithmetic? === |
=== Why use Time Arithmetic? === |
||
Line 496: | Line 583: | ||
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. |
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 |
+ | 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. |
# Begin. |
||
Line 502: | Line 589: | ||
# Perform your loop however many times in your selected interval. |
# 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.) |
# 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 ( |
+ | # 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. |
# 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. |
Over the long run, then, your average number of operations within a specified period of time can become precisely what you have designed. |
||
+ | {{Note|title=You Can’t Do 1+1 on E-Clock Values|text=The arithmetic functions are not designed to operate on EClockVals.}} |
||
− | {| class="wikitable" |
||
− | | ''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 == |
== E-Clock Time and Its Relationship to Actual Time == |
||
Line 517: | Line 602: | ||
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. |
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. |
||
+ | <syntaxhighlight> |
||
− | <pre> |
||
/* E-Clock Fractions of a second fragment |
/* E-Clock Fractions of a second fragment |
||
* |
* |
||
* This fragment reads the E-Clock twice and subtracts the two ev_lo values |
* This fragment reads the E-Clock twice and subtracts the two ev_lo values |
||
− | * time2- |
+ | * time2->ev_lo - time1->ev_lo |
* and divides the result by the E-Clock tics/secs returned by ReadEClock() |
* and divides the result by the E-Clock tics/secs returned by ReadEClock() |
||
* to get the fractions of a second |
* to get the fractions of a second |
||
*/ |
*/ |
||
+ | struct TimeRequest* TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST, |
||
− | struct EClockVal *time1,*time2; |
||
+ | ASOIOR_Size, sizeof(struct TimeRequest), |
||
− | ULONG E_Freq; |
||
+ | ASOIOR_ReplyPort, port, |
||
− | LONG error; |
||
+ | TAG_END); |
||
− | struct timerequest *TimerIO; |
||
− | + | struct EClockVal *time1 = (struct EClockVal *)AllocVecTags(sizeof(struct EClockVal ), |
|
+ | AVT_ClearWithValue, 0, |
||
− | MEMF_CLEAR | MEMF_PUBLIC); |
||
+ | TAG_END); |
||
− | + | struct EClockVal *time2 = (struct EClockVal *)AllocVecTags(sizeof(struct EClockVal ), |
|
+ | AVT_ClearWithValue, 0, |
||
− | MEMF_CLEAR | MEMF_PUBLIC); |
||
+ | TAG_END); |
||
+ | int32 error; |
||
− | time2 = (struct EClockVal *)AllocMem(sizeof(struct EClockVal ), |
||
+ | if (!(error = IExec->OpenDevice(TIMERNAME,UNIT_ECLOCK,(struct IORequest *)TimerIO,0L)) ) |
||
− | MEMF_CLEAR | MEMF_PUBLIC); |
||
+ | { |
||
+ | TimerBase = (struct Library *)TimerIO->Request.io_Device; |
||
+ | ITimer = IExec->GetInterface(TimerBase, "main", 1, NULL); |
||
+ | uint32 E_Freq; |
||
− | if (!(error = OpenDevice(TIMERNAME,UNIT_ECLOCK,(struct IORequest *)TimerIO,0L)) ) |
||
+ | E_Freq = ITimer->ReadEClock((struct EClockVal *) time1); /* Get initial reading */ |
||
− | { |
||
− | 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 = ITimer->ReadEClock((struct EClockVal *) time2); /* Get second reading */ |
|
+ | |||
− | printf("\nThe operation took: %f fractions of a second\n", |
||
+ | IDOS->Printf("\nThe operation took: %f fractions of a second\n", |
||
− | (time2->ev_lo-time1->ev_lo)/(double)E_Freq); |
||
+ | (time2->ev_lo-time1->ev_lo)/(double)E_Freq); |
||
− | + | IExec->CloseDevice( (struct IORequest *) TimerIO ); |
|
+ | } |
||
− | } |
||
+ | </syntaxhighlight> |
||
− | </pre> |
||
+ | {{Note|title=The Code Takes Some Liberties|text=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.}} |
||
− | {| class="wikitable" |
||
− | | ''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 == |
== Example Timer Program == |
||
− | Here is an example program showing how to use the |
+ | Here is an example program showing how to use the Timer Device. |
+ | <syntaxhighlight> |
||
− | <pre>/* Simple_Timer.c |
||
+ | /* Simple_Timer.c |
||
− | * |
||
− | * A simple example of using the timer device. |
||
* |
* |
||
+ | * A simple example of using the Timer Device. |
||
− | * Compile with SAS C 5.10: LC -b1 -cfistq -v -y -L |
||
* |
* |
||
* Run from CLI only |
* Run from CLI only |
||
*/ |
*/ |
||
− | #include |
+ | #include <exec/types.h> |
− | #include |
+ | #include <exec/io.h> |
− | #include |
+ | #include <exec/memory.h> |
− | #include |
+ | #include <devices/timer.h> |
− | #include |
+ | #include <proto/exec.h> |
− | #include |
+ | #include <proto/dos.h> |
− | #include <clib/dos_protos.h> |
||
− | |||
− | #include <stdio.h> |
||
− | |||
− | #ifdef LATTICE |
||
− | int CXBRK(void) { return(0); } /* Disable SAS CTRL/C handling */ |
||
− | int chkabort(void) { return(0); } /* really */ |
||
− | #endif |
||
/* Our timer sub-routines */ |
/* Our timer sub-routines */ |
||
− | void delete_timer (struct |
+ | void delete_timer (struct TimeRequest *); |
− | + | int32 get_sys_time (struct TimeVal *); |
|
− | + | int32 set_new_time (int32); |
|
− | void wait_for_timer(struct |
+ | void wait_for_timer(struct TimeRequest *, struct TimeVal *); |
− | + | int32 time_delay ( struct TimeVal *, int32 ); |
|
− | struct timerequest *create_timer( |
+ | struct timerequest *create_timer( uint32 ); |
− | void show_time ( |
+ | void show_time (uint32); |
− | |||
− | struct Library *TimerBase; /* to get at the time comparison functions */ |
||
− | /* manifest constants -- |
+ | /* manifest constants -- "will never change" */ |
#define SECSPERMIN (60) |
#define SECSPERMIN (60) |
||
#define SECSPERHOUR (60*60) |
#define SECSPERHOUR (60*60) |
||
#define SECSPERDAY (60*60*24) |
#define SECSPERDAY (60*60*24) |
||
+ | |||
− | void main(int argc,char **argv) |
||
+ | int main(void) |
||
{ |
{ |
||
− | + | int32 seconds; |
|
− | struct |
+ | struct TimeVal oldtimeval; /* timevals to store times */ |
+ | struct TimeVal mytimeval; |
||
− | struct timeval oldtimeval; /* timevals to store times */ |
||
− | struct |
+ | struct TimeVal currentval; |
− | struct timeval currentval; |
||
− | + | IDOS->Printf("\nTimer test\n"); |
|
− | /* sleep for two seconds */ |
+ | /* sleep for two seconds */ |
− | currentval.tv_secs = 2; |
+ | currentval.tv_secs = 2; |
− | currentval.tv_micro = 0; |
+ | currentval.tv_micro = 0; |
− | time_delay( & |
+ | time_delay( ¤tval, UNIT_VBLANK ); |
− | + | IDOS->Printf( "After 2 seconds delay\n" ); |
|
− | /* sleep for four seconds */ |
+ | /* sleep for four seconds */ |
− | currentval.tv_secs = 4; |
+ | currentval.tv_secs = 4; |
− | currentval.tv_micro = 0; |
+ | currentval.tv_micro = 0; |
− | time_delay( & |
+ | time_delay( ¤tval, UNIT_VBLANK ); |
− | + | IDOS->Printf( "After 4 seconds delay\n" ); |
|
− | /* sleep for 500,000 micro-seconds = 1/2 second */ |
+ | /* sleep for 500,000 micro-seconds = 1/2 second */ |
− | currentval.tv_secs = 0; |
+ | currentval.tv_secs = 0; |
− | currentval.tv_micro = 500000; |
+ | currentval.tv_micro = 500000; |
− | time_delay( & |
+ | time_delay( ¤tval, UNIT_MICROHZ ); |
− | + | IDOS->Printf( "After 1/2 second delay\n" ); |
|
− | + | IDOS->Printf( "DOS Date command shows: " ); |
|
− | (void) Execute( |
+ | (void) IDOS->Execute( "date", 0, 0 ); |
− | /* save what system thinks is the time... |
+ | /* save what system thinks is the time... we'll advance it temporarily */ |
− | get_sys_time( & |
+ | get_sys_time( &oldtimeval ); |
− | + | IDOS->Printf("Original system time is:\n"); |
|
− | show_time(oldtimeval.tv_secs ); |
+ | show_time(oldtimeval.tv_secs ); |
− | + | IDOS->Printf("Setting a new system time\n"); |
|
− | seconds = 1000 * SECSPERDAY + oldtimeval.tv_secs; |
+ | seconds = 1000 * SECSPERDAY + oldtimeval.tv_secs; |
− | set_new_time( seconds ); |
+ | set_new_time( seconds ); |
− | /* (if user executes the AmigaDOS DATE command now, he will*/ |
+ | /* (if user executes the AmigaDOS DATE command now, he will*/ |
− | /* see that the time has advanced something over 1000 days */ |
+ | /* see that the time has advanced something over 1000 days */ |
− | + | IDOS->Printf( "DOS Date command now shows: " ); |
|
− | (void) Execute( |
+ | (void) IDOS->Execute( "date", 0, 0 ); |
− | get_sys_time( & |
+ | get_sys_time( &mytimeval ); |
− | + | IDOS->Printf( "Current system time is:\n"); |
|
− | show_time(mytimeval.tv_secs); |
+ | show_time(mytimeval.tv_secs); |
− | /* Added the microseconds part to show that time keeps */ |
+ | /* Added the microseconds part to show that time keeps */ |
− | /* increasing even though you ask many times in a row */ |
+ | /* 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( & |
+ | get_sys_time( &mytimeval ); |
− | + | IDOS->Printf("First TR_GETSYSTIME \t%ld.%ld\n",mytimeval.tv_secs, mytimeval.tv_micro); |
|
− | get_sys_time( & |
+ | get_sys_time( &mytimeval ); |
− | + | IDOS->Printf("Second TR_GETSYSTIME \t%ld.%ld\n",mytimeval.tv_secs, mytimeval.tv_micro); |
|
− | get_sys_time( & |
+ | 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 ); |
+ | set_new_time( oldtimeval.tv_secs ); |
− | get_sys_time( & |
+ | get_sys_time( &mytimeval ); |
− | + | IDOS->Printf( "Current system time is:\n"); |
|
− | show_time(mytimeval.tv_secs); |
+ | show_time(mytimeval.tv_secs); |
+ | return 0; |
||
− | /* just shows how to set up for using the timer functions, does not */ |
||
− | /* demonstrate the functions themselves. (TimerBase must have a */ |
||
− | /* legal value before AddTime, SubTime or CmpTime are performed. */ |
||
− | tr = create_timer( UNIT_MICROHZ ); |
||
− | TimerBase = (struct Library *)tr->tr_node.io_Device; |
||
− | |||
− | /* and how to clean up afterwards */ |
||
− | TimerBase = (struct Library *)(-1); |
||
− | delete_timer( tr ); |
||
} |
} |
||
+ | |||
− | struct timerequest *create_timer( ULONG unit ) |
||
+ | struct TimeRequest *create_timer(uint32 unit) |
||
{ |
{ |
||
− | /* return a pointer to a timer request. If any problem, return NULL */ |
+ | /* return a pointer to a timer request. If any problem, return NULL */ |
− | + | int32 error; |
|
− | struct MsgPort *timerport; |
+ | struct MsgPort *timerport = NULL; |
− | struct |
+ | struct TimeRequest *TimerIO = NULL; |
− | timerport = |
+ | timerport = IExec->AllocSysObjectTags(ASOT_PORT, 0); |
− | if (timerport == NULL ) |
+ | if (timerport == NULL ) |
− | return |
+ | return NULL; |
+ | TimerIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST, |
||
− | TimerIO = (struct timerequest *) |
||
− | + | ASOIOR_Size, sizeof(struct TimeRequest), |
|
+ | ASOIOR_ReplyPort, timerport, |
||
− | if (TimerIO == NULL ) |
||
− | + | TAG_END); |
|
− | DeletePort(timerport); /* Delete message port */ |
||
− | return( NULL ); |
||
− | } |
||
+ | if (TimerIO == NULL ) |
||
− | error = OpenDevice( TIMERNAME, unit,(struct IORequest *) TimerIO, 0L ); |
||
+ | { |
||
− | if (error != 0 ) |
||
+ | 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 ); |
delete_timer( TimerIO ); |
||
− | return |
+ | return NULL; |
− | + | } |
|
+ | |||
− | return( TimerIO ); |
||
+ | return TimerIO; |
||
} |
} |
||
Line 715: | Line 788: | ||
/* more precise timer than AmigaDOS Delay() */ |
/* 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; |
||
+ | struct TimeRequest *tr = create_timer( unit ); |
||
− | /* 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 ); |
||
− | /* any nonzero return says timedelay routine didn't work. */ |
||
− | if (tr == NULL ) |
||
− | return( -1L ); |
||
+ | /* deallocate temporary structures */ |
||
− | wait_for_timer( tr, tv ); |
||
+ | delete_timer( tr ); |
||
+ | return 0L; |
||
− | /* deallocate temporary structures */ |
||
− | delete_timer( tr ); |
||
− | return( 0L ); |
||
} |
} |
||
− | void wait_for_timer(struct |
+ | void wait_for_timer(struct TimeRequest *tr, struct TimeVal *tv) |
{ |
{ |
||
+ | tr->Request.io_Command = TR_ADDREQUEST; /* add a new timer request */ |
||
+ | /* structure assignment */ |
||
− | tr->tr_node.io_Command = TR_ADDREQUEST; /* add a new timer request */ |
||
+ | tr->Time = *tv; |
||
+ | /* post request to the timer -- will go to sleep till done */ |
||
− | /* structure assignment */ |
||
+ | IExec->DoIO((struct IORequest *) tr ); |
||
− | tr->tr_time = *tv; |
||
− | |||
− | /* post request to the timer -- will go to sleep till done */ |
||
− | DoIO((struct IORequest *) tr ); |
||
} |
} |
||
− | + | int32 set_new_time(int32 secs) |
|
{ |
{ |
||
− | struct |
+ | struct TimeRequest *tr = create_timer( UNIT_MICROHZ ); |
− | tr = create_timer( UNIT_MICROHZ ); |
||
− | /* non zero return says error */ |
+ | /* non zero return says error */ |
− | if (tr == 0 ) |
+ | if (tr == 0 ) |
− | return |
+ | return -1L; |
− | tr- |
+ | tr->Time.Seconds = secs; |
− | tr- |
+ | tr->Time.Microseconds = 0; |
− | tr- |
+ | tr->Request.io_Command = TR_SETSYSTIME; |
− | DoIO((struct IORequest *) tr ); |
+ | IExec->DoIO((struct IORequest *) tr ); |
− | delete_timer(tr); |
+ | delete_timer(tr); |
+ | |||
− | return(0); |
||
+ | return 0L; |
||
} |
} |
||
− | + | int32 get_sys_time(struct TimeVal *tv) |
|
{ |
{ |
||
− | struct |
+ | struct TimeRequest *tr = create_timer( UNIT_MICROHZ ); |
− | tr = create_timer( UNIT_MICROHZ ); |
||
− | /* non zero return says error */ |
+ | /* non zero return says error */ |
− | if (tr == 0 ) |
+ | if (tr == 0 ) |
− | return |
+ | return -1L; |
− | tr- |
+ | tr->Request.io_Command = TR_GETSYSTIME; |
− | DoIO((struct IORequest *) tr ); |
+ | IExec->DoIO((struct IORequest *) tr ); |
− | /* structure assignment */ |
+ | /* structure assignment */ |
− | *tv = tr- |
+ | *tv = tr->Time; |
− | delete_timer( tr ); |
+ | delete_timer( tr ); |
+ | |||
− | return( 0 ); |
||
+ | return 0L; |
||
} |
} |
||
− | void delete_timer(struct |
+ | void delete_timer(struct TimeRequest *tr ) |
{ |
{ |
||
− | struct MsgPort *tp; |
+ | struct MsgPort *tp = NULL; |
− | if (tr != 0 ) |
+ | if (tr != 0 ) |
− | + | { |
|
− | tp = tr- |
+ | tp = tr->Request.io_Message.mn_ReplyPort; |
+ | IExec->FreeSysObject(ASOT_PORT, tp); |
||
− | if (tp != 0) |
||
− | DeletePort(tp); |
||
− | CloseDevice( (struct IORequest *) tr ); |
+ | IExec->CloseDevice( (struct IORequest *) tr ); |
+ | IExec->FreeSysObject(ASOT_IOREQUEST, tr); |
||
− | DeleteExtIO( (struct IORequest *) tr ); |
||
− | + | } |
|
} |
} |
||
− | void show_time( |
+ | void show_time(uint32 secs) |
{ |
{ |
||
− | + | uint32 days, hrs, mins; |
|
− | /* Compute days, hours, etc. */ |
+ | /* Compute days, hours, etc. */ |
− | mins=secs/60; |
+ | mins = secs / 60; |
− | hrs=mins/60; |
+ | hrs = mins / 60; |
− | days=hrs/24; |
+ | days = hrs / 24; |
− | secs=secs%60; |
+ | secs = secs % 60; |
− | mins=mins%60; |
+ | mins = mins % 60; |
− | hrs=hrs%24; |
+ | 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); |
||
+ | } |
||
+ | </syntaxhighlight> |
||
− | /* Display the time */ |
||
− | printf("* Hour Minute Second (Days since Jan.1,1978)\n"); |
||
− | printf("*%5ld:%5ld:%5ld (%6ld )\n\n",hrs,mins,secs,days); |
||
− | } /* end of main */</pre> |
||
== Additional Information on the Timer Device == |
== Additional Information on the Timer Device == |
||
Line 831: | Line 904: | ||
|- |
|- |
||
|rowspan="4"|Includes||devices/timer.h |
|rowspan="4"|Includes||devices/timer.h |
||
− | |- |
||
− | |devices/timer.i |
||
|- |
|- |
||
|utility/date.h |
|utility/date.h |
||
− | |- |
||
− | |utility/date.i |
||
|- |
|- |
||
|rowspan="2"|AutoDocs||timer.doc |
|rowspan="2"|AutoDocs||timer.doc |
Latest revision as of 20:35, 29 October 2015
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.
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
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 { 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 several 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 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:
- 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(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( ¤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(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 |