Authors
Publication
Publication Details
Volume: 3 Issue: 3
Date
Pages
Or, The Joy Of Using Interrupts On Your Computer
“All right”, comes the chorus, “what’s an interrupt, and why should I care?” I’ll admit, it’s possible to lead a normal, happy life even if you’ve never heard of an interrupt. But in that case, you’ll have missed something that’s at least lots of fun and, at most very useful.
This series of articles will try to give something to everyone. Those who despise technical details will be able to pick out some programs that can be entered and immediately used, to give new power to their computers. Beginning machine code programmers will learn of a hidden “bug” in the system that can do weird things to their software. Advanced machine code programmers will find a versatile tool that will allow them to do things they may not have suspected possible. Those who like to build hardware will also find a few interesting tricks. By the end of this first article, we’ll understand what interrupts are and have a small program that demonstrates “interrupts in action”, and which may be of use, once incorporated into a BASIC program.
In order to accommodate the many levels of experience of various SINCUS members, this article is laid out in topics. Each starts with simple explanations and progresses into technical detail. If you find yourself in “too deep,” the water becomes shallow again at the start of the next topic!
What’s An Interrupt?
Perhaps an analogy would be the best way to begin. Suppose, while you’re reading this article, the telephone rings. You’ll probably set the newsletter down, mentally remembering where you were and go answer the phone. When you’re done, you’ll come back and resume where you left off. You’ve just “serviced an interrupt.”
Let’s try another analogy. Suppose you find my articles so interesting that you absolutely can’t be disturbed while reading them. Because of this, you unplug your phone before you start reading and plug it back when you’re done. If the world outside tries to interrupt you, you won’t know and won’t respond. During that time, you’ve “disabled the interrupt.”
Now for one more analogy. Your neighbor knows you have a habit of unplugging your phone, so he comes to your house and rings your doorbell. He can see you through the window so you can’t ignore him. You set down the newsletter, and open the door. You are “servicing a non-maskable interrupt.”
The T/S 2068 has both maskable and non-maskable interrupts, activated by pulling one of two pins on the expansion connector to ground. When this happens, the present value of the program counter goes on the stack, and the machine starts executing at some new location, where the “interrupt handler” software is.
If it will make it easier to picture, it acts as though a CALL (machine code, but very much like a GOSUB) instruction has been added right where the computer happens to be running code. In fact, the interrupt handler is written as a subroutine that actually can be CALLed. Exactly where in memory the interrupt handler may be located will be dealt with later.
The T/S 2068 generates its own (maskable) Interrupt every 1/60 second. This causes the keyboard to be scanned and the 3 byte system variable FRAMES to be increased by one count. This variable can be used as a clock or timer and, in fact is what the PAUSE instruction uses to determine whether it has waited long enough. (Have you noticed that the number that follows PAUSE is a count, also in sixtieth of a second?). This 60 Hertz interrupt is also synchronized to the beginning of each video frame on your TV or monitor, which can be useful. It’s not hard to divert this interrupt so it can do some work for us on top of its normal duties. We’ll demonstrate this in a moment.
Can’t An Interrupt Disrupt A Program That Is Running?
Absolutely. One place where our “phone answering” analogy breaks down is in the fact that you remember having answered the phone, but the routine being interrupted “has no knowledge” that it’s been temporarily set aside.
This means that the interrupt handler software has to be carefully written so as not to change anything unexpectedly. For example, the first thing usually done is to PUSH all registers onto the stack. The last thing it does is to POP them all back into place before it RETurns to the program that was interrupted. Therefore, even though the interrupt handler may have temporarily changed the registers, it leaves them exactly as it “found” them.
What About Programs Where The Exact Time Required To Execute A Loop Is Critical? Won’t An Interrupt Change That Timing?
Yes, it would, in such circumstances, an interrupt could be disastrous. When such things are expected, (LOADing, SAVEing, BEEPing, LPRINTing are all examples) the maskable interrupt is disabled with the DI machine code instruction. The non-maskable interrupt cannot be disabled, and could be quite disruptive, if misused. It is normally not used with the T/S 2068, and a ROM bug generated by Sinclair and faithfully copied by TIMEX, makes it nearly impossible to use, any way.
The following “experiments” show how things can go when unexpected interrupts appear, or when necessary interrupts fail to materialize. I’ve mentioned that the T/S 2068 generates its own maskable interrupt (from now on, we’ll just call it “the interrupt”) every sixtieth of a second. This can be turned off in hardware by setting bit 6 or I/O port FF. It’s not quite the same as executing a DI, but it has the same effect, and can be done from BASIC. TYPE IN:
10 OUT 255,64 \n20 PAUSE 5
If you RUN 20, the program runs in a flash; PAUSE 5 doesn’t take very long, after all. However, if you just RUN, the computer is “locked up” until you shut off the machine. Line 10 shut off the interrupts. (The analogy now is not so much like unplugging your phone as it is shutting down the phone company!) Remember the system variable FRAMES is incremented every time an interrupt occurs. PAUSE 5 waits for it to get incremented 5 times. Unfortunately, with no interrupts, FRAMES doesn’t change, and the computer sets out to prove that it’s more patient than its owner.
For the case where we don’t want interrupts, those who own or can get access to a T/S 2040 PRINTER may type in the following:
10 PRINT AT 10,10; "WES" \n20 RANDOMIZE USR 2562 \n30 STOP \n40 PRINT AT 10,10; "WES" \n50 RANDOMIZE USR 2563
The ROM routine at 2563 contains the COPY command. If you RUN this, you’ll get a piece of paper with my name on it. However, the 2040 printer is controlled by a precisely timed set of pulses. An interrupt would cause some of these pulses to “be lost”.
For this reason, the first instruction in the COPY command is DI, which disables the interrupt. If we instead RUN 40, we will have skipped around the DI instruction, and the print sequence is disrupted 60 times a second by unwanted interrupts. This time, my name comes out as a meaningless blur. I liked the first way better!
The moral to machine code programmers is, no matter how tight the little loops in your programs, the computer is sneaking in 60 times a second unless you DI first. Do that DI before entering any critical timing loops and restore things later with EI. Don’t forget that the keyboard won’t be scanned and FRAMES won’t be updated while your DI is active.
Where Does The Interrupt Handler Have To Be Placed In Memory? Can I Put It Where I Want It, Or Add My Own Handler?
You have a little control, in some cases. The non-maskable interrupt always starts at location 0066h. In the T/S 2068, this is in the ROM. I mentioned a bug there that keeps us from normally using this feature. If any one has built my Universal AROS/LROS BOARD (SINCUS NEWS, Nov 84) and fitted it with RAM memory, you can simply load in Spectrum BASIC, change the bug, and go.
The maskable interrupt operates in 3 software selectable modes. MODE 0 causes the interrupt to start executing at a location defined solely by external hardware. We won’t use it here, but it’s mentioned for completeness. MODE 1 causes the interrupt to start executing at location 0038h. This is how the T/S 2068 normally operates, and the interrupt handler is located there. The T/S 2068 Technical Manual in Section 5.3.1 suggests a totally worthless method of intercepting the MODE 1 interrupt; I consider it worthless because it can’t be used along with BASIC. Let’s be greedy and demand it all. Once again, we can use the AROS/LROS Board with a change to the interrupt handler, but this still requires one to build the board. Let’s demand a software only BASIC-compatible technique. It turns out that one exists!
Our ability to easily use the interrupt lies in interrupt MODE 2. In it, the most significant byte of an address is kept in the Z80’s “I” register. The least significant byte is read from the data bus. IMPORTANT: users of Spectrum emulators should note that real Spectrums put a different value on the data bus (FF) than do T/S 2068’s (I’ve detected 0F, 2F, 3F and 0E so far, with evidence that there may be others). For this reason, certain Spectrum software that uses interrupt MODE 2 won’t work on a T/S 2068, even with an emulator. It appears that putting pull-up resistors in the data bus fixes this problem. (Only bit 2 already has a pull-up resistor; probably used by code at location 0BFFh in the EXROM, for detecting whether additional memory expansion banks are present.)
In the spirit of true greediness, wanting our own interrupt handler to work even without pull-up resistors, we will want to tolerate any value on the data bus. We even want to tolerate variable values on the data bus. Fortunately, there’s a renegade Spectrum add-on joystick that does just such a thing during interrupts. This is fortunate for us, because it’s caused our British friends to solve the problem for us.
One thing I haven’t mentioned is that the address assembled from the “I” register and the data bus is not the address of the interrupt handler. It’s the ADDRESS OF THE ADDRESS of the interrupt handler. Although this makes it a bit more difficult to understand MODE 2, it lets us put the handler wherever we want; we can even change it easily while a program is running.
In designing our interrupt code, we’ll borrow rather heavily from the solution proposed by Tom Webb, in Advanced Spectrum Machine Language. If we put FE in the “I” register, but don’t know what will appear on the data bus, the machine will get the address of the interrupt handler from somewhere between locations FE00h and FF00h. If we fill this 256 byte block with FD’s, then the address of the interrupt handler will always be FDFD! This is 3 bytes before the block of FD’s, and is just long enough for a JP instruction to the real interrupt handler. Doing this, our “software only” fix for the hardware problem takes up only 260 bytes of memory, and it’s all in one continuous block! We have 255 bytes of memory available above the FD block and it would be most convenient to locate our interrupt handler there. We’ll end the handler with a JP to the ROM interrupt handler, so that the keyboard will still be scanned, as usual. (Being lazy as well as greedy, we’d rather not do that ourselves.)
Can We Do Something Useful With This Handler?
There’s nothing wrong with being practical, so why not? There are a number of Spectrum programs that use MODE 2 to actually add new commands to BASIC. The following program will give a much simpler, but distinctly related, example by adding a new function to the T/S 2068. As long as the interrupt is enabled, you can immediately COPY the screen to the printer by simultaneously pressing SYMBOL SHIFT and BREAK. This can even be done while a program is running, and even in the middle of a PRINT statement. When the copy is done, the program will continue, completely oblivious to the fact that it’s been interrupted. The printout will include the edit line.
Certain BASIC commands disable the interrupt. During such intervals, this copy-screen function won’t work. These comands are LOAD, SAVE, VERIFY, MERGE, COPY, LLIST, LPRINT and BEEP. Some commercial machine code programs also disable the interrupt.
Add the following to your own BASIC program (exact line numbers aren’t important, as long as you get the lines in the right order). Make sure your program executes it once; more times won’t hurt, but they won’t help, and take a few seconds to run. Once this is done, the copy-screen command is active, and will remain so, even if you STOP the programs and LOAD in a new one. NEW shuts off the interrupt mode, but leaves the code intact, so that it can be reactivated with only the RANDOMIZE USR statement.
10 REM IM2 Demonstration Program\n20 REM Causes a Copy-Screen when BREAK and SYMBOL SHIFT are pressed together\n30 CLEAR 65020\n40 FOR J=65024 TO 65280: POKE J,253: NEXT J\n50 POKE 65021,195: POKE 65022,8: POKE 65023,255\n60 FOR J=65281 TO 65314: READ K: POKE J,K: NEXT J\n70 DATA 62,254,237,71,237,94,201,245,197,213,229,62,127,219,254,246,224,254,252,32,6,243,6,192,205,5,10,225,209,193,241,195,56,0\n80 RANDOMIZE USR 65281