Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to do .1S increment delays on x86 CPU?

182 views
Skip to first unread message

Dave Dittrich

unread,
Oct 10, 1991, 5:28:35 PM10/10/91
to
I have an application that was written by another person for a PC/AT.
In this application he does delays in an assembly language routine
using a busy loop. His code uses a constant of 42000 executions of the
LOOP instruction to simulate a .1S delay, as follows:

tenth_sec EQU 42000
...
short_wait MACRO count ; takes count as # or reg or Mem loc
; and loops CX counter that number
; 42000 = .1 sec
push cx
mov cx,count
loop $
pop cx
ENDM ; short_wait MACRO

Given the number of clock ticks for the LOOP instruction on a 286 (9 per
iteration, plus 4 for the final non-jump, and ~18 for push/pop/mov
instructions) it looks like this was coded for a 6MHz 80286. The code is
now being executed on a 25MHz 80386 machine, and may be used later on
a ?MHz 80486 CPU. Obviously the timing is now off!

I would like to calculate the delay factor for each CPU, thereby getting
a consistent (more so, at least) delay matched to the clock speed, still
keeping .1S time increments.

I assume that I could capture the clock interrupt, start an infinite loop
that simply increments a counter (to calculate a relative loop index value
for the timer interrupt period) and then calculate my desired .1S delay
value from there. (Is there an easier way to do this?)

If anyone has some suggestions (or preferably code :-) for doing this,
I would appreciate receiving some email from you!

Thanks in advance!
--
Dave Dittrich
ditt...@u.washington.edu ...!uw-beaver!u.washington.edu!dittrich

Stan Brown

unread,
Oct 11, 1991, 4:35:01 PM10/11/91
to
In article <1991Oct10.2...@milton.u.washington.edu> ditt...@milton.u.washington.edu (Dave Dittrich) writes:
>I have an application that was written by another person for a PC/AT.
>In this application he does delays in an assembly language routine
>using a busy loop. His code uses a constant of 42000 executions of the
>LOOP instruction to simulate a .1S delay [code deleted]

>
>I would like to calculate the delay factor for each CPU, thereby getting
>a consistent (more so, at least) delay matched to the clock speed, still
>keeping .1S time increments.

The following code calculates the number of loop cycles per millisecond
the first time it is called. After that, it simply waits the appropriate
number of loop cycles to sleep for the designated time. You could easily
modify this code to work in terms of deciseconds (0.1 s) instead of ms.

#include <bios.h>
#pragma loop_opt(off)
static unsigned long cycles_per_msec = 0;
static void calibrate(void);


void sleep_msec(unsigned msec)

/* sleep_msec: wait for a specified number of milliseconds

Note: The first time this function is called, it will calibrate itself. This
involves a 275-msec delay. You can control when this delay happens
(but not its duration), by calling sleep_msec(0) before beginning your
process.

89/12/25 author: Stan Brown, Oak Road Systems
90/09/23 Rewrite again: use a delay loop with one-time calibration.

Copyright (C) 1990 Oak Road Systems (216) 371-0043 */

{
unsigned long loop_count;
long system_clock;

if (!cycles_per_msec)
calibrate( );

for (loop_count=cycles_per_msec*msec; loop_count; --loop_count)
_bios_timeofday(_TIME_GETCLOCK, &system_clock);
}


static void calibrate(void)
{
long system_clock, prev_clock, final_clock;
#define INTERVALS 5
#define MSEC (INTERVALS * 55)

_bios_timeofday(_TIME_GETCLOCK, &prev_clock);
do {
_bios_timeofday(_TIME_GETCLOCK, &system_clock);
} while (system_clock == prev_clock);

cycles_per_msec = 0;
final_clock = system_clock + INTERVALS;
while (system_clock < final_clock) {
++cycles_per_msec;
_bios_timeofday(_TIME_GETCLOCK, &system_clock);
}

cycles_per_msec = (cycles_per_msec + (MSEC>>1)) / MSEC;
}

--
Stan Brown, Oak Road Systems +1 216 371 0043
Cleveland, Ohio, USA email: br...@ncoast.org
"I told you, but you wouldn't listen. So now you have to pay--as all
must pay who do not listen to the word of Brown. Hah!"

Paul Ducklin

unread,
Oct 14, 1991, 12:54:24 PM10/14/91
to
Thus spake ditt...@milton.u.washington.edu (Dave Dittrich):

>I would like to calculate the delay factor for each CPU, thereby getting
>a consistent (more so, at least) delay matched to the clock speed, still
>keeping .1S time increments.

>I assume that I could capture the clock interrupt, start an infinite loop
>that simply increments a counter (to calculate a relative loop index value
>for the timer interrupt period) and then calculate my desired .1S delay
>value from there. (Is there an easier way to do this?)

Perhaps the easiest way to deal with delays and time-driven events on
286 and better machines under DOS is to utilise the BIOS event handling
routines in the INT 15h services. The easiest is INT 15h Subfunction 86h
(WAIT):

AH = 86h
CX:DX = number of microseconds to wait before return to caller

Or try Subfunction 83h (if you'd like to do something else during the wait,
simply polling a byte of memory in the interim) EVENT WAIT:

AH = 83h
AL = 00h Set Interval:
ES:BX = pointer to byte in caller's memory which will
have high bit set asap after interval elapses
CX:DX = microsecs to elapse before posting event bit
AL = 01h Cancel:
Abort pending EVENT WAIT

Returns: CF set if AL!=0 && AL!=1 or if EVENT WAIT already busy.

This, of course, doesn't work BIOSes where INT 15h gives cassette support!!

Paul Ducklin
Raving around on the Southern Tip (well almost) of Africa
In the Good Ole' City of Pretoria, ZA.

Jesse Chisholm AAC-RjesseD

unread,
Oct 15, 1991, 12:46:44 PM10/15/91
to
ditt...@milton.u.washington.edu (Dave Dittrich) writes:
: I have an application that was written by another person for a PC/AT.

: In this application he does delays in an assembly language routine
: using a busy loop. His code uses a constant of 42000 executions of the
: LOOP instruction to simulate a .1S delay, as follows:
[ code deleted]
: If anyone has some suggestions (or preferably code :-) for doing this,

: I would appreciate receiving some email from you!
:
: Thanks in advance!
: --
: Dave Dittrich
: ditt...@u.washington.edu ...!uw-beaver!u.washington.edu!dittrich

Another way to do timings is to watch the system Time Of Day ticker,
which changes value every 54.9 milliseconds. The C code for this is:

Delay(duration)
unsigned duration;
{
unsigned t;

while (duration--) {
t = *(unsigned*)(0x0040006C);
while (t == *(unsigned*)(0x0040006C))
; /* wait for change */
}
}

Delay(1) is about 1/18th of a second, Delay(2) is about 1/9th of a second,
etcetra. This will produce the same timing on all x86 CPUs.

Jesse Chisholm | Disclaimer: My opinions are rarely understood, let
je...@altos86.altos.com | tel: 1-408-432-6200 | alone held, by this company.
je...@gumby.altos.com | fax: 1-408-435-8517 |-----------------------------
======== This company has officially disavowed all knowledge of my opinions.
--
"Question Authority!" -- Wallace Stegner
"And that's an order!"

Bill E. Eickmeier

unread,
Oct 17, 1991, 10:04:01 AM10/17/91
to
In article <53...@gumby.Altos.COM> je...@gumby.Altos.COM (Jesse Chisholm AAC-RjesseD) writes:
>
>Another way to do timings is to watch the system Time Of Day ticker,
>which changes value every 54.9 milliseconds. The C code for this is:
>
> Delay(duration)
> unsigned duration;
> {
> unsigned t;
>
> while (duration--) {
> t = *(unsigned*)(0x0040006C);
> while (t == *(unsigned*)(0x0040006C))
> ; /* wait for change */
> }
> }
>
>Delay(1) is about 1/18th of a second, Delay(2) is about 1/9th of a second,
>etcetra. This will produce the same timing on all x86 CPUs.
>

Well, not really. The time when this function gets starts could be
anywhere between just after the Time of Day ticker changes and just
before the Time of Day ticker changes - a range of 1/18 of a second.
Soooo, this routine only guarantees that:

delay(1) will be somewhere between 0 and 1/18 of a second
delay(2) will be somewhere between 1/18 and 2/18 of a second
delay(3) will be somewhere between 2/18 and 3/18 of a second
...and so on

But, granted, it may be good enough for some people to use...

Bill

Bill E. Eickmeier, The John P. Robarts Research Institute
P.O. Box 5015, 100 Perth Drive, Room 4-01.1, London, Ontario, N6A 5K8
519-663-5777 ext. 4406 fax:519-663-3789 b...@valve.heart.rri.uwo.ca

nathan engle

unread,
Oct 17, 1991, 10:56:13 AM10/17/91
to
In article <53...@julian.uwo.ca> b...@valve.heart.rri.uwo.ca
(Bill E. Eickmeier) writes:
>In article <53...@gumby.Altos.COM> je...@gumby.Altos.COM
>(Jesse Chisholm AAC-RjesseD) writes:
>>Delay(1) is about 1/18th of a second, Delay(2) is about 1/9th of a second,
>>etcetra. This will produce the same timing on all x86 CPUs.
>
>Well, not really. The time when this function gets starts could be
>anywhere between just after the Time of Day ticker changes and just
>before the Time of Day ticker changes - a range of 1/18 of a second.
>Soooo, this routine only guarantees that:
>
> delay(1) will be somewhere between 0 and 1/18 of a second
> delay(2) will be somewhere between 1/18 and 2/18 of a second
> delay(3) will be somewhere between 2/18 and 3/18 of a second
> ...and so on

You're correct that the length of the delay will vary, however I
think the important thing in this instance is that the timing (and the
size of the error) is consistant no matter what CPU or clock speed
you're using.

--
Nathan Engle Software Juggler
Indiana University Dept of Psychology
nen...@copper.ucs.indiana.edu

Daniel Gross

unread,
Oct 19, 1991, 12:44:09 AM10/19/91
to

>I have an application that was written by another person for a PC/AT.
>In this application he does delays in an assembly language routine
>using a busy loop. His code uses a constant of 42000 executions of the
>LOOP instruction to simulate a .1S delay, as follows:

>tenth_sec EQU 42000
>...
>short_wait MACRO count ; takes count as # or reg or Mem loc
> ; and loops CX counter that number
> ; 42000 = .1 sec
> push cx
> mov cx,count
> loop $
> pop cx
>ENDM ; short_wait MACRO

>Given the number of clock ticks for the LOOP instruction on a 286 (9 per
>iteration, plus 4 for the final non-jump, and ~18 for push/pop/mov
>instructions) it looks like this was coded for a 6MHz 80286. The code is
>now being executed on a 25MHz 80386 machine, and may be used later on
>a ?MHz 80486 CPU. Obviously the timing is now off!

The following is some code i wrote for a multimedia application
where all delays had to be in 1/30ths of a second to provide
the optimal synch with videodisc. Regardless of processor type
or *CPU* clock speed, the internal DOS timer is always ringing
away at 18.2 ticks/second. You can reprogram this timer to
produce any frequency you wish up to 1,193,280 Hz! The
InitInts() function below shows how to hook PC interrupt 8
(the timer interrupt) so you can process ticks anyway you like
and still let DOS do its thing. (Note I'm assuming some smart
declarations and interrupt-specific functions from the Microsoft
C 6.0 function library -- but the C code could be assembler).

#define CHANGING_TIMER YES
.
#define CLOCKFRQ 1193280L /* Processor clock tick Hz */
#define CDIVISOR 39776 /* Divisor to get 30 Hz timer interrupt */
.
/* I.e. CLOCKFRQ / CDIVISOR = 30. */
.
.
void InitInts( void )
.
{
.
#ifdef CHANGING_TIMER
_asm
{
cli ; disable interrupts to change ticks
push ax ; save ax register
mov al, 182
out 67, al ; tell port to expect tick divisor
mov al, LOW CDIVISOR
out 66, al ; send the divisor
mov al, HIGH CDIVISOR
out 66, al ; ok, now we have 30 Hz ticks!
pop ax ; put ax back as it was
sti ; re-enable interrupts
}
#endif
OldTimer = _dos_getvect(8); /* Get old INT 28 routine address */
_dos_setvect( 8, UpdateStatus ); /* point to our handler */
.
}
.
.
void UndoInts( void )
.
/* Reset timer, plug interrupt 8 back to its original state */
.
{
#ifdef CHANGING_TIMER
.
_asm
{
cli ; disable interrupts to change ticks
push ax ; save ax register
mov al, 182
out 67, al ; tell port to expect tick divisor
mov al, 255 ; high byte of 65,535 - low also
out 66, al ; send the divisor -- same byte twice
out 66, al ; ok, now we have 18.2 Hz ticks!
pop ax ; put ax back as it was
sti ; re-enable interrupts
}
#endif
.
_dos_setvect( 8, OldTimer ); /* point back to original handler */
}
.
.
.
void _fastcall pause( unsigned int ms )
/* pause for ms tenths of a second */
{
register clock_t when;
if (OkToContinue) {
when = CurrClock + (ms * 3); /* express in 18ths of a second */
while ((when > CurrClock) && OkToContinue)
;
}
}
.
UpdateStatus() is a function declared with the Microsoft non-standard
_interrupt keyword. It handles all my event-processing. I didn't need
*exact* timing on delays in my application, so the pause() function
above, with its lowly while loop and C calling-convention overhead,
was not a liability. But, you get the point.

I hope all this helps. Contact me directly via mail if you have
questions or want to discuss this further.


--
Daniel Gross
FLOW Research
ent...@panix.com

Frank Budzelaar

unread,
Oct 21, 1991, 4:01:52 AM10/21/91
to
If you just need an acurate delay, and don't want to
reprogram the 8254 on your pc, do what the borland boys
do in BC++ : calibrate!

their delay function (in miliseconds) is impressingly
acurate. what they do is first time how often a loop
is executed in that 1/18.2 seconds between two timer
interrupts. Then use a loop counter based on this value
the next time you need an acurate delay.

Greetings,

frank budzelaar


/----/----\
/ / /
----/----/----/------
/ / \
/ /-----/


Disclaimer:
Of course my opinions are my own.

------------------------------------------------------------------------
Frank Budzelaar "My other computer is a 486"
Eindhoven University of Technology seen on an XT
email: bu...@eb.ele.tue.nl
-----------------------------------------------------------------------

David DeSimone

unread,
Oct 21, 1991, 4:45:22 PM10/21/91
to

In a previous article, bu...@ebs.eb.ele.tue.nl (Frank Budzelaar) says:

>If you just need an acurate delay, and don't want to
>reprogram the 8254 on your pc, do what the borland boys
>do in BC++ : calibrate!

>their delay function (in miliseconds) is impressingly
>acurate. what they do is first time how often a loop
>is executed in that 1/18.2 seconds between two timer
>interrupts. Then use a loop counter based on this value
>the next time you need an acurate delay.

This works okay, I suppose, but what happens when you have interrupts
active, or you're working in a multitasking system, or....

Oh, sorry. Just muddying the waters a bit.

--
David DeSimone an...@cleveland.freenet.edu
Also known as Fuzzy Fox

"If you're happy and you know it, wag your tail..."

Ralf....@b.gp.cs.cmu.edu

unread,
Oct 21, 1991, 8:49:03 AM10/21/91
to
In article <13...@eba.eb.ele.tue.nl>, bu...@ebs.eb.ele.tue.nl (Frank Budzelaar) wrote:
}If you just need an acurate delay, and don't want to
}reprogram the 8254 on your pc, do what the borland boys
}do in BC++ : calibrate!
}
}their delay function (in miliseconds) is impressingly
}acurate. what they do is first time how often a loop
}is executed in that 1/18.2 seconds between two timer
}interrupts. Then use a loop counter based on this value
}the next time you need an acurate delay.

Unfortunately, the delays will be way off if you are running under a
multitasker and another task gets the CPU just as the calibration loop
is run.
--
{backbone}!cs.cmu.edu!ralf ARPA: RA...@CS.CMU.EDU FIDO: Ralf Brown 1:129/26.1
BITnet: RALF%CS.CMU.EDU@CARNEGIE AT&Tnet: (412)268-3053 (school) FAX: ask
DISCLAIMER? Did | "Secrecy is the beginning of tyranny."
I claim something?| -- from the Notebook of Lazarus Long

Bruce Evans

unread,
Oct 25, 1991, 5:15:52 AM10/25/91
to
In article <1991Oct21.2...@usenet.ins.cwru.edu> an...@cleveland.Freenet.Edu (David DeSimone) writes:
>
>In a previous article, bu...@ebs.eb.ele.tue.nl (Frank Budzelaar) says:
>
>>If you just need an acurate delay, and don't want to
>>reprogram the 8254 on your pc, do what the borland boys
>>do in BC++ : calibrate!
>>...

>This works okay, I suppose, but what happens when you have interrupts
>active, or you're working in a multitasking system, or....

Or the turbo button.

For an _accurate_ delay, you do have to reprogram the 8254 (it's an 8253
on old 8088 pc's, remember them?) and take over the timer interrupt and
somehow stop it being taken over again.
--
Bruce Evans (b...@runxtsa.runx.oz.au)

Ralf....@b.gp.cs.cmu.edu

unread,
Oct 26, 1991, 3:56:59 PM10/26/91
to
In article <1991Oct25....@runx.oz.au>, b...@runx.oz.au (Bruce Evans) wrote:
}For an _accurate_ delay, you do have to reprogram the 8254 (it's an 8253
}on old 8088 pc's, remember them?) and take over the timer interrupt and
}somehow stop it being taken over again.

That's not a good idea when running under DESQview, or DoubleDOS, or the
OS/2 compatibility box, or .... All of the above reprogram the
interrupt controller so that they get the interrupt first even if the
program takes over INT 08. Speeding up the timer does nasty things to
the multitasker.... For an accurate delay, you should either read the
825[34]'s registers directly or use the CMOS clock chip's periodic
interrupt which occurs at 1024 Hz by default but can be set a few
powers of two faster or slower (I think the range is 32 Hz - 32 kHz).

Frank Budzelaar

unread,
Oct 29, 1991, 8:26:48 AM10/29/91
to

ok, let's stop this trail. I posted a simple answer to a simple question.
the answer suited the question. ofcourse I can think of several ways to
defeat this method, and probably for nearly every method proposed.
reprogramming the 8254 as some people suggested included, as for instance
windows won't like it a bit itself. I think that the person wo stated
the original question is perfectly capable of determining how useful
a certain solution is. and if not, he/she is probably keen enough to
ask for more information.
so, if someone has a really useful contribution to this
line, let him step forward, otherwise...

(after only a load of negative contributions I'm a bit in a bad mood...)


With friendly greetings to everyone,

frank budzelaar (bu...@eb.ele.tue.nl)

0 new messages