High speed clock

57 views
Skip to first unread message

Bengt Oehman

unread,
Nov 20, 1992, 4:12:12 AM11/20/92
to

Does anyone out there know how to do high-speed timing?
The internal clock ticks with 18.2 ticks per second, and
sometimes this is too slow. I want a resolution of about
one millisecond.

Please reply via email.

--------------------------------------------------------------
Bengt Oehman, studying at Lund Institute of Technology, Sweden
email: d9...@efd.lth.se
--------------------------------------------------------------

Peter R. Tattam

unread,
Dec 8, 1992, 1:34:39 AM12/8/92
to
In article <1992Nov20.0...@lth.se> d9...@efd.lth.se (Bengt Oehman) writes:
>From: d9...@efd.lth.se (Bengt Oehman)
>Subject: High speed clock
>Keywords: clock
>Date: 20 Nov 92 09:12:12 GMT

>Please reply via email.


Maybe this will help....

I savaged this from another unit on the fly so it pardon me if it doesn't
compile. BTW, don't forget to stop_clock before you finish or disaster may
ensue :-)

{$F+}
unit msecs;

interface

{ 1 msec timer routines }
var
timer:word; { msec timer }

procedure delay_ticks(t:word); { wait until t clock ticks have elapsed }
procedure start_clock; { starts the 1 msec timer }
procedure stop_clock; { stops the 1 msec timer }

implementation

procedure delay_ticks(t:word);
begin
inc(t,timer);
repeat until integer(timer - t) >= 0;
end;

const clock_active:boolean = false;
one_msec = 1193;
var save_clock:pointer;
clocks:word;

procedure tick_int; assembler;
asm
push ax
push ds
mov ax,seg @data
mov ds,ax
mov al,$20
out $20,al
inc [timer]
add [clocks],one_msec
jnc @1
pushf
call [save_clock]
@1:
pop ds
pop ax
iret
end;


procedure start_clock;
begin
if clock_active then exit;
inc(clock_active);
timer := 0;
clocks := 0;
getintvec($08,save_clock);
setintvec($08,@tick_int);
port[$43] := $36;
port[$40] := lo(one_msec);
port[$40] := hi(one_msec);
end;

procedure stop_clock;
begin
if not clock_active then exit;
dec(clock_active);
port[$43] := $36;
port[$40] := 0;
port[$40] := 0;
setintvec($08,save_clock);
end;

end.
----------------------------------------------------------------------------
P.Tattam International Phone 61-02-202346
Programmer, Psychology Department Australia Phone 002-202346
University of Tasmania, Hobart, Tasmania, Australia
----------------------------------------------------------------------------

Alan Mead

unread,
Dec 11, 1992, 5:04:30 PM12/11/92
to
>In article <1992Nov20.0...@lth.se> d9...@efd.lth.se (Bengt Oehman) writes:
>From: d9...@efd.lth.se (Bengt Oehman)
>Subject: High speed clock
>Keywords: clock
>Date: 20 Nov 92 09:12:12 GMT


>Does anyone out there know how to do high-speed timing?
>The internal clock ticks with 18.2 ticks per second, and
>sometimes this is too slow. I want a resolution of about
>one millisecond.

>Please reply via email.

I'll reply via email too...

First, a solution: there is a self-extracting zip file, TIMER.EXE,
that can be had by anon-ftp from my host, s.psych.uiuc.edu (in /pub I
think) that incudes a driver that will do this.

Now, can anyone EXPLAIN how this is done? I have always wanted to
know--it's really not the best situation to have very OS-specific code
that you cannot understand and thus maintain. And, unfortunately, the
standard in psych is to time events to the millisecond.

Thanks much.

-alan

Timo Salmi

unread,
Dec 12, 1992, 2:05:35 AM12/12/92
to
In article <Bz47z...@news.cso.uiuc.edu> am...@s.psych.uiuc.edu (Alan Mead) writes:
>>Does anyone out there know how to do high-speed timing?

>First, a solution: there is a self-extracting zip file, TIMER.EXE,

>Now, can anyone EXPLAIN how this is done? I have always wanted to


>know--it's really not the best situation to have very OS-specific code

I can't but I have some references I have come across stored in
115799 Sep 20 08:35 garbo.uwasa.fi:/pc/ts/tsfaq30.zip

But hopefully someone else can do better than I on this to explain
in comp.lang.pascal.

All the best, Timo

..................................................................
Prof. Timo Salmi
Moderating at garbo.uwasa.fi anonymous FTP archives 128.214.87.1
Faculty of Accounting & Industrial Management; University of Vaasa
Internet: t...@uwasa.fi Bitnet: salmi@finfun ; SF-65101, Finland

William C. Thompson

unread,
Dec 12, 1992, 2:58:37 AM12/12/92
to

I don't normally do this, but here is a unit I have for timing.

Unit Timer;

{
*********************************************************************
** **
** Author....: Chris Wood **
** Date......: 7-11-1992 **
** Internet..: wo...@jacobs.cs.orst.edu **
** BBS.......: A Separate Reality (503)926-6991 **
** (Leave mail to the sysop) **
** **
** This source code is being released as Free-Ware. You may use **
** this code in your programs and modify it to fit your needs. The **
** only restrictions are that you may not distribute the source **
** code in modified form or charge for the source code itself. If **
** you discover any errors in the source code or find a more **
** efficient way to handle any of the following code, please leave **
** me e-mail at the internet address or the BBS listed above. All **
** comments are welcome. **
** **
*********************************************************************
}

{$O+}
{$N+}
{$E+}

Interface

Var ClockTicks : LongInt; { Number of ticks elapsed }

Procedure TimerOn ( Freq : LongInt );
Procedure TimerOff;
Procedure ResetTimer;
Function TimeElapsed : Real;

Implementation

Uses CRT,DOS;

Const MaxRate = 1193180;

Var OldInt08 : Procedure;
OldInt1C : Procedure;
IntCount08 : Word;
Trigger : Word;
TimerAlreadySet : Boolean;
Frequency : Word;

Procedure IrqOn; Inline($FB);

Procedure IrqOff; Inline($FA);

{$F+}
Procedure NewInt1C; Interrupt;
Begin
Inc(ClockTicks);
End;
{$F-}

{$F+}
Procedure NewInt08; Interrupt;
Begin
IrqOff;
Inline($CD/$1C); {Generate INT 1Ch instruction to call interrupt 1Ch}
If IntCount08=Trigger then
Begin
IntCount08:=0;
Inline($9C);
OldInt08;
End
Else
Begin
Inc(IntCount08);
End;
Port[$20]:=$20; {Sends non-specific EOI to the PIC}
IrqOn;
End;
{$F-}

Procedure TimerOn ( Freq : LongInt );
Var Temp : LongInt;
Count : Word;
Begin
If Not(TimerAlreadySet) then
Begin
ClockTicks:=0;
IntCount08:=0;
Frequency:=Freq;
Trigger:=Trunc(Freq/18.2);
Temp:=MaxRate;
Temp:=Trunc(Temp/Freq);
Count:=Temp;
GetIntVec($08,@OldInt08);
SetIntVec($08,Addr(NewInt08));
GetIntVec($1C,@OldInt1C);
SetIntVec($1C,Addr(NewInt1C));
Port[$43]:=$B6;
Port[$40]:=Lo(Count);
Port[$40]:=Hi(Count);
TimerAlreadySet:=True;
End;
End;

Procedure TimerOff;
Begin
If TimerAlreadySet then
Begin
Port[$43]:=$B6;
Port[$40]:=$FF;
Port[$40]:=$FF;
SetIntVec($08,@OldInt08);
SetIntvec($1C,@OldInt1C);
TimerAlreadySet:=False;
End;
End;

Procedure ResetTimer;
Begin
ClockTicks:=0;
End;

Function TimeElapsed : Real;
Begin
TimeElapsed:=ClockTicks/Frequency;
End;

Begin
TimerAlreadySet:=False;
End.
--
"What do you mean Star Trek isn't real!?" - anonymous

Peter R. Tattam

unread,
Dec 16, 1992, 1:44:44 AM12/16/92
to
In article <Bz47z...@news.cso.uiuc.edu> am...@s.psych.uiuc.edu (Alan Mead) writes:
>From: am...@s.psych.uiuc.edu (Alan Mead)
>Subject: Re: High speed clock
>Date: Fri, 11 Dec 1992 22:04:30 GMT
>Keywords: clock

>>Please reply via email.

>Thanks much.

>-alan

Ok. I posted something a while back to do it.

Basically the idea is to reprogram the timer chip to tick at a different rate.
You will need some data sheets on how to drive the timer chip... I think
help-pc has some info in it. The clock normally divides down a 1.193 Mhz clock
by 65536 (a divisor of 0 which wraps around in 16 bits) This is where the
really wierd number of 18.2 comes from. When they designed the original PC,
they skimped on the design a bit. That 1.193 Mhz is derived from the 4.77 Mhz
master clock. Pity they didn't choose a more palatable clock frequency, it
has made the PC an extremely poor timekeeper.

What you have to do is change the divisor to some other number. To
get 1000 Hz (1 millisecond) you will need a divisor of 1193. This is accurate
to 0.1% which is good enough for most purposes, but the inaccuracy builds up
over time.... for e.g. recording a night's eeg data.

I have experimented with rates down to a divisor of 64 when using the clock to
generate sound samples on the fly with a 386. Quite a fun little way to turn
your pc into a primitive sound processor.

Of course changing the default speed will muck up the time as recorded by DOS.
To get around this, I use the following code to check when the clock *would*
have normally ticked. I have a counter which has added to it the clock
divisor every time the interrupt is called. This counter is a word value so
every time the counter "wraps around", the carry bit is set. Now there is no
mechanism for testing the carry bit in pascal after adding one variable to
another so one has to resort to a little assembly code to make it happen.
Here's the critical section. I have annotated it a little to hopefully
explain what's going on.

procedure tick_int; assembler;
asm

{ save ax and ds }

push ax
push ds

{ set up our ds }

mov ax,seg @data
mov ds,ax

{ tell the interrupt controller we have finished...
this allows next timer interrupt to proceed even when halfway through the
timer tick }

mov al,$20
out $20,al

{ increment our millisecond timer }

inc [timer]

{ here's the tricky bit.... update our count of 1.193Mhz clocks }

add [clocks],one_msec { one_msec is a constant defined as 1193 }
jnc @1

{ our counter overflowed... call the standard bios clock }

pushf
call [save_clock]

@1:
{ we are done... usual return from interrupt }

pop ds
pop ax
iret
end;

Now most of this needn't be done as assembly code (don't flame me even though
it's in a pascal group, but someone did ask *how* it was done.) the only
critical bits would be the "add [clock],one_msec" part and the following
"pushf/call [save_clock]" pair. It's impossible to simulate the pushf without
a lot of hard work. The check for carry could be simulated by using a longint
and check for >= 65536 and taking appropriate actions, but the carry method
will be much safer since the add operation is atomic and the carry is a
side-effect of the operation.

Reprogramming the timer is fairly simple.... just a few port assignments.

port[$43] := $36;
port[$40] := lo(one_msec);
port[$40] := hi(one_msec);

and when finished return it back to normal.

port[$43] := $36;
port[$40] := 0;
port[$40] := 0;

Don't bother trying to save the values of the ports.... reading and writing
accesses different registers.

All that remains to be done is to save the old interrupt vector, attach the
routine to our routine, and away you go.

I will post the full unit again in another posting.

When you've figured all that out, I'll describe a technique I developed which
allows you to send samples to the pc's speaker by twiddling a couple of timer
registers.

Peter

Peter R. Tattam

unread,
Dec 16, 1992, 2:14:44 AM12/16/92
to
Well. Here 'tis.
have fun.
------------------- cut here ----------------------
{ millisecond timer unit }

unit msecs;

interface

var
timer:word; { msec timer }

idle:procedure; { you can change this to do something useful when delaying}

procedure delay_ticks(t:word); { resume until t clock ticks have elapsed }


procedure start_clock; { starts the 1 msec timer }
procedure stop_clock; { stops the 1 msec timer }

implementation

uses dos;

procedure delay_ticks(t:word);
begin
inc(t,timer);

repeat idle until integer(timer - t) >= 0;
end;

const clock_active:boolean = false;
one_msec = 1193;
var save_clock:pointer;
clocks:word;

procedure tick_int; far; assembler;
asm
push ax
push ds


mov ax,seg @data
mov ds,ax

mov al,$20
out $20,al
inc [timer]
add [clocks],one_msec
jnc @1
pushf
call [save_clock]
@1:

pop ds
pop ax
iret
end;

procedure start_clock;
begin
if clock_active then exit;
inc(clock_active);
timer := 0;
clocks := 0;
getintvec($08,save_clock);
setintvec($08,@tick_int);

port[$43] := $36;
port[$40] := lo(one_msec);
port[$40] := hi(one_msec);

end;

procedure stop_clock;
begin
if not clock_active then exit;
dec(clock_active);

port[$43] := $36;
port[$40] := 0;
port[$40] := 0;

setintvec($08,save_clock);
end;

procedure nothing; far;
begin
end;

var saveexit:pointer;

procedure uninstall; far;
begin
exitproc := saveexit;
if clock_active then stop_clock;
end;

begin
timer := 0;
idle := nothing;
saveexit := exitproc;
exitproc := @uninstall;
end.


-------------------- cut here --------------------

phy...@csc.canterbury.ac.nz

unread,
Dec 16, 1992, 8:51:05 PM12/16/92
to
In article <peter.430...@psychnet.psychol.utas.edu.au>, pe...@psychnet.psychol.utas.edu.au (Peter R. Tattam) writes:
> In article <Bz47z...@news.cso.uiuc.edu> am...@s.psych.uiuc.edu (Alan Mead) writes:
>>From: am...@s.psych.uiuc.edu (Alan Mead)
>>>Does anyone out there know how to do high-speed timing?
>>>The internal clock ticks with 18.2 ticks per second, and
>>>sometimes this is too slow. I want a resolution of about
>>>one millisecond.
>
> Basically the idea is to reprogram the timer chip to tick at a different rate.
> ...What you have to do is change the divisor to some other number. To
> get 1000 Hz (1 millisecond) you will need a divisor of 1193.

There is another way, avoiding reprogramming the timer.

You can read the value of the counter out while it is counting, and use it
in conjunction with the longint count of 18.2..Hz ticks at $40:$6C (probably
ignore the least significant byte - you should still read it from the port
though). Remember to disable interrupts for the few instructions, and read the
two halves of the counter twice if you want toi take notice of the least
significant byte.

The following program has two functions - one that returns the time as a real
number of seconds (accurate to about 0.0005 sec on a 10Mhz AT), and one giving
the time in microticks (4770000/4 of them per second), accurate to about 90
microticks (sometimes more due to the PC's timer interrupt service routine) on
a 10MHz AT. Notice the lack of floating point arithmetic helps the speed.

If you have a slow PC/XT with a CMOS clock card, you may prefer to use the
internal timer in that, which can give a resolution down to 1/1000th of a
second if you find the correct I/O port to read, by the way.

----snip----
program HighResClock; {example program by M.Ait...@csc.canterbury.ac.nz}

uses DOS;

function RealTime : real;
const f1 = -$40000/4.77e6; f2 = f1/256;
var TickCount : longint absolute $40:$6C; {approx. 18.1961 ticks per second}
TicksTimes256 : longint absolute $40:$6B;
result : longint;
CounterHi : byte;
CounterLo : byte;
begin
inline($FC); {DisableInterrupts}
result:=TickCount; {number of times counter has ticked over since boot}
CounterLo:=port[$40]; {have to read it, even though we don't use it}
CounterHi:=port[$40];
inline($FB); {EnableInterrupts}
RealTime:=Result*f1 + CounterHi*f2;
end;

function ExactTime : longint;
var TickCount : word absolute $40:$6C; {approx. 18.1961 ticks per second}
Part : record LoByte,HiByte : byte;
HighestWord : word;
end;
result : longint absolute Part;
CounterHi,
CounterLo : byte;
begin
DisableInterrupts;
Part.HighestWord:=TickCount;
CounterLo:=port[$40];
CounterHi:=port[$40]; {over-write the first byte from 46Bh}
Part.LoByte:=Port[$40];
Part.HiByte:=Port[$40]; {read twice, in case Hi byte changed}
EnableInterrupts;
if CounterHi<>Part.HiByte
then Part.LoByte:=200;
ExactTime:=result; {result is units of 0.838574423 microseconds}
end;

begin
writeln('Takes ',RealTime-RealTime:9:6,' sec to call the RealTime function');
writeln('Takes ',-(ExactTime-ExactTime):16,' microticks in ExactTime');
end.

Reply all
Reply to author
Forward
0 new messages