home *** CD-ROM | disk | FTP | other *** search
- (c) Copyright 1989-1999 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. */
- }
-
-
-
-