Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 206 lines 10 kB view raw
1; Joypad Example for the Nintendo Game Boy 2; by Dave VanEe 2022 3; Tested with RGBDS 1.0.0 4; License: CC0 (https://creativecommons.org/publicdomain/zero/1.0/) 5 6include "hardware.inc" ; Include hardware definitions so we can use nice names for things 7 8; The VBlank vector is where execution is passed when the VBlank interrupt fires 9SECTION "VBlank Vector", ROM0[$40] 10VBlank: 11 ; In this example we're only using VBlank as a convenient way to throttle the main loop 12 reti ; Return and enable interrupts (ret + ei) 13 14 15; Define a section that starts at the point the bootrom execution ends 16SECTION "Start", ROM0[$0100] 17 jp EntryPoint ; Jump past the header space to our actual code 18 19 ds $150-@, 0 ; Allocate space for RGBFIX to insert our ROM header by allocating 20 ; the number of bytes from our current location (@) to the end of the 21 ; header ($150) 22 23EntryPoint: 24 di ; Disable interrupts during setup 25 ld sp, $e000 ; Set the stack pointer to the end of WRAM 26 27 ; Turn off the LCD when it's safe to do so (during VBlank) 28.waitVBlank 29 ldh a, [rLY] ; Read the LY register to check the current scanline 30 cp SCREEN_HEIGHT_PX ; Compare the current scanline to the first scanline of VBlank 31 jr c, .waitVBlank ; Loop as long as the carry flag is set 32 ld a, 0 ; Once we exit the loop we're safely in VBlank 33 ldh [rLCDC], a ; Disable the LCD (must be done during VBlank to protect the LCD) 34 35 ldh [hCurrentKeys], a ; Zero our current keys just to be safe (A is already zero from earlier) 36 37 ; Copy our tiles to VRAM 38 ld hl, TileData ; Load the source address of our tiles into HL 39 ld de, STARTOF(VRAM); Load the destination address in VRAM into DE 40 ld bc, TileData.end - TileData ; Load the number of bytes to copy into BC 41.copyLoop 42 ld a, [hl] ; Load a byte from the address HL points to into the register A 43 ld [de], a ; Load the byte in the A register to the address DE points to 44 inc hl ; Increment the source pointer in HL 45 inc de ; Increment the destination pointer in DE 46 dec bc ; Decrement the loop counter in BC 47 ld a, b ; Load the value in B into A 48 or c ; Logical OR the value in A (from B) with C 49 jr nz, .copyLoop ; If B and C are both zero, OR B will be zero, otherwise keep looping 50 51 ; Fill the tilemap with tile zero 52 ld hl, TILEMAP0 ; Point HL to the first byte of the tilemap ($9800) 53 ld bc, TILEMAP1 - TILEMAP0 ; Load the size of the remaining tilemap into BC 54 ld d, 0 ; Load the value to fill the tilemap with into D 55.clearLoop 56 ld [hl], d ; Load the value in D into the location pointed to by HL 57 inc hl ; Increment the destination pointer in HL 58 dec bc ; Decrement the loop counter in BC 59 ld a, b ; Load the value in B into A 60 or c ; Logical OR the value in A (from B) with C 61 jr nz, .clearLoop ; If B and C are both zero, OR B will be zero, otherwise keep looping 62 63 ; Setup palettes and scrolling 64 ld a, %11100100 ; Define a 4-shade palette from darkest (11) to lightest (00) 65 ldh [rBGP], a ; Set the background palette 66 67 ld a, -32 ; Load -32 into the A register 68 ldh [rSCX], a ; Set SCX to center the joypad display horizontally 69 ld a, -60 ; Load -60 into the A register 70 ldh [rSCY], a ; Set SCY to position the joypad vertically as desired 71 72 ; Setup the VBlank interrupt 73 ld a, IE_VBLANK ; Load the flag to enable the VBlank interrupt into A 74 ldh [rIE], a ; Load the prepared flag into the interrupt enable register 75 xor a ; Set A to zero 76 ldh [rIF], a ; Clear any lingering flags from the interrupt flag register to avoid false interrupts 77 ei ; enable interrupts! 78 79 ; Combine flag constants defined in hardware.inc into a single value with logical ORs and load it into A 80 ; Note that some of these constants (LCDC_OBJ_OFF, LCDC_WIN_OFF) are zero, but are included for clarity 81 ld a, LCDC_ON | LCDC_BLOCK01 | LCDC_BG_ON | LCDC_OBJ_OFF | LCDC_WIN_OFF 82 ldh [rLCDC], a ; Enable and configure the LCD to show the background 83 84 85LoopForever: 86 halt ; Halt the CPU, waiting until an interrupt fires (this will sync our loop with VBlank) 87 call UpdateJoypad ; Call the routine which polls the joypad and stores the state 88 call UpdateDisplay ; Call the routine which updates the display based on the joypad state 89 jr LoopForever ; Loop forever 90 91 92; Update the tilemap to reflect the joypad state as stored in hPressedKeys/hHeldKeys 93UpdateDisplay: 94 ld hl, TilemapLocations ; Point HL to our table of tilemap/tile entries for the buttons 95 ldh a, [hCurrentKeys] ; Load the byte of current key states into A 96 ld c, a ; ... and then move it to the C register 97.nextButton 98 ld a, l ; Due to register pressure, instead of using a register as a loop counter, we check the 99 cp LOW(TilemapLocations.end) ; low byte of the TilemapLocations pointer to see when we've reached the end 100 ret z ; If we've reached the end of the table we're done, return 101 102 srl c ; Shift C right logically, pushing the state of the next button into the carry flag 103 ld b, 0 ; Preload B with tile index offset for unpressed buttons 104 jr nc, .notPressed ; Skip the next instruction if the button we're checking isn't pressed 105 ld b, $10 ; Change the tile index offset in B to $10 to use the 'pressed' tiles 106.notPressed 107 108.loop 109 ld a, [hli] ; Load the low byte of the next TilemapLocations entry 110 or a ; Check for the zero terminator value 111 jr z, .nextButton ; If the value is zero jump to process the next button 112 ld e, a ; Load the low byte of the pointer into E 113 ld a, [hli] ; Get the high byte of the pointer 114 ld d, a ; ... and store it in D (DE now points to VRAM where we want to write a tile) 115 116.waitVRAM 117 ldh a, [rSTAT] ; Check the STAT register to figure out which mode the LCD is in 118 and STAT_BUSY ; AND the value to see if VRAM access is safe 119 jr nz, .waitVRAM ; Loop until VRAM access is safe 120 121 ld a, [hli] ; Load the tile index we'd like to write 122 add b ; Add the B offset, which will be 0 for unpressed buttons, and $10 for pressed buttons 123 ld [de], a ; Write the tile index to the tilemap 124 jr .loop ; Jump to process the next entry in the TilemapLocations table for this button 125 126 127SECTION "Joypad Variables", HRAM 128; Reserve space in HRAM to track the joypad state 129hCurrentKeys: ds 1 130hNewKeys: ds 1 131 132 133SECTION "Joypad Routine", ROM0 134 135; Update the newly pressed keys (hNewKeys) and the held keys (hCurrentKeys) in memory 136; Note: This routine is written to be easier to understand, not to be optimized for speed or size 137UpdateJoypad: 138 ; Poll half the controller 139 ld a, JOYP_GET_BUTTONS ; Load a flag into A to select reading the buttons 140 ldh [rP1], a ; Write the flag to P1 to select which buttons to read 141 ldh a, [rP1] ; Perform a few dummy reads to allow the inputs to stabilize 142 ldh a, [rP1] ; ... 143 ldh a, [rP1] ; ... 144 ldh a, [rP1] ; ... 145 ldh a, [rP1] ; ... 146 ldh a, [rP1] ; The final read of the register contains the key state we'll use 147 or $f0 ; Set the upper 4 bits, and leave the action button states in the lower 4 bits 148 ld b, a ; Store the state of the action buttons in B 149 150 ld a, JOYP_GET_CTRL_PAD ; Load a flag into A to select reading the dpad 151 ldh [rP1], a ; Write the flag to P1 to select which buttons to read 152 call .knownRet ; Call a known `ret` instruction to give the inputs to stabilize 153 ldh a, [rP1] ; Perform a few dummy reads to allow the inputs to stabilize 154 ldh a, [rP1] ; ... 155 ldh a, [rP1] ; ... 156 ldh a, [rP1] ; ... 157 ldh a, [rP1] ; ... 158 ldh a, [rP1] ; The final read of the register contains the key state we'll use 159 or $f0 ; Set the upper 4 bits, and leave the dpad state in the lower 4 bits 160 161 swap a ; Swap the high/low nibbles, putting the dpad state in the high nibble 162 xor b ; A now contains the pressed action buttons and dpad directions 163 ld b, a ; Move the key states to B 164 165 ld a, JOYP_GET_NONE ; Load a flag into A to read nothing 166 ldh [rP1], a ; Write the flag to P1 to disable button reading 167 168 ldh a, [hCurrentKeys] ; Load the previous button+dpad state from HRAM 169 xor b ; A now contains the keys that changed state 170 and b ; A now contains keys that were just pressed 171 ldh [hNewKeys], a ; Store the newly pressed keys in HRAM 172 ld a, b ; Move the current key state back to A 173 ldh [hCurrentKeys], a ; Store the current key state in HRAM 174.knownRet 175 ret 176 177 178SECTION "Tile Data", ROMX 179TileData: 180 incbin "joypad-tiles.2bpp" 181.end 182 183SECTION "Tilemap Locations", ROMX 184; Define a macro (provided by Rangi, thanks!) to easily specify the coordinates and IDs of the 185; tiles that make up the joypad display. 186MACRO bmap ; y, x, tile_id 187 rept _NARG / 3 188 dw TILEMAP0 + ((\1) * TILEMAP_WIDTH) + (\2) 189 db \3 190 shift 3 191 endr 192 db $00 ; end 193ENDM 194 195; Each button has a zero-terminated list of entries for the tiles used to draw that button, with 196; each entry made up of a tilemap address (little endian) and a tile index. 197TilemapLocations: 198.A bmap 0,10,$06, 0,11,$07, 1,10,$08, 1,11,$09 199.B bmap 1, 7,$06, 1, 8,$07, 2, 7,$08, 2, 8,$09 200.Select bmap 4, 3,$05 201.Start bmap 4, 5,$05 202.Right bmap 1, 2,$03 203.Left bmap 1, 0,$02 204.Up bmap 0, 1,$01 205.Down bmap 2, 1,$04 206.end