IRQ Programming
From F256 Foenix
Jump to navigationJump to search
Basic IRQ handling on the 6502
The 65C02 processor (or 65816 in compatibility mode) contains an interrupt system which allows for code to be executed outside the normal program flow on the occurrence of an external event. Such event can be the start of a new frame, the expiration of a VIA timer, reception of data on a serial input line etc.
Here is a short explanation of how an interrupt is handled:
1. IRQ Line Goes Low
- The IRQ pin on the 6502 is active low.
- When the IRQ pin is pulled low by a device and interrupts are enabled, the CPU will respond.
2. IRQ Timing
- The CPU checks for IRQ at the end of each instruction cycle.
- If IRQ is active and not masked, the CPU starts the interrupt sequence.
3. Interrupt Sequence
When an IRQ is accepted:
- Complete the current instruction.
- Push the Program Counter (PC) to the stack (2 bytes, high byte first).
- Push the Processor Status Register (P) to the stack.
- Set the Interrupt Disable (I) flag in the status register (to prevent nested IRQs).
- Read the IRQ vector from memory address
$FFFE
(low byte) and$FFFF
(high byte). - Jump to the address fetched from the IRQ vector.
4. Returning from Interrupt
- The interrupt handler ends with the RTI (Return from Interrupt) instruction.
- RTI does the reverse:
- Pulls the status register from the stack.
- Pulls the program counter (2 bytes) from the stack.
- Resumes execution where it left off.
Standard IRQ Handling by MicroKernel
By default, the interrupts in the F256 machines are handled by the MicroKernel:
- Since the MicroKernel is mapped into memory space at $e000-$ffff, the IRQ vector is defined by the MicroKernel.
- Therefore, whenever an interrupt condition occurs, the 6502 jumps into the IRQ service routine located within the MicroKernel.
- The Kernel does the appropriate event handling within the IRQ.
- It clears the interrupt source that triggered the IRQ.
- Finally, it returns control back to normal program execution.
Custom IRQ Handling by User Programs
Sometimes it is useful for user programs to intercept interrupt handling in order to have minimal latency in handling events. Possible reasons for this include:
- Precise music playback in the background of normal program execution.
- Timing game play at timing intervals different from the start of frame.
- Graphics effects like sprite reuse.
- ...
In order to handle interrupts within user land roughly requires the following steps:
- Disable interrupts during the following steps in order to avoid interrupt processing while we are reconfiguring the interrupt handling.
- Set up the interrupt source. This typically includes something like configuring a timer counter interval, and start the timer. A good source for timer interrupts is the VIA chip which has a timer that runs on a 6 MHz timer clock.
- Copy the Kernel memory to a suitable location in RAM. This step is necessary because by default the Kernel is running from Read-Only flash memory. However, we need to modify the IRQ vector at $fffe/$ffff in order to re-route the interrupt service routine to our own custom routine.
- Reconfigure the Memory Management Unit (MMU) in order to access the Kernel in RAM rather than in ROM. While the memory is still being addressed through the address window at $e000-$ffff, the physical memory accessed by the MMU will be pointed to our RAM copy of the Kernel rather than the ROM version.
- Install our custom Interrupt Service Routine (ISR). This step ensures that our own interrupt routine resides in a location in memory that is accessible when the Interrupt occurs, which may be almost any time, i.e. either when the user program runs, or when the kernel is being executed. In particular this means, we need to be able to access the ISR even if our main program memory is banked out by the Kernel!
- Enable our custom interrupt source. For example, this may include setting various registers in the VIA chip which trigger the timer to raise an interrupt.
- Re-enable interrupts, kicking off our interrupt processing.