From 13bc930c7f25df360c5bf4091d1692b2805f8f47 Mon Sep 17 00:00:00 2001 From: goodnamenotbad <3151055304@qq.com> Date: Mon, 11 May 2026 19:42:51 +0800 Subject: [PATCH] Use VBlank interrupt for Unbricked frame timing --- src/part2/objects.md | 29 ++++++++++++++++++---------- unbricked/audio/main.asm | 23 +++++++++++++++------- unbricked/bcd/main.asm | 23 +++++++++++++++------- unbricked/bricks/main.asm | 23 +++++++++++++++------- unbricked/collision/main.asm | 23 +++++++++++++++------- unbricked/functions/main.asm | 23 +++++++++++++++------- unbricked/input/main.asm | 23 +++++++++++++++------- unbricked/objects/main.asm | 27 +++++++++++++++++++------- unbricked/serial-link/demo.asm | 30 +++++++++++++++++++++-------- unbricked/serial-link/main.asm | 34 +++++++++++++++++++++++---------- unbricked/title-screen/main.asm | 21 ++++++++++++++++---- 11 files changed, 198 insertions(+), 81 deletions(-) diff --git a/src/part2/objects.md b/src/part2/objects.md index 41138bbc..9b7af1a3 100644 --- a/src/part2/objects.md +++ b/src/part2/objects.md @@ -96,18 +96,27 @@ There are actually two object palettes, but we're only going to use one. Now that you have an object on the screen, let's move it around. Previously, the `Done` loop did nothing; let's rename it to `Main` and use it to move our object. -We're going to wait for VBlank before changing OAM, just like we did before turning off the screen. +We're going to use the VBlank interrupt to wait for the next frame before changing OAM. -```rgbasm,linenos,start={{#line_no_of "^Main:" ../../unbricked/objects/main.asm}} +First, add an interrupt handler at the start of the file: + +```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/objects/main.asm:vblank-interrupt}} +{{#include ../../unbricked/objects/main.asm:vblank-interrupt}} +``` + +Next, add this helper function before the graphics data: + +```rgbasm,linenos,start={{#line_no_of "" ../../unbricked/objects/main.asm:wait-for-vblank}} +{{#include ../../unbricked/objects/main.asm:wait-for-vblank}} +``` + +For now, the VBlank interrupt handler just returns. This is enough for `halt` to stop the CPU until the next VBlank interrupt wakes it back up. + +Now rename the `Done` loop to `Main` and call that helper before moving the paddle: + +```rgbasm Main: - ; Wait until it's *not* VBlank - ld a, [rLY] - cp 144 - jp nc, Main -WaitVBlank2: - ld a, [rLY] - cp 144 - jp c, WaitVBlank2 + call WaitForVBlank ; Move the paddle one pixel to the right. ld a, [STARTOF(OAM) + 1] diff --git a/unbricked/audio/main.asm b/unbricked/audio/main.asm index 6e2ed4be..86fc1cf6 100644 --- a/unbricked/audio/main.asm +++ b/unbricked/audio/main.asm @@ -6,6 +6,10 @@ DEF BRICK_RIGHT EQU $06 DEF BLANK_TILE EQU $08 ; ANCHOR_END: constants +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + reti + SECTION "Header", ROM0[$100] jp EntryPoint @@ -93,14 +97,15 @@ ClearOam: ld a, 0 ld [wFrameCounter], a + ; Enable the VBlank interrupt + xor a, a + ld [rIF], a + ld a, IE_VBLANK + ld [rIE], a + ei + 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] @@ -347,6 +352,10 @@ Input: .knownret ret +WaitForVBlank: + halt + ret + ; Copy bytes from one area to another. ; @param de: Source ; @param hl: Destination diff --git a/unbricked/bcd/main.asm b/unbricked/bcd/main.asm index 87e17d96..922b1fe8 100644 --- a/unbricked/bcd/main.asm +++ b/unbricked/bcd/main.asm @@ -10,6 +10,10 @@ DEF SCORE_TENS EQU $9870 DEF SCORE_ONES EQU $9871 ; ANCHOR_END: score-tile-location +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + reti + SECTION "Header", ROM0[$100] jp EntryPoint @@ -102,14 +106,15 @@ ClearOam: ld [wScore], a ; ANCHOR_END: init-variables + ; Enable the VBlank interrupt + xor a, a + ld [rIF], a + ld a, IE_VBLANK + ld [rIE], a + ei + 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] @@ -378,6 +383,10 @@ Input: .knownret ret +WaitForVBlank: + halt + ret + ; Copy bytes from one area to another. ; @param de: Source ; @param hl: Destination diff --git a/unbricked/bricks/main.asm b/unbricked/bricks/main.asm index 38668e4f..b6c9f6cf 100644 --- a/unbricked/bricks/main.asm +++ b/unbricked/bricks/main.asm @@ -6,6 +6,10 @@ DEF BRICK_RIGHT EQU $06 DEF BLANK_TILE EQU $08 ; ANCHOR_END: constants +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + reti + SECTION "Header", ROM0[$100] jp EntryPoint @@ -96,14 +100,15 @@ ClearOam: ld [wCurKeys], a ld [wNewKeys], a + ; Enable the VBlank interrupt + xor a, a + ld [rIF], a + ld a, IE_VBLANK + ld [rIE], a + ei + 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] @@ -343,6 +348,10 @@ Input: .knownret ret +WaitForVBlank: + halt + ret + ; Copy bytes from one area to another. ; @param de: Source ; @param hl: Destination diff --git a/unbricked/collision/main.asm b/unbricked/collision/main.asm index 70ae528e..84f9b0a8 100644 --- a/unbricked/collision/main.asm +++ b/unbricked/collision/main.asm @@ -1,5 +1,9 @@ INCLUDE "hardware.inc" +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + reti + SECTION "Header", ROM0[$100] jp EntryPoint @@ -96,15 +100,16 @@ ClearOam: ld [wCurKeys], a ld [wNewKeys], a + ; Enable the VBlank interrupt + xor a, a + ld [rIF], a + ld a, IE_VBLANK + ld [rIE], a + ei + ; 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] @@ -330,6 +335,10 @@ UpdateKeys: .knownret ret +WaitForVBlank: + halt + ret + ; Copy bytes from one area to another. ; @param de: Source ; @param hl: Destination diff --git a/unbricked/functions/main.asm b/unbricked/functions/main.asm index 17e700ec..763b9c2e 100644 --- a/unbricked/functions/main.asm +++ b/unbricked/functions/main.asm @@ -1,5 +1,9 @@ INCLUDE "hardware.inc" +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + reti + SECTION "Header", ROM0[$100] jp EntryPoint @@ -72,15 +76,16 @@ ClearOam: ld a, 0 ld [wFrameCounter], a + ; Enable the VBlank interrupt + xor a, a + ld [rIF], a + ld a, IE_VBLANK + ld [rIE], a + ei + ; 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 @@ -99,6 +104,10 @@ WaitVBlank2: jp Main ; ANCHOR_END: main +WaitForVBlank: + halt + ret + ; ANCHOR: memcpy ; Copy bytes from one area to another. ; @param de: Source diff --git a/unbricked/input/main.asm b/unbricked/input/main.asm index 89e756ca..9627aec1 100644 --- a/unbricked/input/main.asm +++ b/unbricked/input/main.asm @@ -1,5 +1,9 @@ INCLUDE "hardware.inc" +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + reti + SECTION "Header", ROM0[$100] jp EntryPoint @@ -76,15 +80,16 @@ ClearOam: ld [wNewKeys], a ; ANCHOR_END: initialize-vars + ; Enable the VBlank interrupt + xor a, a + ld [rIF], a + ld a, IE_VBLANK + ld [rIE], a + ei + ; ANCHOR: main Main: - ld a, [rLY] - cp 144 - jp nc, Main -WaitVBlank2: - ld a, [rLY] - cp 144 - jp c, WaitVBlank2 + call WaitForVBlank ; Check the current keys every frame and move left or right. call UpdateKeys @@ -120,6 +125,10 @@ Right: jp Main ; ANCHOR_END: main +WaitForVBlank: + halt + ret + ; ANCHOR: input-routine UpdateKeys: ; Poll half the controller diff --git a/unbricked/objects/main.asm b/unbricked/objects/main.asm index eeeec818..8d4e8189 100644 --- a/unbricked/objects/main.asm +++ b/unbricked/objects/main.asm @@ -2,6 +2,12 @@ ; ANCHOR_END: dummy Note that lines matching /^; ANCHOR/ are stripped from the online version INCLUDE "hardware.inc" +; ANCHOR: vblank-interrupt +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + reti +; ANCHOR_END: vblank-interrupt + SECTION "Header", ROM0[$100] jp EntryPoint @@ -98,14 +104,15 @@ ClearOam: ld a, 0 ld [wFrameCounter], a + ; Enable the VBlank interrupt + xor a, a + ld [rIF], a + ld a, IE_VBLANK + ld [rIE], a + ei + 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 @@ -124,6 +131,12 @@ WaitVBlank2: jp Main ; ANCHOR_END: main-loop +; ANCHOR: wait-for-vblank +WaitForVBlank: + halt + ret +; ANCHOR_END: wait-for-vblank + Tiles: dw `33333333 dw `33333333 diff --git a/unbricked/serial-link/demo.asm b/unbricked/serial-link/demo.asm index d694b2c0..16adabf9 100644 --- a/unbricked/serial-link/demo.asm +++ b/unbricked/serial-link/demo.asm @@ -64,6 +64,14 @@ DEF HANDSHAKE_FAILED EQU $F0 ; ANCHOR_END: handshake-codes +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + push af + ld a, 1 + ld [wVBlankDone], a + pop af + reti + ; ANCHOR: serial-interrupt-vector SECTION "Serial Interrupt", ROM0[$58] SerialInterrupt: @@ -145,24 +153,19 @@ WaitVBlank: ld [wFrameCounter], a ld [wCurKeys], a ld [wNewKeys], a + ld [wVBlankDone], a ; ANCHOR: serial-demo-init-callsite call LinkInit Main: ; ANCHOR_END: serial-demo-init-callsite - ld a, [rLY] - cp 144 - jp nc, Main + call WaitForVBlank ; ANCHOR: serial-demo-update-callsite call Input call MainUpdate ; ANCHOR_END: serial-demo-update-callsite -WaitVBlank2: - ld a, [rLY] - cp 144 - jp c, WaitVBlank2 call LinkDisplay ld a, [wFrameCounter] @@ -170,6 +173,16 @@ WaitVBlank2: ld [wFrameCounter], a jp Main +WaitForVBlank: + xor a, a + ld [wVBlankDone], a +.wait + halt + ld a, [wVBlankDone] + and a, a + jr z, .wait + ret + ; ANCHOR: serial-demo-update MainUpdate: @@ -207,7 +220,7 @@ LinkInit: ; enable the serial interrupt ldh a, [rIE] - or a, IEF_SERIAL + or a, IEF_SERIAL | IEF_VBLANK ldh [rIE], a ; enable interrupt processing globally ei @@ -833,6 +846,7 @@ wRxData: wAllowTxAttempts: db wAllowRxFaults: db +wVBlankDone: db ; ANCHOR_END: serial-demo-wram diff --git a/unbricked/serial-link/main.asm b/unbricked/serial-link/main.asm index e791fc68..82568d26 100644 --- a/unbricked/serial-link/main.asm +++ b/unbricked/serial-link/main.asm @@ -29,6 +29,14 @@ DEF MSG_GAME EQU $81 ; ANCHOR_END: link-defs +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + push af + ld a, 1 + ld [wVBlankDone], a + pop af + reti + ; ANCHOR: serial-interrupt-vector SECTION "Serial Interrupt", ROM0[$58] SerialInterrupt: @@ -337,24 +345,19 @@ EntryPoint: ld [wCurKeys], a ld [wNewKeys], a ld [wScore], a + ld [wVBlankDone], a ; ANCHOR: link-main call LinkInit - + ldh a, [rIE] + or a, IEF_VBLANK + ldh [rIE], a Main: ei ; enable interrupts to process transfers call LinkUpdate -.wait_vblank_end - ldh a, [rLY] - cp 144 - jr nc, .wait_vblank_end - -.wait_vblank_start - ldh a, [rLY] - cp 144 - jr c, .wait_vblank_start + call WaitForVBlank di ; disable interrupts for OAM/VRAM access @@ -517,6 +520,16 @@ Right: ld [_OAMRAM + 1], a jp Main +WaitForVBlank: + xor a, a + ld [wVBlankDone], a +.wait + halt + ld a, [wVBlankDone] + and a, a + jr z, .wait + ret + ; Convert a pixel position to a tilemap address ; hl = $9800 + X + Y * 32 ; @param b: X @@ -1119,5 +1132,6 @@ wLink: db wLinkPacketCount: db wShakeFailed: db wRemoteScore: db +wVBlankDone: db ; ANCHOR_END: link-state diff --git a/unbricked/title-screen/main.asm b/unbricked/title-screen/main.asm index 634d4a62..14f43407 100644 --- a/unbricked/title-screen/main.asm +++ b/unbricked/title-screen/main.asm @@ -4,6 +4,10 @@ INCLUDE "hardware.inc" ; ANCHOR_END: includes +SECTION "VBlank Interrupt", ROM0[$40] +VBlankInterrupt: + reti + ; ANCHOR: header SECTION "Header", ROM0[$100] @@ -45,17 +49,22 @@ TitleScreen: ld a, %11100100 ld [rBGP], a + ; Enable the VBlank interrupt + xor a, a + ld [rIF], a + ld a, IE_VBLANK + ld [rIE], a + ei + TitleScreenLoop: + call WaitForVBlank call UpdateKeys ld a, [wCurKeys] and PAD_START jr z, TitleScreenLoop ; ANCHOR_END: title_screen -WaitVBlank2: - ld a, [rLY] - cp 144 - jp c, WaitVBlank2 + call WaitForVBlank ; Turn the LCD off ld a, 0 @@ -99,6 +108,10 @@ Done: jp Done ; ANCHOR_END: end +WaitForVBlank: + halt + ret + ; ANCHOR: memcpy ; Copy bytes from one area to another. ; @param de: Source