packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/analogue.mod

IMPLEMENTATION MODULE AnalogueIO;

	(********************************************************)
	(*							*)
	(*		Analogue Input and Output.		*)
	(*							*)
	(*  Programmer:		P. Moylan			*)
	(*  Last edited:	24 August 1993			*)
	(*  Status:						*)
	(*	Polling method is working.			*)
	(*	DMA method fails, reason not yet known.		*)
	(*							*)
	(********************************************************)

FROM Windows IMPORT	(* for testing *)
    (* type *)	Window, Colour, FrameType, DividerType,
    (* proc *)	OpenWindow, WriteString, WriteLn;

FROM NumericIO IMPORT	(* for testing *)
    (* proc *)	WriteHexByte, WriteCard, WriteLongCard;

FROM SYSTEM IMPORT
    (* type *)	ADDRESS,
    (* proc *)	ADR;

FROM LowLevel IMPORT
    (* proc *)	OutByte, InByte,
		LowByte, HighByte, MakeWord,
		LS, IANDB, AddOffset;

FROM DMA IMPORT
    (* proc *)	LoadDMAparameters;

FROM TaskControl IMPORT
    (* proc *)	WaitForInterrupt, CreateInterruptTask;

FROM Semaphores IMPORT
    (* type *)	Semaphore,
    (* proc *)	CreateSemaphore, DestroySemaphore, Wait, Signal;

(************************************************************************)

CONST testing = FALSE;

VAR debug: Window;	(* for testing *)

CONST

    (* IOBase is the starting port number for the I/O board.  The	*)
    (* value is jumper selectable.  The factory setting is 300H.	*)

    IOBase = 300H;

    (* The interrupt number is jumper selectable, with IRQ2 as the	*)
    (* factory setting.  The interrupt controller normally maps IRQ2 to	*)
    (* processor interrupt number 10.  The PC/AT is different: IRQ2 is	*)
    (* reserved as the slave request from interrupt controller 2, so	*)
    (* the request line which would have been IRQ2 becomes IRQ9, which	*)
    (* is the second request line of interrupt controller 2 and which	*)
    (* the interrupt controller maps to processor interrupt number 113	*)
    (* (=71H).  (Confused yet?  It get worse.)  But then the BIOS maps	*)
    (* interrupt 113 back to processor interrupt number 10.  Where does	*)
    (* this leave us?   If we don't mind the slight overhead caused by	*)
    (* this software mapping, we can just use interrupt number 10 and	*)
    (* keep compatibility across computer models.  That's what this	*)
    (* version of this module does.  If, on the other hand, the		*)
    (* sampling rate is so high that that overhead is intolerable, then	*)
    (* we would have to deal with the slave interrupt controller.	*)

    InterruptNumber = 10;

    (* The I/O board has 14 addressable ports, of which the last two	*)
    (* belong to the on-board counter/timer chip.			*)

    CounterDataPort = IOBase+12;
    CounterControlPort = CounterDataPort+1;

    (* Each of the five counters has a Counter Mode Register.  The bits	*)
    (* in this register govern the following options:			*)
    (*	bits 15-13	gating control (000=no gating)			*)
    (*	bit     12	1 = count on falling clock edge			*)
    (*	bits  11-8	counter source selection			*)
    (*	bits   7-5	counter mode					*)
    (*	bit	 4	0 = binary count, 1 = BCD count			*)
    (*	bit	 3	0 = count down, 1 = count up			*)
    (*	bits   2-0	output control					*)
    (* The following mnemonics define options which we want to use.	*)
    (* For brevity, the options we never use are omitted.		*)

    ActiveHighGate = 8000H;
    F1source = 0B00H;
    ModeQ = 0A0H;
    ModeJ = 60H;
    ModeD = 20H;
    ToggledOutput = 2;
    PulseOutput = 1;

    (* Some useful commands to the counter/timer chip.	*)

    Select4 = 8;
    Select5 = 16;
 
    LoadDataPointerRegister = 0;
    LoadAllCounters = 5FH;
    LoadAndArm = 060H;
    Disarm = 0C0H;
    EnableDataPointerSequencing = 0E0H;
    ClearToggle = 0E0H;
    ResetCounters = 0FFH;

    (* Definitions for the DMA controller. *)

    DMAchannel = 1;
    DMAReadCode = 1;

(************************************************************************)

TYPE
    SamplingMethod = (DMA, Polling);

VAR
    (* The following record keeps track of information needed during	*)
    (* periodic sampling, to save the caller the bother of repeating	*)
    (* the information at every sample time.				*)

    SamplingInfo:   RECORD
			method: SamplingMethod;
			ChannelSelectByte: BYTE;
			BufferAddress: ADDRESS;
			ByteCount: CARDINAL;
		    END (*RECORD*);

    (* The semaphore called tick is used only when analogue input is	*)
    (* being collected in the periodic sampling mode.			*)

    tick: Semaphore;

(************************************************************************)
(*				DIGITAL I/O				*)
(************************************************************************)

PROCEDURE DigitalOut (value: BYTE);

    (* Sends the given value to the digital output port of the board.	*)

    BEGIN
	OutByte (IOBase+11, value);
    END DigitalOut;

(************************************************************************)

PROCEDURE DigitalInput (): BYTE;

    (* Reads the digital input port of the board.	*)

    BEGIN
	RETURN InByte (IOBase+10);
    END DigitalInput;

(************************************************************************)
(*		      ANALOGUE OUTPUT (RTI-815 ONLY)			*)
(************************************************************************)

PROCEDURE AnalogueOut (channel: OutputChannelNumber;  value: WORD);

    (* Analogue output.  The channel number should be 0 or 1.  Only the	*)
    (* least significant 12 bits of the value are used.  The value can	*)
    (* be treated as either a signed or an unsigned 12-bit number,	*)
    (* depending on hardware jumper selections.				*)

    BEGIN
	OutByte (IOBase+5+2*channel, LowByte(value));
	OutByte (IOBase+5+2*channel+1, HighByte(value));
    END AnalogueOut;

(************************************************************************)
(*		      ANALOGUE INPUT - SINGLE SAMPLE			*)
(************************************************************************)

PROCEDURE AnalogueInput (channel: InputChannelNumber; gain: GainCode): WORD;

    (* Analogue input.  The value returned can be a signed or unsigned	*)
    (* number, depending on jumper selections on the board.		*)
    (* This procedure picks up a single sample when called.  It does	*)
    (* not use interrupts or DMA.  It is recommended for use only in	*)
    (* those cases (e.g. isolated sample, or aperiodic sampling) where	*)
    (* the caller takes responsibility for timing.  More commonly, the	*)
    (* periodic sampling procedures, given later, will be more		*)
    (* appropriate.  This procedure should NOT be called when periodic	*)
    (* sampling has been activated; the results would be unpredictable.	*)

    BEGIN
	(* Put the A/D converter into non-interrupt, non-DMA mode, and	*)
	(* clear any flags which might be outstanding.			*)

	OutByte (IOBase, 0);		(* status/control port *)
	OutByte (IOBase+9, 0);		(* "flags clear" port *)

	(* Start the conversion.  (Function LS is a left shift).	*)

	OutByte (IOBase+1, BYTE(channel + ORD(LS(gain, 5))));
	OutByte (IOBase+2, 0);		(* "start conversion" port *)

	(* Wait for conversion complete.  The conversion time is in the	*)
	(* range 8-35 microseconds, depending on things like hardware	*)
	(* options selected, so there's not enough time to make a task	*)
	(* switch worthwhile.  This is why this procedure does not use	*)
	(* an interrupt to sense completion.				*)

	WHILE ORD(IANDB (InByte(IOBase),60H)) = 0 DO
	    (* busy wait, until either the DONE or	*)
	    (* OVERRUN ERROR flag is set.		*)
	END (*WHILE *);

	(* Return the value.  Note that we don't bother telling the	*)
	(* caller about an overrun error, since that is normally caused	*)
	(* by trying to drive the A/D converter too fast, and overheads	*)
	(* like procedure entry time make that impossible with this	*)
	(* procedure.							*)

	RETURN MakeWord (InByte(IOBase+4),InByte(IOBase+3));

    END AnalogueInput;

(************************************************************************)
(*		    ANALOGUE INPUT - PERIODIC SAMPLING			*)
(************************************************************************)

PROCEDURE DumpCounter (w: Window;  counter: SHORTCARD);

    (* For testing: dumps the mode, load, and hold registers of the	*)
    (* given counter.							*)

    BEGIN

	OutByte (CounterControlPort, EnableDataPointerSequencing);
	OutByte (CounterControlPort, LoadDataPointerRegister + counter);

	WriteHexByte (w, InByte (CounterDataPort));
	WriteString (w, "  ");
	WriteHexByte (w, InByte (CounterDataPort));
	WriteString (w, "  ");
	WriteHexByte (w, InByte (CounterDataPort));
	WriteString (w, "  ");
	WriteHexByte (w, InByte (CounterDataPort));
	WriteString (w, "  ");
	WriteHexByte (w, InByte (CounterDataPort));
	WriteString (w, "  ");
	WriteHexByte (w, InByte (CounterDataPort));
	WriteLn (w);

    END DumpCounter;

(************************************************************************)

PROCEDURE LoadCounter (counter: SHORTCARD;
			mode, loadvalue, holdvalue: CARDINAL);

    (* Sets the mode, and the values for the Load and Hold Registers,	*)
    (* for the specified counter.  This does not arm the counter - it	*)
    (* just sets initial register values.  (Note that the Hold Register	*)
    (* contents are irrelevant for many modes; but we specify the value	*)
    (* anyway, in order to have a uniform calling sequence in all cases.*)

    BEGIN

	(* Internal registers of the counter/timer chip are addressed	*)
	(* by sending a command to load the internal data pointer	*)
	(* register, which then sequences through the internal register	*)
	(* file as we send successive bytes to the data port.		*)

	OutByte (CounterControlPort, EnableDataPointerSequencing);
	OutByte (CounterControlPort, LoadDataPointerRegister + counter);

	OutByte (CounterDataPort, LowByte(mode));
	OutByte (CounterDataPort, HighByte(mode));
	OutByte (CounterDataPort, LowByte(loadvalue));
	OutByte (CounterDataPort, HighByte(loadvalue));
	OutByte (CounterDataPort, LowByte(holdvalue));
	OutByte (CounterDataPort, HighByte(holdvalue));

    END LoadCounter;

(************************************************************************)

PROCEDURE PollingMethod (SamplingInterval: LONGCARD);

    (* Sets up periodic sampling by using counter number 4 to trigger	*)
    (* an A/D conversion at the end of each sampling interval, and	*)
    (* cause an interrupt on the "conversion complete" condition.  If	*)
    (* only a single channel is being sampled, then the interrupt task	*)
    (* can finish the job by reading the converted value.  For multi-	*)
    (* channel sampling, the interrupt task can use a polling method to	*)
    (* read the remaining channels.  This approach is suitable for the	*)
    (* case where the sampling interval is long, or where there is only	*)
    (* one channel being sampled.  In the multi-channel case, we have	*)
    (* some processor overhead caused by busy waits in the interrupt	*)
    (* task, but this is not too serious because we only use this	*)
    (* method when we are sampling infrequently.			*)

    VAR clock: CARDINAL;  timer4count: LONGCARD;

    BEGIN

	SamplingInfo.method := Polling;

	(* The count for timer 4 is the sampling interval divided by	*)
	(* the clock frequency.  We have a choice of five clock		*)
	(* frequencies, namely F1=1MHz, F2=F1/16, F3=F2/16, etc.  The	*)
	(* choice of which of these to use is dictated by the fact that	*)
	(* the count must fit into 16 bits.  The clock is specified in	*)
	(* bits 11-8 of the counter mode register as 1011 for F1, 1100	*)
	(* for F2, 1101 for F3, etc.					*)

	clock := 11;  timer4count := SamplingInterval;
	IF testing THEN
	    WriteString (debug, "clock, count = ");
	    WriteCard (debug, clock);  WriteLongCard (debug, timer4count);
	    WriteLn (debug);
	END (*IF*);
	WHILE timer4count > 65535 DO
	    INC (clock);  timer4count := timer4count DIV 16;
	    IF testing THEN
		WriteString (debug, "clock, count = ");
		WriteCard (debug, clock);  WriteLongCard (debug, timer4count);
		WriteLn (debug);
	    END (*IF*);
	END (*WHILE*);

	(* Use mode D for timer 4.  This is a straightforward "rate	*)
	(* generator" mode, with no gating.				*)

	LoadCounter (4, ORD(LS(clock, 8)) + ModeD + PulseOutput,
						CARDINAL(timer4count), 0);

	(* Initialise the A/D converter by clearing any flags which	*)
	(* might be outstanding, specifying the gain and starting	*)
	(* channel number, and enabling interrupts on end of conversion	*)
	(* or overrun.							*)

	OutByte (IOBase+1, SamplingInfo.ChannelSelectByte);
	OutByte (IOBase+9, 0);			(* "flags clear" port *)
	OutByte (IOBase, 3);			(* enable interrupts  *)

	(* Load and arm counter 4.  This will start the periodic	*)
	(* sampling operation.						*)

	OutByte (CounterControlPort, LoadAndArm + Select4);

    END PollingMethod;

(************************************************************************)

PROCEDURE DMAMethod (NumberOfChannels, SamplingInterval: CARDINAL;
					AmplifierGain: GainCode);

    (* Sets up periodic sampling by using counter 4 to trigger the A/D	*)
    (* converter as fast as possible, once for each channel to be	*)
    (* sampled, using DMA to put the results into main memory.  Counter	*)
    (* number 5 is used as a gating source for counter number 4; the	*)
    (* width of the gating pulse controls the number of channels to be	*)
    (* sampled, and the time between gating pulses controls the		*)
    (* sampling interval.  This method can be used only when the	*)
    (* sampling interval is less than 65536 microseconds, because	*)
    (* counter 5 is 16 bits wide and will be driven from a 1 MHz clock.	*)
    (* (We could, with some ingenuity, relax this limit by using a	*)
    (* lower clock frequency, but then we would lose fine control of	*)
    (* the width of the gating pulse, possibly to the point where we	*)
    (* could not correctly control the number of channels to sample.	*)
    (* One way to solve this problem would be to sample more channels	*)
    (* than are actually required, but this would give extra software	*)
    (* complexity, and it is not certain that this is justified).	*)
    (* Remark: there are hardwired connections from the output of	*)
    (* counter 5 to the gate of counter 4, from the output of counter 4	*)
    (* to the A/D converter, and from the output of counter 4 to timer	*)
    (* source number 5.  The first two connections are useful to us,	*)
    (* the last is not; but this is not a limitation since we can	*)
    (* program the counters to ignore the "source" inputs and instead	*)
    (* take their inputs from a 1MHz crystal oscillator called F1.	*)

    VAR timer4count, GateWidth: CARDINAL;

    BEGIN

	SamplingInfo.method := DMA;

	(* First, calculate a count for timer 4 which will produce a	*)
	(* train of pulses at a suitable rate, taking into account the	*)
	(* time needed for an A/D conversion.				*)
	(* The A/D conversion time is 25 microseconds, but for high	*)
	(* gains in the input amplifier this must be increased to allow	*)
	(* for a settling time in the amplifier.			*)
	(* For future thought: the hardware seems to include an option	*)
	(* which would allow us to trim this time down, but it's not	*)
	(* quite clear just how fast we can go.				*)

	IF AmplifierGain = 3 THEN
	    timer4count := 80;
	ELSIF AmplifierGain = 2 THEN
	    timer4count := 40;
	ELSE
	    timer4count := 25;
	END (*IF*);

	GateWidth := NumberOfChannels*timer4count;

	(* Use mode Q for timer 4.  In this mode, the counter does not	*)
	(* count while the gate is low.  The first clock input after	*)
	(* the gate goes high loads the counter from its load register,	*)
	(* and counting starts on the second clock input.  When the	*)
	(* count hits zero, the A/D converter is triggered, and the	*)
	(* counter is reloaded from its load register so that counting	*)
	(* starts again.  This happens repeatedly until the gate goes	*)
	(* low again.							*)

	LoadCounter (4, ActiveHighGate + F1source + ModeQ + PulseOutput,
							timer4count, 0);

	(* Use mode J for timer 5.  This makes the counter a free-	*)
	(* running oscillator where the count is reloaded each time it	*)
	(* gets to zero, alternately reloading from the Load and Hold	*)
	(* registers.  (The Load Register is the one which is used when	*)
	(* we initially load and arm the counter.)			*)

	LoadCounter (5, F1source + ModeJ + ToggledOutput,
			SamplingInterval - GateWidth, GateWidth);

	(* Set the initial output of counter 5 low.  This operation is	*)
	(* necessary because counter 5 has a toggled output (unlike	*)
	(* counter 4, which only puts out a pulse each time its count	*)
	(* goes to zero), so we must explicitly specify its initial	*)
	(* condition.							*)

	OutByte (CounterControlPort, ClearToggle + 5);

	(* Arm the A/D converter and the DMA controller.  The A/D	*)
	(* conversions will not start, however, until triggered by the	*)
	(* first pulse from timer 4.					*)

	WITH SamplingInfo DO
	    LoadDMAparameters (DMAchannel, DMAReadCode,
					BufferAddress, ByteCount);
	    OutByte (IOBase+1, ChannelSelectByte);
	END (*WITH*);

	(* Enable DMA mode, with interrupt on DMA completion.	*)

	OutByte (IOBase, 0CH);

	(* Load and arm both counters.  This will start the cycle.	*)

	OutByte (CounterControlPort, LoadAndArm + Select4 + Select5);

    END DMAMethod;

(************************************************************************)

PROCEDURE StartPeriodicSampling (first, last: InputChannelNumber;
					SamplingInterval: LONGCARD;
					AmplifierGain: GainCode;
					VAR (*OUT*) Buffer: ARRAY OF BYTE);

    (* Initiates a mode of operation in which channels first..last,	*)
    (* inclusive, will be sampled every SamplingInterval microseconds,	*)
    (* with the results stored in array Buffer.  At each sampling time,	*)
    (* the specified channels are read as nearly simultaneously as the	*)
    (* hardware will allow.  Procedure WaitForNextSample, below, should	*)
    (* be called to check when the data are available in array Buffer.	*)
    (* If WaitForNextSample is not called often enough, there can be a	*)
    (* data overrun in which data are overwritten.  We do not signal	*)
    (* this as an error since the only thing which can be done about it	*)
    (* is to use the new data and ignore whatever data have been lost.	*)

    BEGIN
	WITH SamplingInfo DO
	    ChannelSelectByte := BYTE (first + ORD(LS(AmplifierGain, 5)));
	    IF last <> first THEN
		(* Specify that the channel number will auto-increment *)
		INC (ChannelSelectByte, 80H);
	    END (*IF*);
	    BufferAddress := ADR (Buffer);
	    ByteCount := 2*(last-first+1);
	END (*WITH*);
	CreateSemaphore (tick, 0);

	(* Temporary patch: since the DMA method seems to have a bug in	*)
	(* it (reason so far unknown), use the polling method always.	*)

	PollingMethod (SamplingInterval);
(*
	IF (first=last) OR (SamplingInterval>65535) THEN
	    PollingMethod (SamplingInterval)
	ELSE
	    DMAMethod (last-first+1,SHORT(SamplingInterval),AmplifierGain);
	END (*IF*);
*)
	IF testing THEN
	    DumpCounter (debug, 4);
	    DumpCounter (debug, 5);
	END (*IF*);

    END StartPeriodicSampling;

(************************************************************************)

PROCEDURE WaitForNextSample;

    (* Pauses until the buffer specified in the preceding procedure has	*)
    (* been filled with valid data.  Notice that this is essentially a	*)
    (* synchronization procedure; the caller does not have to do any	*)
    (* timing operations since a return from this procedure implies	*)
    (* that the next sample time has arrived.				*)
    (* WARNINGS:							*)
    (*  1. This procedure should not be called unless periodic sampling	*)
    (*     is currently in effect.  Otherwise, it might never return.	*)
    (*  2. Periodic sampling implies that the data buffer is re-filled	*)
    (*	   regularly, regardless of whether the user code has finished	*)
    (*	   with the previous data.  There are no interlocks, and no	*)
    (*	   protection against data being updated just as one is reading	*)
    (*	   it.  (Any such protection could interfere with the precision	*)
    (*	   of the timing of sampling external data).  The caller is	*)
    (*	   advised to move data out of the buffer promptly, especially	*)
    (*	   when the sampling rate is high.				*)

    BEGIN

	(* Wait for the interrupt which indicates operation complete.	*)
	(* This is all we need to do; the interrupt task looks after	*)
	(* all the details of re-arming the hardware, etc.		*)

	Wait (tick);

    END WaitForNextSample;

(************************************************************************)

PROCEDURE StopPeriodicSampling;

    (* Turns off the periodic sampling mode of A/D conversion.	*)

    BEGIN

	(* Disarm the A/D converter.	*)

	OutByte (IOBase, 0);
	OutByte (IOBase+9, 0);

	(* Stop the clocks (counters 4 and 5).	*)

	OutByte (CounterControlPort, Disarm + Select4 + Select5);

	DestroySemaphore (tick);

    END StopPeriodicSampling;

(************************************************************************)
(*			THE INTERRUPT TASK				*)
(************************************************************************)

PROCEDURE InterruptTask;

    (* This task has a job to do only during periodic sampling, since	*)
    (* that is the only time we use interrupts.  Its job depends on	*)
    (* which method of periodic sampling is in use (see above - we use	*)
    (* one of two different methods, depending on the sampling interval	*)
    (* and the number of channels being sampled).  The cases are:	*)
    (*	1. DMA method: the interrupt occurs on DMA completion, so we	*)
    (*	   only have to set up the DMA chip to be ready for the next	*)
    (*	   batch of data, re-arm the A/D converter, and inform the user	*)
    (*	   that the current data have arrived.				*)
    (*	2. Polling method: the interrupt occurs on A/D completion for	*)
    (*	   the first channel to be sampled, and we still have to store	*)
    (*	   the datum in memory.  If multiple channels are to be sampled	*)
    (*	   then the interrupt task must finish the job by sampling the	*)
    (*	   remaining channels.  (This puts a busy wait inside an	*)
    (*	   interrupt task, which is normally not a good idea; it is	*)
    (*	   justified in this case because the amount of time to wait is	*)
    (*	   less than the time it would take to leave and re-enter the	*)
    (*	   interrupt task).  Finally, the A/D converter must be		*)
    (*	   re-armed to be ready for the next batch of samples.		*)

    VAR resultptr: POINTER TO BYTE;
	BytesRemaining: CARDINAL;

    BEGIN
	LOOP (*FOREVER*)
	    WaitForInterrupt;
	    OutByte (IOBase+9, 0);		(* "flags clear" port *)

	    WITH SamplingInfo DO
		IF method = DMA THEN

		    (* Re-arm the DMA controller for the next sample. *)

		    LoadDMAparameters (DMAchannel, DMAReadCode,
						BufferAddress, ByteCount);

		ELSE	(* We are using the polling method *)

		    (* Temporarily disable A/D interrupts. *)

		    OutByte (IOBase, 0);	(* status/control port *)
		    resultptr := BufferAddress;
		    BytesRemaining := ByteCount;

		    LOOP
			(* Read in a result.	*)

			resultptr^ := InByte(IOBase+3);	(* low order byte *)
			resultptr := AddOffset (resultptr, 1);
			resultptr^ := InByte(IOBase+4);	(* high order byte *)
			resultptr := AddOffset (resultptr, 1);

			DEC (BytesRemaining, 2);
			IF BytesRemaining = 0 THEN
			    EXIT (*LOOP*)
			END (*IF*);

			(* Perform the next conversion. *)

			OutByte (IOBase+2, 0);	(* "start conversion" port *)
			WHILE ORD(IANDB (InByte(IOBase),60H)) = 0 DO
			    (* busy wait, until either the DONE or	*)
			    (* OVERRUN ERROR flag is set.		*)
			END (*WHILE *);

		    END (*LOOP*);

		    (* Re-enable interrupts. *)

		    OutByte (IOBase, 3);

		END (*IF*);

		(* Rearm the D/A converter. *)

		OutByte (IOBase+1, ChannelSelectByte);

	    END (*WITH*);

	    Signal (tick);

	END (*LOOP*);

    END InterruptTask;

(************************************************************************)
(*			    INITIALISATION				*)
(************************************************************************)

PROCEDURE InitialSetup;

    (* Resets the A/D board and the counter/timer chip and installs	*)
    (* the interrupt task.						*)

    BEGIN
	OutByte (CounterControlPort, ResetCounters);
	OutByte (CounterControlPort, LoadAllCounters);
	OutByte (CounterControlPort, LoadDataPointerRegister + 4);
	OutByte (IOBase, 0);		(* disable interrupts *)
	OutByte (IOBase+9, 0);		(* clear flags *)
	CreateInterruptTask(InterruptNumber,InterruptTask,"A/D int handler");
    END InitialSetup;

(************************************************************************)

BEGIN
    IF testing THEN
	OpenWindow (debug,yellow,blue,5,11,0,79,doubleframe,nodivider);
    END (*IF*);
    InitialSetup;
END AnalogueIO.