The Z80 interrupt on the Spectrum is triggered at the start of the vertical blank period of the screen refresh. This cannot be changed. What can be changed is the address that is jumped to upon interrupt.
The Z80 supports three interrupt modes:
- IM 0: Executes an instruction that is placed on the data bus by a peripheral.
- IM 1: Jumps to address &0038
- IM 2: Uses an interrupt vector table, indexed by value on data bus.
IM 0 is not used on the Spectrum.
The Spectrum normally operates in IM 1; this is set by the ROM bootup process shortly after power up by a routine at address &121C. The interrupt implements the ROM keyboard scanning routine. As an aside; this interrupt does not preserve the IY register so if you wish to use it in your code you will either need to disable interrupts or use IM 2.
Games programmers will usually set the Z80 to Interrupt Mode 2 (IM 2) on the Spectrum. As the Spectrum interrupts are tied to screen refresh it provides a method of frame locking games for smooth tear free drawing of sprites and backgrounds.
Interrupt Mode 2
An interrupt vector table of 128 words is created. This must be on a 256 byte boundary; i.e. the low byte of the table address is &00. The I register is set to the high byte of that table, an IM 2 instruction is issued and interrupts are enabled.
When an interrupt occurs, the Z80 will index into the interrupt vector table, using whatever value is on the data bus. It is assumed that this value has bit 0 reset. The Z80 will then jump to the address fetched from the table.
The principle behind this is that you can have a number of peripherals, all putting unique values on the data bus and triggering interrupts. The system can then call the relevant interrupt handling routine for that peripheral.
In practice, the Spectrum didn’t use IM 2 like this; and it was widely assumed that you couldn’t guarantee what was on the data bus when the interrupt occurred, so programmers used to generate a vector table with 128 addresses; all pointing to the same interrupt routine.
On the 48K Spectrum there was a sneaky shortcut that saved the 256 bytes required for the interrupt vector table. There is a block of over 256 &FF’s at location &3900 in the 48K ROM. So programmers used to use this as the vector table. The Spectrum would then jump to location &FFFF upon interrupt. This is the clever bit. By placing the opcode for JR (&18) at that location and knowing that the PC would wrap to address &0000 (in the ROM) during execution of that instruction, which contains the value &F3, this would then execute the instruction JR &F3; a negative jump back 13 bytes to address &FFF4. By sticking a JMP instruction here you can then jump to the location of your interrupt routine of choice.
It does not work on the 16K Spectrum as there is no RAM from &8000 onwards, and doesn’t work on the 128K variants as the 128K ROM has code at location &3900. You will need to create your own vector table for games to be compatible with those systems.
Here is some sample code to illustrate the sneaky shortcut:
MAIN: DI ; Make sure no interrupts are called! LD HL,Interrupt ; Address of the interrupt routine LD IX,&FFF0 ; Where to stick this code LD (IX+04h),&C3 ; Z80 opcode for JP LD (IX+05h),L ; Where to JP to (in HL) LD (IX+06h),H LD (IX+0Fh),&18 ; Z80 Opcode for JR LD A,&39 ; High byte address of vector table LD I,A ; Set I register to this IM 2 ; Set Interrupt Mode 2 EI ; Enable interrupts again LOOP: HALT ; Wait for interrupt CALL Some_Routine ; Do some foreground stuff JR LOOP ; Loop around forever Interrupt: DI ; Disable interrupts PUSH AF ; Preserve all registers PUSH BC PUSH DE PUSH HL PUSH IX EXX EX AF,AF' PUSH AF PUSH BC PUSH DE PUSH HL PUSH IY CALL Some_Interrupt_Code ; Do some interrupt stuff POP IY ; Restore all registers POP HL POP DE POP BC POP AF EXX EX AF,AF' POP IX POP HL POP DE POP BC POP AF EI ; Enable interrupts RETI ; Return from interrupt