Parts of this apply also to Delphi.
This tends to assume DOS or a Win3/Win9x DOS box; I have no experience of Win 2000, ME, NT.
2001-01-16 : This page, which contains material more relevant to interval and delay, has been split from Borland Pascal Time and Date, which contains material more related to absolute time and date. They should be read together.
While Crt.Delay is convenient when it works, and while corrections and substitutes are available (see below), often millisecond resolution is not necessary and the DOS timer can be used directly.
The immediately following delay routines loop counting CHANGES in the byte at Seg0040:$006C; without HLT, they hog the CPU. PT18 is a pointer, or a ^longint, already set to contain Seg0040:$006C.
procedure WaitTix(Tix : integer) {MSDOS/DPMI} ; var x46C : byte ; PB18 : ^byte absolute PT18 ; begin x46C := PB18^ ; repeat if x46C<>PB18^ then begin x46C := PB18^ ; Dec(Tix) end ; until Tix<0 end {WaitTix} ;
Probably OK.
Or
procedure Wait(C : integer) ; var T : byte ; Tkr : byte absolute $40:$6C ; begin repeat T := Tkr ; repeat until T<>Tkr ; Dec(C) until C<=0 end {Wait} ;
Or (*)
procedure Wait(C : word) ; var T : byte ; Tkr : byte absolute $40:$6C ; begin while C>0 do begin T := Tkr ; repeat until T<>Tkr ; Dec(C) end end {Wait} ;
To work in approximate milliseconds, first do C := C div 55 ;.
N.B. change-counting methods may give extended delays if another process causes change steps to be missed.
Someone suggested, in news: asm mov cx,18*[delayseconds] ; HLT ; loop $-1 end ; I now think that it will delay for that number of interrupts rather than of timer interrupts.
Rudolf Polzer has code like that after (*) above; but inserts, after "repeat", "asm hlt end ;" to release the CPU until an interrupt occurs :-
procedure dlay(n : integer) ; const t : byte = 0 { static! saves some stack space } ; var timer : byte absolute $0040:$006C ; begin while n > 0 do begin t := timer ; repeat asm hlt end until timer<>t ; Dec(n) ; end end ;
The purpose of HLT is to "give back processor cycles on multitasking OSs, reduce CPU power consumption on notebooks under plain DOS. HLT powers down the processor until an interrupt (for example the timer interrupt) occurs. Then the interrupt routine is called (and increments the timer variable) and then the TP program gets control and compares timer with t. If timer = t, another interrupt (eg serial) has occured." It's reported (LeeJ) that dlay "crashes under win2000/nt"; any ideas?
RP also wrote : "I also used the HLT trick for something else :-
repeat t := timer ; asm hlt end ; until timer = t ;
stops exactly when an interrupt occurs that is NOT the timer interrupt. I used this in a screen saver to catch mouse movements without using the driver itself."
FP has cited, to yield time to other processes (see Ralf Brown's List) :-
push ax ; mov ax,1680h ; int 2fh ; pop ax
Michael Glover, in news:c.l.p.b, has given the following method, derived from the above and slightly modified by me (to be tested) :-
function KWAIT(SecsDelay : integer) : integer ; var Tptr : ^longint ; Finish : longint ; W : word ; Ch : byte ; begin KWAIT := -1 {default} ; Tptr := Ptr(Seg0040, $006C); Finish := Tptr^ + Round(18.2*SecsDelay) ; repeat asm push ax ; mov ax,1680h ; int 2fh ; pop ax end { free CPU } ; if KeyPressed then begin Ch := Ord(ReadKey) ; if Ch=0 then KWAIT := 256+Ord(ReadKey) else KWAIT := Ch ; EXIT ; end ; until Tptr^ > Finish ; end {KWAIT} ;
There is a visible register in the CTC (PIT} (Ports $40..$43) normally counting down at 65536×18.2 Hz = 1.193 MHz but possibly two steps at once; this should be usable for small delays in a DOS program. One must read "The Timing FAQ" Sec.7 very carefully before using the CTC; it may be better to commandeer the speaker channel, #2.
The SmallWait code in int_test.pas appears to work.
In an article of Sun, 19 Jan 1997 in comp.lang.pascal.borland, Martin Lafferty (robots@enterprise.net) wrote :-
I find the best way to get a small machine independent delay is to do a harmless read from something on the ISA bus. Reading a byte port takes about a microsecond, I reckon. I suspect I have run into trouble mixing dummy loops and port reads, as the pentium can get clever and do the port read before it finishes the loop!
e.g. This can fail, even if BigNumber is really big, because the second read can occur before the loop has completed.
something := Port[somewhere] ; for i := 0 to BigNumber do {hardware requires delay in here} Junk := 1 ; somethingElse := Port[somewhereelse] ;I do it like this
something := Port[somewhere] ; for i := 0 to RelativelySmallNumber do {hardware requires delay in here} Junk := Port[HarmlessReadonISABus] ; somethingElse := Port[somewhereelse] ;
For a long delay, one might count 1 Hz cycles of the UIP bit of the RTC; this should be reasonably accurate.
Untested by me -
SuperDelay
delay unit for Turbo Pascal 7.0, Allen Cheng :-
"SuperDelay is a pascal unit of Extremely accurate Delay Routines.
It reprograms the timer chip to take microsecond resolution, and it
is 200-300 times more accurate than the Crt.Delay routine. It'll be
more accurate if running on a faster computer."
Untested by JRS as yet.
Adapted from a post and a message from Michael Glover, Surrey, UK, referring to a simple wait loop in TPW :-
>Try Start:=GetTickCount; >Repeat Until GetTickCount>=Start+Time;Not a good idea, since the application in question hogs the system. Better to yield. Try the following :-
procedure Delay(Numsec : integer) ; var EndTime : LongInt ; Message : TMsg ; begin EndTime := GetTickCount + NumSec*1000 ; repeat if GetMessage(Message, 0, 0, 0) then begin TranslateMessage(Message) ; DispatchMessage(Message) end ; until GetTickCount > EndTime ; { In Delphi, the repeat loop should simply be :- repeat ProcessAppMessages ; until ... ; } end {Delay} ;
Ing. Franz Glaser wrote :-
procedure Delay(ms : LongInt) ; var TickCount : LongInt ; M : TMsg ; begin TickCount := GetTickCount; while GetTickCount - TickCount < ms do if PeekMessage(M,0,0,0,pm_Remove) then begin TranslateMessage(M) ; DispatchMessage(M) end ; end ;
I read that the Borland site has, for 32-bit work :-
procedure Delay(ms : longint) ; var TheTime : LongInt; begin TheTime := GetTickCount + ms ; while GetTickCount < TheTime do Application.ProcessMessages ; end ;
See in my Delphi Programming, and in Win32 Help.
If the version of Windows in use has a Sleep function, then that should be the thing to call.
JCO'C has, for Win16 (a non-preemptive OS), recommended the use of settimer/WM_TIMER.
In Delphi, consider Sleep.
If the delay is for animation purposes, it may be well to sync it to the vertical retrace, at around 70 Hz (mode-dependent).
I have seen something like the following suggested (untested) :-
procedure WaitRetrace ; assembler ; asm mov dx,3DAh { read COLOUR MODE VGA input status register #0 } @@loop1: in al,dx ; and al,8 ; jnz @@loop1 ; @@loop2: in al,dx ; and al,8 ; jz @@loop2 end ;
Discussions of timing and delays can be found in Prof. Salmi's TurboPascal FAQ, in Kris Heidenstrom's Timing FAQ, and in the newsgroup comp.lang.pascal.borland. Try a Google or AltaVista search on "Frank Heckenbach", though older material may now be inaccessible. See links on my other pages, including in PC Links Reference.
My INT70AT2.PAS shows the use of the RTC to generate interrupts at a high, programmable rate, without affecting the 18.2 Hz ticker. This could be a useful tool for generating short delays.
For using the RDTSC instruction, which accesses a counter at CPU clock rate (it needs a Pentium or better), see Delphi Programming.
The standard Borland Crt.Delay procedure uses a count-loop calibrated during program startup, employing the 18.2 Hz DOS clock signal to give a standard interval and using the same loop with this calibration when Delay is running. It cannot be accurate unless the program "owns" the machine (see Misuse of Crt.Delay).
The Crt.Delay method assumes constant CPU speed; I hear that some systems now lower the speed of the CPU if it gets too hot.
Putting "Crt" in any "uses" statement in the program will invoke the calibration, as the initialisation section always calibrates for Delay at start-up, whether or not Delay itself is called.
Therefore, if this calibration fails,
it is necessary to do at least one of :-
(1) avoid the unadjusted Borland Crt unit;
(2) avoid needing Delay, or adjust Delay parameters;
(3) replace the unit;
(4) patch the unit;
(5) patch the program.
See also Startup Overflow Bugs below (which includes a link for TP7/BP7 RunTime Error 200, and refers to speed errors with earlier versions).
Note that the method used by Crt.Delay assumes that the machine is, at run-time, dedicated to the main thread of the program. This is an unsafe assumption under Windows, for example; at program start-up Windows may still be busy shuffling things about, so the CPU seems slower at calibrate-time. In a modern PC, there may be other processes stealing CPU time; display, battery-monitor, ...
I'd say that the real cause of such errors was running a program designed to "own" the PC in an environment in which it did not.
Try the following program, which I compiled with TurboPascal 5.0 in c:\TEMP\ in a DOS box under WfWg3.11 :-
uses Crt ; var T1, T2 : word ; begin repeat Sound(1000) ; Delay(100) ; Nosound ; Delay(900) ; T2 := MemW[$40:$6C] ; Writeln(T2-T1:7) ; T1 := T2 ; until Keypressed ; end.
On its own, that should hoot at 1 Hz and print 18s (with 20% 19s). On my 486DX33 with a 1996 1GB hard disc, when running in a Window, it hoots faster and prints 13±1; however, with the mouse speeding in circles around its pad the program slows to 21. Full-screen, it hoots even faster and prints 9±1. I EXPECT that it would give 18/19 in a Windows-free system.
The following (untested) is independent of Crt, and may give better resolution :-
procedure AnotherDelay(ms : word) ; assembler ; asm mov ax,1000 ; mul ms ; mov cx,dx ; mov dx,ax ; mov ah,$86 ; int $15 end ;
Int 15/86 may be intended for operating system use only - consider consequences.
Moslo will slow down a PC, and may help.
With more recent processors, the delay calibration count in Borland's Crt unit may be large enough to cause errors.
Programs compiled with older versions of TP (... TP5, TP5.5, TP6) can miscalibrate, without reporting any error, when the 16-bit counter overflows during startup - AFAIR, this occurred with a 486DX33, but not with a 286-12. I've heard that "the limit was about 486/25 or perhaps 386/40 with cache". There was a TP6 patch (from Borland?); TP7 & BP7 are OK for this bug. Refer to Timo Salmi's Pascal FAQ, article #67. AIUI, Pedt Scragg's Crt unit will fix this problem.
2001-02-17 : In News (RAS) : "Turbo 3 has a Delay bug similar to the BP7 runtime error 200 on 180mz/200mz Pentium Pro's and above. I had to write a NewDelay function that worked off a timer and discontinue using Delay."
DOS and DPMI (Real and Protected) mode programs, when using the Crt unit supplied by Borland in their BP7/TP7 library file, give, on many CPUs over about 200 MHz, Runtime Error 200 during program startup; many fixes are available. It is known that at least one of these fixes fails at 450-500 MHz.
Pedt Scragg has a trustworthy replacement Crt unit, available via PDCU and at Garbo. Be sure to install it correctly, with TPUMOVER.
Some of the fixes for Runtime Error 200 at program startup make the Crt.Delay procedure give shorter delays.
Windows mode programs, with or without WinCrt, are not affected, since Delay is not available.