diff --git a/examples/irq_demo/cm.sh b/examples/irq_demo/cm.sh new file mode 100755 index 0000000..62064bd --- /dev/null +++ b/examples/irq_demo/cm.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Define filename as variable +PROGNAME="irq_demo" +# Compile and assemble directly +c65gm ${PROGNAME}.c65 diff --git a/examples/irq_demo/irq_demo.c65 b/examples/irq_demo/irq_demo.c65 new file mode 100644 index 0000000..e8b7d93 --- /dev/null +++ b/examples/irq_demo/irq_demo.c65 @@ -0,0 +1,55 @@ +//----------------------------------------------------------- +// Simple IRQ Handler Demo +// +// Installs a custom IRQ handler through the kernal vector at +// $0314. The kernal has already saved the registers for us +// before it calls the vector, so our handler does NOT push or +// pull A/X/Y. When we are done we jump into the kernal so it +// can finish the IRQ and RTI properly: +// +// jmp $ea31 - let the kernal do its full IRQ work +// (scan keyboard, blink cursor, update jiffy +// clock, ...) and then RTI +// jmp $ea81 - skip the kernal work, just restore the +// registers the kernal saved and RTI +// +// This handler chains to $ea31 so the machine stays usable. +//----------------------------------------------------------- + +#INCLUDE +#INCLUDE + +GOTO start + +WORD CONST IRQ_VECTOR = $0314 +WORD handler = @myIRQ // Address of our IRQ handler + +FUNC installIRQ + ASM + sei // Disable interrupts while we patch + ENDASM + + POKEW IRQ_VECTOR, handler // Point the vector at our handler + + ASM + cli // Re-enable interrupts + ENDASM +FEND + + +LABEL start + installIRQ() + SUBEND //exit back to basic + +//----------------------------------------------------------- +// The IRQ handler. +// +// No register saving needed - the kernal did it. Do the work, +// then hand control back to the kernal at $ea31 which restores +// the registers and returns from the interrupt. +//----------------------------------------------------------- +LABEL myIRQ + ASM + inc $0400 // Bump the top-left screen character + jmp $ea31 // Let the kernal finish the IRQ + ENDASM diff --git a/examples/irq_demo/start_in_vice.sh b/examples/irq_demo/start_in_vice.sh new file mode 100755 index 0000000..c3cacaf --- /dev/null +++ b/examples/irq_demo/start_in_vice.sh @@ -0,0 +1 @@ +x64 -autostartprgmode 1 irq_demo.prg diff --git a/language.md b/language.md index 38a697b..8ce02c3 100644 --- a/language.md +++ b/language.md @@ -925,46 +925,49 @@ FEND ### Interrupt Handlers +The C64 kernal calls the IRQ vector at `$0314` on every interrupt. Because +the kernal saves the CPU registers *before* calling the vector, your handler +does **not** need to push/pull A/X/Y itself. When finished, chain into the +kernal so it restores the registers and returns from the interrupt: + +- `jmp $ea31` — let the kernal do its full IRQ work (scan keyboard, blink + cursor, update the jiffy clock, ...) and then `RTI` +- `jmp $ea81` — skip that work; just restore the saved registers and `RTI` + +Once the vector is installed it keeps firing in the background, so the main +program can simply return to BASIC with `SUBEND` — the handler stays live. + ```c65 WORD CONST IRQ_VECTOR = $0314 -WORD oldIRQ +WORD handler = @myIRQ // Address of our IRQ handler FUNC installIRQ ASM - sei // Disable interrupts + sei // Disable interrupts while we patch ENDASM - oldIRQ = PEEKW IRQ_VECTOR - POKEW IRQ_VECTOR, myIRQ + POKEW IRQ_VECTOR, handler // Point the vector at our handler ASM - cli // Enable interrupts + cli // Re-enable interrupts ENDASM FEND +LABEL start + installIRQ() + SUBEND // Return to BASIC; the IRQ stays installed + +// No register saving needed — the kernal already did it. LABEL myIRQ - // IRQ handler code ASM - // Save registers - pha - txa - pha - tya - pha - - // Do IRQ work - inc $d020 - - // Restore and return - pla - tay - pla - tax - pla - rti + inc $0400 // Do the IRQ work + jmp $ea31 // Let the kernal finish the IRQ ENDASM ``` +See `examples/irq_demo/` for a complete, buildable version. + + ### Lookup Tables Generate tables at compile time: