From: peter@psychnet.psychol.utas.edu.au (Peter R. Tattam)
Subject: Re: High speed clock
Keywords: clock
Organization: Psychology Department, University of Tasmania
Date: Wed, 16 Dec 1992 06:44:44 GMT

In article <Bz47zL.5sH@news.cso.uiuc.edu> amead@s.psych.uiuc.edu (Alan Mead) writes:
>From: amead@s.psych.uiuc.edu (Alan Mead)
>Subject: Re: High speed clock
>Date: Fri, 11 Dec 1992 22:04:30 GMT
>Keywords: clock

>>In article <1992Nov20.091212.24871@lth.se> d92bo@efd.lth.se (Bengt Oehman) writes:
>>From: d92bo@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

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

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