Page 1 of 2
How to load a full background?
Posted: Sun Oct 12, 2014 11:27 am
by Tsutarja
So, I have been trying to get the background update loop in my NMI to work for like a month now. I have no problem loading a partial background of 256 tiles, but once it goes past that, I just can't get it to work. Either my program gets stuck in the loop or draws the background incorrectly. I know I need 16 bit math for this, but I just can't program a 16 bit stuff on 8 bit processor.
The way I'm updating my background is that the program sets the high and low bytes of the target background's location in pointers and sets a "flag" that the NMI checks if it's set. If it is, the NMI will update the background from the location that is stored to the pointers. Then the NMI clears the flag and ends the NMI. If the flag is not set the NMI will draw the updated background.
Here is my code if you need to take a look at it:
http://pastebin.com/HbvTbPDM
Re: How to load a full background?
Posted: Sun Oct 12, 2014 11:44 am
by lidnariq
Unrelatedly, I strongly recommend using variables instead of immediate addresses for things in RAM. (i.e. add "NMIcount .rs 1" right after ".rsset" and don't use " $1E")
Here's the loop that vegaplay uses to upload a nametable:
Code: Select all
NameLoop: ; loop to draw entire nametable
LDA (pointerLo),y
STA $2007
INY
BNE NameLoop
INC pointerHi
DEX
BNE NameLoop
Re: How to load a full background?
Posted: Sun Oct 12, 2014 11:53 am
by Tsutarja
lidnariq wrote:Unrelatedly, I strongly recommend using variables instead of immediate addresses for things in RAM. (i.e. add "NMIcount .rs 1" right after ".rsset" and don't use " $1E")
Here's the loop that vegaplay uses to upload a nametable:
Code: Select all
NameLoop: ; loop to draw entire nametable
LDA (pointerLo),y
STA $2007
INY
BNE NameLoop
INC pointerHi
DEX
BNE NameLoop
It's not working. I got a screen full of '0's (tile 0 in my chr file) and a random '1' (tile 1 in my chr file) in the top right corner.
Re: How to load a full background?
Posted: Sun Oct 12, 2014 2:54 pm
by Kasumi
Post your new .asm file with where/how you added it. Could be Y is not initialized to zero before the loop, X is not initialized to 4, $2006 wasn't written with the nametable address before the loop, rendering not being disabled during the loop, $2007 set to increment by 32 instead of 1 or a lot of other things.
Here's the code with the implied things as well.
Code: Select all
ldx #$04;Increment pointer 4 times to write 1024 bytes of data
lda #$20;High byte of first nametable's address
sta $2006
ldy #$00;Low byte of first nametable's address
sty $2006
NameLoop: ; loop to draw entire nametable
LDA (pointerLo),y
STA $2007
INY
BNE NameLoop
INC pointerHi
DEX
BNE NameLoop
edit:
This tokumaru post (directly below) explains a large error in this post. Leaving the error intact in this case because I like to preserve the history, but yeah, also read Tokumaru's post.
For the record, how this works is that after reading 256 bytes from pointerLo (Y has looped from 0-255 and back to 0), pointerHi is incremented, effectively moving the pointer 256 bytes ahead. Which is what you want, since you already wrote the first 256 bytes. It does this four times (Why X is used to keep track), because each nametable is 256 bytes and there are four. Due to mirroring, you can set this up to do just two, but better to get the sure thing working first.
Re: How to load a full background?
Posted: Sun Oct 12, 2014 6:35 pm
by tokumaru
Kasumi wrote:It does this four times (Why X is used to keep track), because each nametable is 256 bytes and there are four.
Kasumi must be sleepy, because I'm sure he knows that each nametable is 960 (32x30 tiles) + 64 (attributes) = 1024 bytes, and the point of using X to repeat the data copy code 4 times is that Y can only index 256 positions, and we need 4 times that in order to fill a whole nametable, incrementing the high byte of the pointer each time to advance to the next "page" of 256 bytes.
Due to mirroring, you can set this up to do just two, but better to get the sure thing working first.
Mirroring doesn't apply if you're working with a single nametable, so don't worry.
BTW, you mentioned you were doing this in the NMI, so you should keep in mind that VBlank time is too short to update an entire nametable. You either have to update it little by little over conseccutive VBlanks, or turn rendering off (blanking the screen for a while) and do it all at once.
Re: How to load a full background?
Posted: Sun Oct 12, 2014 7:20 pm
by Kasumi
Man, wow. Huge mistake.

Thanks.
Re: How to load a full background?
Posted: Sun Oct 12, 2014 8:12 pm
by tokumaru
Kasumi wrote:Man, wow. Huge mistake.

Thanks.
Don't worry, it happens. I started a pretty lame thread recently myself.
Tsutarja, I looked at your code and saw that you are disabling and enabling rendering in your NMI handler, which is good. However, there's one problem I see with the way you're handling your flag (besides using an address instead of a name for it, like has already been pointed out), which is that every NMI it selects between one of the following sets of actions:
1- Turn rendering off, load new background, change flag state;
OR:
2- Turn rendering on and reset the scroll;
Have you considered what will happen if updating the background takes longer than a frame? The background will start being updated, but before it finishes, another NMI will fire! The flag hasn't changed yet, so the background will start to be updated again! This will happen forever and tour program will lock up. This probably won't happen with the loop that has been posted in this thread, because (LDA (pointerLo), y: STA $2007: INY: BNE NameLoop) executed 1024 times around 14336 to 15360 cycles to execute, while a full frame is around 29780 cycles long, but say that the game appears to work and you decide to add some sort of compression to the name tables later on, meaning that decompressing them will take longer... Your game will stop working and you'll have a hard time figuring out why. To fix this you should either add a 3rd state to the flag (UPDATE IN PROGRESS, which you set as soon as the update starts and causes the handler to do nothing, in order to not interfere with an ongoing update) or disable NMIs before the update and reenable afterwards (be careful to do this only during VBlank, and read the PPU status - $2002 - first to prevent another NMI from firing immediately).
Also, another possible frame with the flag, is that you first initialize it AFTER having enabled NMIs. Consider what would happen if an NMI happened between the time you enabled NMIs and before you initilize the flag... the NMI handler will make decisions based on uninitialized variables, and will probably draw garbage to the screen or even crash. Be sure to have everything the NMI handler needs ready BEFORE you enable NMIs, to make sure there will be no uninitialized variables being used.
Re: How to load a full background?
Posted: Sun Oct 12, 2014 11:50 pm
by Tsutarja
tokumaru wrote:
Have you considered what will happen if updating the background takes longer than a frame? The background will start being updated, but before it finishes, another NMI will fire! The flag hasn't changed yet, so the background will start to be updated again! This will happen forever and tour program will lock up. This probably won't happen with the loop that has been posted in this thread, because (LDA (pointerLo), y: STA $2007: INY: BNE NameLoop) executed 1024 times around 14336 to 15360 cycles to execute, while a full frame is around 29780 cycles long, but say that the game appears to work and you decide to add some sort of compression to the name tables later on, meaning that decompressing them will take longer... Your game will stop working and you'll have a hard time figuring out why. To fix this you should either add a 3rd state to the flag (UPDATE IN PROGRESS, which you set as soon as the update starts and causes the handler to do nothing, in order to not interfere with an ongoing update) or disable NMIs before the update and reenable afterwards (be careful to do this only during VBlank, and read the PPU status - $2002 - first to prevent another NMI from firing immediately).
Also, another possible frame with the flag, is that you first initialize it AFTER having enabled NMIs. Consider what would happen if an NMI happened between the time you enabled NMIs and before you initilize the flag... the NMI handler will make decisions based on uninitialized variables, and will probably draw garbage to the screen or even crash. Be sure to have everything the NMI handler needs ready BEFORE you enable NMIs, to make sure there will be no uninitialized variables being used.
I couldn't get it to work. It's the same result as before.
I don't know if I did correctly what you were suggesting so here is my new code:
http://pastebin.com/i8Pnctng
The changes I did:
-swapped the palette and attribute tables so that the attributes are loaded during the BgLoop
-made the program to initialized the NMI flag before allowing NMIs
-disabled NMIs before the BgLoop and enabled them right before the NMI ends
Re: How to load a full background?
Posted: Mon Oct 13, 2014 12:13 am
by Kasumi
Here's what will make it work:
Code: Select all
SetBg: ; Set up information for NMI to update background
LDA #LOW(background);Forgot the # here
STA pointerLo
LDA #HIGH(background);And here
STA pointerHi
When you do LDA without the #, it will load from an address rather than just loading the direct number. It's easy to forget when HIGH/LOW are used. I do that sometimes.
edit for more in depth: Say the background label is at $80E7. When you do "lda LOW(background)", you get lda $E7 which is loading a value from RAM location $E7. (It would be zero for your current program, because you zero out the RAM and don't use it for anything else.)
When you do "lda #LOW(background)" you get lda #$E7 which is the constant value for the low byte of the pointer.
Code: Select all
BgLoop: ; 16 bit loop for drawing background and loading attributes
LDA [pointerLo], y;[] instead of ()
NESASM uses a different syntax than every other assembler for (indirect),y addressing.
With those changes it should work as you designed it. Once you're able to see the output, tokumaru has given some very good points about program structure that might be worth playing around with.
Re: How to load a full background?
Posted: Mon Oct 13, 2014 12:21 am
by Tsutarja
Kasumi wrote:Here's what will make it work:
Code: Select all
SetBg: ; Set up information for NMI to update background
LDA #LOW(background);Forgot the # here
STA pointerLo
LDA #HIGH(background);And here
STA pointerHi
When you do LDA without the #, it will load from an address rather than just loading the direct number. It's easy to forget when HIGH/LOW are used. I do that sometimes.
Code: Select all
BgLoop: ; 16 bit loop for drawing background and loading attributes
LDA [pointerLo], y;[] instead of ()
NESASM uses a different syntax than every other assembler for (indirect),y addressing.
With those two changes it should work as you designed it. Once you're able to see the output, okumaru has made some very valid points about your program structure that might be worth playing around with.
It works now. Thanks!
I guess I start making a code that changes the background when specific button is pressed or something to learn the background changing after the first draw (that shouldn't be too difficult though). Or then I could try drawing a sprite and then making it move when D-pad is pressed.
EDIT: And make some other background than "0123456789ABCDEFFEDCBA9876543210" on every line.
Re: How to load a full background?
Posted: Mon Oct 13, 2014 12:26 am
by Kasumi
Sounds like a good plan. Keep it up!
Re: How to load a full background?
Posted: Mon Oct 13, 2014 2:48 am
by Tsutarja
I was actually surprised how easily I got the background to change when I press a button. By the way, if I include other text files (do they too need to be in .asm or is .txt fine?) that have the background tables, does NESASM3 recognize labels outside the main .asm file when the program is compiled? It would help to keep the program itself clean if I could put the background tables somewhere else than the main .asm file.
Re: How to load a full background?
Posted: Mon Oct 13, 2014 5:49 am
by Kasumi
Yes, you're free to include other files with code in them. Extension doesn't matter to NESASM except to find the file with that exact name. Keeping .asm or .somethingconsistent might be worth it if you are using a text editor with syntax highlighting, though.
is fine. All labels will be read etc, it basically treats it as if everything in "othercode.txt" was placed where the .include line for "othercode.txt" is in the other file.
Re: How to load a full background?
Posted: Mon Oct 13, 2014 2:49 pm
by tokumaru
Tsutarja wrote:It would help to keep the program itself clean if I could put the background tables somewhere else than the main .asm file.
The more your program grows, the more you'll feel the need to use separate files. A huge ASM file is pretty annoying to maintain. I got to the point where I have each routine and each table (or set of related tables) in its own file, while the main file is just a bunch of includes. This way I can easily reorder things in the ROM when I need to.
I would keep file extensions consistent though... If a file contains ASM code, it makes much more sense so give it the .asm extension than .txt, but ultimately, not even the main file has to be .asm, you're free to use whatever you want.
Re: How to load a full background?
Posted: Mon Jun 08, 2015 7:04 pm
by tales
Owww. tks, this code works fine... now I have another problem, when I try to change my background screen in run time, the screen get's crazy, I don't undestand why... For exemple, I load the first screen with this code... nice, works perfect, but how I change this entire screen for another???