Skip to content

Commit ed09d3b

Browse files
committed
Add VBlank interrupt lesson to Unbricked
1 parent f2c855b commit ed09d3b

13 files changed

Lines changed: 1884 additions & 55 deletions

File tree

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
- [Getting started](part2/getting-started.md)
2727
- [Objects](part2/objects.md)
28+
- [VBlank Interrupt](part2/vblank-interrupt.md)
2829
- [Functions](part2/functions.md)
2930
- [Input](part2/input.md)
3031
- [Collision](part2/collision.md)

src/part2/vblank-interrupt.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.

unbricked/audio/main.asm

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ DEF BRICK_RIGHT EQU $06
66
DEF BLANK_TILE EQU $08
77
; ANCHOR_END: constants
88

9+
; ANCHOR: vblank-interrupt
10+
SECTION "VBlank Interrupt", ROM0[$40]
11+
VBlankInterrupt:
12+
push af
13+
ld a, 1
14+
ld [wVBlankFlag], a
15+
pop af
16+
reti
17+
; ANCHOR_END: vblank-interrupt
918
SECTION "Header", ROM0[$100]
1019

1120
jp EntryPoint
@@ -93,14 +102,18 @@ ClearOam:
93102
ld a, 0
94103
ld [wFrameCounter], a
95104

105+
; ANCHOR: enable-vblank-interrupt
106+
; Enable the VBlank interrupt
107+
xor a, a
108+
ld [wVBlankFlag], a
109+
ld [rIF], a
110+
ld a, IE_VBLANK
111+
ld [rIE], a
112+
ei
113+
; ANCHOR_END: enable-vblank-interrupt
114+
96115
Main:
97-
ld a, [rLY]
98-
cp 144
99-
jp nc, Main
100-
WaitVBlank2:
101-
ld a, [rLY]
102-
cp 144
103-
jp c, WaitVBlank2
116+
call WaitForVBlank
104117

105118
; Add the ball's momentum to its position in OAM.
106119
ld a, [wBallMomentumX]
@@ -351,6 +364,17 @@ Input:
351364
; @param de: Source
352365
; @param hl: Destination
353366
; @param bc: Length
367+
; ANCHOR: wait-vblank
368+
WaitForVBlank:
369+
xor a, a
370+
ld [wVBlankFlag], a
371+
.wait
372+
halt
373+
ld a, [wVBlankFlag]
374+
and a, a
375+
jp z, .wait
376+
ret
377+
; ANCHOR_END: wait-vblank
354378
MemCopy:
355379
ld a, [de]
356380
ld [hli], a
@@ -644,6 +668,7 @@ BallEnd:
644668

645669
SECTION "Counter", WRAM0
646670
wFrameCounter: db
671+
wVBlankFlag: db
647672

648673
SECTION "Input Variables", WRAM0
649674
wCurKeys: db

unbricked/bcd/main.asm

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ DEF SCORE_TENS EQU $9870
1010
DEF SCORE_ONES EQU $9871
1111
; ANCHOR_END: score-tile-location
1212

13+
; ANCHOR: vblank-interrupt
14+
SECTION "VBlank Interrupt", ROM0[$40]
15+
VBlankInterrupt:
16+
push af
17+
ld a, 1
18+
ld [wVBlankFlag], a
19+
pop af
20+
reti
21+
; ANCHOR_END: vblank-interrupt
1322
SECTION "Header", ROM0[$100]
1423

1524
jp EntryPoint
@@ -102,14 +111,18 @@ ClearOam:
102111
ld [wScore], a
103112
; ANCHOR_END: init-variables
104113

114+
; ANCHOR: enable-vblank-interrupt
115+
; Enable the VBlank interrupt
116+
xor a, a
117+
ld [wVBlankFlag], a
118+
ld [rIF], a
119+
ld a, IE_VBLANK
120+
ld [rIE], a
121+
ei
122+
; ANCHOR_END: enable-vblank-interrupt
123+
105124
Main:
106-
ld a, [rLY]
107-
cp 144
108-
jp nc, Main
109-
WaitVBlank2:
110-
ld a, [rLY]
111-
cp 144
112-
jp c, WaitVBlank2
125+
call WaitForVBlank
113126

114127
; Add the ball's momentum to its position in OAM.
115128
ld a, [wBallMomentumX]
@@ -382,6 +395,17 @@ Input:
382395
; @param de: Source
383396
; @param hl: Destination
384397
; @param bc: Length
398+
; ANCHOR: wait-vblank
399+
WaitForVBlank:
400+
xor a, a
401+
ld [wVBlankFlag], a
402+
.wait
403+
halt
404+
ld a, [wVBlankFlag]
405+
and a, a
406+
jp z, .wait
407+
ret
408+
; ANCHOR_END: wait-vblank
385409
MemCopy:
386410
ld a, [de]
387411
ld [hli], a
@@ -741,6 +765,7 @@ BallEnd:
741765

742766
SECTION "Counter", WRAM0
743767
wFrameCounter: db
768+
wVBlankFlag: db
744769

745770
SECTION "Input Variables", WRAM0
746771
wCurKeys: db

unbricked/bricks/main.asm

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ DEF BRICK_RIGHT EQU $06
66
DEF BLANK_TILE EQU $08
77
; ANCHOR_END: constants
88

9+
; ANCHOR: vblank-interrupt
10+
SECTION "VBlank Interrupt", ROM0[$40]
11+
VBlankInterrupt:
12+
push af
13+
ld a, 1
14+
ld [wVBlankFlag], a
15+
pop af
16+
reti
17+
; ANCHOR_END: vblank-interrupt
918
SECTION "Header", ROM0[$100]
1019

1120
jp EntryPoint
@@ -96,14 +105,18 @@ ClearOam:
96105
ld [wCurKeys], a
97106
ld [wNewKeys], a
98107

108+
; ANCHOR: enable-vblank-interrupt
109+
; Enable the VBlank interrupt
110+
xor a, a
111+
ld [wVBlankFlag], a
112+
ld [rIF], a
113+
ld a, IE_VBLANK
114+
ld [rIE], a
115+
ei
116+
; ANCHOR_END: enable-vblank-interrupt
117+
99118
Main:
100-
ld a, [rLY]
101-
cp 144
102-
jp nc, Main
103-
WaitVBlank2:
104-
ld a, [rLY]
105-
cp 144
106-
jp c, WaitVBlank2
119+
call WaitForVBlank
107120

108121
; Add the ball's momentum to its position in OAM.
109122
ld a, [wBallMomentumX]
@@ -347,6 +360,17 @@ Input:
347360
; @param de: Source
348361
; @param hl: Destination
349362
; @param bc: Length
363+
; ANCHOR: wait-vblank
364+
WaitForVBlank:
365+
xor a, a
366+
ld [wVBlankFlag], a
367+
.wait
368+
halt
369+
ld a, [wVBlankFlag]
370+
and a, a
371+
jp z, .wait
372+
ret
373+
; ANCHOR_END: wait-vblank
350374
MemCopy:
351375
ld a, [de]
352376
ld [hli], a
@@ -614,6 +638,7 @@ BallEnd:
614638

615639
SECTION "Counter", WRAM0
616640
wFrameCounter: db
641+
wVBlankFlag: db
617642

618643
SECTION "Input Variables", WRAM0
619644
wCurKeys: db

unbricked/collision/main.asm

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
INCLUDE "hardware.inc"
22

3+
; ANCHOR: vblank-interrupt
4+
SECTION "VBlank Interrupt", ROM0[$40]
5+
VBlankInterrupt:
6+
push af
7+
ld a, 1
8+
ld [wVBlankFlag], a
9+
pop af
10+
reti
11+
; ANCHOR_END: vblank-interrupt
312
SECTION "Header", ROM0[$100]
413

514
jp EntryPoint
@@ -96,15 +105,19 @@ ClearOam:
96105
ld [wCurKeys], a
97106
ld [wNewKeys], a
98107

108+
; ANCHOR: enable-vblank-interrupt
109+
; Enable the VBlank interrupt
110+
xor a, a
111+
ld [wVBlankFlag], a
112+
ld [rIF], a
113+
ld a, IE_VBLANK
114+
ld [rIE], a
115+
ei
116+
; ANCHOR_END: enable-vblank-interrupt
117+
99118
; ANCHOR: momentum
100119
Main:
101-
ld a, [rLY]
102-
cp 144
103-
jp nc, Main
104-
WaitVBlank2:
105-
ld a, [rLY]
106-
cp 144
107-
jp c, WaitVBlank2
120+
call WaitForVBlank
108121

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

310+
; ANCHOR: wait-vblank
311+
WaitForVBlank:
312+
xor a, a
313+
ld [wVBlankFlag], a
314+
.wait
315+
halt
316+
ld a, [wVBlankFlag]
317+
and a, a
318+
jp z, .wait
319+
ret
320+
; ANCHOR_END: wait-vblank
297321
UpdateKeys:
298322
; Poll half the controller
299323
ld a, JOYP_GET_BUTTONS
@@ -604,6 +628,7 @@ BallEnd:
604628
; ANCHOR: ram
605629
SECTION "Counter", WRAM0
606630
wFrameCounter: db
631+
wVBlankFlag: db
607632

608633
SECTION "Input Variables", WRAM0
609634
wCurKeys: db

unbricked/functions/main.asm

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
INCLUDE "hardware.inc"
22

3+
; ANCHOR: vblank-interrupt
4+
SECTION "VBlank Interrupt", ROM0[$40]
5+
VBlankInterrupt:
6+
push af
7+
ld a, 1
8+
ld [wVBlankFlag], a
9+
pop af
10+
reti
11+
; ANCHOR_END: vblank-interrupt
312
SECTION "Header", ROM0[$100]
413

514
jp EntryPoint
@@ -72,15 +81,19 @@ ClearOam:
7281
ld a, 0
7382
ld [wFrameCounter], a
7483

84+
; ANCHOR: enable-vblank-interrupt
85+
; Enable the VBlank interrupt
86+
xor a, a
87+
ld [wVBlankFlag], a
88+
ld [rIF], a
89+
ld a, IE_VBLANK
90+
ld [rIE], a
91+
ei
92+
; ANCHOR_END: enable-vblank-interrupt
93+
7594
; ANCHOR: main
7695
Main:
77-
ld a, [rLY]
78-
cp 144
79-
jp nc, Main
80-
WaitVBlank2:
81-
ld a, [rLY]
82-
cp 144
83-
jp c, WaitVBlank2
96+
call WaitForVBlank
8497

8598
ld a, [wFrameCounter]
8699
inc a
@@ -99,6 +112,18 @@ WaitVBlank2:
99112
jp Main
100113
; ANCHOR_END: main
101114

115+
; ANCHOR: wait-vblank
116+
WaitForVBlank:
117+
xor a, a
118+
ld [wVBlankFlag], a
119+
.wait
120+
halt
121+
ld a, [wVBlankFlag]
122+
and a, a
123+
jp z, .wait
124+
ret
125+
; ANCHOR_END: wait-vblank
126+
102127
; ANCHOR: memcpy
103128
; Copy bytes from one area to another.
104129
; @param de: Source
@@ -361,6 +386,7 @@ PaddleEnd:
361386

362387
SECTION "Counter", WRAM0
363388
wFrameCounter: db
389+
wVBlankFlag: db
364390

365391
SECTION "Input Variables", WRAM0
366392
wCurKeys: db

0 commit comments

Comments
 (0)