[comp.sources.amiga] v89i128: ciatimer - high-precision/accuracy elapsed time v0.1

page%swap@Sun.COM (Bob Page) (05/11/89)

Submitted-by: karl@sugar.hackercorp.com (Karl Lehenbauer)
Posting-number: Volume 89, Issue 128
Archive-name: devices/ciatimer01.1

This is the second release of my CIA timer stuff, based on original
code by Paul Higginbottom.

The two programs herein demonstrate the use of a CIA timer to provide
a high-accuracy, high-resolution time reference for multiple running
programs.  The program 'ciatimer' sets up the timer interrupt routine,
while 'ciafinder' locates the data portion of the interrupt code and
from data therein and the value it reads from the CIA timer register,
determines the elapsed time in seconds and microseconds since the
ciatimer program installed its interrupt.

There are more details in the README file, and in the code.  Thanks to
Bob (Kodiak) Burns, Brian P. Dickson and C. Harald Koch for their
fixes to the original; these are described in further detail the
README and code.

# This is a shell archive.
# Remove anything above and including the cut line.
# Then run the rest of the file through 'sh'.
# Unpacked files will be owned by you and have default permissions.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar: SHell ARchive
# Run the following text through 'sh' to create:
#	README
#	ciafinder.c
#	ciatimer.c
#	ciatimer.h
#	makefile
# This is archive 1 of a 1-part kit.
# This archive created: Thu May 11 00:49:48 1989
echo "extracting README"
sed 's/^X//' << \SHAR_EOF > README
XSecond Release of Amiga microsecond-resolution realtime timing routines (0.1)
X-----------------------------------------------------------------------------
X
X	By providing a solid, high-accuracy realtime clock, this code
X	provides a way for timer-releated code that needs to run at
X	specific realtimes, like a SMUS player, MIDI sequencer, etc,
X	to compensate for delays in their execution caused by interrupts,
X	cycle stealing by the blitter, etc.
X
X	What you do is keep track of when in realtime you next want to 
X	run (by adding time intervals to a time returned by ElapsedTime
X	when you start, then when you're ready to set up your timer.device
X	MICROHZ delay timer, call ElapsedTime and calculate the difference 
X	in seconds and microseconds as your arguments for your timer.device
X	request.
X
X	The routine ElapsedTime gets the time by getting the number of
X	55873 microsecond ticks that the handler has seen and retrieving
X	the 0-40000 number of 1.396825 microsecond tisks from the CIA timer
X	registers, scaling them to 1.000 microsecond ticks and returning
X	the shifted-and-or'ed result.
X
X	By using the CIA timer in "continuous" mode, delays in the execution
X	of various routines don't impact the elapsed time timer we maintain.
X
X	Note that what we really want is an improved timer.device where a
X	flag in the timer request could say  "schedule me at this microsecond-
X	resolution time of day seconds and microseconds" instead of only
X	"schedule me in this many seconds and microseconds."
X
X	When the CIA interrupt handler is installed, other tasks need a
X	way to get the count maintained by the timer routine, too.
X
X	This release of the code supports multiple readers:
X
X	There is a sample main routine at the end of this program, run the
X	program in a window to start this guy as a timer.  Control-C within
X	the window to get it to exit.
X
X	Run the ciafinder program from the CLI in another window and it will
X	locate the interrupt data for the timer interrupt maintained by the
X	ciatimer task and give you the elapsed time for your time calculation
X	in this way.
X
X	At the end of the ciafinder program are a couple of routines, commented
X	out, that I use inside my SMUS player to determine the next time I want
X	to run in realtime, using the elapsed timer timer to calculate the
X	delay time for the call to the timer device -- this factors out any
X	delays caused by interrupts, DMA cycle stealing, and other delays in
X	the execution of the routine needing to run at precise intervals.
X
X
XRevisions
X---------
X
XRobert R. Burns (kodiak@amiga.UUCP) pointed out that the hardware manual
Xis incorrect with respect to CIA usage.  Here is the correct usage he
Xprovided:
X
X    CIAA (int 2)
X	timerA      keyboard handshake
X	timerB      uSec timer.device
X	TOD         60Hz timer.device
X    CIAB (int 6)
X	timerA      Commodore serial communication, usually not used
X	timerB      not used
X	TOD         graphics.library beam counter
X
Xciatimer now uses CIAB, timer B
X
XBrian P. Dickson (bpd@dretor.dciem.dnd.ca) identified a problem whereby
Xthe number I was using for the length of a tick, 1.397 microseconds,
Xis actually 1.396825 microseconds.  
X
XAlthough the difference between the two values is less than two nanoseconds,
Xthis error is accumulated every microsecond, causing the timer to run about
X1.7 milliseconds behind per second, or 0.17%, not much, but it's fixable
Xso I adopted his changes which were to change the time ratio from
X46911 timer ticks producing 65536 microseconds to 40000 timer ticks producing
X55873 microseconds.
X
XC. Harald Koch (chk@dretor.dciem.dnd.ca, utzoo!dciem!dretor!chk)
Xreported a bug where a flag used for interrupt control registers
Xwas accidentally used for CIA timer control which happened to
Xwork with timer A and fail with timer B.  The fix is to use the
Xcorrect flag, CIACRAF_START.
X
X
XRegards,
XKarl Lehenbauer @ The Hacker's Haven, Missouri City, Texas
X(Internet: karl@sugar.hackercorp.com, Usenet: uunet!sugar!karl, BIX: kelehen)
SHAR_EOF
echo "extracting ciafinder.c"
sed 's/^X//' << \SHAR_EOF > ciafinder.c
X/* TIMER - Amiga CIA Timer Control Software
X
X  originally by Paul Higginbottom, Public Domain
X
X  hacked on by karl to produce a monotonically increasing microsecond
X  clock, 12/30/88, Public Domain
X
X  second release, 4/27/89
X
X  cc +p ciafinder.c
X  ln ciafinder.o -lcl32
X
X  This code locates the data portion of the timer interrupt installed
X  on CIA B, timer B to provide a high-precision, high-accuracy elapsed
X  time timer.
X
X  It can be used by one of your programs, minus the demo main() routine
X  at the end, to get the elapsed time, provided the companion cia timer
X  interrupt has been installed, typically by the companion program to
X  this one, ciatimer.
X
X*/
X
X#include <exec/types.h>
X#include <exec/tasks.h>
X#include <functions.h>
X#include <exec/interrupts.h>
X#include <hardware/cia.h>
X#include <hardware/custom.h>
X#include <hardware/intbits.h>
X#include <resources/cia.h>
X#include <stdio.h>
X
X#include "ciatimer.h"
X
X#define MATCH 0
X
Xstatic struct Interrupt
X   CIATimerInterrupt,
X   *OldCIAInterrupt = (struct Interrupt *)-1;
X
Xstatic struct Library *CIAResource = NULL;
X
X#define ciatlo ciab.ciatblo
X#define ciathi ciab.ciatbhi
X#define ciacr ciab.ciacrb
X#define CIAINTBIT CIAICRB_TB
X#define CLEAR 0
X
Xvoid DummyCIAInterrupt()
X{
X}
X
X/* install and remove a fake interrupt so we can locate the one we want */
Xstruct CIA_Time *LocateCIATimerData()
X{
X	/* Open the CIA resource */
X	if ((CIAResource = (struct Library *)OpenResource(CIABNAME)) == NULL)
X	{
X		fprintf(stderr,"timer couldn't open cia resource\n");
X		return(NULL);
X	}
X
X	CIATimerInterrupt.is_Node.ln_Type = NT_INTERRUPT;
X	CIATimerInterrupt.is_Node.ln_Pri = 127;
X	CIATimerInterrupt.is_Code = DummyCIAInterrupt;
X	CIATimerInterrupt.is_Node.ln_Name = "ciafinder dummy interrupt";
X
X	/* install interrupt */
X	if ((OldCIAInterrupt = AddICRVector(CIAResource,CIAINTBIT,&CIATimerInterrupt)) == NULL)
X	{
X		RemICRVector(CIAResource, CIAINTBIT, &CIATimerInterrupt);
X		fprintf(stderr,"no CIA timer currently installed!\n");
X		return(NULL);
X	}
X
X	if (strcmp(OldCIAInterrupt->is_Node.ln_Name,CIATIMER_INTERRUPT_NAME) != MATCH)
X	{
X		fprintf(stderr,"CIA interrupt routine is '%s' rather than '%s'\n",OldCIAInterrupt->is_Node.ln_Name,CIATIMER_INTERRUPT_NAME);
X		return(NULL);
X	}
X
X	return((struct CIA_Time *)OldCIAInterrupt->is_Data);
X}
X
X/* return the elapsed real time in seconds and microseconds since the
X * cia timer interrupt handler was installed.
X *
X * ElapsedTime(&secs,&microsecs);
X *
X */
Xvoid ElapsedTime(tickdata_ptr,sec_ptr,usec_ptr)
Xstruct CIA_Time *tickdata_ptr;
Xint *sec_ptr,*usec_ptr;
X{
X	register long seconds, microseconds;
X	register long ciahi, cialo;
X
X	Disable();
X	ciahi = ciathi;
X	cialo = ciatlo;
X	seconds = tickdata_ptr->CIA_Seconds;
X	microseconds = tickdata_ptr->CIA_Microseconds;
X	Enable();
X	/* to multiply the timer ticks * 1.397, you can multiply by 1430
X	 * and divide by 1024 (or shift right by 10, get it?) 
X	 * Note that the timer counts down, so elapsed ticks based
X	 * on reading it are computed as CIA_TIME_CLICE minus ticks read
X	 */
X	ciahi = CIA_TIME_SLICE - ((ciahi << 8) + cialo);
X	ciahi = ((ciahi * 1430) >> 10) & 0xffff;
X
X	microseconds += ciahi;
X	if (microseconds > 1000000)
X	{
X		microseconds -= 1000000;
X		seconds++;
X	}
X
X	*sec_ptr = seconds;
X	*usec_ptr = microseconds;
X	return;
X}
X
Xmain()
X{
X	struct CIA_Time *CIA_CurrentTime_ptr;
X	long secs, microsecs;
X
X	CIA_CurrentTime_ptr = LocateCIATimerData();
X	if (CIA_CurrentTime_ptr == NULL)
X		exit(1);
X
X	ElapsedTime(CIA_CurrentTime_ptr,&secs,&microsecs);
X
X	printf("secs %d microsecs %d\n",secs,microsecs);
X}
X
X
X/*
Xlong reference_seconds, reference_microseconds;
X
Xvoid set_wait_ticks(delay_ticks)
Xlong delay_ticks;
X{
X	register long desired_seconds, desired_microseconds;
X	long current_seconds,current_microseconds;
X
X	timer_request->tr_node.io_Command = TR_ADDREQUEST;
X
X	reference_microseconds += microseconds_per_tick * delay_ticks;
X
X	while (reference_microseconds >= 1000000)
X	{
X		reference_microseconds -= 1000000;
X		reference_seconds++;
X	}
X
X	ElapsedTime(&current_seconds,&current_microseconds);
X	desired_seconds = reference_seconds - current_seconds;
X	desired_microseconds = reference_microseconds - current_microseconds;
X
X	if (desired_microseconds < 0)
X	{
X		desired_microseconds += 1000000;
X		desired_seconds--;
X	}
X
X	if (desired_seconds >= 0)
X	{
X		timer_request->tr_time.tv_secs = desired_seconds;
X		timer_request->tr_time.tv_micro = desired_microseconds;
X	}
X	else
X	{
X		timer_request->tr_time.tv_secs = 0;
X		timer_request->tr_time.tv_micro = 1;
X	}
X	SendIO(timer_request);
X}
X
XSetReferenceTime()
X{
X	ElapsedTime(&reference_seconds,&reference_microseconds);
X}
X
X*/
X
SHAR_EOF
echo "extracting ciatimer.c"
sed 's/^X//' << \SHAR_EOF > ciatimer.c
X
X/* TIMER - Amiga CIA Timer Control Software
X
X  originally by Paul Higginbottom, Public Domain
X
X  hacked on by karl to produce a monotonically increasing microsecond
X  clock, 12/30/88, Public Domain
X
X  second version, released 4/27/89, incorporates changes by Bob (Kodiak)
X  Burns, Brian P. Dickson and C. Harald Koch.  Specifically, Bob pointed
X  out that the hardware manual was wrong and CIA B Timer B was the free
X  timer, Brian P. Dickson provided greater accuracy by using 1.396825 for
X  the time constant and determining the time constants based on that
X  number.  Finally, C. Harald Koch found a bug in which the wrong flag
X  was used to initialize a control bit, this made it work for CIA A but
X  not for CIA B.
X
X  cc +p ciatimer.c
X  ln ciatimer.o -lcl32
X
X  To start the timer, execute BeginCIATimer()
X
X  cc +p ciatimer.c
X  ln ciatimer.o -lcl32
X
X	By providing a solid, high-accuracy realtime clock, this code
X	provides a way for timer-releated code that needs to run at
X	specific realtimes, like a SMUS player, MIDI sequencer, etc,
X	to compensate for delays in their execution caused by interrupts,
X	cycle stealing by the blitter, etc.
X
X	What you do is keep track of when in realtime you next want to 
X	run (by adding time intervals to a time returned by ElapsedTime
X	when you start, then when you're ready to set up your timer.device
X	MICROHZ delay timer, call ElapsedTime and calculate the difference 
X	in seconds and microseconds as your arguments for your timer.device
X	request.
X
X	The routine ElapsedTime gets the time by getting the number of
X	55873 microsecond ticks that the handler has seen and retrieving
X	the 0-40000 number of 1.396825 microsecond tisks from the CIA timer
X	registers, scaling them to 1.000 microsecond ticks and returning
X	the shifted-and-or'ed result.
X
X	Note that what we really want is an improved timer.device where a
X	flag in the timer request could say  "schedule me at this microsecond-
X	resolution time of day seconds and microseconds" instead of only
X	"schedule me in this many seconds and microseconds."
X
X	When the CIA interrupt handler is installed, other tasks need a
X	way to get the count maintained by the timer routine, too.
X
X	This release of the code supports multiple readers.
X
X	There is a sample main routine at the end of this program, run the
X	program in a window to start this guy as a timer.  Control-C within
X	the window to get it to exit.
X
X	Run the ciafinder program from the CLI in another window and it will
X	locate the interrupt data for the timer interrupt maintained by the
X	ciatimer task and give you the elapsed time for your time calculation
X	in this way.
X*/
X
X#include <exec/types.h>
X#include <exec/tasks.h>
X#include <functions.h>
X#include <exec/interrupts.h>
X#include <hardware/cia.h>
X#include <hardware/custom.h>
X#include <hardware/intbits.h>
X#include <resources/cia.h>
X#include <stdio.h>
X#include <libraries/dos.h>
X
X#include "ciatimer.h"
X
Xstruct CIA_Time CIA_CurrentTime = {0, 0};
X
Xstatic struct Interrupt
X   CIATimerInterrupt,
X   *OldCIAInterrupt = (struct Interrupt *)-1;
X
Xstatic struct Library *CIAResource = NULL;
X
X#define ciatlo ciab.ciatblo
X#define ciathi ciab.ciatbhi
X#define ciacr ciab.ciacrb
X#define CIAINTBIT CIAICRB_TB
X#define CLEAR 0
X
Xvoid CIAInterrupt()
X{
X	CIA_CurrentTime.CIA_Microseconds += CIA_TIME_QUANTITY;
X	if (CIA_CurrentTime.CIA_Microseconds > 1000000)
X	{
X		CIA_CurrentTime.CIA_Seconds++;
X		CIA_CurrentTime.CIA_Microseconds -= 1000000;
X	}
X}
X
X/* start the timer, clear pending interrupts, and enable timer A
X * Interrupts */
XStartCIATimer()
X{
X	ciacr &= ~(CIACRBF_RUNMODE);	/* set it to reload on overflow */
X	ciacr |= (CIACRBF_LOAD | CIACRBF_START);
X	SetICR(CIAResource,CLEAR|CIAICRF_TB);
X	AbleICR(CIAResource, CIAICRF_SETCLR | CIAICRF_TB);
X}
X
Xvoid StopCIATimer()
X{
X	AbleICR(CIAResource, CLEAR | CIAICRF_TB);
X	ciacr &= ~CIACRBF_START;
X}
X
X/* set period between timer increments */
Xvoid SetCIATimer(micros)
Xunsigned short micros;
X{
X	ciatlo = micros & 0xff;
X	ciathi = micros >> 8;
X}
X
X/* stop the timer and remove its interrupt vector */
XEndCIATimer()
X{
X	if (OldCIAInterrupt == NULL)
X	{
X		StopCIATimer();
X		RemICRVector(CIAResource, CIAINTBIT, &CIATimerInterrupt);
X	}
X}
X
XBOOL BeginCIATimer()
X{
X	/* Open the CIA resource */
X	if ((CIAResource = (struct Library *)OpenResource(CIABNAME)) == NULL)
X	{
X		fprintf(stderr,"cia periodic timer startup couldn't open cia resource\n");
X		return(0);
X	}
X
X	CIATimerInterrupt.is_Node.ln_Type = NT_INTERRUPT;
X	CIATimerInterrupt.is_Node.ln_Pri = 127;
X	CIATimerInterrupt.is_Node.ln_Name =  CIATIMER_INTERRUPT_NAME;
X	CIATimerInterrupt.is_Code = CIAInterrupt;
X	CIATimerInterrupt.is_Data = (APTR)&CIA_CurrentTime;
X
X	/* install interrupt */
X	if ((OldCIAInterrupt = AddICRVector(CIAResource,CIAINTBIT,&CIATimerInterrupt)) != NULL)
X	{
X		fprintf(stderr,"cia timer interrupt already in use by '%s'",OldCIAInterrupt->is_Node.ln_Name);
X		EndCIATimer();
X		return(0);
X	}
X
X	SetCIATimer(CIA_TIME_SLICE);
X
X	StartCIATimer();
X	return(TRUE);
X}
X
Xmain()
X{
X	if (BeginCIATimer())
X	{
X		Wait(SIGBREAKF_CTRL_C);
X	}
X	else 
X		exit(1);
X
X	EndCIATimer();
X	exit(0);
X}
SHAR_EOF
echo "extracting ciatimer.h"
sed 's/^X//' << \SHAR_EOF > ciatimer.h
X/* TIMER - Amiga CIA Timer Control Software
X
X  originally by Paul Higginbottom, Public Domain
X
X  hacked on by karl to produce a monotonically increasing microsecond
X  clock, 12/30/88, Public Domain
X
X  second release, 4/27/89, Public Domain
X
X  cc +p ciatimer.c
X  ln ciatimer.o -lcl32
X
X  To start the timer, execute BeginCIATimer()
X
X*/
X
Xstruct CIA_Time
X{
X	long CIA_Seconds;
X	long CIA_Microseconds;
X};
X
X/* timeslice is 40000 intervals.  Each interval is 1.396825 microseconds,
X * this should correspond to a timing interval of 55873 microseconds */
X#define CIA_TIME_SLICE ((unsigned short) 40000)
X#define CIA_TIME_QUANTITY ((unsigned short) 55873)
X
X#define CIATIMER_INTERRUPT_NAME "CIA Periodic Timer"
X
SHAR_EOF
echo "extracting makefile"
sed 's/^X//' << \SHAR_EOF > makefile
X
XCFLAGS= +p
X
Xall:	ciatimer ciafinder
X
Xciatimer:	ciatimer.o
X	ln ciatimer.o -lcl32
X
Xciafinder:	ciafinder.o
X	ln ciafinder.o -lcl32
X
Xshar:
X	shar >ciatimer.shar README ciatimer.h ciatimer.c ciafinder.c makefile
SHAR_EOF
echo "End of archive 1 (of 1)"
# if you want to concatenate archives, remove anything after this line
exit