Loading backgrounds

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
Vic
Posts: 6
Joined: Tue Dec 04, 2012 7:08 am

Loading backgrounds

Post by Vic » Tue Dec 18, 2012 5:01 am

Hi, I'm new to the forums, so I'll start by introducing myself a bit: I'm Vic, and I'm a retro-gaming enthousiast, so I recently started learning NES ASM so I could make my own game with a nice vintage feel.
I know a bit of programming, but I'm new to ASM. I'm starting to understand the logic behind it.
I'm currently learning with the Nerdy Nights tutorial, and the NESASM3 assembler.

However, I'm confused when it comes to loading the background for my game (or actually, when it comes to anything handling 16-bit addresses with 8-bit registers, but let's stick to BG loading for now).

I found a few codes for loading BG, some of which I got to work, but I can't understand how they work.

I'm currently using this code:

Code: Select all

(...)

LoadBackground:
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$20
  STA $2006             ; write the high byte of $2000 address
  LDA #$00
  STA $2006             ; write the low byte of $2000 address;

  LDA #$00
  STA pointerLo       ; put the low byte of the address of background into pointer
  LDA #HIGH(background)
  STA pointerHi       ; put the high byte of the address into pointer
  
  LDX #$00            ; start at pointer + 0
  LDY #$00
OutsideLoop:
  
InsideLoop:
  LDA [pointerLo], y  ; copy one background byte from address in pointer plus Y
  STA $2007           ; this runs 256 * 4 times
  
  INY                 ; inside loop counter
  CPY #$00
  BNE InsideLoop      ; run the inside loop 256 times before continuing down
  
  INC pointerHi       ; low byte went 0 to 256, so high byte needs to be changed now
  
  INX
  CPX #$04
  BNE OutsideLoop     ; run the outside loop 256 times before continuing down


(...)


 .bank 1
  .org $E000

background:
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00  ;;row 1
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00  ;
(...)
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00  ;;row 30
  .db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00  ;
So this works, but I don't really understant how, so I can't modify it to do what I want with it.

Could you tell me what "#HIGH(background)" means? I think it gets the high byte of the address of the first value after the "background" label, but i'm not sure.
And why "LDA [pointerLo], y" has "pointerLo" in []?

Also, is the "INC pointerHi" instruction of any use, since pointerHi isn't recalled elsewhere?

And why is the .org $E000 instruction important? If I remove it, I just get a grey screen, but from what I understand, the "#HIGH(background)" should make it useless.


So, if anyone could let me see this a little better, it would be awesome.
Thanks

User avatar
tokumaru
Posts: 11864
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Loading backgrounds

Post by tokumaru » Tue Dec 18, 2012 5:23 am

Vic wrote:Could you tell me what "#HIGH(background)" means?
"background" is an address, meaning it's a 16-bit value. LOW() and HIGH(), which are assembler functions, return the individual bytes of the 16-bit value, so you can load them into the accumulator (which is only 8 bits). This code assumes that "background" will be at the beginning of a memory page, so instead of using LOW() for the low byte it just uses $00.
And why "LDA [pointerLo], y" has "pointerLo" in []?
In NESASM, [] means indirection (most 6502 assemblers use () for indirect addressing though). This instruction uses the byte at pointerLo and the one next to it as a pointer to an address, and adds the value in Y to that to find the actual target.
Also, is the "INC pointerHi" instruction of any use, since pointerHi isn't recalled elsewhere?
It might not be written in the code, but like I said above, the indirect addressing mode will use the byte you tell it to and the one next to it, which in this case is "pointerHi".
And why is the .org $E000 instruction important?
Apparently the purpose is to place the "background" data at the start of a memory page.
from what I understand, the "#HIGH(background)" should make it useless.
Maybe if LOW() was used to set the low byte of the pointer instead of $00 the .org would be useless.

Vic
Posts: 6
Joined: Tue Dec 04, 2012 7:08 am

Re: Loading backgrounds

Post by Vic » Tue Dec 18, 2012 5:39 am

Thanks for the answers!

I guess what I didn't understand is mostly inderect addressing. It gets a lot clearer now. :D

User avatar
Movax12
Posts: 526
Joined: Sun Jan 02, 2011 11:50 am

Re: Loading backgrounds

Post by Movax12 » Tue Dec 18, 2012 8:51 am

You should recognise that a nametable (background) is 960 bytes and the attribute table is 64 bytes for a total of 1024 bytes. Your code writes 1024 bytes, but it wasn't obvious that you have attribute data in your code - it looks like it stops at nametable data.

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Loading backgrounds

Post by thefox » Tue Dec 18, 2012 9:45 am

I want to point out a couple of flaws in the code you pasted:

Code: Select all

  LDA #$00
  STA pointerLo       ; put the low byte of the address of background into pointer
This is bad practice, because if background moves, the low byte of pointer might not be 0 anymore. You should use LOW(background) instead of $00.

--

Some less critical performance optimizations:

Code: Select all

  INY                 ; inside loop counter
  CPY #$00
  BNE InsideLoop      ; run the inside loop 256 times before continuing down
CPY #$00 is not needed here, because INY will set the zero-flag anyways when Y gets set to 0.

Code: Select all

  INX
  CPX #$04
  BNE OutsideLoop     ; run the outside loop 256 times before continuing down
If you initialized X to 4, and used DEX here, you could get rid of CPX #$04 (same reason as above).
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

Vic
Posts: 6
Joined: Tue Dec 04, 2012 7:08 am

Re: Loading backgrounds

Post by Vic » Tue Dec 18, 2012 11:56 am

@Movax12: I also copy the attributes table using this loop, I find it easier that way than having a loop going 960 times for BG, and another 64 times for attribute. I just didn't paste the code for my attribute values

@thefox: Thanks for the advice :wink:
Just to be sure, you mean if backgroung moves within the code, not scrolling, right? Anyway, it sounds reasonable.

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Loading backgrounds

Post by thefox » Tue Dec 18, 2012 12:29 pm

Vic wrote:Just to be sure, you mean if backgroung moves within the code, not scrolling, right?
Yes, in the code.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

User avatar
tokumaru
Posts: 11864
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Loading backgrounds

Post by tokumaru » Tue Dec 18, 2012 4:47 pm

Vic wrote:Just to be sure, you mean if backgroung moves within the code, not scrolling, right?
Scrolling is a whole different issue... You'd hardly store scrolling backgrounds that way, uncompressed in the ROM, unless you only had a handful of screens. At 1KB per screen, an NROM program wouldn't even be able to hold 32 screens, since the space is also needed for code and other data. Scrolling games usually make use of metatiles, often combined with some other compression method (RLE, LZ, multiple layers of metatiles, etc.), to compress their maps.

Vic
Posts: 6
Joined: Tue Dec 04, 2012 7:08 am

Re: Loading backgrounds

Post by Vic » Wed Dec 19, 2012 1:41 pm

OK, I'm starting to get a hold on this, but I still can't get things to work the way I want...

So now my background loading code looks like this

Code: Select all

  LDA #LOW(background)
  STA pointerLo       ; put the low byte of the address of background into pointer
  LDA #HIGH(background)
  STA pointerHi       ; put the high byte of the address into pointer
  JSR LoadBackground

(...)

LoadBackground: ;Load background stored at address pointerHi PointerLo
  LDA $2002             ; read PPU status to reset the high/low latch
  LDA #$20
  STA $2006             ; write the high byte of $2000 address
  LDA #$00
  STA $2006             ; write the low byte of $2000 address;

  LDX #$04            ; start at pointer + 0
  LDY #$00
OutsideLoop:
  
InsideLoop:
  LDA [pointerLo], y  ; copy one background byte from address in pointer plus Y
  STA $2007           ; this runs 256 * 4 times
  
  INY                 ; inside loop counter
  BNE InsideLoop      ; run the inside loop 256 times before continuing down
  
  INC pointerHi       ; low byte went 0 to 256, so high byte needs to be changed now
  
  DEX
  BNE OutsideLoop     ; run the outside loop 256 times before continuing down
  RTS
Which is basically the same code as before, except using a sub-routine so I can use it multiple times and cleaned up following your advice, and it works... sometimes.

So my program looks like this:

Code: Select all

(Variables & constants, Reset vector start, and some init code...)

  LDA #LOW(background1)
  STA pointerLo       ; put the low byte of the address of background into pointer
  LDA #HIGH(background1)
  STA pointerHi       ; put the high byte of the address into pointer
  JSR LoadBackground

(...) 

(at some point wher I want to load a new BG)

  LDA #%00010000   ; disable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  STA $2000

  LDA #LOW(background2)
  STA pointerLo       ; put the low byte of the address of background into pointer
  LDA #HIGH(background2)
  STA pointerHi       ; put the high byte of the address into pointer
  JSR LoadBackground  ;load new BG

  LDA #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
  STA $2000
  LDA #%00001111   ; Disable sprites, enable background, no clipping on left side, Greyscale
  STA $2001
  LDA #$00        ;;tell the ppu there is no background scrolling
  STA $2005
  STA $2005

The first background loads fine (during init), but the second one doesn't work, I have messy graphics in the upper left corner, and the rest of the screen isn't updated.
So if anyone has an idea why this doesn't work, or a suggestion for another code, I'd be glad.

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Loading backgrounds

Post by thefox » Wed Dec 19, 2012 1:56 pm

Just guessing here because you left out some of the code, but you have to disable rendering before loading the 2nd background.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

Vic
Posts: 6
Joined: Tue Dec 04, 2012 7:08 am

Re: Loading backgrounds

Post by Vic » Wed Dec 19, 2012 2:32 pm

That was the problem indeed. Many thanks!

Post Reply