
Spectrum Interrupts
Adding Commands to Spectrum Basic
(Sinclair User, November 1984)

Keith Williams presents a detailed explanation on how
to use interrupts on the Spectrum and provides a useful
trace function for printing program line numbers


[The article title and preamble are misleading, as the code does not add]
[any new BASIC commands; it adds a trace function initialised via a USR ]
[call and activated by setting a 'trace' variable within a program. JimG]


ONE OF THE problems of adding new commands and routines to the
Spectrum Basic is that it is held in ROM. That means that all the
computer operations are under the control of an immutable program
which cannot be stopped.

Although it is easy to write new routines, they can only be called by
means of a very user unfriendly RAND USR call. Passing of information
into and out of those routines is also difficult without a whole chain
of pokes. Even then it may be impossible to get the routine to work as
it may be needed at the execution of every line.

There is, however, one very simple way of beating the ROM and that is
to use interrupts. While exploring this we can also develop a new
command for the the Spectrum. If you are not interested in the
detailed explanation you can still follow the instructions and load up
the final program which gives a very useful trace function, an
essential tool when debugging along programs.

A trace function is a machine code routine which prints on screen,
while a program is running, the number of the line which is currently
being executed.

The Spectrum is based on the popular Z-80A microprocessor. On that
microprocessor are two electrical connections which are involved with
interrupts. The first is the NMI which is of little use to our present
purposes and so we will ignore it. When the voltage applied to the
other pin changes we say that an interrupt occurs. That causes the
processor to stop what it is doing, make a note of where it has
reached and then jump to follow another program called the interrupt
service routine. When that is finished it returns to its place in the
original program.

There are three interrupt modes available on the microprocessor. IM0
is designed for use with peripherals such as printers and the like.
When the peripheral needs servicing, such as requiring more
information, then it sends the interrupt message and then an address.
The computer, in IM0, will run the routine starting at that address
before returning from the interrupt. IM1 is the mode normally used by
the Spectrum. When an interrupt occurs it will carry out the routine
at address 56. That routine in the ROM does various housekeeping tasks
such as reading the keyboard. The Spectrum is wired in such a way that
an interrupt comes with every fluctuation in the mains voltage, in
Britain that occurs 50 times a second.

IM2 is much more useful. In that mode the processor jumps to a service
routine the address of which must calculate from two numbers. The
first of those numbers comes from the thing causing the interrupt. In
a Spectrum this number 255 - or FF in Hex - is supplied by the
hardware. The other number is programmed in. It is kept in a special
store known as the I register. The processor jumps to the address
256*I+255. Just to make things more complicated this computed address
is not the start address but is the address of a store holding the
start address.

To get our program on the way we must first poke its start address
into another address - whose Hex value is xxFF. Then we must put its
vector address - the xx from before - into I. That sounds complicated
but really it is simple. There is, however, one problem. The screen
chip in the Spectrum works in such a way that putting the vector in
the first 16K of RAM causes difficulties for the machine. If you want
to use interrupt routines on a 16K machine you must be very devious.
If you put 40 into the I register then the processor will look at
40*256+255=10495 for a start address. That is in ROM and it contains
the start address 32348 or 7E5C in Hex. On the 48K Spectrum you have
more leeway and can either use the 16K trick or use the number 128 (80
Hex) in I.

And so to the routines. A word first about getting them into memory.
If you have an assembler then it is easy. If you do not then you will
need a Hexloader program. One is provided in listing 1. [Not included
here. See TRACE.ASM for commented assembler listing. JimG] You must
first type it in and then save it. It enables you to type in the
machine code and then save that.

When entering the machine code routines in listing 2 type in only the
digits and letters in the column 'hexcode'. Type in a whole line at a
time and the address and code will appear on the screen. If you make a
mistake then typing in 'r' will enable you to retype the previous
line. When you have typed the last line of hex then an 'S' will lead
to a save routine to keep the hex on tape.

To minimise errors and make it easier to explain the program as a
whole, it has been divided into its various sub-routines. Type those
in one at a time; later they can be put together, using the Collator
program in listing 3.

The first pair of routines, TRON and TROFF, are the ones which switch
the interrupt vectors around. TRON loads the register I with 0FEH,
that is, 254. That means that the vector holding the start address of
our routine must be placed at address 0FEFFH, that is, 65279. The
register I cannot be loaded. directly and so we must first load the
value into the A register. IM2 is selected for the reasons already
stated and the remaining instructions DI and EI switch interrupts off
and on. Strictly speaking they are not essential here but it might
cause confusion for the machine if it was interrupted during this
short routine at the wrong place.

TRON is really quite separate from the rest of the program. Its job is
to set the machine up so that we can switch the trace function on or
off at will. It must therefore be called early on by a USR call. More
about that later when the whole program is in.

TROFF performs the opposite function in that it resets the machine to
normal interrupts. You will notice that I is loaded with 3FH or 63.
That is the address (3FFF) that the ROM sets up as an interrupt vector
on power up. In interrupt mode 2 that would cause a jump to address 60
which is part way through the normal interrupt routine. It is
important, therefore, when resetting to put the machine into IM1. The
only USR call that is necessary anywhere in this program is the
initial one to TRON. TROFF is called by means of a normal Basic
variable.

Type in the machine code and save it. The start address is FE7E. The
pair of routines can be saved together under the name 'tron'.

Once the whole program is in and enabled it is necessary to be able to
switch the trace on and off. That can be done from Basic by inserting
the line LET trace=x, where x can be one of three values: '-1' will
cause a call to TROFF thereby disabling trace; '0' will switch trace
off but leave it enabled. '1' will switch trace on. So if in your
program you wish to trace through lines 300 to 700, say, insert in
your program at line 299 LET trace=1. Line 701 LET trace=0 - stops the
trace. Then run the program.

The next routine TRQ searches the variables area to see if the
variable trace exists. If it does not then it assumes a value of 0. If
it does exist then the value is picked up. The method of finding the
variable is to search through the variables area for the code 0B4H
which is the code for 't' adjusted to show that it is the first letter
of a long name variable (see page 167 of the manual, letter code 96 +
160). When the code is found each of the codes in turn is checked
against the letters 'r', 'a' and 'c', finally against 0E5H which is
the code for 'e' + 128, showing that it is the last letter of the name.

The value of a number is stored in five bytes. The first is 0, the
second the sign byte holding 0 for a positive number and -1 for a
negative. The routine first picks that byte and adds one to it. If the
answer is 0 then the A register is loaded with FF (-1) and a return to
the main control routine is effected. If it is not negative then the
next byte is looked at. That is the least significant byte of the
value, i.e. x-256*INT (x/256). The byte is picked up and decreased by
one. If the answer is 0 then it must have held 1 and so A is loaded
with 1 and a return made. The only possible remaining value is 0 and
so 0 is put into A and then the program returns to the main control.

There are, probably, easier methods of doing this, for example using
the LOOK-VARS routine in ROM at address 28B2H, but it is important not
to alter any of the system variables or the alternate register set
because the processor is in the course of running a Basic program.
Consequently no ROM routines at all have been used but only
simulations of them.

Now type in TRQ and save it under that name. The start address is FE90.

The Print routine is the one that prints out the line number. Again
there would be easier ways of doing it, using RST 10H for example, but
again a simulation has been produced. The routine is entered with 'C'
holding the value of the digit and 'A' its position - 1 for thousands,
2 for hundreds, etc.

Every character printed on the screen consists of eight rows of eight
pixels. Each row can be represented as one byte. As the screen is 32
bytes wide it would seem that the easy way to print a character is to
poke the first byte in and then the next byte 32 bytes further on and
so on. The problem is that the screen is not laid out in memory in
that simple and obvious way. In fact, each row of pixels is 256 bytes
away from the last one. The algorithm therefore requires that each
byte is poked into an address 256 bytes on from the previous. The
pixel bytes are stored at an address pointed to by CHARS + 256 and the
numbers start 128 bytes further on. As there are eight bytes for each
character then the value in C must be multiplied by eight and then
added to that base address.

The routine may now be typed in and saved using the name "print". The
start address is FED3.

The next two routines are involved in sorting the current line number.
Count divides the number in HL by the number in DE and returns the
result in C and the remainder in HL. Line picks up the line number and
uses Count to manipulate it digit by digit and then prints those out
by calls to Print. The current line number is held in PPC so that is
first picked up in HL. DE is loaded with 1000 and then Count is called
to see how many thousands there are. The number is printed using A to
say the print position i.e. the thousands column. Then that is
repeated for the hundreds and so on. Count overlaps the interrupt
vector at FEFFH and there must be a jump just before it so that the
processor does not interpret the vector as instructions.

The start address for this pair of routines is FEF3. They can be typed
in and saved.

The final routine is the control routine called MAIN and it controls
all the others and performs the necessary housekeeping tasks. The
start address is FF2D; the one held in the interrupt vector FEFF. It
is the routine called when an interrupt occurs.

The first task is a call to ROM address 56. That is the normal
interrupt routine and it updates FRAMES, reads the keyboard and so on.
Next the A register and the Flags register are saved so that normal
service can be resumed on return. If a program is not running it will
hold FF(-1) and then a return is made after restoring A and the flags
register. If a program is running then the registers are saved and a
call made to TRQ to see if the trace is to be switched on, off or
disabled. TRQ returns that information in the A register. Depending on
the value in that register a call is made to LINE or TROFF or none of
those. Finally, all the registers are restored and control is returned
to the ROM. All that remains is to collate all those routines. Type in
the second Basic program in listing 3, rewind the tape and then run
it, not forgetting to save MAIN first.

The third Basic program - listing 4 - will load the trace utility
above RAMTOP. It, or something like it, must be used on switching on
and before you load or type in the program under test. To enable the
trace, i.e. to call TRON, you need to give the command RAND USR 65150
after any start or NEW.

Further sophistications can be added, such as a delay loop at the end
of MAIN which will make it easier to follow the trace.


[I didn't bother with the following, as all routines are in        ]
[contiguous memory so I just saved them as one block to start with.]
[Also, the assembler listing was only 210 bytes long, not 214. JimG]


	Listing 3. Collator
  10 CLEAR 65149
  20 FOR n=0 TO 4: LOAD "" CODE: NEXT n
  30 CLS: PRINT "Prepare tape for saving  Press  ENTER"
  40 PAUSE 4e4
  50 SAVE "trace" CODE 65150,214
  60 STOP

	Listing 4: Trace
  10 CLEAR 65149
  20 LOAD "trace" CODE
  30 NEW
