[comp.sys.ibm.pc.programmer] millisecond timing from Turbo C

pts@mendel.acc.Virginia.EDU (Paul T. Shannon) (06/13/90)

Does anyone have tested code for millisecond timing on a (generic) DOS PC?
We use Turbo C 2.0.  Our goal is to measure response times to visual
stimuli, with millisecond accuracy.  Any tips, leads, caveats or source
code will be appreciated.  Thanks.

 - Paul Shannon
   pts@virginia.edu 

cze2529@dcsc.dla.mil ( David E Gaulden) (06/13/90)

In article <1990Jun12.214254.7686@murdoch.acc.Virginia.EDU> pts@mendel.acc.Virginia.EDU (Paul T. Shannon) writes:
>
>Does anyone have tested code for millisecond timing on a (generic) DOS PC?
>We use Turbo C 2.0.  Our goal is to measure response times to visual
>stimuli, with millisecond accuracy.  Any tips, leads, caveats or source
>code will be appreciated.  Thanks.

There is a very good profiler timer program floating around the shareware
market.  It is called TCHRT V2.00.  It is a Turbo C High Resolution Timer
Toolbox ( also written for MSC and TP ) by Ryle Design.  CIS 73047,1765
P.O. Box 22, Mt. Pleasant, Michigan 48804

Allows up to 100 high res timers within your program.  It is a library
and is easy to use.

Registration for this product is all of $7.50. 
-- 

"Man who says, 'It cannot be done', should not interrupt man who is doing it."

 Dave Gaulden cze2529@dcsc.dla.mil

antoniop@cernvax.UUCP (antonio pastore) (06/14/90)

Hi netlanders,

having noticed a certain interest, on the net, on a millisecond timer
for the PC (XT/AT), I decided to post some routines that I wrote some
time ago and I am still using.

Because in general I don't like to write wery low level stuff in high
level languages (as soon you change the compiler you need to rewrite
everything...), the presented routines are in ASSEMBLER (TASM or MASM)
but they are designed to be interfaced with TURBO C or MSC C. Indeed I
am using them in that way, enbedded in a large C program.

In this package, there are the following routines:

SetTicks(unsigned value), RestoreTick(), Interc08() and Release08().

The basic entry point, if you want, is the routine 'SetTicks()'. This
routine allows you to SET the timer to generate interrupts to a certain
RATE (the "value" parameter). To restore the timer to its original
value, use the RestoreTick() routine.

Suppose, for example, you want to set up the timer for an interruption rate
of 1000 ticks per second. Then you call:

	SetTicks(1000);

To restore it to the usual value:

	RestoreTick();


In addition to these two routines, you need to intercept the INT 08h
and restore it at the end  of the program. You can do it by calling
Interc08() and Release08() routines.

This is a sckeleton of a possible program:

MyCroutine()
{
	/* Your code here, to process interruption */
}

main()
{
	Interc08();	/* Intercept the INT 08h */
	SetTicks(1000);	/* Set 1000 interruptions per second */
.
.
.
	RestoreTick();	/* Restore tick */
	Release08();	/* Release INT 08H */
}

Some additional hints

1) The NEW INT 08H routine assumes that an external function (written in C)
exists and has name "MyCroutine()".

2) The program has been designed to run in LARGE model.

3) During the processing of the INT 08H, interrupts from the timer
are disabled. Anyway I suppose that if you need a milliseconds timer,
you don't want to run the 'sieve' benchmark during its processing....
Keep you code SHORT and FAST inside the MyCroutine() function!

-----

Enjoy yourself,
		Antonio Pastore

Antonio Pastore, CERN, SWITZERLAND
antoniop@cernvax.cern.ch

PS: The software here presented is put in the PUBLIC DOMAIN.

------



;**********************************************************************

; FILE: L8086.MAC

;**********************************************************************
;
; This macro library defines the operating environment for the 8086 LARGE 
; memory model using MSC and TURBO 'C' Compiler.
;
;	Written by Antonio Pastore, 19 Dec 1986 - Torino - ITALY
;
; Updated:
;		30 May 1989
;**********************************************************************
;
;**********************************************************************
;
; The DSEG and PSEG macros are defined to generate the appropriate GROUP
; and SEGMENT statements for the memory model being used.  The ENDDS and
; ENDPS macros are then used to end the segments.
;
;**********************************************************************

_DATA	SEGMENT  WORD PUBLIC 'DATA'
_DATA	ENDS

	IFDEF MSC				; For MSC 5.x ONLY!
CONST	SEGMENT  WORD PUBLIC 'CONST'
CONST	ENDS
	ENDIF

_BSS	SEGMENT  WORD PUBLIC 'BSS'
_BSS	ENDS

	IFDEF MSC				; For MSC 5.x ONLY!
DGROUP	GROUP	CONST,	_BSS,	_DATA
	ASSUME  DS: DGROUP, SS: DGROUP
	ELSE
DGROUP	GROUP	_BSS,	_DATA
	ENDIF

DSEG	MACRO	
_DATA	segment word public  'DATA'
	assume	ds:DGROUP, ss:DGROUP, es:DGROUP
	ENDM
;
ENDDS	macro
_DATA	ends
	endm
;
PSEG	macro name
name&_TEXT	segment byte public 'CODE'
	assume	cs:name&_TEXT
	endm
;
ENDPS	macro name
name&_TEXT	ends
	endm
;
;**********************************************************************
;
; The FUNCTION, EXIT_FUNC and ENTRY macros establish appropriate function entry
; points depending on whether NEAR or FAR program addressing is being used. The
; only difference between the two is that FUNCTION generates a PROC operation
; to start a segment.  PAR_BASE is the minimum displacement for paramters.
;
;**********************************************************************
;
PAR_BASE	equ	bp + 6		; parameter base displacement
LOCAL_BASE	equ	bp - 2		; LOCALS base displacement
SIZEPTR		equ	4		; size of pointers
LARGECODE	equ	1		; define model    
SMALLCODE	equ	0		; define model
LARGEDATA	equ	1		; define model
SMALLDATA	equ	0		; define model
		
;**********************************************************************

FUNCTION	MACRO name, args	; Function entry declaration
	PUBLIC name			;
name	PROC FAR			;
	push	bp			; save base pointer
	mov	bp, sp			; create new frame
	IFNB	<args>			;
	IF	args			;
	sub	sp, args		; Create locals
	ENDIF				;
	ENDIF				;
	; NOTE: DS not loaded
	push	si
	push	di
	ENDM				;
					;
EXIT_FUNC	MACRO name		; Function exit declaration
	pop	di
	pop	si
	mov	sp, bp			; Delete locals, if any
	pop	bp			;
	ret				;
name	ENDP				;
	ENDM				;

;**********************************************************************

LOAD_DS	MACRO	NAME			;
	push	ds			;
	mov	ax, DGROUP		; Satisfy ASSUME statement
	mov	ds, ax			;
	ENDM				;

;**********************************************************************

ENTRY	MACRO	NAME			;
	PUBLIC	NAME			;
NAME	LABEL	FAR			;
	ENDM				;

SAVE_REG	macro
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	es
	push	ds
	endm
;
REST_REG	macro
	pop	ds
	pop	es
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	endm

	DSEG
; Declare an empty DATA segment
	ENDDS

;******************** END OF FILE ********************


;**********************************************************************

; FILE: 8259TIM.ASM

;**********************************************************************

; You should include the following file (previous listing)

;	INCLUDE	L8086.ASM	

;**** TIMER 8259 values related.
;* Some explanation:
;* The 8259 has 3 16 bit counters, and counter 0 is used to provide
;* a cyclic interrupt. When you load counter 0 with a certain 16 bit value,
;* the 8259 starts to count DOWNWARD and when it reaches 0 first it
;* issues an interrupt, then reloads the counter and starts again the process.
;* Because the 8259 counts on tick of 1.19 MHz (that is, it is driven
;* at that clock), the current formula to calculate the rate of interruption
;* is:
;*	RATE = 1190000 / VALUE	(result in HERTZ)
;* The standard value used in the PC is 0 (= 65536!!!), so it is possible
;* to have RATE = 18.2 Hz. In our environment the RATE of 100 Hz would be
;* preferable, so we set value to 11900.
;* Because some part of the system BIOS and MSDOS still expect to receive
;* interrupts at the rate of 18.2 Hz, we use the following trick to call
;* the original INT 08H still at 18.2 Hz.
;* Each time the 8259 issues an interrupt and calls our INT 08H handler,
;* this means that Ticks4Second ticks of 1.19MHz are elapsed. Every
;* 65536 1.19MHz ticks original INT 08H must be called, so we keep a
;* variable (ClockModulus) the contains the 16 bit 'modulus' of the sum
;* of the 1.19 ticks elapsed. Every time this variable owerflows after
;* the addition means that 65356 1.19 Mhz ticks are elapsed, and we
;* need to call original INT 08H handler.
;************************************************************************
;TICKS_SECOND	EQU	100D	; 100 timer ticks per second
;CLOCKDIVISOR	EQU	11900D	; TIMER 8259 divisor (11900 for 100 Hz)

	DSEG		; This is the data SEGMENT
Ticks4Second	dw	0	; 
ClockModulus	dw	0	; See previous explanation

old_int08_vector label	dword	; Contains OLD INT 08H entry point
old_int08_offs	dw	?	;
old_int08_seg	dw	?	;

	ENDDS

	EXTRN	_MyCroutine:	FAR	; Far call outside ANY segment!

	PSEG
;****************************************************************************
;****** SetClockSpeed
;* Set 8259 TIMER 0
;* On entry, AX = value to put in the timer
;* Remember the the rule is:
;*   AX = 1190000 [1.19 MHz] / Number_of_timer_ticks_requested
;*   If AX = 0 (= 65536), the 1.19Mhz / 65536 = 18.15 (the standard value)
;****************************************************************************
SetClockSpeed	proc near
	; -------- Load counter 0 of 8259
	push	ax		; Save AX for a while
	mov	al, 00110110B	;
	out	043H, al	;
	pop	ax		; Restore AX
	out	040H, al	; Output lower part
	xchg	ah, al		; 
	out	040H, al	; Output higher part
	xchg	ah, al		; Restore AX
	ret
SetClockSpeed	endp

;****************************************************
;	NEW INTERRUPT 08H ENTRY POINT (On entry INTERRUPTs disabled)
; Note: some part of this routine uses the USER stack!
;****************************************************

new_int08 proc far

	push	ds		; SAVE DS
	push	ax		; Save AX

	mov	ax, DGROUP	; Satisfy ASSUME statement
	mov	ds, ax		;



;**** INSERT HERE YOUR CODE. For example:

	call	_MyCroutine



	; Should we call BIOS?
	mov	ax, Ticks4Second ;
	add	ClockModulus, ax ; Add divisor
	jnc	ts_1proceed	; Do not CALL original int 08H

;********************************************************************

	pushf				; Simulate an INT instruction
	call	old_int08_vector	; Call OLD routine
	jmp	short ts_2proceed	; Skip PIC reset

;*********************************************************************

ts_1proceed:
	mov	al, 00100000B	; ???? (magic value ...)
	out	020H, al	; Reset PIC

ts_2proceed:
	pop	ax		; Restore AX
	pop	ds		; Restore DS
	
	iret			; Return to caller

new_int08 endp

;*********************************************************************
;			Miscellanea
;*********************************************************************

	FUNCTION	_SetTicks

; The following symbol is a PARAMETER!!!
TTICKS	EQU	[PAR_BASE]	; Ticks per second value

	;-------- Modify TIMER CLOCK speed to TTICKS HZ
	mov	bx, TTICKS	; ? HZ
	cmp	bx, 19D		; This is the higest rate
	jae	set_ticks	; OK
	mov	bx, 19D		; Force to this value

set_ticks:
	; Load in DX:AX 1190000 decimal
	mov	dx, 18D		;
	mov	ax, 10352D	; (18 * 65536) + 10352 = 1190000
	div	bx		; AX = value to be used!!!
	
	mov	Ticks4Second, ax	;
	call	SetClockSpeed	; Set it
	mov	ClockModulus, 0	; Clear modulus

	EXIT_FUNC	_SetTicks

;*************************************************************************

	FUNCTION	_RestoreTick

	;-------- Modify TIMER CLOCK speed to 18.2 HZ
	xor	ax, ax		; 18.2 Hz
	call	SetClockSpeed	; Set it
	mov	ClockModulus, 0	; Clear modulus

	EXIT_FUNC	_RestoreTick

;*************************************************************************

	FUNCTION _Interc08

	; Ask DOS for INT 8H address handler
	mov	ah, 35h		;
	mov	al, 8		;
	INT	21H		; Call DOS
	mov	old_int08_offs,bx	; Save for later use
	mov	old_int08_seg,es	;

	push	ds
	push	cs		; DS = CS
	pop	ds		;
	
	; Say to DOS to set interrupt handler 08H
	mov	ah, 25h		;
	mov	al, 08H		;
	mov	dx, offset CS:new_int08	;
	INT	21H		;

	pop	ds		;

	mov	ax, 0		; Return OK

	EXIT_FUNC	_Interc08

;*************************************************************************

	FUNCTION _Restore08

	;-------- Modify TIMER CLOCK speed to 18.2 HZ
	xor	ax, ax		; 18.2 Hz
	call	SetClockSpeed	; Set it

	push	ds		; Save DS

	; Restore INT 08H
	mov	dx, old_int08_offs
	mov	ax, old_int08_seg
	mov	ds, ax
	mov	ah, 25h
	mov	al, 08H
	int	21h

	pop	ds		; Restore DS

	mov	ax, 0		; Return OK

	EXIT_FUNC	_Restore08

	ENDPS

; END of FILE ------------------------------------------