(c)  Copyright 1989 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice, and 
is provided "as is" without warranty of any kind, either expressed or implied.  
The entire risk as to the use of this information is assumed by the user.



 		   A Timer Using the CIA Resource

		 by Adam Levin and Paul Higginbottom


The Amiga has two identical Complex Interface Adaptor (CIA) chips within it
which help it do many things; from communicating with the outside world
through the serial and parallel ports to keeping track of time.
The CIA is capable of carrying out some of these duties entirely on its own,
which helps the Amiga run at top speed.  For example, it is possible to program
a CIA chip to count from a given number down to zero and inform the
Central Processing Unit (CPU) when it has finished.  The CIA can inform the
CPU by means of an interrupt.  This is a hardware signal which causes the
CPU to pause what it was doing, perform a different task, and then resume
right where it left off.  So a clock program, for example, rather than
knowing what a tenth of a second is, could simply tell the CIA to interrupt
it every tenth of a second at which point it would update the time.

The accompanying program ("Timer") makes working with regular intervals of
time very easy.  Your program simply tells Timer how many microseconds it 
wants between interrupts.  It can then either wait for each interrupt, and 
execute its task at that time or work at some task continuously, and check 
the value of a special variable to see how many units of time have passed.



Using The Timer Program

To use the Timer program, follow these steps:

Tell the routine how many microseconds there are in your unit of time
(TIME_SLICE) with SetTimer().

Start the timing with BeginTimer().  A return value of TRUE means the
timing has begun.  A return value of FALSE means that the timer is
unavailable now - the timer or the interrupt vector may already be in use. 
Your routine is still responsible for calling EndTimer().

Every TIME_SLICE microseconds, the value of the external variable "Timer"
will be incremented.  Your program can check "Timer" to find out how many
units of time have passed.  Your program can set "Timer" to zero or another
convenient value at any point.

In addition, you can specifically wait for any or all interrupts to occur
with Wait().  The example program, main, shows how to set up and wait for
each interrupt.  It is important to keep in mind that the task performed
after an interrupt may take longer than the next interval of time.  If your
program checks the value of "Timer" immediately before the task and
immediately after; it can tell how many intervals have gone by.

Timer will compile and run using either the Lattice or Manx C compilers.
The code which is specific to one compiler or the other has been enclosed in
#ifdef/#ifndef/#endif statements to ensure that the correct compiler gets
the correct code.



Timer Limitations

Only ONE of the many programs that may be running in the Amiga's multi-
tasking environment can use the CIA resource.  Once the resource has been 
opened, no one else can use it until it is closed.  Because of this, less 
demanding applications should use the timer.device which fully supports 
multi-tasking.  The drawback of the timer.device is that it loses accuracy 
as system load increases.

The value set for TIME_SLICE will not give you an exact number of microseconds.
This is true because the CIA chip is run at one-tenth the speed of the CPU,
or about 716 KHz.  Multiply the desired number of microseconds by 1.397
to get the value for TIME_SLICE.  Also note that the external variable "Timer"
is an unsigned short, so it can only hold 65,535 counts before it wraps around
to zero.



Allocation of CIA Timers

The example timer program shown below uses Timer B of CIA-B to provide the
clock tick since this is the only timer not reserved for the system.
There are a total of six CIA timers all together, but five of these are
reserved.  The allocation of the CIA timers is as follows: 


CIA Timer Allocations

CIA A - Interrupt 2

	Timer A 	Used for keyboard handshake
	Timer B		Used for uSec timer.device
	TOD		Used for 50/60 Hz timer.device

CIA B - Interrupt 6

	Timer A		Commodore 8-bit serial bus communication
	Timer B		Not used
	TOD		Used for graphics.library beam counter


As you can see, all the timers on CIA-A are reserved for system use.  Do not 
use CIA-A in your applications.  Instead use Timer B of CIA-B.  This is not 
used by the system at all.  

You could also use Timer A of CIA-B.  Officially, this is reserved for a 
"1541-style" interface for products like the C64-Emulator.  In practice, it 
is almost never used for this purpose.

It is important to note that the CIA timer allocations shown in Appendix F of 
the Hardware Manual are wrong.  Both the Addison-Wesley and Commodore versions
have mistakes.  The table above gives the correct allocations.  

The example program listed below originally appeared in the September-October 
1988 issue of Amiga Mail.  In the original version, which was written "by the 
book", the wrong timer was used because of the error in the Hardware Manuals.
The version shown here is corrected and uses CIA-B, Timer B.

Likewise, the CIA program from Fred Fish Disk #178 by Karl Lehenbauer and Paul
Higginbottom also uses the wrong timer.  If you are doing work based on the
Fish Disk version, be sure to change the timer used to CIA-B, Timer B.  

Time-critical applications can use direct control of the CIA resources to get 
a very accurate clock signal with low overhead.  However, programmers must be 
careful to use the right CIA timer.  For applications CIA-B, Timer B should 
be used.  For more about the CIAs, see Appendix F of the Addison-Wesley
Hardware Manual.


---------------------- code starts here ------------------------------

/*
   TIMER - Amiga CIA Timer Control Software
   v1.1
   Written by Paul Higginbottom.
   Placed in the Public Domain.
   Manx make: cc timer.c
              ln timer.o -lc
*/

#include <exec/types.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>
#include <hardware/custom.h>
#include <hardware/intbits.h>
#include <hardware/cia.h>
#include <resources/cia.h>
#include <stdio.h>

/* Manx's C defines ciab in <hardware/cia.h> */
#ifndef AZTEC_C
extern struct CIA ciab;
#endif

/* Set DEBUG to a non-zero value if you want debugging. */
#define DEBUG 0

/*
   Globals: Other files can use these.
*/
int              TimerSigBit = -1;   /* allocated signal bit */
long             TimerSigMask;       /* TimerSigBit converted into a mask */
unsigned short   Timer;              /* CIA timer underflow clock */

/*
   Statics: Only this file need know about these.
*/
static struct Interrupt
   TimerInterrupt,               /* The interrupt structure */
   /*
      OldCIAInterrupt must be non-null to ensure that EndTimer() only
      removes the interrupt if it was properly installed by BeginTimer().
   */
   *OldCIAInterrupt = (struct Interrupt *)-1;
static struct Library *CIAResource = NULL;
static struct Task *thisTask;

/*
   These defines make the code look a little more readable.
*/
#define ciatlo      ciab.ciatblo
#define ciathi      ciab.ciatbhi
#define ciacr       ciab.ciacrb
#define CIAINTBIT   CIAICRB_TB
#define CLEAR       0


/*
   Start the timer, clear pending interrupts, and enable timer B interrupts.
*/
void
StartTimer()
{
   void SetICR(), AbleICR();

   ciacr &= ~CIACRBF_RUNMODE;   /* Set it to reload upon underflow. */
   ciacr |= CIACRBF_LOAD | CIACRBF_START;   /* Load and start. */
   SetICR(CIAResource, CLEAR | CIAICRF_TB);
   AbleICR(CIAResource, CIAICRF_SETCLR | CIAICRF_TB);
}

/*
   Stop the timer.  Disable timer B interrupts first.
*/
void
StopTimer()
{
   void AbleICR();

   AbleICR(CIAResource, CLEAR | CIAICRF_TB);
   ciacr &= ~CIACRBF_START;
} 
/*
   Set specific period between Timer increments.
*/
void
SetTimer(micros)
unsigned short micros;
{
   ciatlo = micros & 0xff;
   ciathi = micros >> 8;
}


/*
   Initialize interrupt structure, allocate a signal and start
   the timer.  If all goes well it returns TRUE, otherwise it
   returns FALSE.  You must call EndTimer() to clean up regardless
   of the return value.
*/
BOOL
BeginTimer()
{
   extern long AllocSignal();
   extern void TimeOut();
   extern struct Library *OpenResource();
   extern struct Interrupt *AddICRVector();
   extern struct Task *FindTask();

   thisTask = FindTask(NULL);

   /*
      Get a signal bit.
   */
   if ((TimerSigBit = AllocSignal(-1L)) == -1)
   {
#if DEBUG
      puts("Timer: AllocSignal failed.");
#endif
      return(FALSE);
   }
   TimerSigMask = 1L << TimerSigBit;
   /*
      Open the CIA resource
   */
   if ((CIAResource = OpenResource(CIABNAME)) == NULL)
   {
#if DEBUG
      printf("Timer: Couldn't open %s.\n", CIABNAME);
#endif
      return(FALSE);
   }
   /*
      Initialize the interrupt structure.
   */
   TimerInterrupt.is_Node.ln_Type = NT_INTERRUPT;
   TimerInterrupt.is_Node.ln_Pri = 127;

#ifdef AZTEC_C
#asm
;   Use machine code to set is_Data field of interrupt
;   structure to the contents of the a4 register.

   include "exec/types.i"
   include "exec/interrupts.i"

   lea      _TimerInterrupt,a0   ; Set up interrupt's data pointer as
   move.l   a4,IS_DATA(a0)       ; pointer to Manx's data segment.
#endasm
#endif

   TimerInterrupt.is_Code = TimeOut;

   /*
      Install the interrupt code.
   */
   if ((OldCIAInterrupt =
         AddICRVector(CIAResource, CIAINTBIT, &TimerInterrupt)) != NULL)
   {
#if DEBUG
      puts("Timer: Interrupt in use.");
#endif
      return(FALSE);
   }
   StartTimer();
   return(TRUE);
}

/*
   Stop the timer and remove it's interrupt vector.
*/
void
EndTimer()
{
   if (TimerSigBit != -1)
   {
      if (OldCIAInterrupt == NULL)
      {
         StopTimer();
         RemICRVector(CIAResource, CIAINTBIT, &TimerInterrupt);
      }
      FreeSignal(TimerSigBit);
   }
}

#ifdef AZTEC_C
#asm
;   Timer Interrupt handler.
;   Increment Timer variable upon timer underflow

   public   _Timer,_LVOSignal,_thisTask
   public   _TimeOut

_TimeOut:
   move.l   a4,a5               ; Save a4; get Manx data segment
   move.l   a1,a4               ; (IS_DATA is passed to interrupt in A1).
   addq.w   #1,_Timer           ; Increment timer.
   move.l   _TimerSigMask,d0    ; Put arguments in registers
   move.l   _thisTask,a1        ; in preparation for call to
   jsr      _LVOSignal(a6)      ; LVOSignal.
   move.l   a5,a4               ; Restore a4.
   rts
#endasm
#else
void TimeOut()
{
   ++Timer;
   Signal(thisTask,TimerSigMask);
}
#endif

/*
     E N D   O F   T I M E R   R O U T I N E S

(What follows is a program to exercise the timer routines).

*/






/*
  DoSomething - "Noise-making" function called by main (demo) program.
*/
void
DoSomething()
{
   int i;

   if ((Timer % 100) == 0) 
   {
      for (i = 0; i < Timer / 100; ++i)
      {
         printf(" ");
      }
      printf("clickety\n");
   }
}

/*
   MAIN - Demo program to exercise "Timer" Timer Control Software.
   Written by Paul Higginbottom.  Placed in the Public Domain.
*/

#include <exec/types.h>
/*
   Update Timer every TIME_SLICE microseconds.
*/
#define TIME_SLICE  ((unsigned short) 10000)

main()
{
   extern BOOL BeginTimer();
   extern unsigned short Timer;
   extern long TimerSigMask;

#ifdef AZTEC_C
   extern short Enable_Abort;
   Enable_Abort = 0;   /* No CTRL-C automatic break-outs allowed. */
#else
   /*  Disable Lattice CTRL-C handling via the method provided
       in your release.
   */
#endif

   SetTimer(TIME_SLICE);   /* Tell the timer the size of a time unit. */
   Timer = 0;
   if (BeginTimer())   /* Try to start the ball rolling. */
   {
      do
      {
          Wait(TimerSigMask);   /* Wait for click. */
          DoSomething();
      } while (Timer < 1000);
   }
   EndTimer();         /* It's a wrap. */
}



