Page 18 of 18

Posted: Tue Dec 13, 2011 7:09 pm
by FinalZero
Okay, so I've meed those changes, and have this now:

Code: Select all

; Inits everything.
.proc reset
	; Clears the flags.
	clc
	sei
	cld
	clv
	
	; Sets the stack pointer.
	ldx #$FF
	txs
	
	; Waits for the PPU to warm up.
:	lda $2002 
	bpl :-
	
	; Inits the PPU.
	inx
	stx PPU_CONTROL
	stx PPU_MASK
	dex
	
	jmp main
.endproc
However, my code still gets stuck in the NMI handler. I don't even understand how it's supposed to escape it. It continually compares nmis to AC, neither of which are programmed to change in the loop.

Code: Select all

	lda nmis
:	cmp nmis
	beq :-

Posted: Tue Dec 13, 2011 7:20 pm
by Kasumi
You need to actually enable the NMI.

Read this: http://wiki.nesdev.com/w/index.php/PPU_registers

Code: Select all

   inx
   stx PPU_CONTROL
   stx PPU_MASK 
What you did there is write 0 to both of those registers. Which means no NMI interrupt happens at the start of each frame. (Because bit 7 of $2000 is clear)

So after your setup code is done, set bit 7 of $2000. What this causes to start happening is an NMI "interrupt". An interrupt actually interrupts whatever your program was doing before the interrupt, and starts running something else. When that "something else" is done, it returns to where it was before the interrupt took place.

This is how nmis will change, even though your current loop does nothing to change it.

So while your main loop is doing this:

Code: Select all

   lda nmis
:   cmp nmis
   beq :-
Eventually, a new frame will start. When that happens, your code will be interrupted and jmp to your NMI routine. Your NMI routine will change the variable nmis, and then return allowing your main loop to continue down.

I don't know how to set the NMI vectors with your current setup, but you need to put the address for the NMI routine directly before the address of your reset vector.

Edit: Here is a quick NMI to get you started that should at least get you out of that loop:

Code: Select all

nmi:
     pha;The interrupt might have happened when you were doing something important
     tya;So we save the registers to the stack
     pha;And restore them later so that when it
     txa;Returns, the same values will be in them.
     pha

     inc nmis

     pla;Restoring the registers
     tax;From the stack.
     pla
     tay
     pla

     rti;Return from interrupt

Posted: Tue Dec 13, 2011 7:40 pm
by FinalZero
Okay, so like this?:

Code: Select all

; Inits everything.
.proc reset
	; Clears the flags.
	clc
	sei
	cld
	clv
	
	; Sets the stack pointer.
	ldx #$FF
	txs
	
	; Waits for the PPU to warm up.
:	lda $2002 
	bpl :-
	
	; Inits the PPU.
	ldx #%10000000
	stx PPU_CONTROL
	
	ldx #%00011000
	stx PPU_MASK
	
	; Sets the stack pointer again.
	ldx #$FF
	
	jmp main
.endproc

Posted: Tue Dec 13, 2011 8:31 pm
by Kasumi
You still want 0 written to both of those PPU registers while the PPU is still warming up. Also, you should wait two frames for the PPU to warm up, not just one.

More like this?

Code: Select all

; Inits everything.
.proc reset
   ; Clears the flags.
   ;clc;This isn't needed in your setup code. Its state being unknown
   ;Doesn't affect how your code will work in the very beginning
   sei;
   cld;We only do this because NES doesn't actually have decimal mode. 
   ;clv;No need to do this either.
   
   ; Sets the stack pointer.
   ldx #$FF
   txs
   inx
   stx PPU_CONTROL
   stx PPU_MASK
   
   ; Waits for the PPU to warm up
:   lda $2002
   bpl :-;Frame 1

   ; Waits for the PPU to warm up.
:   lda $2002
   bpl :-;Frame 2
   
   ; Inits the PPU.
   ldx #%10000000
   stx PPU_CONTROL
   
   ldx #%00011000
   stx PPU_MASK
     
   jmp main
.endproc
And you also need to put the NMI routine I posted in your code.

I don't use what you're using, but it looks like the way to do it is this (from source code Tepples recommended looking at earlier in the topic):

Code: Select all


.segment "VECTORS"
  .addr nmi, reset, irq
Then:

Code: Select all

.proc irq;Sure, here's an IRQ too for good measure
  rti
.endproc
.proc nmi
     pha;The interrupt might have happened when you were doing something important
     tya;So we save the registers to the stack
     pha;And restore them later so that when it
     txa;Returns, the same values will be in them.
     pha

     inc nmis

     pla;Restoring the registers
     tax;From the stack.
     pla
     tay
     pla

     rti;Return from interrupt 

.endproc
One other thing:

Code: Select all

; Sets the stack pointer again.
   ldx #$FF 
That doesn't do anything with the stack pointer. Not that it matters. You already set the stack pointer at the beginning of your program. Once you transfer a number from X to the stack pointer (they are not the same thing) with TXS, you can use X for whatever you like. If you're not going to use the #$FF in X you loaded right then for something, there's no need to do it.

In short:

1. Changing X won't change the stack pointer unless you use TXS.
2. You don't need to change the stack pointer again since you set it up already.

Posted: Tue Dec 13, 2011 9:52 pm
by tokumaru
FinalZero, I must say it looks like you are just guessing and hoping for things to somehow work out, instead of actually understanding what you're doing.

Posted: Wed Dec 14, 2011 4:37 am
by FinalZero
You still want 0 written to both of those PPU registers while the PPU is still warming up.
But I thought I was writing to them after the PPU has already warmed up.
Also, you should wait two frames for the PPU to warm up, not just one.
Oops, it's fixed now.
And you also need to put the NMI routine I posted in your code.
I don't understand yours. You push a bunch of stuff onto the stack only to pop it off in the very same routine after only incrementing nmis.
That doesn't do anything with the stack pointer. Not that it matters. You already set the stack pointer at the beginning of your program. Once you transfer a number from X to the stack pointer (they are not the same thing) with TXS, you can use X for whatever you like. If you're not going to use the #$FF in X you loaded right then for something, there's no need to do it.
I know. My math routines use a stack based on the zeropage with x as the index register, as its stack pointer. That's what I meant by "stack pointer" here.
FinalZero, I must say it looks like you are just guessing and hoping for things to somehow work out, instead of actually understanding what you're doing.
That's sort of how I feel. I *think* I understand it, and then later I find out that I was wrong. Anyways, I've certainly never programmed something where I have to take care of things like vblanks and interrupts before. I admit, lots of it seems cryptic. I apologize if it seems like I'm wasting your time.

Posted: Wed Dec 14, 2011 7:13 am
by tepples
FinalZero wrote:
And you also need to put the NMI routine I posted in your code.
I don't understand yours. You push a bunch of stuff onto the stack only to pop it off in the very same routine after only incrementing nmis.
Hail John Frum, who brings the cargo.

There are three structures for a game loop:
  1. NMI handler just increments nmis. Main loop waits for increment, updates VRAM, updates sound, then runs one frame of game logic.
  2. NMI handler updates VRAM and then updates sound, then increments nmis. Main loop waits for increment then runs one frame of game logic.
  3. NMI handler updates VRAM, updates sound, then runs one frame of game logic.
B and C type NMI handlers generally require pushing all registers and pulling them at the end. Some people are so used to B or C type NMI handlers that they end up typing out the pushes by habit and forgetting that they're not strictly needed for A type NMI handlers. Forgive them.
FinalZero, I must say it looks like you are just guessing and hoping for things to somehow work out, instead of actually understanding what you're doing.
That's sort of how I feel. I *think* I understand it, and then later I find out that I was wrong. Anyways, I've certainly never programmed something where I have to take care of things like vblanks and interrupts before. I admit, lots of it seems cryptic. I apologize if it seems like I'm wasting your time.
You could try a new thread about starting from my project template and hacking around with it.

Posted: Wed Dec 14, 2011 8:41 am
by tokumaru
FinalZero wrote:But I thought I was writing to them after the PPU has already warmed up.
It goes like this: before the warm up, write 0 to both PPU registers (the purpose here is to "reset" the PPU... kinda), and after the warm up you write the actual configuration you're gonna use (this includes enabling NMIs).

Posted: Wed Dec 14, 2011 2:39 pm
by Kasumi
FinalZero wrote:
You still want 0 written to both of those PPU registers while the PPU is still warming up.
But I thought I was writing to them after the PPU has already warmed up.
I apologize for my mistake. I always thought those registers started with an unknown state, which isn't true. Even if they did start at something unknown, writes to them are also totally ignored for the first few frames.

What tokumaru posted is what I was describing, but the wiki says you don't need to.

I'll probably still do it to be safe, because I am paranoid.
I don't understand yours. You push a bunch of stuff onto the stack only to pop it off in the very same routine after only incrementing nmis.
Exactly. I explained why it does that in the comments, but Tepples makes a good point. The NMI I posted currently doesn't affect the status registers, but the idea would be that you add on to it once you're passed this hurdle.

Here's a real world example of why the pushing and popping is done.

Imagine this code is in your main routine:

Code: Select all

   lda #$FF
   ldx #$00
loop:
   sta $0200,x
   inx
   inx
   inx
   inx
;*
   bne loop
;We can continue only when X is exactly 0. I've used code like to remove all sprites from the screen (by putting their y position below the screen).
Now imagine this is your nmi routine. It interrupts at some point during the loop. Say... where that * is.

Code: Select all

nmi:
   inc nmis
   lda #$20;This line will cause a #$20 to be written instead of #$FF
   sta $2006
   ldx #$03;This line will cause an infinite loop.
   sta $2006
  
   ldy #$00
   sta $2007
;
   rti
When that code returns you're in an infinite loop, because the nmi changed X to something that makes completing the loop impossible.

Can (4*Z+3)%256 ever equal 0?
(Z = the number of times we have looped)

No. But (4*Z+0)%256 can be 0 which would allow your main loop to continue.

We push the registers to the stack in the NMI because it can interrupt our code at ANY time. After they're pushed, we can safely change them in the NMI, and restore them when we're done for when we return.

The inc nmis part might make sense now too. Because the opposite happens than this example. The nmi not changing that variable KEEPS us in an infinite loop when it should break us out of one.

Is that clear? If not I'll try a different approach. Tepples also did an explanation of game loops where you can avoid pushing and pulling if you like.
I know. My math routines use a stack based on the zeropage with x as the index register, as its stack pointer. That's what I meant by "stack pointer" here.
My bad. I'm a new guy in this thread, so I might have missed where that was mentioned. I'm just trying to make sure you're not doing anything you don't understand.

Posted: Wed Dec 14, 2011 4:57 pm
by tepples
Kasumi wrote:What tokumaru posted is what I was describing, but the wiki says you don't need to.
Perhaps you need to on the Famicom, which doesn't fully reset the PPU when the player presses reset.

Posted: Wed Dec 14, 2011 5:15 pm
by tokumaru
When coding in assembly I like to assume that everything is in an unknown state on power up. Better safe then sorry.

Posted: Wed Dec 14, 2011 6:16 pm
by Kasumi
Then, I was right the first time! Like I said, I'd do it anyway.

But now a question for my own curiosity: Are writes to $2000 still ignored for a few frames after a famicom reset? If so, even if $2000 and $2001 were set to something potentially harmful, you still couldn't do anything about it before the PPU was warmed up anyway.

Posted: Thu Dec 15, 2011 12:55 am
by thefox
Kasumi wrote:But now a question for my own curiosity: Are writes to $2000 still ignored for a few frames after a famicom reset? If so, even if $2000 and $2001 were set to something potentially harmful, you still couldn't do anything about it before the PPU was warmed up anyway.
Going out on a limb here, but I don't think so. As far as I understand, the reset line of the PPU simply isn't connected to the PPU on the Famicom, so when you push the reset button, PPU keeps running like nothing happened.