                       Multitasking



                    Spectrum Interrupts



                  Easy multitasking with

             Richard Taylor's After and Every



One of the most advanced features of the Amstrad micro it

its ability to handle interrupts directly from Basic. In

other machines such as the Spectrum, interrupts are a tool

available only to the machine-code programmer. Amstrad

Basic releases the power of interrupts to all in a simple

but comprehensive manner.

  Although Spectrum interrupts can be harnessed quite

readily - at least on the 48K machine - from machine code,

the Basic programmer is left out in the cold with no

commands to support interrupt-driven programming. Presented

here is a program which rectifies this shortcoming by

equipping Spectrum Basic with a number of interrupt com-

mands, as well as the On Error and On Break commands found

in Microsoft Basic. Due to the problems associated in

accessing interrupts, even from machine code, on a 16K

machine, it will only operate on the larger model.

  The machine code resides above RAMtop, occupying addres-

ses 63866 to 65367. [It should be loaded] with:

        CLEAR 63000: LOAD ""CODE

  The 10 new Basic commands provided by the program can be

accessed by typing them in in REM statements. There's a

limit of one command per REM and, as usual, a REM statement

must be the last item on a line. To make the computer treat

REMs in this new fashion it is necessary to initialise the

machine code by using a RANDOMIZE USR 63866 as the first

line of your program. Subsequently, REMs will be treated in

the new manner with no further need for USR calls. The full

command list is as follows:

AFTER          ON ERROR GOTO

EVERY          ON BREAK STOP

DISABLE        ON BREAK GOSUB

ENABLE         IGNORE BREAK

DROP           RESUME



I'll now deal with each of the commands individually. The

simplest command is IGNORE BREAK. As you would expect, this

command forces a program to ignore the break key and there-

fore prevents you from breaking into it. As an example,

type in the following short program, but only if there's

nothing important in memory.

        10 RANDOMIZE USR 63866

        20 REM IGNORE BREAK

        30 GO TO 30

  Pulling out the plug is the only way out of this program.

The command in line 20 is typed in letter by letter, a

little more laborious than single key entry but certainly a

lot less confusing. You can type in either upper or lower

case but upper case tends to look a bit neater. If the

first character of a REM is an asterisk then the rest of

the line is ignored. In this way you are still able to add

comments to a program.

  The next command, ON BREAK STOP, restores normality to

the break key:

        10 RANDOMIZE USR 63866

        20 REM *Press 'a' to get out of this

        30 REM IGNORE BREAK

        40 IF INKEY$="a" THEN REM ON BREAK STOP

        50 GO TO 40

  The last of the three commands concerned with the opera-

tion of the break key is ON BREAK GOSUB. This is a much

more powerful command than the previous two; allowing a

full-blooded subroutine to be called when break is pressed.

In order to specify which line should be jumped to this

command must be followed by a valid line number. You have

two options over this, you can either put a number in

directly or the name of a variable. What you're not allowed

to do is have a mathematical expression, so something like

10+1000 is invalid but 1010 is OK.

  The compexity of the break subroutine will obviously

depend on the application it is being used for. In many

cases it would simply consist of a RUN command to restart

the program completely if break is pressed. In any case,

the operation of the routine is entirely up to you. A break

subroutine is written in exactly the same manner as a

normal subroutine, terminated with a RETURN command.

  In order to prevent break subroutines nesting themselves

by the user pressing break while the break-handling routine

is actually running, the key is ignored while the routine

is in progress, just as though you'd used IGNORE BREAK.

When the terminating RETURN is reached, the original status

of break handling is restored. It is possible to redefine

the operation of the break key from within the break

handler itself, although its effect won't be initialised

until the break routine has finished. The following short

example will call the break handler at line 1000 only the

first time break is pressed.

        10 RANDOMIZE USR 63866

        20 REM ON BREAK GOSUB 1000

        30 GO TO 30

      1000 PRINT "You've pressed break"

      1010 REM ON BREAK STOP

      1020 IF INKEY$=" " THEN GO TO 1020:

           REM *Wait if user still pessing break

      1030 RETURN

  When you write a break handler it is important not to use

any variables utilised in the main program as altering them

might upset the program when it is returned to. It's not

really a good idea to print on the screen either, as your

printing might corrupt the layout of the program's own

output or even, in some circumstances, cause it to halt

with an error.

  If your program is menu-driven then it is a good idea to

terminate the break routine with a jump to the part of the

program which prints the menu. If a user selects the wrong

option at the menu then pressing break will immediately

return control to the menu without harm.

  Disabling the break key might not completely protect a

program as there are other places where you may be able to

stop it, namely when the computer asks "scroll?", during

any printer/cassette/Microdrive access, or by typing STOP

in response to an input prompt. However, you can intercept

these possibilities by detecting the error reports that

they cause using the ON ERROR GOTO n command, where n is a

line number.

  This command allows you to trap any sort of error that

occurs in a program. When an error is detected the computer

does a GO TO to the given line - not a GO SUB, although the

computer does remember where the error occurred. The error

handling routine should be able to competently handle any

error that can occur in a program. In common with break

subroutines, error routines may just consist of a RUN

command to restart the program if an error appears.

  Some Basics automatically list the line at which an error

occurred to give you the opportunity of modifying it if,

indeed, it is where the mistake lies. Using the ON ERROR

command it is possible to add this facility to ZX Basic. It

is necessary to know the line at which the error was found.

To be able to do this you need to insert a DEF FN command

in the first line of the program along with the USR call:

        10 DEF FN v(a)=USR 63872: RANDOMIZE USR 63866

  Inside the error routine, FN v(8) returns the line at

which the error occurred. The self-listing routine is as

follows; you might find it a very time-saving debugging

aid:

        10 DEF FN v(a)=USR 63972: RANDOMIZE USR 63866

        20 REM ON ERROR GOTO 9990

           <Rest of program>

      9990 PRINT "Error ";CHR$ FN v(10);" at line ";

           FN v(8);":";FN v(9)

      9991 PRINT

      9992 LIST FN v(8)

  FN v(9) returns the statement number of the erroneous

command and CHR$ FN v(10) returns the alphanumeric code for

the error.

  The RESUME command can be used optionally at the end of

an error-handling routine to continue execution of a pro-

gram from the statement where the error was detected. If

you follow RESUME by a line number then execution continues

from that line instead of from the point of the error. You

should always terminate error handlers with a RESUME if

you intend to re-enter the program, never use a GO TO. To

prevent error handlers from becoming nested, any errors

detected within the error routine itself are reported in

the usual manner. The following program repeats forever -

or at least until you answer no to the "scroll?" prompt. It

illustrates the danger of resuming from the point of the

error without actually correcting the error's cause.

        10 DEF FN v(a)=USR 63972: RANDOMIZE USR 63866

        20 REM ON ERROR GOTO 1000

        30 LET a=b: REM *What b?

      1000 PRINT "Oh dear, there's been an error"

      1010 REM RESUME

  The use of VERIFY or LOAD commands from within a program

often causes problems if there is a tape error, since you

are left in command mode once the error has been reported.

If the program is going to be used by people other than its

author, then it is necessary to print instructions on the

screen of what to do in the event of a tape error to re-

enter the program. This works, but is hardly state of the

art in user-friendiness.

  The ON ERROR command provides a useful solution to this

problem by allowing an error handler to be called if a tape

error is detected which can invite the user to try again.

There are many instances in a program where it is useful to

use an ON ERROR command, most notably when checking the

validity of user input. Rather than undergoing complicated

checking routines it is much easier to assume that it is

right but set up an error routine as a safety net while the

input is being processed so that any errors caused by

invalid data can be solved by looping back to the input

statement to ask the user for the input again.



The major function of the program is of course to provide

interrupt handling. This is implemented in the form of the

commands: AFTER, EVERY, DISABLE, ENABLE and DROP. The

scheme of things is basically this: there is a timer avail-

able counting at a rate of 50 units per second - the Frames

rate. Using the AFTER command you can set the timer to a

specific value. Immediately, the timer starts counting down

towards zero. Your program can happily continue, not having

to worry about the timer any further. When the counter

reaches zero, the flow of your main program is temporarily

diverted to a certain subroutine, just as if a GO SUB had

been magically inserted in the right position in the pro-

gram.

  The subroutine is not terminated by a RETURN command but

by the normally innocuous CONTINUE. The syntax of the AFTER

command is AFTER x, GOSUB y. The GOSUB has to be typed out

in full. It is necessary, I'm afraid, to type in the

seemingly redundant comma just before the GOSUB bit.

  The ability for a program to be freely interrupted in

this way is often loosely termed Multitasking or parallel

processing.

  Here's a silly example - a listing for a computerised egg

timer.

        10 RANDOMIZE USR 63866

        20 REM AFTER 9000,GOSUB 1000

        30 PRINT "Start boiling the egg. I'll just

           brush up on my mental arithmetic."

        40 LET a=INT(RND*1000)

        50 LET b=INT(RND*1000)

        60 PRINT a;" + ";b;" = ";a+b

        70 FOR c=1 TO 100: NEXT c

        80 POKE 23692,255: REM *Allow automatic scrolling

        90 GO TO 40

      1000 PRINT "The egg is done"

      1010 STOP

  Line 20 sets up the time delay of 9000 1/50ths of a

second - three minutes. Lines 30 to 90 just waste time by

doing something completely unrelated to eggs: adding

numbers together. Lines 1000 and 1010 are the subroutine

called when the three minutes are up.

  The AFTER command is a "single shot" command in that the

subroutine is called only once, after which the timer

becomes inactive. In most applications you would want a

certain routine to be called at regular intervals. You

could do this with the AFTER command if you re-initialised

the timer with AFTER at the start of the subroutine. How-

ever, a much better way is to use the EVERY command. EVERY

is used in exactly the same manner as the AFTER command.

When the "interrupt service routine" - the subroutine

called when the timer reaches zero - is reached, the timer

is automatically re-armed to its starting value, ready for

the next time. This program will constantly update the time

at the top left-hand side of the screen even while another

program is running:

        10 RANDOMIZE USR 63866

        20 LET min=0: LET sec=0

        30 REM EVERY 50,GOSUB 9900

        40 LET a=0

        50 PRINT AT 21,0;a: LET a=a+1

        60 GO TO 50

      9900 LET sec=sec+1

      9910 IF sec>59 THEN LET min=min+1: LET sec=0

      9920 PRINT AT 0,0;("0" AND min<10);min;":";

           ("0" AND sec<10);sec

      9930 CONTINUE

  The delay time in an AFTER or EVERY command can be up to

65,535 units, about 21 minutes 51 seconds. If you need

longer delays than this for some reason then you could use

the following method. For instance a delay of one hour

could be produced by setting up an interrupt service

routine - or ISR if you like abbreviations - called every

10 minutes. At the start of the program you would initia-

lise a variable - a, say - to zero. Each time the ISR is

called the value in a is incremented and when it reaches

six, an hour has passed. Bear in mind, however, that the

Spectrum's clock isn't very accurate and you could end up

with a quite drastic error with such a long delay.

  So far I've talked about the "timer" in a singular sense.

There are, in fact, eight timers. All eight are completely

independent of one another in all respects. You can direct

information to specific timers in an EVERY or AFTER command

by tapping another number in after the delay time; e.g.,

        AFTER 100,7,GOSUB 9000

uses timer 7.

  The timers are numbered 0 to 7. If you miss the timer

number out, as in previous examples, then it defaults to

timer 0.

  The DISABLE command enables - no put intended - you to

suspend the operation of one or all of the timers. DISABLE

used on its own disables all eight timers whereas DISABLE

followed by a number disables only that particular timer.

The inverse command is ENABLE, which is used in a similar

fashion to re-enable previously disabled timers.

        10 RANDOMIZE USR 63866

        20 REM AFTER 100,GOSUB 1000

        30 REM DISABLE 0

        40 GO TO 40

      1000 PRINT "Time up!"

      1010 STOP

does nothing unless you remove the DISABLE statement at

line 30 or put in an ENABLE command at line 35. You'd

usually use the DISABLE/ENABLE commands to protect certain

parts of a program from being interrupted, possibly because

that part manipulates variables used by the ISR(s) and

could leave the variables in temporary states that may

upset the ISR(s).

  The last command connected with interrupts is DROP. In an

analogous way to normal GO SUBs, before an ISR is called

the current line and statement numbers are stored away on a

stack so that normal program execution can continue quite

happily when the ISR is finished. In some circumstances,

however, you might not want to ever return from the ISR.

For example, in a game you might wish to set a time limit

to complete a certain task, say 10 seconds. You could use

AFTER 500,GOSUB 8000 so that when the time is up a jump to

line 8000 will be made. Obviously you wouldn't want to

return from the ISR to continue that particular part of the

game.

  To save leaving the stack in an unbalanced state you

would use the DROP command. This command simply makes the

computer take the top item of its stack and throw it in its

electronic dustbin. In an application such as this, you can

disarm the timer with an AFTER 0,GOSUB x as soon as the

task has been completed, otherwise you could find the com-

puter calling the ISR at a rather inappropriate time.

  When a timer counts down to zero, the computer remembers

that a certain line is to be called by placing its line

number on yet another stack. When the statement currently

being executed is finished it looks at the number it remem-

bered and calls the appropriate ISR. Because ISR calls are

not processed until the current statement is completed,

INPUT, PAUSE, LOAD, SAVE, MERGE and BEEP may hold things

up. The computer, being a meticulous beast by nature, care-

fully piles up all the numbers of the ISRs it's got to call

in preparation for such a time when it is able to process

them.

  Eventually the computer will run out of room and will no

longer bother to store the lines. The amount of room the

computer has got for stack storage is determined by how

much memory you leave free between RAMtop and the start of

the program at 63866. A good value for RAMtop is 63000,

which leaves room for all but the most complex applica-

tions.

  Using the user-defined function introduced under the

ON ERROR command, it is possble to interrogate any of the

timers and find their current status. Use FN v(x), where x

is the timer you wish to look at, numbered 0 to 7.

        10 DEF FN v(a)=USR 63872: RANDOMIZE USR 63866

        20 REM AFTER 1000,5,GOSUB 1000

        30 PRINT AT 0,0;FN v(5);" "

        40 GO TO 30

      1000 STOP

This program displays the time remaining on timer 5 as it

plunges towards zero. The function will return a zero if

the timer is either inactive or disabled.

  Unlike error and break handler routines, ISRs will nest

to as many levels as you like, although in practice the

number of levels is determined by the amount of free memory

available above RAMtop for the stack.

  One thing to avoid is to define an EVERY command with a

time interval smaller than the time needed to execute the

associated ISR. The routine will "interrupt itself" in such

a circumstance, as the next interrupt will have occurred

before the ISR to handle the previous interrupt is finish-

ed. Eventually the computer will get clogged up with an

enormous stack of return lines which it never quite gets

round to using. If this sort of situation is a possibility

in your program then disable the timer right at the start

of the ISR and enable it again right at the end.

  When writing ISRs, as with break and error handlers, it

is important to remember that the routine should not do

anything to unduly upset the main program by changing the

value of one of the variables it is using, for instance.

The computer looks after you a bit by automatically storing

the print and plot positions at the start of the ISR and

restoring them to those values when the ISR is terminated.

Print and plot positions are therefore "local" to the ISR.

If you print and draw from within an ISR, then the computer

will automatically tidy any damage you might have done by

changing the print/plot position.



There are a couple of points of interest to Interface 1

users. First, you should not use OPEN # and CLOSE #

commands in conjunction with this program. More precisely,

programs using the interrupt facilities; ON ERROR and

ON BREAK etc. don't matter. Secondly, any other Microdrive/

Interface 1 commands should be immediately followed by

        RANDOMIZE USR 63866: POKE 23728,0

sequences.

  REM statements typed in as direct commands are treated

in the normal way and cannot, therefore, contain any new

commands. The CONTINUE statement cannot be used in the

normal way, as when an error is actually reported (in con-

trast to an error handler being called) then all the new

stacks are cleared and the timers are all forced inactive.

CONTINUE will not, then, resume a program without actually

clearing or changing anything.