Skip to content

Raster Splits

Fire an interrupt when the beam reaches a chosen scanline and change a VIC-II register mid-screen — the basis of colour bars, split panels, and beating the chip's limits.

rasterinterruptirqvic-ii

Overview

The VIC-II can raise an interrupt the instant the raster beam reaches a scanline you choose. In the handler you change a register — and because the change lands part-way down the screen, the top and bottom show different settings. That one idea, “split the screen at a line,” is behind colour bars, separate score panels that don’t scroll, showing more than the usual sprites or colours, and almost every advanced C64 display trick.

This pattern sets up a raster interrupt that splits the background into two colours.

Code

; =============================================================================
; RASTER SPLITS - C64
; A raster interrupt changes the background colour part-way down the screen
; CPU: A handful of cycles per split | Memory: ~70 bytes
; =============================================================================

VIC_RASTER  = $D012     ; Raster compare line (low 8 bits)
VIC_CTRL1   = $D011     ; Bit 7 = raster compare line bit 8
VIC_IRQ     = $D019     ; Interrupt status — write a 1 to a bit to acknowledge
VIC_IRQEN   = $D01A     ; Interrupt enable — bit 0 = raster
BGCOLOR     = $D021     ; Background colour
CIA1_ICR    = $DC0D     ; CIA #1 interrupt control
IRQVEC      = $0314     ; KERNAL IRQ vector (low byte / high byte)

SPLIT_LINE  = 130       ; Split the screen here

setup:
    sei                     ; no interrupts while we wire this up

    lda #<raster_irq        ; point the IRQ vector at our handler
    sta IRQVEC
    lda #>raster_irq
    sta IRQVEC+1

    lda #$7f                ; switch off CIA #1 timer interrupts...
    sta CIA1_ICR
    lda CIA1_ICR            ; ...and acknowledge any that were pending

    lda #$01                ; enable the VIC-II raster interrupt
    sta VIC_IRQEN

    lda #0                  ; first interrupt at the top of the screen
    sta VIC_RASTER
    lda VIC_CTRL1           ; clear compare-line bit 8 (our lines are < 256)
    and #$7f
    sta VIC_CTRL1

    lda #$01                ; acknowledge any pending raster interrupt
    sta VIC_IRQ

    cli                     ; interrupts back on
    rts

; The handler fires twice a frame: once at the top, once at SPLIT_LINE.
; Read the raster line to tell which, paint that zone, and aim at the other.
raster_irq:
    lda #$01
    sta VIC_IRQ             ; acknowledge the raster interrupt

    lda VIC_RASTER
    cmp #SPLIT_LINE
    bcs at_split

at_top:
    lda #$00               ; black above the split
    sta BGCOLOR
    lda #SPLIT_LINE        ; next interrupt: the split line
    sta VIC_RASTER
    jmp $EA81              ; KERNAL exit — restore registers and RTI

at_split:
    lda #$06               ; blue below the split
    sta BGCOLOR
    lda #0                 ; next interrupt: top of the next frame
    sta VIC_RASTER
    jmp $EA81

Trade-offs

Aspect Cost
CPU A few cycles in the handler per split
Memory ~70 bytes for setup + handler
Limitation Jitter — without a stable raster, the change line wobbles a pixel or two

When to use: Colour bars, a fixed status panel above a scrolling playfield, raster-timed effects, and as the engine for sprite multiplexing.

When to avoid: If a single static screen is all you need — the basic game loop polling $D012 is simpler than wiring an interrupt.

How the interrupt is wired

Four things have to be set up, all behind sei/cli so nothing fires mid-change:

  1. The vector. $0314/$0315 is the KERNAL’s IRQ vector in RAM. Point it at your handler; the KERNAL saves the registers before jumping through it.
  2. Silence the CIA. The KERNAL’s own 60 Hz interrupt comes from CIA #1. Writing $7F to $DC0D disables all of its interrupt sources so only the raster fires.
  3. Enable the raster source with bit 0 of $D01A, and set the compare line in $D012 (with bit 8 in $D011).
  4. Acknowledge. At the start of the handler, write a 1 to bit 0 of $D019 — until you do, the VIC-II keeps the request asserted and the interrupt re-fires forever.

Exit through $EA81, the KERNAL routine that pulls the saved registers and runs RTI.

Colour bars

Reprogram the line and the colour each time, cycling through a table, and you get bars:

bar_irq:
    lda #$01
    sta VIC_IRQ
    ldx bar_index
    lda bar_colors,x       ; next colour
    sta BGCOLOR
    lda bar_lines,x        ; next line
    sta VIC_RASTER
    inx
    cpx #BAR_COUNT
    bne +
    ldx #0                 ; wrap at the end of the frame
+   stx bar_index
    jmp $EA81

Each entry is “at line L, switch to colour C” — a tiny copper-list-by-hand. The same loop drives sprite multiplexing, swapping sprite positions instead of colours.

Stable rasters

The split line wobbles because the CPU might be mid-instruction (or stalled on a badline) when the interrupt arrives, so the handler reaches the colour write a few cycles late and at a different point each frame. For a rock-steady edge you need a stable raster — a double-interrupt or cycle-counted NOP sled that lands the write on the exact same cycle every frame. See Stable Raster.

Patterns: Game Loop (Basic), Hardware Sprites

Vault: VIC-II | Commodore 64