For this system, the CPU is put into a wait condition prior to allowing interrupt processing. Therefore, the interrupt service routine is very simple: return from all valid interrupts. However, as mentioned before, the BRK instruction is used to indicate a software fault, which will call the same interrupt service routine as the maskable interrupt signal IRQ. The interrupt service routine must be able to tell the difference between the two, and act appropriately.
The interrupt service routine shown below includes code to detect when a BRK instruction has occurred and stops the CPU from further processing. The interrupt service routine is in a file named "interrupt.s".
; ---------------------------------------------------------------------------
; interrupt.s
; ---------------------------------------------------------------------------
;
; Interrupt handler.
;
; Checks for a BRK instruction and returns from all valid interrupts.
.import _stop
.export _irq_int, _nmi_int
.segment "CODE"
.PC02 ; Force 65C02 assembly mode
; ---------------------------------------------------------------------------
; Non-maskable interrupt (NMI) service routine
_nmi_int: RTI ; Return from all NMI interrupts
; ---------------------------------------------------------------------------
; Maskable interrupt (IRQ) service routine
_irq_int: PHX ; Save X register contents to stack
TSX ; Transfer stack pointer to X
PHA ; Save accumulator contents to stack
INX ; Increment X so it points to the status
INX ; register value saved on the stack
LDA $100,X ; Load status register contents
AND #$10 ; Isolate B status bit
BNE break ; If B = 1, BRK detected
; ---------------------------------------------------------------------------
; IRQ detected, return
irq: PLA ; Restore accumulator contents
PLX ; Restore X register contents
RTI ; Return from all IRQ interrupts
; ---------------------------------------------------------------------------
; BRK detected, stop
break: JMP _stop ; If BRK is detected, something very bad
; has happened, so stop running
The following discussion explains the purpose of several important assembler level directives in this file.
.import _stop
This line instructs the assembler to import the symbol _stop
from another module. This routine will be called if a BRK instruction
is encountered, signaling a software fault.
.export _irq_int, _nmi_int
This line instructs the assembler that the symbols _irq_int
and
_nmi_int
are to be accessible from other modules. In this
example, the address of these symbols will be placed in the interrupt
vector table.
.segment "CODE"
This line instructs the assembler that the code is to be placed in the
CODE segment of memory. Note that because there are 65C02 mnemonics in
the assembly code, the assembler is forced to use the 65C02 instruction
set with the .PC02
directive.
The final step is to define the interrupt vector memory locations. Recall that a segment named VECTORS was defined in the memory configuration file, which started at location $FFFA. The addresses of the interrupt service routines from "interrupt.s" along with the address for the initialization code in crt0 are defined in a file named "vectors.s". Note that these vectors will be placed in memory in their proper little-endian format as:
$FFFA - $FFFB:
NMI interrupt vector (low byte, high byte)
$FFFC - $FFFD:
Reset vector (low byte, high byte)
$FFFE - $FFFF:
IRQ/BRK interrupt vector (low byte, high byte)
using the .addr
assembler directive. The contents of the file are:
; ---------------------------------------------------------------------------
; vectors.s
; ---------------------------------------------------------------------------
;
; Defines the interrupt vector table.
.import _init
.import _nmi_int, _irq_int
.segment "VECTORS"
.addr _nmi_int ; NMI vector
.addr _init ; Reset vector
.addr _irq_int ; IRQ/BRK vector
The cc65 toolset will replace the address symbols defined here with the actual addresses of the routines during the link process.