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