MILLISEC.TXT

Dean Pentcheff
Department of Integrative Biology
University of California at Berkeley
dean@violet.berkeley.edu

The code here provides PC programs with a millisecond resolution timer.  It
is entirely based on "tctimer" 1.0 by Richard S. Sadowsky (released to the
public domain 8/10/88) which, in turn, is based on "tptime" by Brian Foley
and Kim Kokkonen of TurboPower Software (also public domain).  I have
slightly rewritten the code to conform to C language conventions (in tctimer,
results were passed as argument pointers rather than function returns).  In
this version, elapsedtime() returns a time in seconds rather than
milliseconds, saving a floating point division by 1000.0.

These routines reprogram the timer chip, so they probably won't work if your
program does the same.  Other than that, there should be no incompatibility
problems.  They do not interfere with Turbo's sound() and nosound()
functions.

I've tested these under Turbo C 2.0.  Note that the file tctimer.c must be
separately compiled with the standalone "tcc" compiler since it uses inline
assembly.  Attempting to compile it under "tc" will result in compile-time
error messages.

The routine "initializetimer()" MUST be called first to reset the timer.  The
calling program MUST also call "restoretimer()" before exiting to reset the
timer to its original state.


The following routines are included (see testtimer.c for examples):

/*Reprogram the timer chip to allow 1 microsecond resolution*/
void initializetimer(void);

/*Read the timer with 1 microsecond resolution*/
long readtimer(void);

/*Calculate time elapsed (in seconds) between Start and Stop*/
double    elapsedtime(long start, long stop);

/*Restore the timer chip to its normal state*/
void restoretimer(void);


Limitations
-----------
Because long integers are used to represent time, TCTIMER cannot be used to
time events longer than about 60 minutes:

  4,294,967,295 (= $FFFFFFFF, largest unsigned value represented by longint)
/     1,193,181 (timer resolution in counts/second)
---------------
          3,599
        /    60 (seconds/minute)
        -------
           59.9 minutes

This should hardly be a problem, however, since an event longer than an hour
presumably doesn't need to be timed with 1-microsecond accuracy anyway.

Also note that the process of reading the time takes time. Hence, results of
timing very short events will be skewed by the overhead of reading the timer.
The following table shows the time measured between two calls to ReadTimer,
one right after the other.

  Toshiba 1000 (4.77MHz 8088)    125 microseconds  (tctimer)
  ATT 6300 (8MHz 8086)            53     "         (tctimer)
  Deskpro 286 (8MHz 80286)        35     "         (tctimer)
  Sperry IT (7.1MHz 286, 0 wait)  32     "         (tctimer)
  IBM PS/2 model 50               25     "         (tctimer)
  PC Designs GV386 (16MHz)        27     "         (tctimer)
  PC Source Standard 286 (10MHz)  21     "         (these routines)




--SOURCE CODE FOLLOWS--
--CUT FILES BELOW AT DOTTED LINES--

--MAKEFILE------------------------------------------------------------------
# makefile for either Borland's brain-dead make, or Ndmake

OBJS = testtime.obj timer.obj
MODEL = s
TCINC = d:\;c:\usr\tc\include
TCLIB = d:\;c:\usr\tc\lib

testtime.exe:	$(OBJS)
	tcc -o$*.exe $(CFLAGS) -m$(MODEL) -I$(TCINC) -L$(TCLIB) $(OBJS)

timer.c:	timer.h
testtime.c:	timer.h timer.c

.c.obj:
	tcc -c $(CFLAGS) -m$(MODEL) -I$(TCINC) $<


--TESTIME.C-----------------------------------------------------------------
#include	
#include	
#include	
#include	"timer.h"

void main()
{
	long	start_time;
	long	stop_time;
	double	time;

	initializetimer();
/*
	delay(100);
*/

	start_time=readtimer();
	stop_time=readtimer();
	time = elapsedtime(start_time, stop_time);
	printf("time between succesive calls: %f s  (%f ms)\n",
		time, time*1000.0);

	start_time=readtimer();
/*
	delay(2);
*/
	stop_time=readtimer();
	time = elapsedtime(start_time, stop_time);
	printf("time of a 'delay(2)' call   : %f s  (%f ms)\n",
		time, time*1000.0);

	printf("time until you press a key...");
	start_time = readtimer();
	getch();
	stop_time = readtimer();
	time = elapsedtime(start_time, stop_time);
	printf("\b\b\b\b\b\b\b\b\bed a key: %f s  (%f ms)\n",
		time, time*1000.0);

	restoretimer();
}


--TIMER.C------------------------------------------------------------------
#include	
#include	"timer.h"
/*#pragma		inline*/

#define TimerResolution    1193181.667

double
cardinal(long l)
{
	return((l<0)?4294967296.0 + (long)l : (long)l);
}

double
elapsedtime(long start, long stop)
{
	return(cardinal(stop - start) / TimerResolution);
}

void
initializetimer(void)
{

  outp(0x043,0x034);
  _asm\
  {
   jmp short NullJump1

NullJump1:;
  }
  outp(0x040,0x000);
  _asm\
  {
   jmp short NullJump2

NullJump2:;
  }
  outp(0x040,0x000);

}

void
restoretimer(void)
{
  outp(0x043,0x036);
  _asm\
  {
   jmp short NullJump1

NullJump1:;
  }
  outp(0x040,0x000);
  _asm\
  {
   jmp short NullJump2

NullJump2:;
  }
  outp(0x040,0x000);

}

/* #pragma warn -rvl*/	/* shut up Turbo C warning about return value */
long
readtimer(void)
{
  _asm\
  {
  cli				/* Disable interrupts */
  mov  dx,020h		/* Address PIC ocw3   */
  mov  al,00Ah		/* Ask to read irr    */
  out  dx,al
  mov  al,00h		/* Latch timer 0 */
  out  043h,al
  in   al,dx		/* Read irr      */
  mov  di,ax		/* Save it in DI */
  in   al,040h		/* Counter --> bx*/
  mov  bl,al		/* LSB in BL     */
  in   al,040h
  mov  bh,al		/* MSB in BH     */
  not  bx			/* Need ascending counter */
  in   al,021h		/* Read PIC imr  */
  mov  si,ax		/* Save it in SI */
  mov  al,00FFh		/* Mask all interrupts */
  out  021h,al
  mov  ax,040h		/* read low word of time */
  mov  es,ax		/* from BIOS data area   */
  mov  dx,es:[06Ch]
  mov  ax,si		/* Restore imr from SI   */
  out  021h,al
  sti				/* Enable interrupts */
  mov  ax,di		/* Retrieve old irr  */
  test al,001h		/* Counter hit 0?    */
  jz   done			/* Jump if not       */
  cmp  bx,0FFh		/* Counter > 0x0FF?    */
  ja   done			/* Done if so        */
  inc  dx			/* Else count int req. */
done:;
  mov ax,bx			/* set function result */
  }
}
/*#pragma warn .rvl*/	/* reset Turbo C return value warning */

--TIMER.H-------------------------------------------------------------------
/* timer.h
 * Include file defining the timer routines.
 * Include this file in any program using them.
 */

/*Calculate time elapsed (in seconds) between Start and Stop*/
double	elapsedtime(long start, long stop);

/*Reprogram the timer chip to allow 1 microsecond resolution*/
void	initializetimer(void);

/*Restore the timer chip to its normal state*/
void	restoretimer(void);

/*Read the timer with 1 microsecond resolution*/
long	readtimer(void);


--TESTIME.MAK---------------------------------------------------------------
PROJ	=TESTTIME
DEBUG	=1
CC	=qcl
CFLAGS_G	= /AS /W1 /Za 
CFLAGS_D	= /Zi /Zr /Gi$(PROJ).mdt /Od 
CFLAGS_R	= /Od /DNDEBUG 
CFLAGS	=$(CFLAGS_G) $(CFLAGS_D)
LFLAGS_G	= /CP:0xffff /NOI /SE:0x80 /ST:0x800 
LFLAGS_D	= /INCR 
LFLAGS_R	= 
LFLAGS	=$(LFLAGS_G) $(LFLAGS_D)
RUNFLAGS	=
OBJS_EXT = 	
LIBS_EXT = 	

all:	$(PROJ).exe

testtime.obj:	testtime.c

timer.obj:	timer.c

$(PROJ).exe:	testtime.obj timer.obj $(OBJS_EXT)
	echo >NUL @<<$(PROJ).crf
testtime.obj +
timer.obj +
$(OBJS_EXT)
$(PROJ).exe

$(LIBS_EXT);
<<
	ilink -a -e "link $(LFLAGS) @$(PROJ).crf" $(PROJ)

run: $(PROJ).exe
	$(PROJ) $(RUNFLAGS)