home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Developer CD v1.2
/
amidev_cd_12.iso
/
reference
/
amiga_mail_vol1
/
serparcia
/
timercia
< prev
next >
Wrap
Text File
|
1990-01-26
|
12KB
|
417 lines
(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. */
}