I am working a quick demo that alters the background tiles. The catch is when i change some of the tiles, the screen does a visible skip, then resettles where it should be. This doesn't ruin the game in anyway, but i would preferably remove this before a final release.
anyways this is the code i am using the update the location of the screen with a dynamically changing string from RAM...
Code:
; Wait a vblank
lda VBlank
- cmp VBlank
beq -
;kill ppu
lda #0
sta $2000
sta $2001
;Rewrite the word
lda #$22
sta $2006
lda #$f2
sta $2006
ldy #$00
- lda (ActiveString), y
sta $2007
iny
cpy #$0c ;word length is 12
bne -
;This recenters the screen
lda #$20
sta $2006
lda #$00
sta $2006
;reinitialize ppu
lda #%10001000
sta $2000
lda #%00011110
sta $2001
Any advice would be appreciated, as this is the last bug in my otherwise finished demo.
Try also resetting the scroll. Just do
Code:
lda #$00
sta $2005
sta $2005
along with what you have. I don't think you have to write to $2006 if you're still inside natural Vblank.
if i comment out the writes to 2006 to recenter and just clear to scroll, i still get the screen hic-up, but it is less noticable. it is now more of a quick flash, with the location i am writing to at the top of the screen, and then it all centers again.
Thanks for the input. i am still going to see if i can fix it better before releasing it. Is there anything else that i am doing wrong (perhaps expecting a background write to happen smoothly?)
I'm not sure this will help but maybe you should use a vblank IRQ instead of polling the PPU for vblank?
it seems like you shouldn't see anything if you make all your changes before vblank ends, but i can't say for sure
frantik wrote:
I'm not sure this will help but maybe you should use a vblank IRQ instead of polling the PPU for vblank?
it seems like you shouldn't see anything if you make all your changes before vblank ends, but i can't say for sure
Unless i am misunderstanding what you are telling me, i believe i am.
I have the NMI set to trigger on VBlank, where it is incremented.
Other then that, i push the registers onto the stack, then run the next note from the embedded NSF file, then pull the registers back off the stack, and return from interrupt.
does that code run during the vblank interrupt (like before the RTI command) or do you just increment the VBlank variable, play the note, and return, and then that code executes?
if the graphics code executes after the note is played i think it would cause problems.. but i think you would see more than a "hickup". so i don't really know.. lol
When I was developing Pegs, I had the same issue. I was changing 12 tiles in the background, and when I did so, I got the hiccup you are talking about. I ended up curing this by writing only four tiles at a time, as well as the using the same code that Celius posted, I believe (check the source, because I honestly do not remember at this point). I'm pretty sure I just polled $2002 each time before writing, which as I understand from other posts isn't the best way, but it worked for me, and I've never experienced any lag on that game when played on the system.
It kind of makes me wonder if there is a time when polling $2002 on the NES is acceptable, but has to be done at a certain point in the cycles. I don't know, I'm just speculating : P
Roth wrote:
It kind of makes me wonder if there is a time when polling $2002 on the NES is acceptable
Soon after power-on, in order to make sure the PPU reset finished all the way. The typical sequence is poll $2002, clear CPU RAM, poll $2002, set palette, clear nametables. There's about a 1 out of 21 chance that you'll get lag on any frame; this chance is acceptable in something like power-on. But after that, use NMI.
Well this code is meant to run under certain conditions of an 'A' press.
I don't want to rewrite the background every time the NMI is triggered (or at least the 12 tiles.) So unless i create a state system to handle different types of background writes, then embed that code into the NMI to handle those writes, that wont work.
One of my goals in this demo was to create a simple procedural example of coding on the NES, but this adds needless complication for an inexperienced observer.
If the aforementioned state system is my only option, i will take it, but i would prefer handling it in inline code to make it easier on the newbies that happen to read the source, or use it as a reference.
So Vblank is a variable incremented in the NMI? Exactly what is happening when the NMI is fired?
Oh, and really make sure in your NMI routine you save A, X, and Y. If the A register is destroyed, the desired value won't be loaded into A upon returning.
Here's my suggestion: put "STA $5555" at the very end of the posted code. Then open the ROM in FCEUXD. Go to Tools... Debug... Then under the "break points" box click "add". Then in the first text box next to "address" write $5555 and under it, click "write", then "OK". Then press 'A'.
It will break after that code is done. You can see what scanline it ends at and other information.
And writing less probably isn't the answer. I was able to fit 182 PPU writes into 1 Vblank, so you just have to keep track of how many cycles everything is taking. Make sure it doesn't spill out of Vblank. That's probably what's causing the problem here, actually.
This is my NMI interrupt code...
Code:
NMI:
;Alter the VBlank flag
inc VBlank
;Push registers onto stack
pha
txa
pha
tya
pha
;Play the next Music note
jsr PLAY_ADDR
;Pull registers from stack
pla
tay
pla
tax
pla
rti ;Return from interrupt
I try to keep it simple as possible and handle most timing by tracking the VBlank flag.
You're supposed to handle music
after you handle the graphics. This is because the PSG allows writes at any time, not just during blanking.
If you don't use a register in your NMI handler, you don't need to spend cycles saving and loading it. My NES programs' NMI code typically looks like this:
Code:
nmi:
inc retraces
rti
And then your main loop will look like this:
Code:
forever:
jsr read_pads ; read each controller twice and keep readings that agree
jsr game_logic ; apply all game rules
jsr prepare_ppu ; prepare data to be copied to OAM and VRAM
lda retraces
:
cmp retraces ; wait for NMI handler to signal the start of vblank
beq :-
jsr update_ppu ; partly-unrolled copies from CPU RAM to tiles, nametables, palette
jsr PLAY_ADDR
jmp forever
is there a reason it's better to increment a variable inside of vblanks iterrupt routine and then handle the variable change, vs just putting the handling code between the interrupt vector and the RTI?
frantik wrote:
is there a reason it's better to increment a variable inside of vblanks iterrupt routine and then handle the variable change, vs just putting the handling code between the interrupt vector and the RTI?
No, except that the pattern of an ISR that just sets a flag might be more familiar to developers on platforms where you don't get to write your own ISRs, such as any multitasking OS. You could put the entire game in the NMI; Super Mario Bros. does just this. Just make sure you do the PPU updates as soon as possible after the start of vblank, which means before you run the sound engine.
Polling the VBL count is simpler to code for, because you don't have to deal with re-entrancy and concurrency issues. If the code is executed from NMI, you need a flag to tell it when it can run, and where it's called from the mainline code is less-clear. Unless you're doing split-screen effects, you don't need code guaranteed to run every NMI, so the polling approach is better.
thanks, makes a lot of sense
ok, yeah. If i comment out the 'jsr PLAY_ADDR,' it works fine.
I will apparently have to reorganize some of my code to get this working the way i want, which sucks, but oh well. I guess i know what is wrong and what to do to fix it.
Thanks for the input, everyone.
Compton wrote:
This is my NMI interrupt code...
Code:
NMI:
;Alter the VBlank flag
inc VBlank
;Push registers onto stack
pha
txa
pha
tya
pha
;Play the next Music note
jsr PLAY_ADDR
;Pull registers from stack
pla
tay
pla
tax
pla
rti ;Return from interrupt
I try to keep it simple as possible and handle most timing by tracking the VBlank flag.
Like Tepples said, you should not play the music
before the PPU writes. I bet your code is spilling outside of Vblank. The primary purpose of the NMI is for PPU updates in natural Vblank. I know that it gets rid of complexity to have it the way it's set up now like Blargg explained, but you'll probably want to reorganize this, simply because the NMI tells you when you are inside of Vblank. My suggestion is to have PPU writing code in the NMI and have a one-write-lock system of some kind. For example, my PPU updating code has an instruction in RAM that does:
JSR $A432
And if everything isn't quite ready in terms of PPU updates, all I have to do is change the low byte of the address to point to an RTS. It's important that locking and unlocking the routine can be done with one instruction, because that way it can't be like half updated and interrupted which would cause catastrophe.
And with a system like mine, there aren't really any "flags" that you have to check, since it's changing an address. So maybe you want to set aside like 4 bytes of RAM and put:
JSR $xxxx
RTS
in those 4 bytes, and just JSR to that place in RAM in the beginning of the NMI. And that JSR $xxxx would jump to the PPU updating code. Then say it was at $A432, if you wanted to "lock" it, you could make it point to $A431 where an RTS instruction would be placed.
Heh, Celius did a much better job of showing how much more complex it is to run the code from inside NMI.
Contrast with this:
Code:
title_screen:
jsr wait_nmi
jsr update_graphics
jsr handle_controller
beq title_screen
jsr do_time_consuming_setup
select_screen:
jsr wait_nmi
jsr update_graphics
jsr handle_controller
beq select_screen
jsr do_time_consuming_setup
game_loop:
jsr wait_nmi
jsr update_graphics
jsr move_objects
jsr handle_controller
beq game_loop
...
wait_nmi:
lda nmi_count
@wait:
cmp nmi_count
beq @wait
rts
Nothing to bite you in the ass, since "locking" is handled implicitly, and the current game phase is "encoded" into the program counter. And even once you start doing split-screen effects which require some code in the NMI handler, it can be limited to doing the mid-screen changes, and leave the rest to the non-interrupt code.
My solution was quite simple actually. I just added another flag called 'SkipNote' then incremented it before the VBlank check. Then added this to my nmi...
Code:
;Start of Interupt Functions
NMI:
;Alter the VBlank flag
inc VBlank
;Push accumulator onto stack
pha
;check music priority
lda SkipNote
cmp #0
bne +
txa
pha
tya
pha
;Play the next Music note
jsr PLAY_ADDR
pla
tay
pla
tax
jmp ++
+
lda #0
sta SkipNote
++
;Pull registers from stack
pla
rti ;Return from interrupt
I'll admit it doesn't fix the flaw in my programs design, but it works well enough for what i had in mind.
EDIT: This would probably be a bigger problem if i had to update the screen a lot, but for what i needed, a few missed notes aren't even noticable.
BTW, anything with A as its destination (LDA, TXA, PLA, ADC, ORA, etc.) sets the status flags, so you don't need to compare A with zero just after. Also, if you store flags in the high bit of a byte, you don't have to modify A to test it; you can use BIT. This is useful in interrupt handlers so you don't have to save as many registers:
Code:
lda #$00
sta skipping_note ; or just inc skipping_note if you know that it was previously $FF
nmi:
inc nmi_count
bit skipping_note ; set sign flag based on high bit
bmi @skipped ; branch if sign flag set
...
lda #$FF
sta skipping_note ; or just dec skipping_note if you know it was $00 before
@skipped:
rti
EDIT: Fixed values put into skipping_note. Before its meaning was confused with "not_skipping_note".
blargg wrote:
BTW, anything with A as its destination (LDA, TXA, PLA, ADC, ORA, etc.) sets the status flags, so you don't need to compare A with zero just after. Also, if you store flags in the high bit of a byte, you don't have to modify A to test it; you can use BIT. This is useful in interrupt handlers so you don't have to save as many registers:
Thanks, that will help clean up some of my code. I didn't know that about the 'A' register.
Quote:
You're supposed to handle music after you handle the graphics. This is because the PSG allows writes at any time, not just during blanking.
But if you don't update the music when the VBlank triggers, then that means the music could get updated at different times during the frame, which means that the music could end up sounding out of sync. I guess it's not really noticeable though.
So a note-on in an NES game might be delayed by 10 milliseconds. Big whoop. If your song's tempo doesn't divide easily into 901.5 BPM (NTSC) or 750 BPM (PAL), the note-ons will be jittered anyway. Or apart from the NES, if you have a live musician, how long might his notes get delayed? Music is supposed to sound slightly out of sync. Drum-machine precision is mostly an affectation of the past three decades.