home *** CD-ROM | disk | FTP | other *** search
- WAIT A MINUTE!
- © Copyright 1990 by Timm Martin
-
-
- In the good ol' days, all a programmer had to do to wait within a program was
- to create a simple for-loop and experiment with the test variable until the
- program waited the desired length of time. With the ever-increasing Amiga
- technology, however, one can no longer rely on the program being run on a
- 7.16MHz 68000 Amiga. Amigas can now come equipped with any one of the 680x0
- processors in the Motorola family, from the 7.16Mhz 68000 to the soon-to-be-
- released 50MHz 68040. In addition, a math coprocessor or memory management
- unit--both standard on the new Amiga 3000--will affect a program's speed.
-
- The answer to this dilemma is to use one of the handiest built-in Amiga
- facilities--the timer device. The simplest way to access the timer device is
- to use the dos.library Delay() function. The format of that function is:
-
- void Delay( long ticks );
-
- where ticks is the number of "ticks" to wait. A tick is an AmigaDOS unit of
- time that occurs fifty times per second. So, for example, if you wanted to
- wait for two seconds, you could specify
-
- Delay( 100L );
-
- A more reliable method is to use the TICKS_PER_SECOND definition in the
- libraries/dos.h include file:
-
- Delay( 2 * TICKS_PER_SECOND );
-
- There are a few problems inherent with the Delay() function, however. The
- most obvious problem is that the time resolution is limited to the duration
- of a tick. In other words, the shortest time you can wait is one fiftieth of
- a second. Another problem is that the wait is synchronous, meaning your
- program has to sit idle for the specified amount of time.
-
- The timer device solves both of these problems. The following code is a
- self-contained module that you can place in its own source file, compile, and
- link with your program:
-
- #include <devices/timer.h>
- #include <exec/types.h>
-
- /********************
- * SHARED VARIABLES
- *********************/
-
- long timer_error = 1;
- struct timerequest timer_req;
- struct MsgPort * timer_port = NULL;
-
- /***************
- * TIMER CLOSE
- ****************/
-
- /*
- This function closes the timer device and deletes the timer port.
- */
-
- void timer_close( void )
- {
- if (!timer_error)
- {
- CloseDevice( (struct IORequest *)&timer_req );
- timer_error = NULL;
- }
- if (timer_port)
- {
- DeletePort( timer_port );
- timer_port = NULL;
- }
- }
-
- /**************
- * TIMER OPEN
- ***************/
-
- /*
- This function opens the timer device and initializes it.
- */
-
- BOOL timer_open( void )
- {
- if (!(timer_port = CreatePort( NULL, 0L )) ||
- (timer_error = OpenDevice( TIMERNAME, UNIT_VBLANK,
- (struct IORequest *)&timer_req, NULL )))
- return (0);
-
- timer_req.tr_node.io_Message.mn_ReplyPort = timer_port;
- timer_req.tr_node.io_Command = TR_ADDREQUEST;
- timer_req.tr_node.io_Flags = 0;
- return (1);
- }
-
- /**************
- * TIMER WAIT
- ***************/
-
- /*
- This function waits for the specified number of microseconds.
- */
-
- #define MICROS_PER_SEC 1000000L
-
- void timer_wait( long micros )
- {
- long secs;
-
- /* a bug in Kickstart v1.3 requires this check */
- if (micros < 2) return;
-
- secs = micros / MICROS_PER_SEC;
- micros %= MICROS_PER_SEC;
-
- timer_req.tr_time.tv_secs = secs;
- timer_req.tr_time.tv_micro = micros;
- SendIO( &timer_req.tr_node );
-
- /* wait until time is up */
- Wait( 1L<<timer_port->mp_SigBit );
- GetMsg( timer_port );
- }
-
- You should call the timer_open() function during program startup when you
- open other things such as libraries, windows, devices, etc. The first thing
- this function does is create a message port so it can communicate with the
- timer device. A message port is analogous to a real-world mailbox; it's a
- depository for messages--in this case reply messages--from the timer device.
- Your program will send a message to the timer device telling it to notify you
- in a certain amount of time. When that time has elapsed, the timer device
- will reply to your message, placing the reply in the message port you created.
-
- The CreatePort() function allows you to easily create message ports. This
- function is not located in ROM, but rather in the amiga.lib (for Lattice C
- users) or c.lib (for Manx C users) link-time libraries. This function
- allocates memory for the message port, allocates a signal bit to notify your
- program when a message has been received, and properly initializes the
- message port. You pass it the name of the port you want to create and the
- priority of the port. Since you will not be looking for this port with the
- FindPort() function, you can set the name of the port to NULL, and the system
- will not add the port to the public message port list. The priority is a
- value between -128 and +127 that represents the importance of messages
- received in this port. Unless you are creating multiple ports and expect
- contention between the ports, you can safely set this value to zero. A
- pointer to the new message port is returned or NULL if the CreatePort()
- function fails.
-
- The next step is to open the timer device for use by your program. The
- OpenDevice() function requires four arguments: the name of the device, the
- unit number, a pointer to a timerequest structure, and a flags value. The
- name of the timer device is always the same and is defined in the devices/
- timer.h include file as TIMERNAME.
-
- The unit number can either be UNIT_VBLANK or UNIT_MICROHZ as defined in
- devices/timer.h. The MICROHZ timer has a very high resolution of one
- microsecond, meaning it updates its counter one million times per second. As
- you would expect, this requires quite a lot of overhead to maintain, and the
- MICROHZ timer can fall behind when the system is busy with other tasks.
- Therefore, it should only be used for short, critical time measurements. The
- VBLANK timer, on the other hand, is better suited for most applications. It
- updates its counter sixty times per second, meaning it is accurate to within
- .0167 seconds (which is close to the resolution of the Delay() function) and
- requires very little overhead. The VBLANK timer maintains its accuracy
- regardless of how busy the system is.
-
- The next argument is a pointer to a timerequest structure that you have
- allocated. The structure is defined as follows:
-
- struct timerequest
- {
- struct IORequest tr_node;
- struct timeval tr_time;
- };
-
- The tr_node member is a standard IORequest structure used to communicate with
- Amiga devices. It contains information such as a Message structure for
- communicating with the device, a pointer to the Device and Unit structures,
- etc. Most of this information is used internally by Exec. You just need to
- allocate memory for the timerequest structure and pass its address, and the
- OpenDevice() function will do the rest.
-
- The final argument for the OpenDevice() function is the Flags variable. This
- is not used by the timer device and should be set to zero.
-
- If the OpenDevice() function fails, it will return a non-zero error number.
- Notice that the way the timer_open() function is written, if either the
- CreatePort() or OpenDevice() function fails, the function will return 0, in
- which case your program should act accordingly. Accessing the timer device
- without having successfully opened it will surely cause problems.
-
- Assuming everything opened OK, a few entries in the timerequest structure
- need to be initialized. First you need to set the mn_ReplyPort pointer in
- the Message structure of the timerequest to the timer_port you created. This
- tells the timer device where to reply to messages you send it. Next, you
- need to specify what you want the timer device to do. (In addition to simple
- time counting, you can ask the timer device what time it is or even set the
- system time). The TR_ADDREQUEST command indicates that you will be asking
- the timer device to count time. Finally, setting the io_Flags value to zero
- indicates that you want normal I/O as opposed to Quick I/O. With Quick I/O,
- the device will respond to your request immediately. This is handy for a
- device such as the serial device in which your program may not want to wait
- for the device to service your request before responding. But in this case,
- the goal is for the timer device to notify you after the specified time has
- elapsed.
-
- While we are on the subject of opening the timer device, let's discuss
- closing it. Your program should call the timer_close() function during
- program shutdown when you close other things such as libraries, windows, etc.
- Notice the timer_error, timer_port, and timer_req variables are global to all
- three functions, allowing each function to access them. These variables act
- as their own "flags," indicating when the corresponding device or port has
- been opened. For example, if the timer_error variable is zero, then the
- timer device has been opened and is consequently closed by the timer_close()
- function. Also, if the timer_port variable is not NULL, then the timer
- message port has been created and is freed by timer_close(). The variables
- are initialized globally and then reset in the timer_close() function so that
- it is safe to call timer_close() even if timer_open() was never called. This
- is handy in case your program terminates prematurely before calling
- timer_open() (for example, because you could not open a library).
-
- Once you have opened the timer device, you can use the timer_wait() function
- to wait for a specified number of microseconds. For example:
-
- timer_wait( 1000000L ); /* wait one second */
- timer_wait( 500000L ); /* wait one-half second */
- timer_wait( 5000000L ); /* wait five seconds */
-
- The first line in the timer_wait() function checks to make sure micros is not
- less than 2 microseconds. A bug in Kickstart v1.3 or earlier will cause the
- system to crash if you specify 0 or 1 microseconds. The next two lines
- break the microsecond value up into its seconds and microseconds components.
-
- The next two lines fill in the amount of time you want to wait. Even if the
- time was the same for each request, you must reinitialize it because the
- timer device destroys your old values (it actually uses the tv_secs and
- tv_micro structure members to count down your time). The SendIO() function
- then sends your time request to the timer device. By using SendIO() instead
- of DoIO(), your program will continue executing even though the timer device
- has not yet responded to your request (allowing you to perform an
- asynchronous wait if desired).
-
- The next step is to wait until you receive a signal from the timer device
- that the specified amount of time has elapsed. When the timer device
- finishes counting the time, it will reply to your time request by sending a
- message to the timer_port you created earlier. When it places the message in
- your timer port, Exec will set the signal bit associated with that port.
- Hence, you Wait() for that signal bit to be set. There is no need to then
- ReplyMsg() since the timer device was replying to your original message. The
- GetMsg() function then removes the message from the timer port.
-
- As you may have guessed, the timer_wait() function is similar to the Delay()
- function in that it is a synchronous wait--the program halts until the
- specified amount of time has elapsed. (Note that this is NOT a busy wait.
- Your program sleeps while in the Wait() function.) Using similar code,
- however, it is easy to create an asynchronous wait.
-
- Suppose you want to create a clock that sits in a small window on the
- Workbench screen. In addition to having the timer device notify you once
- each second so you can update the time, you would also want to monitor for
- the user clicking on the close gadget in the window to end the program. You
- can create a function that is identical to timer_wait() except that it does
- not wait for the time to count down:
-
- /***************
- * TIMER START
- ****************/
-
- /*
- This function issues a request to the timer device to notify the program
- in the specified number of microseconds. This function does not wait for
- a reply from the timer device.
- */
-
- void timer_start( long micros )
- {
- long secs;
-
- /* a bug in Kickstart v1.3 requires this check */
- if (micros < 2) return;
-
- secs = micros / MICROS_PER_SEC;
- micros %= MICROS_PER_SEC;
-
- timer_req.tr_time.tv_secs = secs;
- timer_req.tr_time.tv_micro = micros;
- SendIO( &timer_req.tr_node );
- }
-
- You could then wait for both window input and the timer device signal in the
- same Wait() call:
-
- struct Window *window;
- struct IntuiMessage *imessage;
-
- Wait( 1L<<window->UserPort->mp_SigBit | 1L<<timer_port->mp_SigBit );
-
- while (imessage = (struct IntuiMessage *)GetMsg( window->UserPort ))
- {
- /* handle the window input */
- ReplyMsg( (struct Message *)imessage );
- }
-
- if (GetMsg( timer_port ))
- /* time is up! */
-
- A word of caution here--never use a timerequest structure that is currently
- being serviced by the timer device. In other words, don't send a new time
- request until the previous time request is complete (though you could create
- multiple time requests by allocating multiple timerequest structures). If
- for some reason you want to cancel a time request (so you could issue
- another, for example), you need to use the AbortIO() function:
-
- /***************
- * TIMER ABORT
- ****************/
-
- /*
- This function cancels an existing time request.
- */
-
- void timer_abort( void )
- {
- AbortIO( &timer_req.tr_node );
- Wait( 1L<<timer_port->mp_SigBit );
- GetMsg( timer_port );
- }
-
- The AbortIO() function will force the timer device to respond to your request
- immediately whether or not the requested time has elapsed. The Wait()
- function then clears the signal bit, and GetMsg() removes the reply message
- for the aborted request. It is safe to call AbortIO() even if the time
- request may have been satisfied. However, there MUST be a pending time
- request (satisfied or not) or the system will crash.
-
- As you can see, using the timer device is not only easy but has many
- advantages over the old for-loop method: 1) it's not a "busy" wait, 2) it
- can be asynchronous, and 3) you are guaranteed of waiting an exact amount of
- time regardless of the hardware conditions.
-
-
-
- REFERENCES
-
- Commodore-Amiga, Inc., Amiga ROM Kernal Manual: Libraries and Devices,
- Addison-Wesley Publishing Company, Inc., New York, 1989, pp. 289-98, 871-82.
-
- Mortimore, Eugene P., Amiga Programmer's Handbook: Volume II, SYBEX, Inc.,
- San Francisco, 1987, pp. 305-20.
-