[comp.lang.c] PRECISE TIMING ON IBM PC/AT COMPATIBLE

trejo@nprdc.arpa (Leonard J. Trejo) (07/21/89)

Say, we're trying to measure human reaction times (<1ms) by timing the
interval between a video field and a keypress (on the keyboard) on 
our PC-compatible (Compaq 386/25) using the C subroutine call 
"ftime()", which purports to provide millisecond accuracy (MSC 
5.1).  Some net-news discussions I've followed say that the 
precision of ftime() may actually be _worse_ than 1 ms (say 8 ms), 
even though the time returned is measured in ms.  So, I thought I'd 
ask to see how others accurately measure intervals on PC's.  Would 
you be kind enough to send me a short description of how you do it 
and/or some example programs?

			L. J. T.

============================================================================
ARPANET : trejo@nprdc.navy.mil		UUCP:  ucsd!nprdc!trejo

U.S. Mail: Leonard J. Trejo, Ph. D.	Phone: (619) 553-7711
	   Neuroscience Laboratory		(AV) 553-7711
	   NPRDC, Code 141
	   San Diego, CA 92152-6800

Bob.Stout@p6.f506.n106.z1.fidonet.org (Bob Stout) (07/24/89)

In an article of <20 Jul 89 21:39:07 GMT>, (Leonard J. Trejo) writes:

 >Say, we're trying to measure human reaction times (<1ms) by timing the
 >interval between a video field and a keypress (on the keyboard) on 
 >our PC-compatible (Compaq 386/25) using the C subroutine call 
 >"ftime()", which purports to provide millisecond accuracy (MSC 5.1).

  Most PC C compilers use the basic system clock interrupt interval (18.2 Hz  
or 55 msec.) for their minimum timing resolution. Following is a public domain  
source for achieving 1 usec. timing accuracy in a PC or close compatible. I've  
used it on Compaqs before and it should work just fine for your application.  
Note that this code has a few rough spots. I adapted it for use in a  
commercial library which is considerably cleaner. This should get you going,  
but if you want more info on the library (it's currently only available for TC  
and ZTC although MSC should be out in the next few months), let me know...
--------------------------------- Cut here ---------------------------------
/* clock.c -- Microsecond resolution clock routine.      */
/*                                                       */
/*  Written by David L. Fox.                             */
/*  Implements in C the timer chip tweaking described by */
/*  Byron Sheppard, _Byte_, Jan 1987, p 157-164.         */
/*  Replaces standard clock() from the library.          */
/*  The definition of CLK_TCK in time.h may have to      */
/*  be changed to 1000000L.                              */
/*  Does not correctly handle intervals spanning         */
/*  midnight or intervals greater than about 6 hrs.      */

#include    <time.h>
 
/* Interrupt handling and i/o ports are compiler dependent.  */
/* The following set of preprocessor directives selects the  */
/* correct include files and macros for various compilers.   */

#ifdef      __ZTC__
#include    <dos.h>
#include    <int.h>
#define     inportb     inp
#define     outportb    outp
#else
#ifdef      __TURBOC__
#include    <dos.h>
#define     int_off     disable
#define     int_on      enable
#else
#error Unknown compiler
#endif
#endif
 
/* Constants */

#define CONTVAL   0x34   /* == 00110100 Control byte for 8253 timer.    */
                  /* Sets timer 0 to 2-byte read/write, mode 2, binary. */
#define T0DATA    0x40    /* Timer 0 data port address.                 */
#define TMODE     0x43    /* Timer mode port address.                   */
#define BIOS_DS   0x40    /* BIOS data segment.                         */
#define B_TIKP    0x6c    /* Address of BIOS (18.2/s) tick count.       */
#define SCALE    10000    /* Scale factor for timer ticks.              */

/* The following values assume 18.2 BIOS ticks per second resulting from
   the 8253 being clocked at 1.19 MHz.                                  */

#define us_BTIK  54925    /* Micro sec per BIOS clock tick.             */
#define f_BTIK    4595    /* Fractional part of micro sec per BIOS tick.*/
#define us_TTIK   8381    /* Micro sec per timer tick * SCALE. (4/4.77 MHz) */
 
clock_t
clock(void) {
    unsigned char msb, lsb;
    unsigned int tim_ticks;
    static int init = 0;
    unsigned long count, us_tmp;
    static unsigned long init_count;
 
    if (0 == init) {
        init = 1;     /* This is the first call, have to set up timer.  */
        int_off();
        outportb(TMODE, CONTVAL);   /* Write new control byte to timer. */
        outportb(T0DATA, 0);        /* Initial count = 0 = 65636.       */
        outportb(T0DATA, 0);
        init_count = *(unsigned long int far *)MK_FP(BIOS_DS, B_TIKP);
        int_on();
        return 0;                   /* First call returns zero.         */
    }
    int_off();    /* Don't want an interrupt while getting time.        */
    outportb(TMODE, 0);               /* Latch count.                   */
    lsb = inportb(T0DATA);            /* Read count.                    */
    msb = inportb(T0DATA);

    /* Get BIOS tick count (read BIOS ram directly for speed and
       to avoid turning on interrupts).                                 */

    count =  *(unsigned long far *)MK_FP(BIOS_DS, B_TIKP) - init_count;
    int_on();                   /* Interrupts back on.                  */
    tim_ticks = (unsigned)-1 - ((msb << 8) | lsb);
    us_tmp = count*us_BTIK;
    return us_tmp + ((long)tim_ticks*us_TTIK + us_tmp%SCALE)/SCALE;
}