--- 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) ```asm SEI ; disable interrupts while modifying vector 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) ```asm SEI 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 ```asm ; 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!) ```asm ; 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 ```asm SEI ; Point IRQ vector to our handler 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): ```asm ; 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 $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 $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 ```asm ; Install custom NMI handler (catches RESTORE key) 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: ```asm ; 1. Initialize music player (call once) JSR MUSIC_INIT ; 2. Install IRQ that calls the play routine SEI 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) | ```asm ; 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. ```asm ; 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 ```