|
| 1 | +# VBlank Interrupt |
| 2 | + |
| 3 | +In the previous chapter, we waited for VBlank by repeatedly reading `rLY`. |
| 4 | +That works, but it keeps the CPU busy doing nothing for most of the frame. |
| 5 | +The Game Boy can notify us when VBlank starts instead: this is what the VBlank interrupt is for. |
| 6 | + |
| 7 | +An interrupt is a small function that the CPU jumps to automatically when some hardware event happens. |
| 8 | +For VBlank, that function must live at address `$40`. |
| 9 | +Add this before the header: |
| 10 | + |
| 11 | +```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:vblank-interrupt}} |
| 12 | +{{#include ../../unbricked/vblank-interrupt/main.asm:vblank-interrupt}} |
| 13 | +``` |
| 14 | + |
| 15 | +The handler only marks that VBlank happened, then returns with `reti`. |
| 16 | +We save and restore `af` because interrupts can happen between two unrelated instructions, and the interrupted code should not see its registers unexpectedly changed. |
| 17 | + |
| 18 | +We need one byte of RAM for that mark, next to the frame counter we already added: |
| 19 | + |
| 20 | +```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:variables}} |
| 21 | +{{#include ../../unbricked/vblank-interrupt/main.asm:variables}} |
| 22 | +``` |
| 23 | + |
| 24 | +After the LCD is on and our variables are initialized, enable the VBlank interrupt: |
| 25 | + |
| 26 | +```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:enable-vblank-interrupt}} |
| 27 | +{{#include ../../unbricked/vblank-interrupt/main.asm:enable-vblank-interrupt}} |
| 28 | +``` |
| 29 | + |
| 30 | +`rIE` chooses which interrupts are allowed to run, and `ei` enables interrupt handling globally. |
| 31 | +Clearing `rIF` first discards any old pending interrupt request so our first wait starts from a known state. |
| 32 | + |
| 33 | +Now we can replace the `rLY` polling at the top of the main loop with a function: |
| 34 | + |
| 35 | +```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:wait-vblank}} |
| 36 | +{{#include ../../unbricked/vblank-interrupt/main.asm:wait-vblank}} |
| 37 | +``` |
| 38 | + |
| 39 | +`halt` stops the CPU until an enabled interrupt fires. |
| 40 | +When VBlank starts, the handler above sets `wVBlankFlag`, `halt` returns, and the loop can safely update OAM for this frame. |
| 41 | + |
| 42 | +With that helper in place, the main loop starts like this: |
| 43 | + |
| 44 | +```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:main-loop-start}} |
| 45 | +{{#include ../../unbricked/vblank-interrupt/main.asm:main-loop-start}} |
| 46 | +``` |
| 47 | + |
| 48 | +The rest of the loop is the same game logic as before, but the CPU is not wasting the visible part of the frame in a busy loop. |
| 49 | +Later, when we add more interrupts, this flag check will also make sure we only continue when the interrupt that woke us was actually VBlank. |
0 commit comments