I am working on a loading screen, and what I wanted was a simple raster trick (just change the border colour on the bottom half of the screen), and also call the KERNAL to load some more data from a disk. Easy peasy, right?

Here is my code. It loads with LOAD"*",8,1.

; The following is a short BASIC program;
; 10 SYS 2304
; and causes the machine to jump to the machine code include later on.
; 2304 decimal is 900 hex.
        .byt $01, $08, $0C, $08, $0A, $00, $9E, $20
        .byt $32, $33, $30, $34, $00, $00, $00

; Fill up with no-ops until the start of the program.
        .dsb 242,$EA

* = $0900

; Turn BASIC off but leave KERNAL and IO on.
        lda #$36
        sta $01

; Enable raster interrupt signals from VIC
        lda #%00000001
        sta $d01a

; Set the irqserve routine to start when raster line 0 hits.
        lda #0
        sta $d012
        lda #<irqserve
        sta $0314
        lda #>irqserve
        sta $0315

; enable interrupts again

; Infinite loop.
        jmp stop

As you can see, it ends in an infinite loop, which means the program should end there, right? That's not how this program behaves. This program will eventually return the user to the BASIC prompt. I'm trying to figure out why, and the problem appears to be in the routine irqserve:

; Look at the raster counter to see how far down the screen we are
        lda $d012
        beq bluborder
        cmp #116
        bcc blackborder
        jmp irqend

        lda #$01
; change border colour
        sta $d020
; Next interrupt should fire at raster line 116.
        lda #116
        sta $d012
        jmp irqend

        lda #$00
; change border colour
        sta $d020
; Next interrupt should fire at raster line 0.
        sta $d012

But what I can't figure out though is what to do at the end of my interrupt handler. Should it jump back to the KERNAL? Or do rti or rts?

2 Answers 2


Assuming you're not actually going to chain the existing interrupt handler to your own, I think you should end with rti and more besides.

A disassembly of the C64 firmware shows that IRQs jump to FF48, which pushes A, X and Y to the stack in that order, loads the value that was at the top of the stack and if it does not have $10 set — i.e. if the triggering event was presumably an interrupt — it JMPs to your (0314).

So at the end of your routine you should pull Y, X and A from the stack, then perform an rti to pull the status register and the old program counter.

Or just jump to EA81, where the normal IRQ routine that started at EA31 does that.

Addendum: the safest way, assuming you're not trying to usurp the kernal, would be just to keep hold of whatever was in (0314) before you installed yourself, and jump to that. Dynamically writing it into your own code is a decent way of doing that without worrying about storage — end with a dummy JMP $0000 and then copy whatever is currently in 0314 to the argument of that instruction before writing in your own code's address.

  • Jumping to $EA81 seems to have solved my problem. I'll look at chaining into whatever was in $0314 later. Commented Feb 13, 2018 at 21:39
  • Testing against $10 is not testing that the interrupts were enabled, it's testing the break flag. If it's set, it means that you have entered the interrupt routine as the result of a BRK instruction rather than as a result of a hardware interrupt.
    – JeremyP
    Commented Feb 14, 2018 at 16:13
  • @JeremyP right, but follow through on your logic. If you "have entered the interrupt routine ... as a result of a hardware interrupt" then what does that imply about whether interrupts are enabled? It was poorly phrased (and, now, hopefully fixed), but strictly accurate to your description. This I offer as an explanation of how I used the one thing as shorthand for the other.
    – Tommy
    Commented Feb 14, 2018 at 17:36
  • @Tommy it implies nothing. You can executa a BRK if interrupts are enabled or not. You can execute a non maskable interrupt if interrupts are disabled or not. Anyway, your revised text fixes the problem.
    – JeremyP
    Commented Feb 15, 2018 at 9:35
  • @JeremyP it'd be petty to follow it up much longer so I'll just repeat that I disagree. The processor cannot end up somewhere "as a result of a hardware interrupt" if interrupts are disabled. My point is that we both made the same improper implication; that I hoped made it more forgiveable; my use of quotation marks in these two comments ts is very deliberate; I fully understood what you meant to say that PC at address X implies nothing in and of itself.
    – Tommy
    Commented Feb 15, 2018 at 12:05

RTS un-stacks the caller address (Program Counter) and resumes at the instruction following the original JSR So, in your interrupt handler returns to an undefined location because it is loading the processor flags (The three registers and the CPU flags) put there by the Interrupt trigger.

You must use RTI for an interrupt handler.

Since the question has changed, here's some more comments.

You'll want to wedge your interrupt handler in front of the existing one. That's what @Tommy is explaining.

Also the C64 architecture overloads the IRQ line with several triggers:

  • VIC raster interrupt (what you want to do)
  • CIA clock
  • CIA timer 1
  • CIA timer 2
  • CIA serial I/O
  • CIA cassette I/O

So an interrupt handler has to determine which of these has been triggered and act accordingly.

  • The question did not change, but have a +1. Commented Apr 13, 2018 at 7:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .