No announcement yet.

Precise timing for pbdos in windows

  • Filter
  • Time
  • Show
Clear All
new posts

  • Precise timing for pbdos in windows


    I've been sniffing around for some code to allow precise timing in pb for dos during a windows(98/xp) session. No success yet. For now I use the closest thing I've found: tdelay(below).
    It seems to work accurately enough, however I need a timing function, not a delay function. Is there a way to turn this into a function that returns a time instead of delaying? I'm not much familiar with assembly.

    FUNCTION TDelay(BYVAL Tiks%)
    ! Mov CX,Tiks%
    ! Jcxz Done
    ! Or CX,CX
    ! Mov DX,&H40
    ! Xor BX,BX
    ! Xor AX,AX
    ! In AL,DX
    ! Sub AX,0
    ! In AL,DX
    ! Mov BL,AL
    ! Cli
    ! In AL,DX
    ! Sub AX,0
    ! In AL,DX
    ! Sti
    ! Cmp BL,AL
    ! Jz L1
    ! Sub BL,AL
    ! Sub CX,BX
    ! Jz Done
    ! Js Done
    ! Mov BL,AL
    ! Jmp Short L1

    Thanks for any info.

    Harry Mercury

  • #2
    Hi Harry,

    What sort of time periods are you looking for here? For periods of 54 mSec or less, MTIMER is usually sufficiently accurate.

    However, since WIndows is a multi-tasking operating system, providing accurate timing for anything is difficult, since time-slice context changes (ie, Windows may do a task-switch right after the event, but before you read the elapsed time) thus distorting the reported time.

    To assist with your timing measurement under Windows, you can place the DOS app into "critical section" state, which disables almost all of Windows while a block of code runs, but this is not recommended to long periods of time, since it can interfere with the operation of Windows itself.

    IF UCASE$(ENVIRON$("OS")) = "WINDOWS_NT" OR (ISTRUE BIT(pbvHost,8)) then IsWin% = -1
    if IsWin% then    ' Start Critical section mode
        ! MOV AX,&H1681                                                   
        ! INT &H2F                                                        
    end if                                                                
    ...timing sensitive code here
    if IsWin% then    ' Release CS mode
        ! MOV AX,&H1682                                                   
        ! INT &H2F                                                        
    end if
    Finally, trying searching for "timer" or "timing" - you should probably find some other discussions in this forum.

    PowerBASIC Support
    mailto:[email protected][email protected]</A>
    mailto:[email protected]


    • #3
      Lance, I'm afraid the critical section method won't do, I need continuous timing (a game). Mtimer doesn't do the trick either since it's reset every time it's read and the function takes some 50ms to complete. I've tried quite a lot of code (also from this board) and while some of it works ok in windows98, none of them provide stable timing in newer windows versions with their more emulated dos systems. The only timer or delay I've found working stable in XP is the TDelay asm code I mentioned before and I was wondering if it could be modified into something that lets you actually read precise time.

      Thanks for the reply,
      Harry Mercury



      • #4
        How about using MTIMER and keeping track of the accumulated time ech time you read/reset it?

        Beyond that, I'll dig through my archives and see what else I have around (I recall having a routine to program one of the hardware timers).

        However, as I noted, doing anything like this under Windows is going to be problematic. Under NT/2000/XP, Windows only provides virtualized hardware access, so the stability/accuracy of this approach is going to be questionable.

        PowerBASIC Support
        mailto:[email protected][email protected]</A>
        mailto:[email protected]


        • #5
          I have indeed tried adding mtimers, or even a better method, namely reading mtimer and then tdelay remaining time until the next frame. The problem is that mtimer seems to take 50 ms to complete so that would give me a maximum rate of 20fps, putting me right back where I started.

          Thanks and sorry for the crosspost.

          Harry Mercury


          • #6
            ok, i found the following in my archives, and it comes from these forums! see
            ' a more accurate, programmable delay (typ. <20usec resoloution) for pb3.5
            ' intended to replace delay which has a resoloution of only 50msec.
            ' because this is meant to be fast, the scaling of the delay value from
            ' usec to internal clock ticks is done outside the delay routine. the
            ' routine is now called with the number of timer ticks needed for the
            ' delay and not the no.of usecs. this is to keep the routine suitable for
            ' use on machines with no fpu where the muliplication by 1.19318 can take
            ' 100's of usec.the scaling can then be done in advance. the multiplication
            ' can easily be moved back into the delay routine if required to give a
            ' delay in microseconds.
            ' the routine uses pit timer 0 which is also used by the system clock.
            ' when called for the first time the timmer is set to the mode required
            ' by the routine the system clock may therefore lose upto 1 tick (54msec)
            ' when the routine is first called. subsequent calls only read the counter.
            ' the routines are free to use but i would be appreciate it if you informed
            ' me that you have used them and what for (just for interest).
            ' [email protected]
            rem test mdelay against mtimer
            input"delay for how long(us)";del&&
            del&&=del&& * 1.19318167 :rem convert from usec to timer ticks
            mdelay (del&&)
            print k&
            sub mdelay(duration&&)
            rem delay for duration&& times 0.838usec(1/1.19318167mhz clk)
            !test byte calledbefore,&hff    ;has routine been called before?
            !jnz noinit             ;not zero, so it's been called, don't init
            !dec byte calledbefore  ;set calledbefore flag
            !mov al,&h34            ;make sure pit timer0 is in mode 2
            !out &h43,al
            !mov al,0               ;and set to 0 (i.e a 65536 count)
            !out &h40,al
            !out &h40,al
            !les bx,int1cv          ;get address of int1c vector
            !mov ax,es:[bx]         ;copy offset to exitvector
            !mov exitvector[01],ax
            !mov ax,es:[bx+02]      ;copy segment to exitvector
            !mov exitvector[03],ax
            !cli                    ;disable interrupts while changing interrupt vector
            !lea ax,int1centry      ;get int routine entry to int1c vector
            !mov es:[bx],ax         ;move offset of my routine to int1c vector
            !mov es:[bx+02],cs      ;move segment of my routine to int1c vector
            !sti                    ;enable interrupts
            !call near gettime      ;get the time
            !les bx,duration&&      ;add duration to result and get into working store
            !mov ax,result          ;add the current time to the duration in 0.838us clks
            !add ax,es:[bx]
            !mov timeout,ax         ;and put the result in timeout
            !mov ax,result[02]
            !adc ax,es:[bx+02]
            !mov timeout[02],ax
            !mov ax,result[04]      ;only need 37 bits to hold usec for a day
            !adc ax,es:[bx+04]      ;so 3 words is enough for 5 years
            !mov timeout[04],ax
            !call near gettime      ;read the time again
            !mov ax,result          ;compare with the timeout value
            !sub ax,timeout
            !mov ax,result[02]
            !sbb ax,timeout[02]
            !mov ax,result[04]
            !sbb ax,timeout[04]
            !jb waitlonger          ;it's below that value so loop back again
            !cli                    ;disable interrupts while restoring interrupt vector
            !les bx,int1cv          ;get address of int1c vector
            !mov ax,exitvector[01]  ;restore int1c vector
            !mov es:[bx],ax         ;restore offset
            !mov ax,exitvector[03]
            !mov es:[bx+02],ax      ;restore segment
            !sti                    ;enable interrupts
            exit sub
            !cli                    ;stop interrupts while reading timer
            !mov al,&h00            ;latch current counter in timer 0
            !out &h43,al
            !mov ax,delaytimer      ;get s/w part of time
            !mov result[02],ax
            !mov ax,delaytimer[02]
            !mov result[04],ax
            !in al,&h40             ;get h/w part of timer
            !xchg al,ah             ;move it out of the way so i can...
            !in al,&h40             ;..get msb of timer
            !xchg al,ah             ;swap bytes to the right order
            !neg ax                 ;negate so down counter becomes an up counter
                                    'and adjust for 1->0 causing interrupt and not 0->ffff
            !mov result,ax          ;put least significant result in result
            !sti                    ;re-enable interrupts
            !retn                   ;return
            rem int1c is the user exit from bios of the 18.2hz interrupt routine
            !pushf                  ;save flags
            !inc word delaytimer    ;increment delaytimer (a 4 byte counter)
            !jnz int1cexit
            !inc word delaytimer[02]
            !popf                   ;restore flags
            !jmp far exitvector     ;exit via copy of original int1c vector copied into
                                    'this location immediately after the jmp byte
            !dd 0                   ;timer, counts 18.2hz interrupts from int1c
            !dd 0                   ;result of reading timer
            !dw 0
            !dd 0                   ;general workspace and calculated time out value
            !dw 0
            !dd &h00000070          ;int1c vector
            !db 0
            end sub

            powerbasic support
            mailto:[email protected][email protected]</a>
            mailto:[email protected]


            • #7
              I know that one but as I stated earlier, I already have a delay function, I need to read the time so I can time events regularly. Does anyone know of a way to read the time into a variable using this function? If only I knew asm...
              Harry Mercury


              • #8
                It looks to me like there should be sufficient comments in that code to get you started converting it from a delay routine into a timing routine...

                Have you thought about learning a bit of x86 assembler? For high-performance real-time game programming, it would probably be a good investment of your time. Search the web for "assembly language tutorial" and you should get plenty of hits to get started with.

                The alternative is to seek out some of the DOS game programming sites on the 'net, and look for code that already does what you want... start out with and search for "resolution timer DOS BASIC" or visit for ideas...

                Good luck with your project!

                PowerBASIC Support
                mailto:[email protected][email protected]</A>
                mailto:[email protected]


                • #9
                  Ok thanks loads,



                  • #10
                    Try this Harry:

                    Functions by courtesy of Alan Earnshaw (you still around, Alan )

                    COLOR 0,3,3: CLS
                    TIMESTART = TimeToSeconds!(Time$)
                    TIMEDELAY = 10
                        A$ = "": A$ = INKEY$
                        TIMENOW = TimeToSeconds!(Time$)
                        COLOR 14,0: LOCATE 2,2: PRINT " THE BOMB WILL GO OFF IN : "TIMEDELAY" Seconds !!! "
                        COLOR 15,4: LOCATE 4,2: PRINT " TIMESTART : "TIMESTART" "
                                    LOCATE 6,2: PRINT " TIMENOW   : "TIMENOW" "
                        COLOR 14,1: LOCATE 8,2: PRINT " COUNTING  : "TIMENOW - TIMESTART" "
                        IF TIMENOW - TIMESTART = TIMEDELAY THEN
                           FOR I = 1 TO 3
                              PLAY "L16O3EC"
                           NEXT I
                           GOTO BYEBYE
                        END IF
                        IF A$ = "" OR A$ <> CHR$(27) THEN INPUTLOOP
                        COLOR 0,3,3: CLS
                    END : END
                    FUNCTION LIMIT(Time1$, Time2$)
                        LOCAL Time1!, Time2!
                        Time1! = TimeToSeconds!(Time1$)
                        Time2! = TimeToSeconds!(Time2$)
                        Elapsed! = ABS(Time2! - Time1!)
                        LIMIT = ELAPSED! / 60
                    END FUNCTION
                    FUNCTION TimeToSeconds!(TTime$)
                        LOCAL Hours%, Minutes%, Seconds!
                        CALL SeparateTime(TTime$, Hours%, Minutes%, Seconds!)
                        TimeToSeconds! = (3600 * Hours%) + (60 * Minutes%) + Seconds!
                    END FUNCTION
                    SUB SeparateTime(STime$, Hour%, Minute%, Seconds!)
                        Hour% = VAL(LEFT$(STime$, 2))
                        Minute% = VAL(MID$(STime$, 4, 2))
                        Seconds! = VAL(RIGHT$(STime$, (LEN(STime$) - 6)))
                    END SUB

                    [This message has been edited by OTTO WIPFEL (edited April 01, 2002).]


                    • #11
                      The Windows API provides a call to QueryPerformaceCounter.

                      The value returned by this call must be available somewhere
                      in DOS as it comes from the hardware.

                      It sounds like that's what you want.


                      [This message has been edited by Jay Rogozinsky (edited April 03, 2002).]