[comp.sys.ibm.pc.hardware] Accurate timing on AT-computer - here's ASM source

alan@oetl1.scf.lmsc.lockheed.com (Alan Strassberg) (05/27/91)

In article <1991May23.152030.7710@ugle.unit.no> gunnarkn@itk.unit.no (Gunnar Knutson) writes:
>
>-- 
>	I need to time the interval between two interrupts with
>accuracy in the milli-seconds range. The maximum resolution on the AT
>real time clock is one second, so I can't use that. Does anyone out
>there have a better idea ?

	Here's the ASM source to do it. From Dr. Dobbs.

				alan

################################################################################
;TITLE:  software performance analyzer
;DESCRIPTION:  fully compensated, high resolution timer.
;Internal timing resolution = 838 ns.
;CALLING SEQUENCE:  call TIMER_START (FAR call)
;                   code to be timed
;                   call TIMER_STOP (FAR call)
;OUTPUT:  Display of elapsed time between START and STOP calls
;STACK REQUIREMENTS:  10 bytes
;SPECIAL NOTES:  timing events > 54.925 ms requires interrupts
;                 to be enabled.

_DATA		segment word 'data' public
timer_low		equ 	ds:[006ch]
bios_dataseg	equ 	0040h
timer_mode		equ 	43h
timer0		equ 	40h
count		dw	0	;# of interrupt ticks
				;(54.925 ms)
count_micro		dw	0
count_milli		dw	0
timer_micro		dw	0	;from 8253 countdown
timer_milli		dw	0	;final value
timer_sec		dw	0	;final value
max_count		dw	65535	;65536 ticks in a full count
adjustm		dw	16	;compensation factor
timer_convert	dw	8381	;838.096 nsec per tick
count_convert	dw	54925	;54.925 msec per count
ten_thousand	dw	10000
five_thousand	dw	5000
thousand		dw	1000
ten		dw	10
message_sec		db	'Seconds:  ','$'
message_milli	db	'Milli-seconds:  ','$'
message_micro	db	'Micro-seconds:  ','$'
ASCII_string	db	5 dup('d'),0dh,0ah,'$'

_DATA		ends

;print macro
print_string	macro	;DOS function to print strings
		mov ah,9	;pointed to by DS:DX
		int 21h
		endm

	public	_timer_start,_timer_stop,bin_asc

_TEXT		segment byte 'code' public
		assume cs:_text,ds:_data

_timer_start	proc far
		push bp
		mov bp,sp
		push ax
		push dx
		push ds
		mov dx,_DATA
		mov ds,dx
		mov timer_micro,0
		mov timer_milli,0
		mov timer_sec,0

;initialize counter 0 of 8253 timer
		mov al,00110100B	;ctr 0, LSB then MSB
				;mode 2, binary
		out timer_mode,al	;mode register of 8253
		sub ax,ax
		out timer0,al	;LSB first
		out timer0,al	;MSB next

;read current bios time of day
		mov dx,bios_dataseg	;point to bios data segment
		mov ds,dx
		mov ax,timer_low	;get count
		mov dx,_DATA	;point to my data
		mov ds,dx
		mov count,ax	;save count
		pop ds
		pop dx
		pop ax
		pop bp
		ret
_timer_start	endp

_timer_stop		proc far
		push bp
		mov bp,sp
		push ax
		push bx
		push dx
		push ds
		mov ax,_DATA
		mov ds,ax

;elapsed time since TIMER_START consists of:
;  1) timer count intervals - 840 ns
;  2) interrupt ticks - 54 ms

;read counter 0 of 8253 timer

		mov al,0h		;latch counter for read
		cli		;interrupts off until
				;bios tod is read
		out timer_mode,al	;8253 mode register
		in al,timer0
		mov dl,al
		in al,timer0
		mov dh,al		;dx has 16 bit timer count

;calculate the time due to 8253 counting
		mov ax,max_count
		sub ax,dx		;timer count value
		mul timer_convert	;get in usable form
		div ten_thousand	;gives time in usec
		mov timer_micro,ax	;save usec, round nsec
		cmp dx,five_thousand
		jb cont		;round down
		inc timer_micro	;round up

;get bios time due to interrupt ticks

cont:		mov dx,bios_dataseg	;point to bios data segment
		mov ds,dx
		mov ax,timer_low
		mov dx,_DATA
		mov ds,dx
		sti		;interrupts OK now
		sub ax,count	;now have # of 54 ms ticks
		mul count_convert	;get in usable form
		div thousand
		mov count_milli,ax	;save millisecond part
		mov count_micro,dx	;save usec part
		
;check for jitter

		cmp ax,0		;check if elapsed time is small
		jne jitter_ok	;if not, don't worry
		mov ax,adjustm
		cmp timer_micro,ax
		jae jitter_ok	;no jitter so OK
		mov timer_micro,ax	;else, fix up

;combine timer and count values, put result in timer variables.

jitter_ok:		mov ax,dx		;get count_micro
		add ax,timer_micro	;sum micro fields
		cmp ax,adjustm	;check for underflow
		jae compensate	;go ahead - safe
		dec count_milli	;borrow
		add ax,1000

compensate:		sub ax,adjustm	;compensate for time delays
		mov timer_micro,ax
		cmp ax,1000		;check for field overflow
		jb fld_ok
		sub dx,dx
		div thousand
		mov timer_milli,ax
		mov timer_micro,dx

fld_ok:		mov ax,count_milli
		add timer_milli,ax
		cmp timer_milli,1000
		jb display
		sub dx,dx
		mov ax,timer_milli
		div thousand
		mov timer_sec,ax
		mov timer_milli,dx

;display results

display:		lea dx,message_sec
		print_string
		lea bx,ASCII_string
		mov ax,timer_sec
		call bin_asc
		mov dx,bx
		print_string
		lea dx,message_milli
		print_string
		lea bx,ASCII_string
		mov ax,timer_milli
		call bin_asc
		mov dx,bx
		print_string
		lea dx,message_micro
		print_string
		lea bx,ASCII_string
		mov ax,timer_micro
		call bin_asc
		mov dx,bx
		print_string
		pop ds
		pop dx
		pop bx
		pop ax
		pop bp
		ret
_timer_stop		endp

;binary to ASCII conversion routine
; Entry bx = pointer to string buffer
;       ax = unsigned binary number
; Exit bx = pointer to ASCII number

bin_asc		proc near
		push dx
		push cx
		push ax
		mov cx,5
clear_buf:		mov byte ptr [bx],30h
		inc bx
		loop clear_buf
convert:		sub dx,dx
		div ten
		add dx,30h
		dec bx
		mov [bx],dl
		or ax,ax
		jnz convert
		pop ax
		pop cx
		pop dx
		ret
bin_asc		endp
_TEXT		ends
		end

				


-- 
Alan Strassberg             alan@oetl1.scf.lmsc.lockheed.com
(408) 425-6139              alan@oetl.UUCP