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