Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

9.4KB


name: interrupts description: > Use this skill for interrupt programming on the Commodore 64. Covers IRQ, NMI, BRK, CIA timers, raster interrupts, custom IRQ handlers, printer spooling, music playback routines, and stable raster techniques. Sources: The Advanced ML Book for the C64, COMPUTE!‘s Kernal Toolkit, COMPUTE!‘s Mapping the C64.

Interrupt Programming on the Commodore 64

Interrupt Types

The C64 has three interrupt types:

Type Vector Description Maskable?
IRQ (Interrupt ReQuest) $FFFE/$FFFF → $0314/$0315 Hardware timer, VIC raster Yes (SEI/CLI)
NMI (Non-Maskable Interrupt) $FFFA/$FFFB → $0318/$0319 RESTORE key, expansion port No
BRK (Break) Same as IRQ Software interrupt instruction Yes

IRQ System

Default IRQ Behavior

The default IRQ at $EA31 performs (in order):

  1. Increment jiffy timer (at $00A0-$00A2)
  2. Toggle cursor blink
  3. Scan keyboard
  4. Test STOP key
  5. Poll tape cassette
  6. Update RS-232 timing
  7. RTI

The IRQ is triggered approximately 60 times per second (NTSC) or 50 times per second (PAL) by CIA #1 Timer A.

Additionally, the VIC-II chip can generate IRQs on:

  • Raster line match
  • Sprite-sprite collision
  • Sprite-background collision
  • Light pen signal

IRQ Sources

CIA #1 ($DC00-$DCFF) Timer A:

  • Default: generates IRQ every 1/60 second (NTSC) — value $4025 at 1 MHz

VIC-II ($D000-$D3FF):

  • Raster IRQ when beam hits programmed scan line
  • Collision detection IRQs

Installing a Custom IRQ Handler

Simple Method (chain to default)

        SEI                 ; disable interrupts while modifying vector
        LDA #<MYIRQ         ; install our handler
        STA $0314           ; CINV low
        LDA #>MYIRQ
        STA $0315           ; CINV high
        CLI                 ; re-enable interrupts
        RTS

MYIRQ   ; Your code here (save/restore registers if needed)
        ; A, X, Y are already saved by Kernal if coming through $0314
        
        INC $D020           ; flash border (example)
        
        JMP $EA31           ; chain to default IRQ handler (jiffy, keyboard, etc.)

Full Replacement (skip Kernal IRQ overhead)

        SEI
        LDA #<MYIRQ
        STA $0314
        LDA #>MYIRQ
        STA $0315
        CLI
        RTS

MYIRQ   PHA                 ; save A, X, Y ourselves
        TXA
        PHA
        TYA
        PHA
        
        ; ... your time-critical code ...
        
        PLA
        TAY
        PLA
        TAX
        PLA
        RTI                 ; note: RTI, NOT RTS

CIA #1 Timer for Custom Timing

CIA #1 is at $DC00-$DCFF.

Timer A Registers

Address Name Description
$DC04 TIMALO Timer A latch/counter low byte
$DC05 TIMAHI Timer A latch/counter high byte
$DC0D CIAICR Interrupt control register
$DC0E CIACRA Control register A

Setting IRQ Interval

; Set CIA #1 Timer A for 1/100 second interval (NTSC: ~10226 cycles)
        LDA #$4E            ; 0x4E = 78 low byte of 10000 = $2710
        STA $DC04           ; Timer A low latch
        LDA #$27            ; high byte
        STA $DC05           ; Timer A high latch
        LDA #$81            ; enable Timer A interrupt
        STA $DC0D           ; interrupt control (bit 7=1=enable, bit 0=timer A)
        LDA #$11            ; start timer, continuous, system clock source
        STA $DC0E           ; control register A

Stopping the Default Timer (caution!)

; Disable default CIA #1 Timer A IRQ (keyboard/cursor will stop working!)
        LDA #$7F            ; bit 7=0 means disable
        STA $DC0D           ; disable timer A interrupt

CIA ICR Read/Write

Reading $DC0D returns the interrupt status and clears it:

Bit 7: (read) 1=any interrupt occurred
Bit 4: FLAG interrupt
Bit 3: Serial shift register
Bit 2: Timer B
Bit 1: Timer A
Bit 0: Time of Day alarm

Writing $DC0D sets/clears interrupt enables:

Bit 7: 1=enable the bits that follow; 0=disable
Bits 0-4: individual interrupt sources

VIC-II Raster Interrupts

How Raster IRQ Works

The VIC-II generates an IRQ when the electron beam reaches the scan line stored in:

  • $D012: Low 8 bits of target raster line
  • $D011 bit 7: Bit 8 (for lines 256+)

NTSC: lines 0-261 (263 total), PAL: lines 0-311 (312 total) Visible area begins approximately line 50 (NTSC) or line 48 (PAL).

Setting Up Raster IRQ

        SEI
        ; Point IRQ vector to our handler
        LDA #<RASTER_IRQ
        STA $0314
        LDA #>RASTER_IRQ
        STA $0315
        
        ; Set raster line to trigger on
        LDA $D011
        AND #$7F            ; clear bit 8 of raster
        STA $D011
        LDA #100            ; trigger at line 100
        STA $D012
        
        ; Enable VIC raster interrupt, disable CIA IRQ
        LDA #$7F
        STA $DC0D           ; disable CIA #1 timer interrupt
        LDA #$01
        STA $D01A           ; VIC IRQ enable: raster
        CLI

RASTER_IRQ:
        LDA $D019           ; read VIC interrupt register
        STA $D019           ; acknowledge (writing 1 to bit clears it)
        
        ; Check it's really a raster IRQ
        AND #$01
        BEQ NOT_RASTER
        
        ; --- Your raster effect code ---
        LDA #$02
        STA $D020           ; change border color at this scanline
        ; --------------------------------
        
NOT_RASTER:
        ; Manually do jiffy work if CIA IRQ disabled
        ; (or use a secondary CIA timer IRQ for jiffy)
        RTI

Raster IRQ Stability

The basic raster IRQ can be off by ±1 scanline due to CPU interrupt latency. For demo effects requiring pixel-perfect stability:

Double-IRQ method (from demo scene lore):

; First IRQ fires, then sets a second IRQ for exact same line
; The second one fires at a consistent point in the raster
STAGE1  LDA #<STAGE2
        STA $0314
        LDA #>STAGE2
        STA $0315
        LDA #101            ; trigger second IRQ at next line
        STA $D012
        LDA #$01
        STA $D019           ; ack
        RTI

STAGE2  ; We are now at a stable point in line 101
        LDA #<STAGE1
        STA $0314
        LDA #>STAGE1
        STA $0315
        LDA #100
        STA $D012
        LDA #$01
        STA $D019
        ; ... stable raster effect ...
        RTI

NMI (Non-Maskable Interrupt)

The NMI is triggered by the RESTORE key or the NMI line on the expansion port.

Default NMI vector chain:

  • Hardware: $FFFA/$FFFB → ROM at $FE47
  • ROM at $FE47 → RAM at $0318/$0319
; Install custom NMI handler (catches RESTORE key)
        LDA #<MYNMI
        STA $0318           ; NMINV low
        LDA #>MYNMI
        STA $0319           ; NMINV high
        RTS

MYNMI   PHA
        TXA
        PHA
        TYA
        PHA
        
        ; RESTORE key was pressed — handle it
        
        PLA
        TAY
        PLA
        TAX
        PLA
        RTI

CIA #2 Timer (NMI Source)

CIA #2 ($DD00) can generate NMIs. Useful for background tasks that should not be stoppable.

Address Description
$DD04 Timer A low latch
$DD05 Timer A high latch
$DD0D CIA #2 ICR (read=status, write=enable/disable)
$DD0E CIA #2 Control Register A

Music Playback via IRQ

A common pattern for SID music playback:

; 1. Initialize music player (call once)
        JSR MUSIC_INIT

; 2. Install IRQ that calls the play routine
        SEI
        LDA #<MUSIC_IRQ
        STA $0314
        LDA #>MUSIC_IRQ
        STA $0315
        CLI
        RTS

MUSIC_IRQ:
        JSR MUSIC_PLAY      ; call one frame of music player
        JMP $EA31           ; chain to default (keeps keyboard working)

; MUSIC_INIT and MUSIC_PLAY are from your music player (e.g., GoatTracker)

Time of Day Clock (CIA TOD)

CIA #1 also has a real-time clock (BCD format):

Address Description
$DC08 TOD Tenths of seconds (BCD)
$DC09 TOD Seconds (BCD)
$DC0A TOD Minutes (BCD)
$DC0B TOD Hours + AM/PM flag (bit 7)
; Read current time
        LDA $DC0B       ; hours (latch all registers by reading hours first)
        LDA $DC0A       ; minutes
        LDA $DC09       ; seconds
        LDA $DC08       ; tenths

; Set time (write hours last to start clock)
        LDA #$00        ; tenths
        STA $DC08
        LDA #$30        ; 30 seconds (BCD)
        STA $DC09
        LDA #$45        ; 45 minutes (BCD)
        STA $DC0A
        LDA #$12        ; 12 hours (BCD)
        STA $DC0B       ; writing hours starts the clock

Printer Spooling (Background I/O via IRQ)

A technique from The Advanced ML Book: use the IRQ to send printer data in the background.

; Simplified spooler skeleton
; Store data in SPOOLBUF, SPOOLPTR points to current byte, SPOOLEND to end

SPOOLIRQ:
        LDA SPOOLPTR
        CMP SPOOLEND
        BEQ DONE_SPOOLING   ; nothing to send
        
        ; Send one byte to printer
        LDA $DFLTO          ; check if printer already busy
        CMP #4              ; device 4?
        BEQ SKIP_SEND
        
        LDA (SPOOLPTR),Y    ; get next byte
        JSR $FFD2           ; BSOUT
        INC SPOOLPTR
        BNE SKIP_SEND
        INC SPOOLPTR+1
        
SKIP_SEND:
DONE_SPOOLING:
        JMP $EA31           ; chain to default IRQ

Powered by TurnKey Linux.