It is currently Mon Feb 18, 2019 12:47 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: Mon Jan 21, 2019 9:57 am 
Offline

Joined: Sun Jan 02, 2011 9:58 pm
Posts: 9
So I'm going through the Nerdy Nights tutorials (the version for ASM6) and I was trying to move parts of my code into subroutines to make things cleaner, easier to read, and a little more object oriented. For some reason the subroutines don't execute, but if I manually put my code where the JSR calls are made it works just fine. It compiles just fine either way, and I can't figure out what I'm doing wrong. Any help would be appreciated.

Here's the main chunk of the program:

Code:
;********** Header ****************************************

byte "NES",$1a         ;"NES" plus a terminator
byte $01            ;1x16Kb Program ROM block ($C000)
byte $01            ;1x8Kb Character ROM block
byte $00            ;don't care
byte $00            ;don't care
dsb 8               ;8 bytes padding

;********** Program Code ****************************************
   .org $C000

vblankWait:         ;vblankWait - waits for Vblank (NMI)
   BIT $2002
   BPL vblankWait
   RTS
   
clearMem:         ;Clears memory to get ready to start game
   LDA #$00
   STA $0000, x
   STA $0100, x
   STA $0200, x
   STA $0400, x
   STA $0500, x
   STA $0600, x
   STA $0700, x
   LDA #$FE
   STA $0300, x
   INX
   BNE clearMem   ;Branch if not equal to zero
   RTS
   
loadPalettes:      ;loads Palette data into $3F00
   LDA $2002      ;read PPU status to reset the high/low latch
   LDA #$3F
   STA $2006      ;write high byte of $3F00 address
   LDA #$00
   STA $2006      ;write low byte of $3F00 address
   LDX #$00
loadPalettesLoop:
   LDA palette, x   ;load data from address (palette + x)
   STA $2007
   INX
   CPX #$20      ;Compare X to 32
   BNE loadPalettesLoop
   RTS
   
loadSprites:
   LDX #$00      ;start at 0
loadSpritesLoop:
   LDA player1_sprite, x   ;load data from address (player1_sprite + x)
   STA $0200, x         ;store into RAM address ($0200 + x)
   INX
   CPX #$20            ;Compare X to 32
   BNE loadSpritesLoop      ;Branch if not equal to 0
   RTS
   
RESET:         ;Reset Vector - Program execution starts here on Reset/PowerOn
   SEI            ;Disable IRQs
   CLD            ;disable Decimal mode
   LDX #$40      ;load 0x40 (64) into X
   STX $4017      ;store X into $4017, disables APU frame IRQ
   LDX #$FF      ;load 0xFF (255) into X
   TXS            ;transfer X to Stack, sets up stack
   INX            ;increment X, X now = 0
   STX $2000      ;disable NMI
   STX $2001      ;disable rendering
   STX $4010      ;disable DMC IRQs
   
   JSR vblankWait
   JSR clearMem
   JSR vblankWait
   JSR loadPalettes
   JSR loadSprites
   
;PPU CONTROL REGISTER SETUP
   ;VPHB SINN - Bits to write to PPU Control register ($2000)
   ;V = Vblank NMI enable
   ;P = PPU Master/Slave select
   ;S = Sprite Size. 0 = 8x8, 1 = 8x16
   ;B = Background pattern table address. 0 = $0000, 1 = $1000
   ;S = Sprite pattern table address for 8x8 sprites (ignored in 8x16 mode). 0 = $0000, 1 = $1000
   ;I = Pattern table increment. 0 = Add 1 (going across), 1 = add 32 (going down)
   ;NN = Base nametable address. 0 = $2000, 1 = $2400, 2 = $2800, 3 = $2C00
   LDA #%10000000      ;enable NMI
   STA $2000         ;Write to PPU Control register
   
;PPU MASK REGISTER SETUP
   ;BGRs bMmG
   ;B = emphasize blue
   ;G = emphasize green
   ;R = emphasize red
   ;s = show sprites
   ;b = show background
   ;M = show sprites in leftmost margin (8 pixels)
   ;m = show sprites in rightmost margin (8 pixels)
   ;G = grayscale (0 = normal colour, 1 = grayscale)
   LDA #%00010000      ;enable sprites
   STA $2001         ;Write to PPU Mask register
   
MAIN:      ;MAIN loop
   JMP MAIN

NMI:      ;NMI - Vblank interrupt
   LDA #$00
   STA $2003   ;set the low byte of the RAM address
   LDA #$02
   STA $4014   ;set the high byte of the RAM address and start the transfer
   
   RTI         ;Return from Interrupt


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 10:19 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21097
Location: NE Indiana, USA (NTSC)
What happens if the NMI handler is called while something is using register A?


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 10:24 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11166
Location: Rio de Janeiro - Brazil
You can't JSR to a subroutine that clears memory because it also clears the stack ($0100-$01FF), where the return addresses for subroutines are stored. Since you're wiping out the return address, the RTS will try to return to address $0000 (actually $0001, but that's irrelevant), and the program crashes.


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 10:34 am 
Offline

Joined: Sun Jan 02, 2011 9:58 pm
Posts: 9
tepples wrote:
What happens if the NMI handler is called while something is using register A?


I don't think NMI would get called during those subroutines. Near the start of the code at the RESET vector it's turning NMI and rendering off before doing any of my subroutines. Register A isn't getting used at all before those are turned off, and it's getting loaded with relevant data during each subroutine.

tokumaru wrote:
You can't JSR to a subroutine that clears memory because it also clears the stack ($0100-$01FF), where the return addresses for subroutines are stored. Since you're wiping out the return address, the RTS will try to return to address $0000 (actually $0001, but that's irrelevant), and the program crashes.


Ah okay that makes sense. Didn't know clearMem was also erasing the stack. Just tested it and it made clearMem not call to a subroutine (wrote it inline with the other stuff after RESET:) and it works great.

Thanks!


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 11:35 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2437
Location: DIGDUG
Technically, you don't really need to zero the stack at all.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 1:09 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11166
Location: Rio de Janeiro - Brazil
Technically, you don't really need to zero *any* memory at all.

If you initialize every variable before using them, as you should, clearing memory is redundant... Most people do it anyway, to be safe I guess.


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 5:16 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8136
Location: Seattle
There could be merit, if you are testing with an emulator that randomizes memory, in not clearing all memory so that you can find situations where you aren't initializing memory. Fail fast.

But for a final build, there is no advantage in failing to clear memory at boot.


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 5:42 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21097
Location: NE Indiana, USA (NTSC)
Even better is an emulator that breaks on reading uninitialized memory. BGB, a Game Boy emulator, does this.


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 5:48 pm 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 1064
Location: Hokkaido, Japan
Yeah and for example flashcarts may also leave uninitialized memory in a non-random state because the menu used RAM before the game loaded. In other words, not clearing RAM or relying on the randomness of RAM (like Final Fantasy 1 apparently does) may result in compatibility problems.


BTW, a nitpick but $2003 doesn't "set the low byte of RAM address", it selects OAM address ($00-$FF) for OAM access. If using OAM-DMA it should be set to 0, which you do correctly though. I suppose Nerdy Nights is "guilty" for this little misconception.


Top
 Profile  
 
PostPosted: Mon Jan 21, 2019 5:55 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11166
Location: Rio de Janeiro - Brazil
Pokun wrote:
BTW, a nitpick but $2003 doesn't "set the low byte of RAM address", it selects OAM address ($00-$FF) for OAM access. If using OAM-DMA it should be set to 0, which you do correctly though. I suppose Nerdy Nights is "guilty" for this little misconception.

Yeah, that comment about "setting the low byte" shows up here every once in a while. I knew it came from some sort of tutorial, but was never sure which one.

As for clearing RAM or not, I'm not gonna argue about it anymore. I don't do it in my programs, because I find it redundant, but I'm OK work the fact that almost everyone does it.


Top
 Profile  
 
PostPosted: Tue Jan 22, 2019 4:59 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1458
Pokun wrote:
If using OAM-DMA it should be set to 0, which you do correctly though. I suppose Nerdy Nights is "guilty" for this little misconception.

I'm 99% certain that that originated from the "GBAGuy" NES tutorials, not Nerdy Nights.

[edit] Disregard - I was thinking of the mistaken belief that $2003 was a 16-bit register that you had to write twice (like $2005/$2006).

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Last edited by Quietust on Tue Jan 22, 2019 4:40 pm, edited 1 time in total.

Top
 Profile  
 
PostPosted: Tue Jan 22, 2019 7:04 am 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 1064
Location: Hokkaido, Japan
Maybe, but it got into Nerdy Nights as well. I learned from Nerdy Nights and also used to get this wrong until I learned more. I thought it was funny that half of the address is set to a PPU port register (starting with $2) and the other to a DMA register (starting with $4).


Top
 Profile  
 
PostPosted: Wed Jan 23, 2019 12:57 am 
Online
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7664
Location: Chexbres, VD, Switzerland
tokumaru wrote:
You can't JSR to a subroutine that clears memory because it also clears the stack ($0100-$01FF), where the return addresses for subroutines are stored. Since you're wiping out the return address, the RTS will try to return to address $0000 (actually $0001, but that's irrelevant), and the program crashes.

Actually back when I started NES programming in 2002 I did a routine that clears memory, exept the stack, and uses TSX instructions to avoid clearing sections pas the stack pointer. I was very proud that this worked. However I must admit this was completely useless since clearing memory is a single-time operation done at startup, so we can put this in the main program and don't need a subroutine in the first place.


Top
 Profile  
 
PostPosted: Thu Jan 24, 2019 5:24 am 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 1064
Location: Hokkaido, Japan
Yeah and if you like the procedural approach of dividing the program into many subroutines, you can make a macro of it instead. It's useful for avoiding the overhead of any subroutine that is only called once, or when speed is more important than space.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group