SusiKette's project help thread
Moderator: Moderators
SusiKette's project help thread
Instead of making new threads every time I have a problem or something to ask, I decided to make a one thread that I post whenever I'm in need of help.
After several years of not doing any NES stuff, I'm trying to remember things. I'm currently making the initialization code. The only functions if does is do the usual reset stuff, load the default palettes to the PPU and then turn rendering on. However, When I run the ROM, the backdrop color is desaturated red instead of black that I set it to. FCEUX's shows Pattern Tables, Palettes and Nametables as the default grey color when you open any of the PPU related tools. I have tried to debug this issue for quiet some time now and still can't find the cause for this. It's probably some really stupid mistake that I have just forgotten or something.
Bonus question:
How does NESASM3's bank logic work? I followed a tutorial that showed a code where the banks were assigned in a particular way. It didn't really explain the banks other than saying that everything is arranged in 8 Kb (or 16 Kb) sized banks. If I rearrange the banks to contain something different (regardles of using the correct .org $---- to set the proper address they need to be in ROM) the ROM just won't work. I'm assuming these banks are also used when doing bank switching, but for the sake of simplicity; how do they work on a ROM that has no mapper?
EDIT: The attachment has my project source code so far as well as a compiled ROM.
After several years of not doing any NES stuff, I'm trying to remember things. I'm currently making the initialization code. The only functions if does is do the usual reset stuff, load the default palettes to the PPU and then turn rendering on. However, When I run the ROM, the backdrop color is desaturated red instead of black that I set it to. FCEUX's shows Pattern Tables, Palettes and Nametables as the default grey color when you open any of the PPU related tools. I have tried to debug this issue for quiet some time now and still can't find the cause for this. It's probably some really stupid mistake that I have just forgotten or something.
Bonus question:
How does NESASM3's bank logic work? I followed a tutorial that showed a code where the banks were assigned in a particular way. It didn't really explain the banks other than saying that everything is arranged in 8 Kb (or 16 Kb) sized banks. If I rearrange the banks to contain something different (regardles of using the correct .org $---- to set the proper address they need to be in ROM) the ROM just won't work. I'm assuming these banks are also used when doing bank switching, but for the sake of simplicity; how do they work on a ROM that has no mapper?
EDIT: The attachment has my project source code so far as well as a compiled ROM.
- Attachments
-
- main.zip
- (7.5 KiB) Downloaded 207 times
Avatar is pixel art of Noah Prime from Astral Chain
Re: SusiKette's project help thread
Banks really are just 8KB segments in NESASM.
.bank 0 is the first 8KB of your ROM.
.bank 1 is the second 8KB of your ROM.
etc.
The NES ROM format requires PRG banks to be first, CHR banks (if there are any) to be last. The 6502 requires vectors to be at $FFFA-$FFFF so it knows where to start running your code.
One thing that's a bit of a gotcha is that the ROM header (.inesprg) specifies the number of SIXTEEN KB PRG banks.
So if that value is 1, you need .bank 0 and .bank 1. .bank 2 would be your first CHR bank.
If that value is 2, you need .bank 0, .bank 1, .bank 2, and .bank 3. .bank 4 would be your first CHR bank.
If you are using no mapper, the order of your banks in the ROM file directly corresponds to their order in NES Memory. So the last PRG bank (either .bank 1 or .bank 3 depending on if you have one 16KB PRG bank for two) must have your vectors in its last six bytes. That's less about NESASM and more about NES.
So as far as bank organization, you could totally put .bank 1 as it is before .bank 0 as it is in your file and it'd work. But you're still beholden to the 6502 vector rules. You can't only have .org $FFFA with your vectors in bank 0 because then it doesn't end up in the right place in NES memory.
Edit: To further explain that... .org helps your assembler make references. Say there's a label and a jump to that label:
That would get assembled as jmp $8000. And.
That would get assembled as jmp $FFF0
But that's really it. You could do this:
Even though bank 1 is .org'd to $8000, that's actually not where the code will end up in memory. (Assuming no mapper.) Both jmp label2 and jmp label would go to label. (Because in a mapperless ROM, the first 8KB of your ROM file will get mapped to $8000. Bank 1 would get mapped to $A000... So label2 is REALLY $A000 and should have been .org'd that way so those jmps would make sense. That's similar to why a .org $FFFA wouldn't work in bank 0 (in a mapper less ROM.) It still doesn't end up in $FFFA in NES memory.
And you might wonder why the assembler can't do it automatically... and the reason is because mappers can sort of put any part of the ROM anywhere. Bank 0 and bank 1 could BOTH be designed to be at $8000. (Though not at the same time)
You've got some subtle problems. This never writes $FC to $0200:
At MemLoop1, imagine X is 1. sta $0200,x will make $0201 equal to $FC. Dex. X is 0. 0 is equal to 0, so we don't branch again. So an sta $0200,x where x was zero never happens.
You (usually) don't need to do a comparison with zero, because the zero flag is set/cleared by most instructions.
This would do the same thing (in this context):
But that still missed zero. Instead, start with zero and count up. Getting back to zero will still break the loop, but that's fine since you did zero at the start before any conditional branch check:
The same problems are in your second clear memory loop. Here's both with that change:
Your NMI will break your game because it doesn't restore the registers from the stack correctly.
You want:
The way you're writing to PPU memory is not correct.
So the fixes for all that look like:
If you do all these fixes, your ROM will work. But the palette will still not be black.
$00 is not black. Try $0F.
Edit: Oh! I was wondering how sprites were moving on the screen when there was seemingly no code to make them do anything.
lda #$02 is different than lda $02.
So this code...
Draws sprites using RAM at the zero page when the value at $02 is zero. You want:
.bank 0 is the first 8KB of your ROM.
.bank 1 is the second 8KB of your ROM.
etc.
The NES ROM format requires PRG banks to be first, CHR banks (if there are any) to be last. The 6502 requires vectors to be at $FFFA-$FFFF so it knows where to start running your code.
One thing that's a bit of a gotcha is that the ROM header (.inesprg) specifies the number of SIXTEEN KB PRG banks.
So if that value is 1, you need .bank 0 and .bank 1. .bank 2 would be your first CHR bank.
If that value is 2, you need .bank 0, .bank 1, .bank 2, and .bank 3. .bank 4 would be your first CHR bank.
If you are using no mapper, the order of your banks in the ROM file directly corresponds to their order in NES Memory. So the last PRG bank (either .bank 1 or .bank 3 depending on if you have one 16KB PRG bank for two) must have your vectors in its last six bytes. That's less about NESASM and more about NES.
So as far as bank organization, you could totally put .bank 1 as it is before .bank 0 as it is in your file and it'd work. But you're still beholden to the 6502 vector rules. You can't only have .org $FFFA with your vectors in bank 0 because then it doesn't end up in the right place in NES memory.
Edit: To further explain that... .org helps your assembler make references. Say there's a label and a jump to that label:
Code: Select all
.org $8000
label:
jmp label
Code: Select all
.org $FFF0
label:
jmp label
But that's really it. You could do this:
Code: Select all
.bank 0
.org $8000
label:
jmp label
.bank 1
.org $8000
label2:
jmp label2
And you might wonder why the assembler can't do it automatically... and the reason is because mappers can sort of put any part of the ROM anywhere. Bank 0 and bank 1 could BOTH be designed to be at $8000. (Though not at the same time)
Code: Select all
ldx #$7F;Why $7F and not $FF?
stx stack_ptr
txs
Code: Select all
MemoryClear:
ldx #$FF
lda #$FC
MemLoop1:
sta $0200,x
dex
cpx #$00
bne MemLoop1
You (usually) don't need to do a comparison with zero, because the zero flag is set/cleared by most instructions.
This would do the same thing (in this context):
Code: Select all
MemoryClear:
ldx #$FF
lda #$FC
MemLoop1:
sta $0200,x
dex
bne MemLoop1
Code: Select all
MemoryClear:
ldx #$0
lda #$FC
MemLoop1:
sta $0200,x
inx
bne MemLoop1
Code: Select all
MemoryClear:
ldx #0
lda #$FC
MemLoop1:
sta $0200,x
inx
bne MemLoop1
;We know X is zero, because the branch didn't happen.
;So no need to reload it
lda #$00;For the astute, txa would also work. Since, again, we know X is zero.
MemLoop2:
sta $00,x
sta $0100,x
sta $0300,x
sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
inx
bne MemLoop2
Code: Select all
NMI:
pha;Pushes A to the stack: Current Stack: A
txa
pha;Pushes X to the stack: Current Stack: AX
tya
pha;Pushes Y to the stack: Current Stack: AXY
;...
SkipNMI:
pla;Get the old value for Y into A ;Current stack: AX
tya;Transfer Y to A... now the old value for Y is completely gone.
pla;Get the old value for X into A ; Currently stack: A
txa;Transfer X to A... now the old value for X is completely gone.
pla
Code: Select all
SkipNMI:
pla
tay;So that the old value for Y is back in Y when we return
pla
tax;So that the old value for X is back in X when we return
pla
Code: Select all
lda HIGH(PPU_PAL);First, you need the # here. lda HIGH(PPU_PAL) will make
;A the value stored in RAM location $3F rather than the constant #$3F
;As opposed to lda #HIGH(PPU_PAL) which would make A equal to #$3F
;You write the high byte to PPU_ADDR
sta PPU_ADDR;But you also need to write the low byte and you don't.
;X and Y should be switched here. ,x is used as an offset in bg_pal,x, so X is the one you should load
;with update_pal_start
ldy update_pal_start
ldx #$00
UpdatePalLoop:
lda bg_pal1,x
sta PPU_DATA,y;$2007 (PPU_DATA) is the register that writes a byte to the PPU. Not $2008. Not $2009.
;If you write to $2007,y and Y is not zero, you're usually not actually writing a byte
iny
inx
cpx update_pal_size
bne UpdatePalLoop
NoPalUpdate:
rts
Code: Select all
UpdatePal:
lda update_pal
beq NoPalUpdate
bit $2002;Makes it so our next write PPU_ADDR will write the high byte
lda #HIGH(PPU_PAL)
sta PPU_ADDR
lda #LOW(PPU_PAL)
sta PPU_ADDR
ldx update_pal_start
; ldy #$00;No need for this
UpdatePalLoop:
lda bg_pal1,x
sta PPU_DATA
;iny;No need for this
inx
cpx update_pal_size
bne UpdatePalLoop
NoPalUpdate:
rts
Code: Select all
DefaultPal:
.db $00,$30,$21,$02
.db $00,$30,$21,$02
.db $00,$30,$21,$02
.db $00,$30,$21,$02
.db $00,$30,$21,$02
.db $00,$30,$21,$02
.db $00,$30,$21,$02
.db $00,$30,$21,$02
Edit: Oh! I was wondering how sprites were moving on the screen when there was seemingly no code to make them do anything.
lda #$02 is different than lda $02.
Code: Select all
;If #$FF is stored at $02
lda $02;A = $FF
lda #$02;A = $02
Code: Select all
lda $00
sta OAM_ADDR
lda $02
sta OAM_DMA
Code: Select all
lda #$00
sta OAM_ADDR
lda #$02
sta OAM_DMA
Re: SusiKette's project help thread
Could you please consider changing your signature? It's really distracting.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Re: SusiKette's project help thread
Thanks for taking the time to make such a long postKasumi wrote:Banks really are just 8KB segments in NESASM... *cut*
Everything is working as it should for now.
Now for the next set of questions:
1. If I have, lets say a 64kb chr file and I put it like this:
Code: Select all
.bank 0
.incbin "64kb_file.chr"
2. Is it common for games to update palettes and bank registers every frame (even if it's not needed)? I noticed this oddity when I was looking through a particular game's NMI.
3. How to set sprites to use second the pattern table in 8x16 mode? According to the wiki, the bit that controls which pattern table sprites use is ignored in 8x16 mode, yet I've seen games use the second pattern table in this mode.
4. Is it possible to split the stack into several smaller segment by changing the stack pointer around the "stack page" and not affect the contents of another segment?
Avatar is pixel art of Noah Prime from Astral Chain
Re: SusiKette's project help thread
It might depend on which version of NESASM you use.SusiKette wrote:1. If I have, lets say a 64kb chr file and I put it like this:Can NESASM divide the file to multiple banks as needed or do I have to split the file into smaller chunks and do it manually?Code: Select all
.bank 0 .incbin "64kb_file.chr"
Aside: If you're putting CHR data in bank 0, that'll work only in a CHR RAM situation. Banks 2 and 3 of Haunted: Halloween '85 are all sprite tiles, for instance.
Some games don't come close to using all the time in vertical blanking for video memory updates. This allows them to have more of a "fixed function" VRAM update routine that makes all updates that might be needed without having to check a bunch of enable flags every time.SusiKette wrote:2. Is it common for games to update palettes and bank registers every frame (even if it's not needed)? I noticed this oddity when I was looking through a particular game's NMI.
Set bit 0 of the tile index (byte 4n+1 of OAM) to true (1). For example, tile $68 draws a sprite using tiles at $0680 and $0690, and $69 instead uses $1680 and $1690.SusiKette wrote:3. How to set sprites to use second the pattern table in 8x16 mode? According to the wiki, the bit that controls which pattern table sprites use is ignored in 8x16 mode, yet I've seen games use the second pattern table in this mode.
Yes. The Popslide VRAM updater, for instance, uses this to switch between a program's main stack at $01C0-$01FF and an update buffer at $0108-$01AF.SusiKette wrote:4. Is it possible to split the stack into several smaller segment by changing the stack pointer around the "stack page" and not affect the contents of another segment?
Re: SusiKette's project help thread
I added it to my adblock list, personally, and it works wonders.thefox wrote:Could you please consider changing your signature? It's really distracting.
- rainwarrior
- Posts: 8731
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: SusiKette's project help thread
I don't know what was in SusiKette's signature (it seems to have been removed?) but this board has user preferences that let you turn off signatures entirely:
https://forums.nesdev.com/ucp.php?i=prefs&mode=view
https://forums.nesdev.com/ucp.php?i=prefs&mode=view
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: SusiKette's project help thread
It was a very large (very tall) graphic; SusiKette must have removed it after thefox's request. : )rainwarrior wrote:I don't know what was in SusiKette's signature (it seems to have been removed?)
That's really cool - and fantastic to learn! Thank you SusiKette for being brilliant and asking that and thank you tepples for answering.tepples wrote:Yes. The Popslide VRAM updater, for instance, uses this to switch between a program's main stack at $01C0-$01FF and an update buffer at $0108-$01AF.SusiKette wrote:4. Is it possible to split the stack into several smaller segment by changing the stack pointer around the "stack page" and not affect the contents of another segment?
Re: SusiKette's project help thread
Now that I have the palette update working when using stack, I guess I should do a palette fader next. I probably need some simple example to help me getting started and then possibly edit to fit my needs better.
Edit: A couple of ways that came in mind for this:
1. Subtract #$10 from the color (or #$20 if original color is#$3x). If result sets the negative flag, then color is set to #$0F. Fading in might be a bit harder since the fader would have to know what color to fade to.
2. I guess a bit easier, but more space consuming would be to have pre-made fade palettes and use indices to figure which one to use based on the "fade step".
Edit: A couple of ways that came in mind for this:
1. Subtract #$10 from the color (or #$20 if original color is#$3x). If result sets the negative flag, then color is set to #$0F. Fading in might be a bit harder since the fader would have to know what color to fade to.
2. I guess a bit easier, but more space consuming would be to have pre-made fade palettes and use indices to figure which one to use based on the "fade step".
Last edited by SusiKette on Thu Apr 19, 2018 11:34 pm, edited 1 time in total.
Avatar is pixel art of Noah Prime from Astral Chain
Re: SusiKette's project help thread
Here's the NES palette with numbers scribbled all over it:
The $3X row is lightest, $2X is a little darker, $1X is darker still, $0X is darkest. If you need to go darker than $0X, you can only go to black.
So to make the palette darker in a simple way, all you need to do is subtract $10 from every color each frame. If a color is already in the $0X row, subtracting $10 will clear the carry. And then you store black instead.
One Caveat! $0D is a color to avoid using, since some TVs don't like it. So you want a special case to avoid it appearing. The column is affects is $XD. You still want $3D to go to $2D, but you want $2D to go straight to black.
Fade ins are a touch more complicated because every index has a different end point, but give it a shot based on this.
The $3X row is lightest, $2X is a little darker, $1X is darker still, $0X is darkest. If you need to go darker than $0X, you can only go to black.
So to make the palette darker in a simple way, all you need to do is subtract $10 from every color each frame. If a color is already in the $0X row, subtracting $10 will clear the carry. And then you store black instead.
Code: Select all
ldx #15;Last color in the palette.
palettefadeloop:
lda palette,x;Load
sec
sbc #$10
bcs storedarker;It the subtract didn't cross zero, store the result
lda #$0F;Else load black
storedarker:
sta palette,x
dex
bpl palettefadeloop
Code: Select all
ldx #15;Last color in the palette.
palettefadeloop:
lda palette,x;Load
cmp #$2D
beq fadetoblack
sec
sbc #$10
bcs storedarker;It the subtract didn't cross zero, store the result
fadetoblack:
lda #$0F;Else load black
storedarker:
sta palette,x
dex
bpl palettefadeloop
Re: SusiKette's project help thread
You were pretty fast with replying. I was in the middle of editing my post when you posted :p
Fading in might work if you either store the target palette to RAM or use indices based on where the target palette is located.
One method of implementing the fader could be by having it as a part of the palette buffer subroutine. If a flag for it is set, a counter will count every frame from set value (fade speed) to zero. Then a palette update would be requested and the fade step counter would be incremented or decremented based on fade direction. The fade step variable is used to calculate how much to subtract from the original color and then push it to the stack.
Fading in might work if you either store the target palette to RAM or use indices based on where the target palette is located.
One method of implementing the fader could be by having it as a part of the palette buffer subroutine. If a flag for it is set, a counter will count every frame from set value (fade speed) to zero. Then a palette update would be requested and the fade step counter would be incremented or decremented based on fade direction. The fade step variable is used to calculate how much to subtract from the original color and then push it to the stack.
Avatar is pixel art of Noah Prime from Astral Chain
Re: SusiKette's project help thread
Subtracting from the original values for both fade in and fade out is an excellent idea.
Re: SusiKette's project help thread
The palette fading itself seems to work perfectly, although for some reason during this process the screen is shaking. It might be some weird thing with the PPU that I'm not aware of since the variables I'm loading to the $2005 register stay at #$00 the entire time. I'm once again including both source code and compiled ROM.
EDIT: The nametable viewer does show the scroll position jumping around, but I'm still not sure what could cause this
EDIT: The nametable viewer does show the scroll position jumping around, but I'm still not sure what could cause this
- Attachments
-
- main.zip
- (10.01 KiB) Downloaded 201 times
Avatar is pixel art of Noah Prime from Astral Chain
- rainwarrior
- Posts: 8731
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: SusiKette's project help thread
$2006 and $2005 are shared access to the same internal register, one makes it easy for addressing (for data upload) and one makes it easy for scrolling.
If you write $2006 to update the palettes, the scroll position is clobbered, so you need to write the scroll to $2005 before the end of vblank to restore the scroll position.
So, in the NMI when updating the graphics data, always set the scroll position last.
More info here: https://wiki.nesdev.com/w/index.php/PPU_scrolling
If you write $2006 to update the palettes, the scroll position is clobbered, so you need to write the scroll to $2005 before the end of vblank to restore the scroll position.
So, in the NMI when updating the graphics data, always set the scroll position last.
More info here: https://wiki.nesdev.com/w/index.php/PPU_scrolling
Re: SusiKette's project help thread
Now that you mention it I remember that begin the case. I guess I forgot it during the BRK I had from asm.
Next up would be background update. I want to try to make the actual code on my own at first, but I need a "hint" to help a bit. So my question is:
What lookup tables do I need (aside the compressed level data)? Scrolling is vertical (I'm making a shoot em up).
I was planning on compressing the levels into 16x16 metatiles, then to rows on metatiles, and then to screens. Would the compressing to rows be useful if you keep decoding time and the additional compression rate in mind?
Next up would be background update. I want to try to make the actual code on my own at first, but I need a "hint" to help a bit. So my question is:
What lookup tables do I need (aside the compressed level data)? Scrolling is vertical (I'm making a shoot em up).
I was planning on compressing the levels into 16x16 metatiles, then to rows on metatiles, and then to screens. Would the compressing to rows be useful if you keep decoding time and the additional compression rate in mind?
Avatar is pixel art of Noah Prime from Astral Chain