User Tools

Site Tools


Edit: Isn't actually stable. Use another example.

Introduction to raster IRQs

Those uninterested in the basic technical background may jump a few paragraph down.

From wikipedia:

In computing, an interrupt is an asynchronous signal from hardware indicating the need for attention or a synchronous event in software indicating the need for a change in execution. A hardware interrupt causes the processor to save its state of execution via a context switch, and begin execution of an interrupt handler. Hardware interrupts were introduced as a way to avoid wasting the processor's valuable time in polling loops, waiting for external events.

In human language:

Interrupts were introduced, so the processor doesn't have to busy waiting for something, like in our case a certain rasterline. Without interrupts we would have to do this:

     lda #rasterline
wait cmp $d012
     bne wait

Certainly it's more handy to simply ask the VICII to tell the CPU when it has reached a certain rasterline. When this happens the CPU will jump to the address specified at $fffe/$ffff, and store the return address and the status register in the stack. The CPU will finish its current instruction before doing so, and as instructions take a different time to complete, and interrupt condition happens within a certain 'random' timeframe. This will introduce a variable amount of delay in jumping to the interrupt handler code. This is the source of all timing problems.

To make the interrupt handler code to be always in synch with the horizontal screen refresh, the so called “stable raster” approaches are used. Read Jackasser's introduction to stable timing for more information about that.

Setting up a raster interrupt on the c64:

sei        ;disable maskable IRQs

lda #$7f
sta $dc0d  ;disable timer interrupts which can be generated by the two CIA chips
sta $dd0d  ;the kernal uses such an interrupt to flash the cursor and scan the keyboard, so we better
           ;stop it.

lda $dc0d  ;by reading this two registers we negate any pending CIA irqs.
lda $dd0d  ;if we don't do this, a pending CIA irq might occur after we finish setting up our irq.
           ;we don't want that to happen.

lda #$01   ;this is how to tell the VICII to generate a raster interrupt
sta $d01a

lda #$xx   ;this is how to tell at which rasterline we want the irq to be triggered
sta $d012

lda #$1b   ;as there are more than 256 rasterlines, the topmost bit of $d011 serves as
sta $d011  ;the 9th bit for the rasterline we want our irq to be triggered.
           ;here we simply set up a character screen, leaving the topmost bit 0.

lda #$35   ;we turn off the BASIC and KERNAL rom here
sta $01    ;the cpu now sees RAM everywhere except at $d000-$e000, where still the registers of
           ;SID/VICII/etc are visible

lda #<irq  ;this is how we set up
sta $fffe  ;the address of our interrupt code
lda #>irq
sta $ffff

cli        ;enable maskable interrupts again

jmp *      ;we better don't RTS, the ROMS are now switched off, there's no way back to the system


;Being all kernal irq handlers switched off we have to do more work by ourselves.
;When an interrupt happens the CPU will stop what its doing, store the status and return address
;into the stack, and then jump to the interrupt routine. It will not store other registers, and if
;we destroy the value of A/X/Y in the interrupt routine, then when returning from the interrupt to
;what the CPU was doing will lead to unpredictable results (most probably a crash). So we better
;store those registers, and restore their original value before reentering the code the CPU was
;interrupted running.

;If you won't change the value of a register you are safe to not to store / restore its value.
;However, it's easy to screw up code like that with later modifying it to use another register too
;and forgetting about storing its state.

;The method shown here to store the registers is the most orthodox and most failsafe.

pha        ;store register A in stack
pha        ;store register X in stack
pha        ;store register Y in stack

lda #$ff   ;this is the orthodox and safe way of clearing the interrupt condition of the VICII.
sta $d019  ;if you don't do this the interrupt condition will be present all the time and you end
           ;up having the CPU running the interrupt code all the time, as when it exists the
           ;interrupt, the interrupt request from the VICII will be there again regardless of the
           ;rasterline counter.

           ;it's pretty safe to use inc $d019 (or any other rmw instruction) for brevity, they
           ;will only fail on hardware like c65 or supercpu. c64dtv is ok with this though.

tay        ;restore register Y from stack (remember stack is FIFO: First In First Out)
tax        ;restore register X from stack
pla        ;restore register A from stack

rti        ;Return From Interrupt, this will load into the Program Counter register the address
           ;where the CPU was when the interrupt condition arised which will make the CPU continue
           ;the code it was interrupted at also restores the status register of the CPU

When you set up a raster irq having the ROMS still on, the same as above happens behind the curtains. $fffe/$ffff will be now a ROM address and will point to a ROM routine which will store the registers to stack, then check if the interrupt was generated by a BRK instruction or not. These 2 cases are served by different routines in the ROM, but most likely a BRK interrupt will never happen. After this a JMP ($0314) instruction is in the ROM, that's why you set up your Kernal based raster irqs using $0314/$0315 as vectors. The usual JMP $EA31 at the end of your raster routine jumps back into ROM in a part which calls the routines to move/flash the cursor, read keyboard, etc etc. JMP $EA7E will jump on a code which does nothing that restores the registers from stack and then an rti.

The fastest method to serve a raster interrupt:

      sta atemp+1
      stx xtemp+1
      sty ytemp+1

      lsr $d019    ;as stated earlier this might fail only on exotic HW like c65 etc.
                   ;lda #$ff sta $d019 is equally fast, but uses two more bytes and
                   ;trashes A

      atemp lda #$00
      xtemp ldx #$00
      ytemp ldy #$00

You only need to use the stack if you do weird stuff like doing a cli inside an interrupt handler and let another interrupt occur.

base/introduction_to_raster_irqs.txt · Last modified: 2016-12-08 21:46 by cruzer