Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

- [Getting started](part2/getting-started.md)
- [Objects](part2/objects.md)
- [VBlank Interrupt](part2/vblank-interrupt.md)
- [Functions](part2/functions.md)
- [Input](part2/input.md)
- [Collision](part2/collision.md)
Expand Down
49 changes: 49 additions & 0 deletions src/part2/vblank-interrupt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# VBlank Interrupt

In the previous chapter, we waited for VBlank by repeatedly reading `rLY`.
That works, but it keeps the CPU busy doing nothing for most of the frame.
The Game Boy can notify us when VBlank starts instead: this is what the VBlank interrupt is for.

An interrupt is a small function that the CPU jumps to automatically when some hardware event happens.
For VBlank, that function must live at address `$40`.
Add this before the header:

```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:vblank-interrupt}}
{{#include ../../unbricked/vblank-interrupt/main.asm:vblank-interrupt}}
```

The handler only marks that VBlank happened, then returns with `reti`.
We save and restore `af` because interrupts can happen between two unrelated instructions, and the interrupted code should not see its registers unexpectedly changed.

We need one byte of RAM for that mark, next to the frame counter we already added:

```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:variables}}
{{#include ../../unbricked/vblank-interrupt/main.asm:variables}}
```

After the LCD is on and our variables are initialized, enable the VBlank interrupt:

```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:enable-vblank-interrupt}}
{{#include ../../unbricked/vblank-interrupt/main.asm:enable-vblank-interrupt}}
```

`rIE` chooses which interrupts are allowed to run, and `ei` enables interrupt handling globally.
Clearing `rIF` first discards any old pending interrupt request so our first wait starts from a known state.

Now we can replace the `rLY` polling at the top of the main loop with a function:

```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:wait-vblank}}
{{#include ../../unbricked/vblank-interrupt/main.asm:wait-vblank}}
```

`halt` stops the CPU until an enabled interrupt fires.
When VBlank starts, the handler above sets `wVBlankFlag`, `halt` returns, and the loop can safely update OAM for this frame.

With that helper in place, the main loop starts like this:

```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/vblank-interrupt/main.asm:main-loop-start}}
{{#include ../../unbricked/vblank-interrupt/main.asm:main-loop-start}}
```

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.
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.
39 changes: 32 additions & 7 deletions unbricked/audio/main.asm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ DEF BRICK_RIGHT EQU $06
DEF BLANK_TILE EQU $08
; ANCHOR_END: constants

; ANCHOR: vblank-interrupt
SECTION "VBlank Interrupt", ROM0[$40]
VBlankInterrupt:
push af
ld a, 1
ld [wVBlankFlag], a
pop af
reti
; ANCHOR_END: vblank-interrupt
SECTION "Header", ROM0[$100]

jp EntryPoint
Expand Down Expand Up @@ -93,14 +102,18 @@ ClearOam:
ld a, 0
ld [wFrameCounter], a

; ANCHOR: enable-vblank-interrupt
; Enable the VBlank interrupt
xor a, a
ld [wVBlankFlag], a
ld [rIF], a
ld a, IE_VBLANK
ld [rIE], a
ei
; ANCHOR_END: enable-vblank-interrupt

Main:
ld a, [rLY]
cp 144
jp nc, Main
WaitVBlank2:
ld a, [rLY]
cp 144
jp c, WaitVBlank2
call WaitForVBlank

; Add the ball's momentum to its position in OAM.
ld a, [wBallMomentumX]
Expand Down Expand Up @@ -351,6 +364,17 @@ Input:
; @param de: Source
; @param hl: Destination
; @param bc: Length
; ANCHOR: wait-vblank
WaitForVBlank:
xor a, a
ld [wVBlankFlag], a
.wait
halt
ld a, [wVBlankFlag]
and a, a
jp z, .wait
ret
; ANCHOR_END: wait-vblank
MemCopy:
ld a, [de]
ld [hli], a
Expand Down Expand Up @@ -644,6 +668,7 @@ BallEnd:

SECTION "Counter", WRAM0
wFrameCounter: db
wVBlankFlag: db

SECTION "Input Variables", WRAM0
wCurKeys: db
Expand Down
39 changes: 32 additions & 7 deletions unbricked/bcd/main.asm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ DEF SCORE_TENS EQU $9870
DEF SCORE_ONES EQU $9871
; ANCHOR_END: score-tile-location

; ANCHOR: vblank-interrupt
SECTION "VBlank Interrupt", ROM0[$40]
VBlankInterrupt:
push af
ld a, 1
ld [wVBlankFlag], a
pop af
reti
; ANCHOR_END: vblank-interrupt
SECTION "Header", ROM0[$100]

jp EntryPoint
Expand Down Expand Up @@ -102,14 +111,18 @@ ClearOam:
ld [wScore], a
; ANCHOR_END: init-variables

; ANCHOR: enable-vblank-interrupt
; Enable the VBlank interrupt
xor a, a
ld [wVBlankFlag], a
ld [rIF], a
ld a, IE_VBLANK
ld [rIE], a
ei
; ANCHOR_END: enable-vblank-interrupt

Main:
ld a, [rLY]
cp 144
jp nc, Main
WaitVBlank2:
ld a, [rLY]
cp 144
jp c, WaitVBlank2
call WaitForVBlank

; Add the ball's momentum to its position in OAM.
ld a, [wBallMomentumX]
Expand Down Expand Up @@ -382,6 +395,17 @@ Input:
; @param de: Source
; @param hl: Destination
; @param bc: Length
; ANCHOR: wait-vblank
WaitForVBlank:
xor a, a
ld [wVBlankFlag], a
.wait
halt
ld a, [wVBlankFlag]
and a, a
jp z, .wait
ret
; ANCHOR_END: wait-vblank
MemCopy:
ld a, [de]
ld [hli], a
Expand Down Expand Up @@ -741,6 +765,7 @@ BallEnd:

SECTION "Counter", WRAM0
wFrameCounter: db
wVBlankFlag: db

SECTION "Input Variables", WRAM0
wCurKeys: db
Expand Down
39 changes: 32 additions & 7 deletions unbricked/bricks/main.asm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ DEF BRICK_RIGHT EQU $06
DEF BLANK_TILE EQU $08
; ANCHOR_END: constants

; ANCHOR: vblank-interrupt
SECTION "VBlank Interrupt", ROM0[$40]
VBlankInterrupt:
push af
ld a, 1
ld [wVBlankFlag], a
pop af
reti
; ANCHOR_END: vblank-interrupt
SECTION "Header", ROM0[$100]

jp EntryPoint
Expand Down Expand Up @@ -96,14 +105,18 @@ ClearOam:
ld [wCurKeys], a
ld [wNewKeys], a

; ANCHOR: enable-vblank-interrupt
; Enable the VBlank interrupt
xor a, a
ld [wVBlankFlag], a
ld [rIF], a
ld a, IE_VBLANK
ld [rIE], a
ei
; ANCHOR_END: enable-vblank-interrupt

Main:
ld a, [rLY]
cp 144
jp nc, Main
WaitVBlank2:
ld a, [rLY]
cp 144
jp c, WaitVBlank2
call WaitForVBlank

; Add the ball's momentum to its position in OAM.
ld a, [wBallMomentumX]
Expand Down Expand Up @@ -347,6 +360,17 @@ Input:
; @param de: Source
; @param hl: Destination
; @param bc: Length
; ANCHOR: wait-vblank
WaitForVBlank:
xor a, a
ld [wVBlankFlag], a
.wait
halt
ld a, [wVBlankFlag]
and a, a
jp z, .wait
ret
; ANCHOR_END: wait-vblank
MemCopy:
ld a, [de]
ld [hli], a
Expand Down Expand Up @@ -614,6 +638,7 @@ BallEnd:

SECTION "Counter", WRAM0
wFrameCounter: db
wVBlankFlag: db

SECTION "Input Variables", WRAM0
wCurKeys: db
Expand Down
39 changes: 32 additions & 7 deletions unbricked/collision/main.asm
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
INCLUDE "hardware.inc"

; ANCHOR: vblank-interrupt
SECTION "VBlank Interrupt", ROM0[$40]
VBlankInterrupt:
push af
ld a, 1
ld [wVBlankFlag], a
pop af
reti
; ANCHOR_END: vblank-interrupt
SECTION "Header", ROM0[$100]

jp EntryPoint
Expand Down Expand Up @@ -96,15 +105,19 @@ ClearOam:
ld [wCurKeys], a
ld [wNewKeys], a

; ANCHOR: enable-vblank-interrupt
; Enable the VBlank interrupt
xor a, a
ld [wVBlankFlag], a
ld [rIF], a
ld a, IE_VBLANK
ld [rIE], a
ei
; ANCHOR_END: enable-vblank-interrupt

; ANCHOR: momentum
Main:
ld a, [rLY]
cp 144
jp nc, Main
WaitVBlank2:
ld a, [rLY]
cp 144
jp c, WaitVBlank2
call WaitForVBlank

; Add the ball's momentum to its position in OAM.
ld a, [wBallMomentumX]
Expand Down Expand Up @@ -294,6 +307,17 @@ IsWallTile:
ret
; ANCHOR_END: is-wall-tile

; ANCHOR: wait-vblank
WaitForVBlank:
xor a, a
ld [wVBlankFlag], a
.wait
halt
ld a, [wVBlankFlag]
and a, a
jp z, .wait
ret
; ANCHOR_END: wait-vblank
UpdateKeys:
; Poll half the controller
ld a, JOYP_GET_BUTTONS
Expand Down Expand Up @@ -604,6 +628,7 @@ BallEnd:
; ANCHOR: ram
SECTION "Counter", WRAM0
wFrameCounter: db
wVBlankFlag: db

SECTION "Input Variables", WRAM0
wCurKeys: db
Expand Down
40 changes: 33 additions & 7 deletions unbricked/functions/main.asm
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
INCLUDE "hardware.inc"

; ANCHOR: vblank-interrupt
SECTION "VBlank Interrupt", ROM0[$40]
VBlankInterrupt:
push af
ld a, 1
ld [wVBlankFlag], a
pop af
reti
; ANCHOR_END: vblank-interrupt
SECTION "Header", ROM0[$100]

jp EntryPoint
Expand Down Expand Up @@ -72,15 +81,19 @@ ClearOam:
ld a, 0
ld [wFrameCounter], a

; ANCHOR: enable-vblank-interrupt
; Enable the VBlank interrupt
xor a, a
ld [wVBlankFlag], a
ld [rIF], a
ld a, IE_VBLANK
ld [rIE], a
ei
; ANCHOR_END: enable-vblank-interrupt

; ANCHOR: main
Main:
ld a, [rLY]
cp 144
jp nc, Main
WaitVBlank2:
ld a, [rLY]
cp 144
jp c, WaitVBlank2
call WaitForVBlank

ld a, [wFrameCounter]
inc a
Expand All @@ -99,6 +112,18 @@ WaitVBlank2:
jp Main
; ANCHOR_END: main

; ANCHOR: wait-vblank
WaitForVBlank:
xor a, a
ld [wVBlankFlag], a
.wait
halt
ld a, [wVBlankFlag]
and a, a
jp z, .wait
ret
; ANCHOR_END: wait-vblank

; ANCHOR: memcpy
; Copy bytes from one area to another.
; @param de: Source
Expand Down Expand Up @@ -361,6 +386,7 @@ PaddleEnd:

SECTION "Counter", WRAM0
wFrameCounter: db
wVBlankFlag: db

SECTION "Input Variables", WRAM0
wCurKeys: db
Expand Down
Loading