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 ------------------------------------------