-
-
Notifications
You must be signed in to change notification settings - Fork 92
Interrupt Management
PVSnesLib provides a built-in Vertical Blank Interrupt Service Routine (VBlank ISR / NMI handler). In most games you never need to write your own interrupt entry point; instead, you register a callback that is executed every frame during VBlank.
This design keeps the library responsible for critical hardware operations while allowing your game to execute code safely during VBlank.
When VBlank interrupts are enabled, the library ISR executes the following operations every frame:
- Upload OAM (sprites) when the frame is not a lag frame.
- Call the user VBlank callback (nmi_handler).
- Increment the internal VBlank counter.
- Read joypads automatically.
- Update the lag-frame counter when necessary.
This execution order guarantees that your callback runs after sprite upload and before controller data becomes available for the next frame.
This is why calling nmiSet() changes the user callback executed by VBlank, not the interrupt vector itself.
Normally an application only needs:
int main(void)
{
// initialization...
while(1)
{
WaitForVBlank();
// game logic
}
}WaitForVBlank() synchronizes the game loop with the next VBlank interrupt.
To execute code automatically every frame during VBlank, register a callback with nmiSet().
void myVBlank(void)
{
// VRAM updates
// CGRAM updates
// DMA transfers
}
int main(void)
{
nmiSet(myVBlank);
while(1)
{
WaitForVBlank();
// main game logic
}
}nmiSet() safely replaces the current callback, enables VBlank interrupts, enables automatic joypad reading, and disables active IRQ interrupts before installing the new handler.
The example graphics/Backgrounds/Mode1ContinuosScroll demonstrates one of the most common uses of nmiSet().
The game scrolls across a map larger than VRAM can hold at once. During gameplay, the main loop determines which background pages must be refreshed.
Instead of writing directly to VRAM, it fills a small structure describing the pending transfers.
bgInfo.refreshBG1 = 1;
bgInfo.refreshBG2 = 1;The registered VBlank callback myconsoleVBlank checks these flags during VBlank and performs the required VRAM transfers using DMA.
Sometimes the game requires a completely different VBlank routine.
For example:
- loading a level
- streaming graphics
- performing large tilemap updates
- enabling special rendering effects
Instead of testing many conditions inside a single interrupt handler, PVSnesLib allows replacing the callback at runtime.
void normalVBlank(void)
{
// regular frame updates
}
void loadingVBlank(void)
{
// transfer level graphics
}
...
nmiSet(normalVBlank);
...
// During level loading
nmiSet(loadingVBlank);
...
// Return to the normal handler
nmiSet(normalVBlank);Because nmiSet() safely updates the callback, it should always be preferred over writing directly to nmi_handler. Writing to nmi_handler directly is not atomic and can crash if a VBlank occurs while the function pointer is being modified.
Only operations that must occur during VBlank should be placed here.
Typical examples include:
- DMA transfers
- VRAM uploads
- Tilemap updates
- Palette updates
- Register updates that require VBlank
Game logic, AI, physics and collision detection should remain inside the main loop whenever possible.
A short interrupt routine leaves more VBlank time available for graphics transfers.
Important
Keep the VBlank callback as short as possible.
Only perform operations that require VBlank.
Do not place gameplay logic inside the interrupt unless it must execute there.
Exchange data between the main loop and the VBlank callback through shared variables or flags.
Always use WaitForVBlank() in the main loop to keep gameplay synchronized with the display refresh.
Every PVSnesLib project contains a hdr.asm file that defines the SNES cartridge header and the CPU interrupt vector tables.
Near the end of the file you will find two sections similar to the following:
.SNESNATIVEVECTOR ; Native Mode interrupt vector table
COP EmptyHandler
BRK EmptyHandler
ABORT EmptyHandler
NMI VBlank
IRQ EmptyHandler
.ENDNATIVEVECTOR
.SNESEMUVECTOR ; Emulation Mode interrupt vector table
COP EmptyHandler
ABORT EmptyHandler
NMI EmptyHandler
RESET tcc__start
IRQBRK EmptyHandler
.ENDEMUVECTORThese tables are required by the SNES hardware. They tell the 65C816 CPU which function to execute when an interrupt or exception occurs.
The 65C816 processor has two operating modes:
- Emulation Mode, which behaves like a 6502-compatible CPU.
- Native Mode, which exposes the full 16-bit capabilities of the 65C816.
PVSnesLib switches to Native Mode immediately after reset. As a result:
- the Emulation Mode vectors are only used during startup, before the C runtime is initialized;
- once the game begins, only the Native Mode vectors are normally used.
.SNESNATIVEVECTOR
COP EmptyHandler
BRK EmptyHandler
ABORT EmptyHandler
NMI VBlank
IRQ EmptyHandler
.ENDNATIVEVECTOREach entry corresponds to one CPU exception.
| Vector | Description | Default handler |
|---|---|---|
| COP | Software coprocessor instruction | EmptyHandler |
| BRK | Software breakpoint | EmptyHandler |
| ABORT | Memory abort (rarely used on SNES) | EmptyHandler |
| NMI | Vertical Blank interrupt | VBlank |
| IRQ | Maskable interrupt (HBlank, timers, peripherals...) | EmptyHandler |