home *** CD-ROM | disk | FTP | other *** search
/ Fish 'n' More 2 / fishmore-publicdomainlibraryvol.ii1991xetec.iso / dirs / timer_429.lzh / Timer / Article < prev    next >
Text File  |  1991-01-10  |  15KB  |  349 lines

  1. WAIT A MINUTE!
  2. © Copyright 1990 by Timm Martin
  3.  
  4.  
  5. In the good ol' days, all a programmer had to do to wait within a program was
  6. to create a simple for-loop and experiment with the test variable until the
  7. program waited the desired length of time.  With the ever-increasing Amiga
  8. technology, however, one can no longer rely on the program being run on a
  9. 7.16MHz 68000 Amiga.  Amigas can now come equipped with any one of the 680x0
  10. processors in the Motorola family, from the 7.16Mhz 68000 to the soon-to-be-
  11. released 50MHz 68040.  In addition, a math coprocessor or memory management
  12. unit--both standard on the new Amiga 3000--will affect a program's speed.
  13.  
  14. The answer to this dilemma is to use one of the handiest built-in Amiga
  15. facilities--the timer device.  The simplest way to access the timer device is
  16. to use the dos.library Delay() function.  The format of that function is:
  17.  
  18.   void Delay( long ticks );
  19.  
  20. where ticks is the number of "ticks" to wait.  A tick is an AmigaDOS unit of
  21. time that occurs fifty times per second.  So, for example, if you wanted to
  22. wait for two seconds, you could specify
  23.  
  24.   Delay( 100L );
  25.  
  26. A more reliable method is to use the TICKS_PER_SECOND definition in the
  27. libraries/dos.h include file:
  28.  
  29.   Delay( 2 * TICKS_PER_SECOND );
  30.  
  31. There are a few problems inherent with the Delay() function, however.  The
  32. most obvious problem is that the time resolution is limited to the duration
  33. of a tick.  In other words, the shortest time you can wait is one fiftieth of
  34. a second.  Another problem is that the wait is synchronous, meaning your
  35. program has to sit idle for the specified amount of time.
  36.  
  37. The timer device solves both of these problems.  The following code is a
  38. self-contained module that you can place in its own source file, compile, and
  39. link with your program:
  40.  
  41.     #include <devices/timer.h>
  42.     #include <exec/types.h>
  43.  
  44.     /********************
  45.     *  SHARED VARIABLES
  46.     *********************/
  47.  
  48.     long                timer_error = 1;
  49.     struct timerequest  timer_req;
  50.     struct MsgPort *    timer_port = NULL;
  51.  
  52.     /***************
  53.     *  TIMER CLOSE
  54.     ****************/
  55.  
  56.     /*
  57.     This function closes the timer device and deletes the timer port.
  58.     */
  59.  
  60.     void timer_close( void )
  61.     {
  62.         if (!timer_error)
  63.         {
  64.             CloseDevice( (struct IORequest *)&timer_req );
  65.             timer_error = NULL;
  66.         }
  67.         if (timer_port)
  68.         {
  69.             DeletePort( timer_port );
  70.             timer_port = NULL;
  71.         }
  72.     }
  73.  
  74.     /**************
  75.     *  TIMER OPEN
  76.     ***************/
  77.  
  78.     /*
  79.     This function opens the timer device and initializes it. 
  80.     */
  81.  
  82.     BOOL timer_open( void )
  83.     {
  84.         if (!(timer_port = CreatePort( NULL, 0L )) ||
  85.             (timer_error = OpenDevice( TIMERNAME, UNIT_VBLANK,
  86.                 (struct IORequest *)&timer_req, NULL )))
  87.             return (0);
  88.  
  89.         timer_req.tr_node.io_Message.mn_ReplyPort = timer_port;
  90.         timer_req.tr_node.io_Command = TR_ADDREQUEST;
  91.         timer_req.tr_node.io_Flags = 0;
  92.         return (1);
  93.     }
  94.  
  95.     /**************
  96.     *  TIMER WAIT
  97.     ***************/
  98.  
  99.     /*
  100.     This function waits for the specified number of microseconds.
  101.     */
  102.  
  103.     #define MICROS_PER_SEC 1000000L
  104.     
  105.     void timer_wait( long micros )
  106.     {
  107.         long secs;
  108.         
  109.         /* a bug in Kickstart v1.3 requires this check */
  110.         if (micros < 2) return;
  111.  
  112.         secs = micros / MICROS_PER_SEC;
  113.         micros %= MICROS_PER_SEC;
  114.         
  115.         timer_req.tr_time.tv_secs  = secs;
  116.         timer_req.tr_time.tv_micro = micros;
  117.         SendIO( &timer_req.tr_node );
  118.  
  119.         /* wait until time is up */
  120.         Wait( 1L<<timer_port->mp_SigBit );
  121.         GetMsg( timer_port );
  122.     }
  123.  
  124. You should call the timer_open() function during program startup when you
  125. open other things such as libraries, windows, devices, etc.  The first thing
  126. this function does is create a message port so it can communicate with the
  127. timer device.  A message port is analogous to a real-world mailbox; it's a
  128. depository for messages--in this case reply messages--from the timer device. 
  129. Your program will send a message to the timer device telling it to notify you
  130. in a certain amount of time.  When that time has elapsed, the timer device
  131. will reply to your message, placing the reply in the message port you created.
  132.  
  133. The CreatePort() function allows you to easily create message ports.  This
  134. function is not located in ROM, but rather in the amiga.lib (for Lattice C
  135. users) or c.lib (for Manx C users) link-time libraries.  This function
  136. allocates memory for the message port, allocates a signal bit to notify your
  137. program when a message has been received, and properly initializes the
  138. message port.  You pass it the name of the port you want to create and the
  139. priority of the port.  Since you will not be looking for this port with the
  140. FindPort() function, you can set the name of the port to NULL, and the system
  141. will not add the port to the public message port list.  The priority is a
  142. value between -128 and +127 that represents the importance of messages
  143. received in this port.  Unless you are creating multiple ports and expect
  144. contention between the ports, you can safely set this value to zero.  A
  145. pointer to the new message port is returned or NULL if the CreatePort()
  146. function fails.
  147.  
  148. The next step is to open the timer device for use by your program.  The
  149. OpenDevice() function requires four arguments: the name of the device, the
  150. unit number, a pointer to a timerequest structure, and a flags value.  The
  151. name of the timer device is always the same and is defined in the devices/
  152. timer.h include file as TIMERNAME.
  153.  
  154. The unit number can either be UNIT_VBLANK or UNIT_MICROHZ as defined in
  155. devices/timer.h.  The MICROHZ timer has a very high resolution of one
  156. microsecond, meaning it updates its counter one million times per second.  As
  157. you would expect, this requires quite a lot of overhead to maintain, and the
  158. MICROHZ timer can fall behind when the system is busy with other tasks. 
  159. Therefore, it should only be used for short, critical time measurements.  The
  160. VBLANK timer, on the other hand, is better suited for most applications.  It
  161. updates its counter sixty times per second, meaning it is accurate to within
  162. .0167 seconds (which is close to the resolution of the Delay() function) and
  163. requires very little overhead.  The VBLANK timer maintains its accuracy
  164. regardless of how busy the system is.
  165.  
  166. The next argument is a pointer to a timerequest structure that you have
  167. allocated.  The structure is defined as follows:
  168.  
  169.     struct timerequest
  170.     {
  171.         struct IORequest tr_node;
  172.         struct timeval tr_time;
  173.     };
  174.  
  175. The tr_node member is a standard IORequest structure used to communicate with
  176. Amiga devices.  It contains information such as a Message structure for
  177. communicating with the device, a pointer to the Device and Unit structures,
  178. etc.  Most of this information is used internally by Exec.  You just need to
  179. allocate memory for the timerequest structure and pass its address, and the
  180. OpenDevice() function will do the rest.
  181.  
  182. The final argument for the OpenDevice() function is the Flags variable.  This
  183. is not used by the timer device and should be set to zero.
  184.  
  185. If the OpenDevice() function fails, it will return a non-zero error number. 
  186. Notice that the way the timer_open() function is written, if either the
  187. CreatePort() or OpenDevice() function fails, the function will return 0, in
  188. which case your program should act accordingly.  Accessing the timer device
  189. without having successfully opened it will surely cause problems.
  190.  
  191. Assuming everything opened OK, a few entries in the timerequest structure
  192. need to be initialized.  First you need to set the mn_ReplyPort pointer in
  193. the Message structure of the timerequest to the timer_port you created.  This
  194. tells the timer device where to reply to messages you send it.  Next, you
  195. need to specify what you want the timer device to do.  (In addition to simple
  196. time counting, you can ask the timer device what time it is or even set the
  197. system time).  The TR_ADDREQUEST command indicates that you will be asking
  198. the timer device to count time.  Finally, setting the io_Flags value to zero
  199. indicates that you want normal I/O as opposed to Quick I/O.  With Quick I/O,
  200. the device will respond to your request immediately.  This is handy for a
  201. device such as the serial device in which your program may not want to wait
  202. for the device to service your request before responding.  But in this case,
  203. the goal is for the timer device to notify you after the specified time has
  204. elapsed.
  205.  
  206. While we are on the subject of opening the timer device, let's discuss
  207. closing it.  Your program should call the timer_close() function during
  208. program shutdown when you close other things such as libraries, windows, etc.
  209. Notice the timer_error, timer_port, and timer_req variables are global to all
  210. three functions, allowing each function to access them.  These variables act
  211. as their own "flags," indicating when the corresponding device or port has
  212. been opened.  For example, if the timer_error variable is zero, then the
  213. timer device has been opened and is consequently closed by the timer_close()
  214. function.  Also, if the timer_port variable is not NULL, then the timer
  215. message port has been created and is freed by timer_close().  The variables
  216. are initialized globally and then reset in the timer_close() function so that
  217. it is safe to call timer_close() even if timer_open() was never called.  This
  218. is handy in case your program terminates prematurely before calling
  219. timer_open() (for example, because you could not open a library).
  220.  
  221. Once you have opened the timer device, you can use the timer_wait() function
  222. to wait for a specified number of microseconds.  For example:
  223.  
  224.     timer_wait( 1000000L ); /* wait one second  */
  225.     timer_wait( 500000L );  /* wait one-half second */
  226.     timer_wait( 5000000L ); /* wait five seconds */
  227.  
  228. The first line in the timer_wait() function checks to make sure micros is not
  229. less than 2 microseconds.  A bug in Kickstart v1.3 or earlier will cause the
  230. system to crash if you specify 0 or 1 microseconds.  The next two lines
  231. break the microsecond value up into its seconds and microseconds components.
  232.  
  233. The next two lines fill in the amount of time you want to wait.  Even if the
  234. time was the same for each request, you must reinitialize it because the
  235. timer device destroys your old values (it actually uses the tv_secs and
  236. tv_micro structure members to count down your time).  The SendIO() function
  237. then sends your time request to the timer device.  By using SendIO() instead
  238. of DoIO(), your program will continue executing even though the timer device
  239. has not yet responded to your request (allowing you to perform an
  240. asynchronous wait if desired).
  241.  
  242. The next step is to wait until you receive a signal from the timer device
  243. that the specified amount of time has elapsed.  When the timer device
  244. finishes counting the time, it will reply to your time request by sending a
  245. message to the timer_port you created earlier.  When it places the message in
  246. your timer port, Exec will set the signal bit associated with that port. 
  247. Hence, you Wait() for that signal bit to be set.  There is no need to then
  248. ReplyMsg() since the timer device was replying to your original message.  The
  249. GetMsg() function then removes the message from the timer port.
  250.  
  251. As you may have guessed, the timer_wait() function is similar to the Delay()
  252. function in that it is a synchronous wait--the program halts until the
  253. specified amount of time has elapsed.  (Note that this is NOT a busy wait. 
  254. Your program sleeps while in the Wait() function.)  Using similar code,
  255. however, it is easy to create an asynchronous wait.
  256.  
  257. Suppose you want to create a clock that sits in a small window on the
  258. Workbench screen.  In addition to having the timer device notify you once
  259. each second so you can update the time, you would also want to monitor for
  260. the user clicking on the close gadget in the window to end the program.  You
  261. can create a function that is identical to timer_wait() except that it does
  262. not wait for the time to count down:
  263.  
  264.     /***************
  265.     *  TIMER START
  266.     ****************/
  267.  
  268.     /*
  269.     This function issues a request to the timer device to notify the program
  270.     in the specified number of microseconds.  This function does not wait for
  271.     a reply from the timer device.
  272.     */
  273.  
  274.     void timer_start( long micros )
  275.     {
  276.         long secs;
  277.         
  278.         /* a bug in Kickstart v1.3 requires this check */
  279.         if (micros < 2) return;
  280.  
  281.         secs = micros / MICROS_PER_SEC;
  282.         micros %= MICROS_PER_SEC;
  283.  
  284.         timer_req.tr_time.tv_secs  = secs;
  285.         timer_req.tr_time.tv_micro = micros;
  286.         SendIO( &timer_req.tr_node );
  287.     }
  288.  
  289. You could then wait for both window input and the timer device signal in the
  290. same Wait() call:
  291.  
  292.     struct Window *window;
  293.     struct IntuiMessage *imessage;
  294.  
  295.     Wait( 1L<<window->UserPort->mp_SigBit | 1L<<timer_port->mp_SigBit );
  296.  
  297.     while (imessage = (struct IntuiMessage *)GetMsg( window->UserPort ))
  298.     {
  299.         /* handle the window input */
  300.         ReplyMsg( (struct Message *)imessage );
  301.     }
  302.  
  303.     if (GetMsg( timer_port ))
  304.         /* time is up! */
  305.  
  306. A word of caution here--never use a timerequest structure that is currently
  307. being serviced by the timer device.  In other words, don't send a new time
  308. request until the previous time request is complete (though you could create
  309. multiple time requests by allocating multiple timerequest structures).  If
  310. for some reason you want to cancel a time request (so you could issue
  311. another, for example), you need to use the AbortIO() function:
  312.  
  313.     /***************
  314.     *  TIMER ABORT
  315.     ****************/
  316.  
  317.     /*
  318.     This function cancels an existing time request.
  319.     */
  320.  
  321.     void timer_abort( void )
  322.     {
  323.         AbortIO( &timer_req.tr_node );
  324.         Wait( 1L<<timer_port->mp_SigBit );
  325.         GetMsg( timer_port );
  326.     }
  327.  
  328. The AbortIO() function will force the timer device to respond to your request
  329. immediately whether or not the requested time has elapsed.  The Wait()
  330. function then clears the signal bit, and GetMsg() removes the reply message
  331. for the aborted request.  It is safe to call AbortIO() even if the time
  332. request may have been satisfied.  However, there MUST be a pending time
  333. request (satisfied or not) or the system will crash.
  334.  
  335. As you can see, using the timer device is not only easy but has many
  336. advantages over the old for-loop method:  1) it's not a "busy" wait, 2) it
  337. can be asynchronous, and 3) you are guaranteed of waiting an exact amount of
  338. time regardless of the hardware conditions.
  339.  
  340.  
  341.  
  342. REFERENCES
  343.  
  344. Commodore-Amiga, Inc., Amiga ROM Kernal Manual: Libraries and Devices,
  345. Addison-Wesley Publishing Company, Inc., New York, 1989, pp. 289-98, 871-82.
  346.  
  347. Mortimore, Eugene P., Amiga Programmer's Handbook: Volume II, SYBEX, Inc.,
  348. San Francisco, 1987, pp. 305-20.
  349.