Skip to content
Techniques & Technology

Raster Interrupts

Precision timing with the video beam

Raster interrupts trigger code at exact screen positions, enabling split-screen effects, colour bars, and multiplexed sprites that defined 8-bit graphics.

C64AmigaNES graphicsinterruptsadvanced 1977–present

Overview

The video display is drawn line by line, top to bottom, 50 or 60 times per second. Raster interrupts let your code execute when the electron beam reaches a specific line. This enables effects impossible otherwise: different colours in different screen regions, multiplexed sprites, and split-screen displays.

How it works

Display generation:
  Line 0   → top of screen
  Line 1   →
  ...      →
  Line 100 → [INTERRUPT FIRES HERE]
  ...      → your code runs, changes colours/sprites
  Line 200 →
  ...      →
  Line 311 → bottom of frame (PAL)
  VBlank   → vertical retrace
  Line 0   → next frame starts

Commodore 64 implementation

Setting up a raster interrupt

setup_raster:
    sei                     ; disable interrupts

    ; Select raster as interrupt source
    lda #$01
    sta $d01a               ; enable raster interrupt

    ; Set raster line (low 8 bits)
    lda #100
    sta $d012

    ; Clear bit 7 of $d011 for lines 0-255
    ; (or set for lines 256-311)
    lda $d011
    and #$7f
    sta $d011

    ; Point IRQ vector to our handler
    lda #<raster_handler
    sta $0314
    lda #>raster_handler
    sta $0315

    ; Acknowledge any pending interrupt
    lda #$01
    sta $d019

    cli                     ; enable interrupts
    rts

raster_handler:
    ; Change something (colour, sprite, etc.)
    lda #2                  ; red
    sta $d020               ; border colour

    ; Acknowledge interrupt
    lda #$01
    sta $d019

    ; Set up next interrupt at different line
    lda #150
    sta $d012

    ; Point to second handler
    lda #<raster_handler2
    sta $0314
    lda #>raster_handler2
    sta $0315

    ; Return from interrupt
    jmp $ea31               ; KERNAL IRQ return

Chained interrupts

Multiple raster interrupts per frame:

raster_handler1:         ; line 50 - red border
    lda #2
    sta $d020
    lda #100
    sta $d012
    ; point to handler2
    ...
    jmp $ea31

raster_handler2:         ; line 100 - blue border
    lda #6
    sta $d020
    lda #150
    sta $d012
    ; point to handler3
    ...
    jmp $ea31

raster_handler3:         ; line 150 - black border
    lda #0
    sta $d020
    lda #50
    sta $d012
    ; point back to handler1
    ...
    jmp $ea31

NES implementation

The NES uses NMI for VBlank and mapper IRQs for mid-screen:

VBlank NMI

nmi_handler:
    ; VBlank started - safe to update VRAM
    pha
    txa
    pha
    tya
    pha

    ; Update sprites
    lda #$02
    sta $4014               ; OAM DMA

    ; Update palettes, nametables, etc.
    ...

    pla
    tay
    pla
    tax
    pla
    rti

MMC3 scanline counter

; Set IRQ to fire at line 100
    lda #100
    sta $C000               ; set counter
    sta $C001               ; reload counter
    sta $E001               ; enable IRQ

irq_handler:
    ; Change scroll, palette, etc.
    ...
    sta $E000               ; acknowledge IRQ
    rti

Amiga implementation

The Amiga uses the Copper for beam-synced effects, but can also use interrupts:

Copper-triggered interrupt

copperlist:
    dc.w    $0180,$0000     ; COLOR00 = black
    dc.w    $8007,$fffe     ; wait line 128
    dc.w    $009c,$8010     ; trigger copper interrupt
    dc.w    $ffff,$fffe     ; end

; Level 3 interrupt handler
copper_interrupt:
    btst    #4,$dff01f      ; copper interrupt?
    beq     .not_copper
    move.w  #$0010,$dff09c  ; acknowledge

    ; Do stuff
    ...

.not_copper:
    rte

Common uses

EffectHow it works
Colour barsChange background colour at each scanline
Split screenDifferent scroll values above/below split
Sprite multiplexReposition sprites after they’re drawn
Status barFixed area with different graphics mode
ParallaxDifferent scroll speeds per region

Timing precision

Jitter

Interrupts don’t fire at exactly the same cycle each time:

Instruction being executed varies
Interrupt acknowledged after instruction completes
Handler entry timing varies by 0-7 cycles

Stable raster

For cycle-exact effects, synchronise first:

stable_raster:
    ; Known entry point timing
    lda #$00            ; 2 cycles
    sta $d012           ; 4 cycles
    ...

    ; Double interrupt technique or
    ; NOPs to align to specific cycle

Performance considerations

FactorImpact
Interrupt latency~40 cycles to enter handler
Handler lengthMust complete before next interrupt
Nested interruptsComplex to manage correctly
KERNAL returnUses ~30 cycles; skip for speed

Skipping KERNAL

For maximum speed:

fast_handler:
    ; Save registers
    pha
    txa
    pha
    tya
    pha

    ; Your code here
    ...

    ; Acknowledge
    lda #$01
    sta $d019

    ; Restore registers
    pla
    tay
    pla
    tax
    pla
    rti                 ; direct return, skip KERNAL

See also