Is there a better TIMER in dos?

DOS specific questions.
Post Reply
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Is there a better TIMER in dos?

Post by lassar »

I was trying out the hrtimer routine in Virtual PC and DosBox, and it just didn't work.

Is there a hig resolultion timer routine that will work in DosBox?
monochromator
Posts: 42
Joined: Mar 05, 2013 5:37

Re: Is there a better TIMER in dos?

Post by monochromator »

It depends on what you want from the timer. If you just want to measure the time, the easiest way
to combine the BIOS timer counter (it is located at memory address 0:&H46C) with the precise current
value of the zero channel PIT counter.
BIOS timer can be received with function DOSMemoryRead.
Zero channel PIT counter can be obtained through a direct work with ports.
Simple example:

Code: Select all

 '$LANG: "fblite"
 '$INCLUDE: 'dos/dpmi.bi'
 '$INCLUDE: 'dos/go32.bi'
 '$INCLUDE: 'dos/sys/farptr.bi'

 FUNCTION GetPreciseTimer! ()
  'Function return current time in microseconds.
  DOSMEMGET &H46C, 4, @BIOSTimer& 'read BIOS timer counter
  OUT &H43, 0 'latch PIT counter for zero channel
  LowBytePIT& = INP(&H40) 'low byte of PIT counter
  HighBytePIT& = INP(&H40) 'high byte of PIT counter
  PITValue& = (HighBytePIT& SHL 8) + LowBytePIT& 'calculate PIT counter
  'PIT work frequency is 1193180 Hertz
  GetPreciseTimer! = (CSNG(BIOSTimer&) * 65536 + PITValue&) / 119318 * 100000
 END FUNCTION

 StartTime! = GetPreciseTimer!
 PRINT "Current time - "; StartTime!
 PRINT "Press any key"
 WHILE LEN(INKEY$) = 0: WEND
 PRINT "Elapsed time - "; GetPreciseTimer! - StartTime!
 END
This program tested in DOSBOX 0.74 and it works fine.

If you want to receive events with high frequency, there are two ways to do it:
reprogram zero channel PIT (you may achieve very high frequencies, but this way
very bad compatible with many virtual DOS machines) or use so called "second"
PC timer (INT 70h, 1024 interrupt per second).
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Re: Is there a better TIMER in dos?

Post by lassar »

Thanks !

I had translated my windows program to dos.

This timer routine makes my dos program work just like my windows program.
monochromator
Posts: 42
Joined: Mar 05, 2013 5:37

Re: Is there a better TIMER in dos?

Post by monochromator »

I apologize, but in the above example there is complete stupid mess with types
of variables. I don't believe I could write such nonsense.
There are corrected versions. The first function (GetPreciseTimerF!) contains
floating-point operations, the second (GetPreciseTimer) - no.

Code: Select all

FUNCTION GetPreciseTimerF! ()
DIM AS UINTEGER BIOSTimer, PITValue
DIM AS UBYTE LowBytePIT, HighBytePIT
'Function return current time in microseconds.
DOSMEMGET &H46C, 4, @BIOSTimer 'read BIOS timer counter
OUT &H43, 0 'latch PIT counter for zero channel
LowBytePIT = INP(&H40) 'low byte of PIT counter
HighBytePIT = INP(&H40) 'high byte of PIT counter
PITValue = (CUINT(HighBytePIT) SHL 8) + LowBytePIT 'calculate PIT counter
'PIT work frequency is 1193180 Hertz. There are 1000000 microseconds in 1 second.
'(1000000 / 1193180) = 0.8380965
GetPreciseTimerF! = (CSNG(BIOSTimer) * 65536 + PITValue) * 0.8380965
END FUNCTION

FUNCTION GetPreciseTimer () AS ULONGINT
DIM AS UINTEGER BIOSTimer, PITValue
DIM AS UBYTE LowBytePIT, HighBytePIT
'Function returns current time in microseconds.
DOSMEMGET &H46C, 4, @BIOSTimer 'read BIOS timer counter
OUT &H43, 0 'latch PIT counter for zero channel
LowBytePIT = INP(&H40) 'low byte of PIT counter
HighBytePIT = INP(&H40) 'high byte of PIT counter
PITValue = (CUINT(HighBytePIT) SHL 8) + LowBytePIT 'calculate PIT counter
'PIT work frequency is 1193180 Hertz. There are 1000000 microseconds in 1 second.
'(1000000 / 1193180) = (50000 / 59659)
GetPreciseTimer = (((CULNGINT(BIOSTimer) SHL 16) + PITValue) * 50000) \ 59659
END FUNCTION
I must warn you, that both timers are based on the BIOS timer counter.
Therefore, they are reset to zero at midnight and start counting from begin.
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Re: Is there a better TIMER in dos?

Post by lassar »

I was looking over the last code you posted.

Should not the code give a double result , and not a integer.

A integer would give results in nearest seconds.

Something like this

Code: Select all

FUNCTION GetPreciseTimer () AS DOUBLE
DIM AS UINTEGER BIOSTimer, PITValue
DIM AS UBYTE LowBytePIT, HighBytePIT
'Function returns current time in microseconds.
DOSMEMGET &H46C, 4, @BIOSTimer 'read BIOS timer counter
OUT &H43, 0 'latch PIT counter for zero channel
LowBytePIT = INP(&H40) 'low byte of PIT counter
HighBytePIT = INP(&H40) 'high byte of PIT counter
PITValue = (CUINT(HighBytePIT) SHL 8) + LowBytePIT 'calculate PIT counter
'PIT work frequency is 1193180 Hertz. There are 1000000 microseconds in 1 second.
'(1000000 / 1193180) = (50000 / 59659)
FUNCTION = (((CULNGINT(BIOSTimer) SHL 16) + PITValue) * 50000) / 59659
END FUNCTION
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: Is there a better TIMER in dos?

Post by counting_pine »

An integer should give the results to the nearest microsecond, not second.

For a function returning seconds, I would just divide GetPreciseTimer() by 1e6.
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Re: Is there a better TIMER in dos?

Post by lassar »

Just did some more testing.

Your first one gives better results. It gives results in seconds.

The second one just does not work properly.

And using the command TIMER is absolutely useless.

Code: Select all

'$LANG: "fblite"
'$INCLUDE: 'dos/dpmi.bi'
'$INCLUDE: 'dos/go32.bi'
'$INCLUDE: 'dos/sys/farptr.bi'




' Timer 1

FUNCTION PreciseTimer! ()
  'Function return current time in microseconds.
  DOSMEMGET &H46C, 4, @BIOSTimer& 'read BIOS timer counter
  OUT &H43, 0 'latch PIT counter for zero channel
  LowBytePIT& = INP(&H40) 'low byte of PIT counter
  HighBytePIT& = INP(&H40) 'high byte of PIT counter
  PITValue& = (HighBytePIT& SHL 8) + LowBytePIT& 'calculate PIT counter
  'PIT work frequency is 1193180 Hertz
  FUNCTION = (CSNG(BIOSTimer&) * 65536 + PITValue&) / 1193180
END FUNCTION


#IF 0
' Timer 2
FUNCTION PreciseTimer2!()
DIM AS UINTEGER BIOSTimer, PITValue
DIM AS UBYTE LowBytePIT, HighBytePIT
'Function returns current time in microseconds.
DOSMEMGET &H46C, 4, @BIOSTimer 'read BIOS timer counter
OUT &H43, 0 'latch PIT counter for zero channel
LowBytePIT = INP(&H40) 'low byte of PIT counter
HighBytePIT = INP(&H40) 'high byte of PIT counter
PITValue = (HighBytePIT SHL 8) + LowBytePIT 'calculate PIT counter
 'PIT work frequency is 1193180 Hertz
FUNCTION = (BIOSTimer * 65536 + PITValue&) / 1193180
END FUNCTION
#ENDIF

' Timer 2
FUNCTION PreciseTimer2!()
DIM AS UINTEGER BIOSTimer, PITValue
'Function returns current time in microseconds.
DOSMEMGET &H46C, 4, @BIOSTimer 'read BIOS timer counter
OUT &H43, 0 'latch PIT counter for zero channel
ASM
        MOV     EAX,0
        OUT     &H43,al
        IN      AL,&H40
        IN      Ah,&H40
        MOV     [PITValue],EAX
        MOV     EAX,[BIOSTimer]   'multiply BIOSTimer by 65536
        SHL     EAX,8
        MOV     [BIOSTimer],EAX
END ASM
 'PIT work frequency is 1193180 Hertz
'FUNCTION = (BIOSTimer * 65536 + PITValue&) / 1193180
FUNCTION = (BIOSTimer + PITValue&) / 1193180
END FUNCTION



' Timer 3
    FUNCTION GetPreciseTimer () AS ULONGINT
    DIM AS UINTEGER BIOSTimer, PITValue
    DIM AS UBYTE LowBytePIT, HighBytePIT
    'Function returns current time in microseconds.
    DOSMEMGET &H46C, 4, @BIOSTimer 'read BIOS timer counter
    OUT &H43, 0 'latch PIT counter for zero channel
    LowBytePIT = INP(&H40) 'low byte of PIT counter
    HighBytePIT = INP(&H40) 'high byte of PIT counter
    PITValue = (CUINT(HighBytePIT) SHL 8) + LowBytePIT 'calculate PIT counter
    'PIT work frequency is 1193180 Hertz. There are 1000000 microseconds in 1 second.
    '(1000000 / 1193180) = (50000 / 59659)
    GetPreciseTimer = ((((CULNGINT(BIOSTimer) SHL 16) + PITValue) * 50000) \ 59659)/1000000
    END FUNCTION


PRINT "Press AnyKey to Start"
SLEEP

StartTime! = PreciseTimer!
StartTime2! = PreciseTimer2!
StartTime3! = GetPreciseTimer
TimerStart! = TIMER

PRINT "Current time = "; StartTime!
PRINT "Current time2 = "; StartTime2!
PRINT "Current Timer3 = "; StartTime3!
PRINT "Standard TIMER = "; TimerStart!
PRINT "Press any key"
SLEEP

ElapsedTime! = PreciseTimer! - StartTime!
ElapsedTime2! = PreciseTimer2! - StartTime2!
ElapsedTime3! = GetPreciseTimer - StartTime3!
ElapsedTime4! = TIMER - TimerStart!
PRINT
PRINT


PRINT "Current time = "; PreciseTimer!
PRINT "Current time2 = "; PreciseTimer2!
PRINT "Lastest Timer = "; StartTime3!
PRINT "Standard TIMER = "; TIMER
PRINT
PRINT
PRINT "Elapsed timer1 = "; ElapsedTime!
PRINT "Elapsed timer2 = "; ElapsedTime2!
PRINT "Elapsed timer3 = "; GetPreciseTimer
PRINT "Standard TIMER Elapsed time = "; ElapsedTime4!
END
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: Is there a better TIMER in dos?

Post by counting_pine »

The ULONGINT function should probably be returning a DOUBLE, if it's returning seconds. (The comment should probably be updated too.)
The second one may be having problems because the ASM is doing a shift left by 8, which is a multiply by 256, not 65536.
You may also want to adapt all the code to work without lang fblite to ensure no implicit declaration/type errors have crept in.
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Re: Is there a better TIMER in dos?

Post by lassar »

Found out that TIMER requires Double to work properly.

Tested out both TIMER and PreiseTimer# in DosBox. I think PreiseTimer# works better.

The Code below is what I ended up using.

Code: Select all

#IF 1
FUNCTION PreciseTimer#
  DIM AS UINTEGER BIOSTimer, PITValue
  DIM AS UBYTE LowBytePIT, HighBytePIT
  'Function returns current time in seconds.
  DOSMEMGET &H46C, 4, @BIOSTimer 'read BIOS timer counter
  OUT &H43, 0 'latch PIT counter for zero channel
  LowBytePIT = INP(&H40) 'low byte of PIT counter
  HighBytePIT = INP(&H40) 'high byte of PIT counter
  PITValue = (HighBytePIT SHL 8) + LowBytePIT 'calculate PIT counter
  'PIT work frequency is 1193180 Hertz
  FUNCTION = (BIOSTimer * 65536 + PITValue) / 1193180
END FUNCTION
#EndIF
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: Is there a better TIMER in dos?

Post by MichaelW »

The most likely possibility I see for HrTimer not working in VirtualPC or DosBox is that the CPU clock speed on the test system is varying. If a varying clock speed is the problem, then the only solutions I can see involve a hardware timer with a fixed frequency reference, which at least on older systems are limited to the PIT and the RTC timer.

I posted a DOS timer based on the RTC timer here. In theory the resolution is 1/1024 second, and while it could reasonably be pushed to 1/8192 second, even so it would still be far from a microsecond resolution.

monochromator,

Before I developed HrTimer I tested the method that you are using in a FreeBASIC DOS app and determined that accessing the timer was much slower than I expected it to be, taking milliseconds per access. Since my goal was a timer with a resolution on the order of a few microseconds, I switched to the method that I used in HrTimer, sacrificing long-term accuracy for a much higher short-term resolution. This is the FreeBASIC DOS test app that I used to compare your timer to HrTimer and DosTimer.

Code: Select all

#include "mctimer.bas"
#include "hrtimer.bas"
#include "DosTimer.bas"

dim as single t1,t2
dim as double t3,t4,t5,t6
dim as integer i,n,x

t1 = GetPreciseTimer()
t3 = HrTimer()
t5 = DosTimer()

print "OK"
print

sleep

''------------------------------------------------------
'' This code to allow timing against an external clock.
''------------------------------------------------------

t1 = GetPreciseTimer()
t3 = HrTimer()
t5 = DosTimer()

sleep

t2 = GetPreciseTimer()
t4 = HrTimer()
t6 = DosTimer()

print "GetPreciseTimer: ";(t2-t1)
print "HrTimer:         ";(t4-t3)*1000000
print "DosTimer:        ";(t6-t5)*1000000
print
sleep

''---------------------------------------------------------------------
'' This code for a short-term timing test, where the delay loop count
'' is read from the command line. Note that the delay loop uses an
'' xchg reg, mem instruction with an implied lock prefix that prevents
'' it from using the cache, making it VERY slow. For a 500 MHz P3 the
'' count for a ~~5ms delay was the default 70000, which obviously will
'' need to be scaled up for faster processors.
''---------------------------------------------------------------------

n = val(command(1))

if n = 0 then n = 70000

''------------------------------------------------------
'' Sync with the timer for each of the following tests.
''------------------------------------------------------

t2 = GetPreciseTimer()
do
    t1 = GetPreciseTimer()
loop until t1 > t2
for i = 1 to n
    asm xchg eax, [x]
next
t2 = GetPreciseTimer()

t4 = HrTimer()
do
    t3 = HrTimer()
loop until t3 > t4
for i = 1 to n
    asm xchg eax, [x]
next
t4 = HrTimer()

t6 = DosTimer()
do
    t5 = DosTimer()
loop until t5 > t6
for i = 1 to n
    asm xchg eax, [x]
next
t6 = DosTimer()

print "GetPreciseTimer: ";(t2-t1)
print "HrTimer:         ";(t4-t3)*1000000
print "DosTimer:        ";(t6-t5)*1000000
print
sleep

''----------------------------------------------------------------
'' This code to sample and display the effective timer resolution
'' (IOW the best-case short-term precision) in microseconds.
''----------------------------------------------------------------

print "GetPreciseTimer:"
n = 0
for i = 1 to 40
    t1 = GetPreciseTimer()
    do
        n += 1
        t2 = GetPreciseTimer()
    loop until t2 > t1
    print csng(t2-t1),
next
print n

sleep

print "HrTimer:"
n = 0
for i = 1 to 40
    t3 = HrTimer()
    do
        n += 1
        t4 = HrTimer()
    loop until t4 > t3
    print csng((t4-t3)*1000000),
next
print n

sleep

print "DosTimer:"
n = 0
for i = 1 to 40
    t5 = DosTimer()
    do
        n += 1
        t6 = DosTimer()
    loop until t6 > t5
    print csng((t6-t5)*1000000),
next
print n

sleep

Running from a Windows 98 startup diskette, and whatever version of DOS that uses (7.1 IIRC), with CWSDPMI or HDPMI, all three of the methods worked well for timing relatively long intervals. In the short term timing test (using only CWSDPMI, because I forgot to test the faster HDPMI) I had to get the timed interval up to ~60ms before GetPreciseTimer returned useful values.

I think a more reasonable approach would be to reprogram system timer 0 for a higher frequency, and install a protected-mode handler for IRQ0 that would call the original handler at something close to the normal frequency. For a millisecond-resolution timer I have used an initial count of 1193 and called the original handler every 55 ticks. Given a fast processor, I think much higher frequencies could reasonable be used, perhaps as far as an initial count close to 1. For reference, starting with the original PC, the refresh request generated by system timer 1 started with an initial count of 18, generating a request every 15.09 microsecond. The request didn’t generate an interrupt, and subsystems other than the CPU are involved, but consider how slow a 4.77MHz 8088 was compared to recent processors.

Edit:

FWIW I tried this with an initial count of 1, and it did work in that the timer continued to generate interrupts and the test app did not hang, but the interrupt rate was something like 1000 times lower than expected, tested under DOS. I suspect that the bottleneck is an interrupt controller subsystem never designed for such a high rate.
Post Reply