Is there a better TIMER in dos?
Is there a better TIMER in dos?
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?
Is there a hig resolultion timer routine that will work in DosBox?
-
- Posts: 42
- Joined: Mar 05, 2013 5:37
Re: Is there a better TIMER in dos?
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:
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).
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
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).
Re: Is there a better TIMER in dos?
Thanks !
I had translated my windows program to dos.
This timer routine makes my dos program work just like my windows program.
I had translated my windows program to dos.
This timer routine makes my dos program work just like my windows program.
-
- Posts: 42
- Joined: Mar 05, 2013 5:37
Re: Is there a better TIMER in dos?
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.
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.
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
Therefore, they are reset to zero at midnight and start counting from begin.
Re: Is there a better TIMER in dos?
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
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
-
- Site Admin
- Posts: 6323
- Joined: Jul 05, 2005 17:32
- Location: Manchester, Lancs
Re: Is there a better TIMER in dos?
An integer should give the results to the nearest microsecond, not second.
For a function returning seconds, I would just divide GetPreciseTimer() by 1e6.
For a function returning seconds, I would just divide GetPreciseTimer() by 1e6.
Re: Is there a better TIMER in dos?
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.
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
-
- Site Admin
- Posts: 6323
- Joined: Jul 05, 2005 17:32
- Location: Manchester, Lancs
Re: Is there a better TIMER in dos?
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.
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.
Re: Is there a better TIMER in dos?
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.
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
Re: Is there a better TIMER in dos?
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.
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.
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
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.