main.asm - RLH-2110/gbCalc GitHub Wiki
Table of Contents
vBlank Interupt Handler
Executed every vBlank
Calls the main routine
SECTION "VBlankInterupt", rom0[$40]
jp Main
The code works togehter with this code:
waitForever:
ei ; enable interups
nop ; not yet enabled
halt ; main gets called via interupts, halt in the meantime.
nop
jr waitForever
The above code just puts the CPU in sleep mode till an interrupt occurs, this should save some energy (see Pan docs)
Cardridge Header
This should explain itself, if not see Pan Docs - The Cardridge Header
jp EntryPoint ; entry point
nop
ds $30,0 ; nintendo logo (rgbfix will insert it)
db $47, $42, $20, $43, $41, $4C, $43, 0,0,0,0 ; game title
ds 4,0 ; Manufacturer code
db 0 ; CGB flag (we dont use gbc yet, maybe never. having backwards compatability with the gameboy may be nice)
dw 0 ; New licensee code
db 0 ; SGB flag
db 0 ; Cartridge type (let rgbfix figure this out)
db 0 ; ROM size (let rgbfix figure this out)
db 0 ; RAM size (let rgbfix figure this out)
db 0 ; Destination code ( should not be imporant)
db 0 ; Old licensee code
db 0 ; Mask ROM version number (maybe we use this later)
db 0 ; Header checksum (let rgbfix figure this out)
dw 0 ; Global checksum (let rgbfix figure this out, though it should be unused)
Entry Point
This part sets up the RAM data and the screen
Here we set up the Video RAM and OAM, we turn off the LCD for that, so we don't need to worry about This
EntryPoint:
di ; no interrupts
call WaitVBlank
; turn off LCD
ld a,0
ld [rLCDC], a
; copy tile data
ld DE, Tiles
ld HL, $9000
ld BC, TilesEnd - Tiles
call Memcpy
; copy tilemap
ld DE, Tilemap
ld HL, $9800
ld BC, TilemapEnd - Tilemap
call Memcpy
; clear OAM
ld a,0
ld b,160
ld hl,_OAMRAM
ClearOAM:
ld [hl+],a
dec b
jp nz, ClearOAM
The grapics data is stored in grapicsROM.asm
Here we initialize some RAM and turn on the screen (we also copy some things into OAM, probably just zeros)
; clear wFinishedWork
ld [wFinishedWork],a ; a = 0
; clear stored numbers (a is still 0)
ld [wStoredNumber0],a
ld [wStoredNumber1],a
; load objects
ld de,Objects
ld hl,_OAMRAM
ld bc,ObjectsEnd - Objects
call Memcpy
; enable LCD
ld a, LCDCF_ON | LCDCF_BGON | LCDCF_OBJON
ld [rLCDC],a
;init display registers
ld a,%11_10_01_00
ld [rBGP],a
ld [rOBP0],a
; init ram
ld a,1
ld [wCursorState],a ; wCursorState = 1
xor a,a ; a = 0
ld [wCursorPos],a ; wCursorPos = 0
ld hl,wNumber0
ld bc,6
ld d,0
call SetMem ; clears wNumber0, wNumber1 and wResult
...
xor a,a ; a = 0
ld [wFinishedWork],a
ld [wPrintResult],a
; enable vblank interupt
ld a,%0000_0001
ld [rIE],a
You might saw a '...', that's because I want to talk about that part separately
;copy multiplication loop into ram
ld de,Math_mul.loop
ld hl,Math_mul_loop_ram
ld bc,$FF
call Memcpy
This code copies a routine from calc.asm into RAM for SMC
Probably not the best way to go about it, but it was fun to write ^w^
Endless Loop
this is covered by the vBlank Interupt Handler section:
Main Function
The main routine gets called via an vBlank interrupt, but sometimes we don't want to actually do anything in the main and just return to where we were before, so we use wFinishedWork to see if we actually want to go into the actual main routine
; check if we can execute the main without problems
push af
ld a,[wFinishedWork]
cp a,0
jr z,.doMain ; if we do not have more work to do, then continue the main function
;else return since this is an interupt
pop af
reti ; we have more work to do
Fist we set wFinishedWork, so we dont get interrupted, and if we need to print a result, then we do that
.doMain:
pop af
ld a,$ff
ld [wFinishedWork],a ; now we have work to do
; check if we need to print the result of an operation
ld a,[wPrintResult]
cp a,0
jr z, .noPrint
call displayResult ; call it now, since I dont know how many cyles a big division or multiplication might take. I dont want to risk writing outside vblank
After that, we just check every key and execute some routines based on what button we press
.noPrint:
call UpdateKeys
.check_dpad
.CheckUp
ld a, [wNewKeys]
and a, PADF_UP
jr z, .CheckDown
; pressed!
ld e,1
call CursorHandler
jr .InputDone
.CheckDown:
ld a, [wNewKeys]
and a, PADF_DOWN
jr z, .CheckLeft
; pressed!
ld e,2
call CursorHandler
jr .InputDone
.CheckLeft
ld a, [wNewKeys]
and a, PADF_LEFT
jr z, .CheckRight
; pressed!
ld e,3
call CursorHandler
jr .InputDone
.CheckRight:
ld a, [wNewKeys]
and a, PADF_RIGHT
jr z, .checkSelect
; pressed!
ld e,4
call CursorHandler
jr .InputDone
.checkSelect:
ld a, [wNewKeys]
and a, PADF_SELECT
jr z, .checkA
; pressed!
call clearSelectedNumber
jr .InputDone
.checkA:
ld a, [wNewKeys]
and a, PADF_A
jr z, .checkB
; pressed!
call Calculate
jr .InputDone
.checkB:
ld a, [wNewKeys]
and a, PADF_B
jr z, .checkStart
; pressed!
call StoreVal
jr .InputDone
.checkStart:
ld a, [wNewKeys]
and a, PADF_START
jr z, .InputDone
; pressed!
call LoadVal
jr .InputDone
.InputDone:
xor a,a ; a = 0
ld [wFinishedWork],a ; no more work to do
reti
Key Update Routine
This routine is not written by me, I got it from my initial tutorial, however, I will still explain it.
This is the entry point to the routine, and it loads the pressed buttons. Detailed info about Input on Pan Docs
UpdateKeys:
; Poll half the controller
ld a, P1F_GET_BTN
call .onenibble
ld b, a ; B7-4 = 1; B3-0 = unpressed buttons
; Poll the other half
ld a, P1F_GET_DPAD
call .onenibble
swap a ; A3-0 = unpressed directions; A7-4 = 1
xor a, b ; A = pressed buttons + directions
ld b, a ; B = pressed buttons + directions
; And release the controller
ld a, P1F_GET_NONE
ldh [rP1], a
Save the pressed buttons to wCurKeys and the changed to being pressed to wNewKeys
; Combine with previous wCurKeys to make wNewKeys
ld a, [wCurKeys]
xor a, b ; A = keys that changed state
and a, b ; A = keys that changed to pressed
ld [wNewKeys], a
ld a, b
ld [wCurKeys], a
Reads the pressed buttons
.onenibble
ldh [rP1], a ; switch the key matrix
call .knownret ; burn 10 cycles calling a known ret
ldh a, [rP1] ; ignore value while waiting for the key matrix to settle
ldh a, [rP1]
ldh a, [rP1] ; this read counts
or a, $F0 ; A7-4 = 1; A3-0 = unpressed keys
.knownret
ret