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)