I have a little program that paints a little pixel on the screen and each time the user presses a key the pixel moves down. I want to move the pixel down but I want to do it within a certain interval of time, say 500 milliseconds. How can I do that? I know that I have to use int 21h that puts milliseconds in ah, but I don´t know how to start. Here goes my code:
STACK SEGMENT PARA STACK
DB 64 DUP ('MYSTACK ')
STACK ENDS
MYCODE SEGMENT PARA 'CODE'
MYPROC PROC FAR
ASSUME CS:MYCODE,SS:STACK
PUSH DS
SUB AX,AX
PUSH AX
MOV AH, 00h
MOV AL, 04h
INT 10h
; MOV AH, 11
; MOV BH, 00
; MOV BL, 01
; INT 10h
MOV AH, 11
MOV BH, 01
MOV BL, 00
INT 10h
MOV DX, 64h
MOV CX,20
Game:
PUSH CX
MOV AL, 02 ;pixel cor vermelha
MOV AH, 12
MOV CX, 9Eh
INT 10h
MOV ah, 01
INT 21h ;espera por uma tecla
MOV AL, 00 ;pixel cor preto
MOV AH, 12
MOV CX, 9Eh
INT 10h
INC DX
POP CX
Loop Game
MOV ah, 01
INT 21h
MOV AH, 00h
MOV AL, 02h
INT 10h
RET
MYPROC ENDP
MYCODE ENDS
END
i would use BIOS INT 1Ah, function 0
;===================================================================
DELAY PROC NEAR
;
;time delay
;
;AX = tick count to wait, 0 to 255 (0 to 14 seconds)
;
;----------------------------------------
OR AX,AX
JNZ EXECUTE_DELAY
RET ;if AX = 0, exit immediately
EXECUTE_DELAY:
PUSH CX
PUSH DX
PUSH BX
PUSH AX
OR AH,AH
JZ UNDER_LIMIT
MOV AX,0FFh ;max limit = 255 ticks (~14 seconds)
UNDER_LIMIT:
PUSH AX
INT 1Ah ;CX:DX = tick count
POP AX
SUB BX,BX ;BX:AX = wait tick count
ADD AX,DX
ADC BX,CX ;BX:AX = timeout value
MOV DX,0B0h
MOV CX,18h ;CX:DX = midnight rollover value
SUB AX,DX
SBB BX,CX ;subtract rollover
JS OVERFLOW_ADJUST
PUSH BX
PUSH AX
ROLLOVER_LOOP:
MOV AH,0
INT 1Ah
JCXZ ROLLOVER_ENTRY
JMP ROLLOVER_LOOP
OVERFLOW_ADJUST:
ADD AX,DX
ADC BX,CX
WAIT_LOOP:
PUSH BX
PUSH AX
MOV AH,0
INT 1Ah
ROLLOVER_ENTRY:
POP AX
POP BX
CMP BX,CX
JB DELAY_EXIT
JA WAIT_LOOP
CMP AX,DX
JA WAIT_LOOP
DELAY_EXIT:
POP AX
POP BX
POP DX
POP CX
RET
DELAY ENDP
;===================================================================
What I don´t understand is how can I interoperate this routine with my code.
each clock tick is ~55 ms
500 ms is about 9 ticks, so...
mov ax,9
call DELAY
EDIT there are better ways to get a more repeatable delay
that involves revectoring one of the timer interrupts
this is a simple delay - it won't be exactly 500 ms everytime you call it
Well I discovered that it has to be faster but it works just fine. Thanks!!
I´ve putten the smallest delay. Is there some code in the procedure that I can change to make the delay smaller? Here goes "my" code:
STACK SEGMENT PARA STACK
DB 64 DUP ('MYSTACK ')
STACK ENDS
MYCODE SEGMENT PARA 'CODE'
MYPROC PROC FAR
ASSUME CS:MYCODE,SS:STACK
PUSH DS
SUB AX,AX
PUSH AX
MOV AH, 00h
MOV AL, 04h
INT 10h
; MOV AH, 11
; MOV BH, 00
; MOV BL, 01
; INT 10h
MOV AH, 11
MOV BH, 01
MOV BL, 00
INT 10h
mov dx,10h ;selecciona a linha 100
mov cx,9eh ; seleciona a coluna 158
;MOV DX, 64h
MOV CX,180
Game:
PUSH CX
MOV AL, 02 ;pixel cor vermelha
MOV AH, 12
MOV CX, 9Eh
INT 10h
;MOV ah, 01
;INT 21h ;espera por uma tecla
mov ax, 1 ;-------------------------------------------------------------------------------------------------<<<<<HERE IS THE SMALLER DELAY THAT I COULD MAKE
call DELAY
MOV AL, 00 ;pixel cor preto
MOV AH, 12
MOV CX, 9Eh
INT 10h
INC DX
POP CX
Loop Game
MOV ah, 01
INT 21h
MOV AH, 00h
MOV AL, 02h
INT 10h
RET
MYPROC ENDP
DELAY PROC NEAR
;
;time delay
;
;AX = tick count to wait, 0 to 255 (0 to 14 seconds)
;
;----------------------------------------
OR AX,AX
JNZ EXECUTE_DELAY
RET ;if AX = 0, exit immediately
EXECUTE_DELAY:
PUSH CX
PUSH DX
PUSH BX
PUSH AX
OR AH,AH
JZ UNDER_LIMIT
MOV AX,0FFh ;max limit = 255 ticks (~14 seconds)
UNDER_LIMIT:
PUSH AX
INT 1Ah ;CX:DX = tick count
POP AX
SUB BX,BX ;BX:AX = wait tick count
ADD AX,DX
ADC BX,CX ;BX:AX = timeout value
MOV DX,0B0h
MOV CX,18h ;CX:DX = midnight rollover value
SUB AX,DX
SBB BX,CX ;subtract rollover
JS OVERFLOW_ADJUST
PUSH BX
PUSH AX
ROLLOVER_LOOP:
MOV AH,0
INT 1Ah
JCXZ ROLLOVER_ENTRY
JMP ROLLOVER_LOOP
OVERFLOW_ADJUST:
ADD AX,DX
ADC BX,CX
WAIT_LOOP:
PUSH BX
PUSH AX
MOV AH,0
INT 1Ah
ROLLOVER_ENTRY:
POP AX
POP BX
CMP BX,CX
JB DELAY_EXIT
JA WAIT_LOOP
CMP AX,DX
JA WAIT_LOOP
DELAY_EXIT:
POP AX
POP BX
POP DX
POP CX
RET
DELAY ENDP
MYCODE ENDS
END
for very small delays, you can just use a loop
the value you place into CX will deteremine how long the loop takes
mov cx,DelayCount ;this can be a constant
Delay0: loop Delay0
here is another example where i place a loop inside another loop
it can create somewhat longer delays
mov cx,DelayCount ;this can be a constant
Delay0: push cx
xor cx,cx
Delay1: loop Delay1
pop cx
loop Delay0
the problem with these kinds of delay loops is - they run at different speeds on different machines
a more complicated approach is to use one of the counter/timers
it takes a bit more programming, but will yield the same results on most machines
Hi,
Depending on your system, INT 15H function 86H in a
wait function. See Ralf Brown's Interrupt List (RBIL).
AH = 86H
CX:DX = 32 bit count in microseconds (resolution is 976 us)
INT 15H
It did not work on my Windows 2000 system, but someone
mentioned a service pack may have changed that. Instead
of the 18.2 times a second the INT 1CH gives you (55
millisecond wait), you get about a millisecond wait.
You should be able to read the timer counting down as well
as it generates the 18.2 per second interrupts. That could
give you very short delays. That is the ports from 40H to
43H. Again see RIBL.
Port (data from "Undocumented DOS")
40H = Timer 0, system ticks
41H = Timer 1, DRAM refresh
42H = Timer 2, general use
43H = Tomers 0 - 2 mode control
Regards,
Steve N.
Thanks once again for your reply. Is there a way in which I can freeze the process of making the little pixel fall? I need to have that functionality in my game. I mean the pixel is falling but when the user presses a key the pixel stops.
Here is another delay procedure.
; -----------------------------------------------------------
; This proc delays for the specified number of milliseconds.
; It does this by counting memory refresh requests, which
; for AT-class systems are generated by system timer 1,
; normally programmed by the BIOS to generate an output once
; every 18/1193182 = 15.09 microseconds. Each request toggles
; bit 4 of I/O port 61h. We are counting full cycles of the
; bit, with two toggles per cycle, so each cycle takes 30.18
; microseconds and we count 34 cycles for each millisecond.
;
; Call with the number of milliseconds in AX.
;
; Preserves all registers other than AX.
;
; Note that the delay will be much shorter when running
; under the NT versions of Windows.
; -----------------------------------------------------------
MsDelay proc
push cx
mov cx, ax ; load msLoop count
msLoop:
push cx ; preserve msLoop counter
mov cx, 34 ; load repeat count
wait1:
in al, 61h ; read byte from port
test al, 10h ; test bit 4
jz wait1 ; jump if bit clear
wait2:
in al, 61h ; read byte from port
test al, 10h ; test bit 4
jnz wait2 ; jump if bit not clear
dec cx ; decrement repeat count
jnz wait1 ; jump if count not zero
pop cx ; recover msLoop counter
dec cx ; decrement msLoop count
jnz msLoop ; jump if count not zero
pop cx
ret
MsDelay endp
lol - that is some old-timie stuff, Michael
how old are you, anyways ?
I don´t understand. Does this mean that the second procedure permits me to stop at any moment the pixel from falling and the first procedure doesn't?
I tried the second procedure and it runs much faster. But can I stop the pixel from moving. I tried putting the value in ax to zero but it still moves from times to times.
well - stopping the pixel should not be dependant on a delay routine
that is more of a program flow issue
Hi,
Looks good Michael. Will check it out on some different
systems. Do you have a correction for the "NT versions"?
Regards,
Steve N.
Yes, It does work faster. The first procedure is too slow even if I put it to work at the fastest it can get. And it has much less code.
Quote from: dedndave on December 30, 2009, 10:10:48 AM
lol - that is some old-timie stuff, Michael
how old are you, anyways ?
I first noticed the technique when I was examining some BIOS code. I think it was code that operated the diskette or hard drive. I'm half way to 118, so I'm approaching middle age :lol
Quote from: FORTRANS on December 30, 2009, 02:25:18 PM
Do you have a correction for the "NT versions"?
I never considered trying to derive a correction. For a delay value of 20000, on my Windows 2000 system it delays for ~6990ms. So applying a correction factor of 3, for a delay value of 60000 it delays for ~20900ms.
Hi,
Well, Michael's routine does perform differently on different
systems. So, before using it one should probably run some
checks or a calibration. My Windows 2000 performs like
Michael's, about 3x.
On two machines running DOS it runs pretty well as posted.
For a 15 second delay, one was 15 seconds or just over. the
other one was a second or two slow.
On an PC/XT compatible it just locks up as expected as
that port changed on the PC/AT.
On two OS/2 VDM's, one ran 2x and one was 7x. (?)
May have to retry that one with a cold boot.
The Int 15H wait function 86H does not work on the
Windows 2000 and PC/XT machines and returns immediately.
The rest ran okay.
It is nice that Michael's routine works on Win2k, where the
Int 15 wait does not. And it provides significantly shorter
delays than the ones I was using there to get around Int 15
not working.
Thanks,
Steve N.
With int 21h and ah equal to 2ch I get the milliseconds in al. But I find this strange because I can only have a maximum value of 255 milliseconds. Is this correct?
those are probably 100th's of a second (0-99)
as a side-note - the dos clock updates 18.2 times per second, so the hundredths do not step "evenly"
the hundredths skips over several values on each clock tick (it doesn't count 1,2,3,4...)
AL isn't a return value -
INT 21 - DOS 1+ - GET SYSTEM TIME
AH = 2Ch
Return: CH = hour
CL = minute
DH = second
DL = 1/100 seconds
Note: on most systems, the resolution of the system clock is about 5/100sec,
so returned times generally do not increment by 1
on some systems, DL may always return 00h
For Function 2Ch, Get Time, the documented (in the Microsoft MS-DOS Programmer's Reference) return values are:
CH = hour in 24-hour format
CL = minutes (0 to 59)
DH = seconds (0 to 59)
DL = hundredths of a second (0 to 99)
Running under Windows 2000, on return AL is always zero, and the value in DL changes in increments of 5 or 6 counts so the resolution is effectively the period of the timer tick ~ 55ms. The code that generated this displayed the return values each time the value in DL changed:
AL CH CL DH DL
0 19 10 41 78
0 19 10 41 83
0 19 10 41 89
0 19 10 41 94
0 19 10 42 0
0 19 10 42 5
0 19 10 42 11
0 19 10 42 16
0 19 10 42 22
0 19 10 42 27
0 19 10 42 33
0 19 10 42 38
0 19 10 42 44
0 19 10 42 49
0 19 10 42 55
0 19 10 42 60
0 19 10 42 66
0 19 10 42 71
0 19 10 42 77
0 19 10 42 82
To get a higher resolution you need to access a counter that runs faster than the system timer tick. Running under Windows you have only a limited number of choices.