Page 3 of 4

Posted: Mon Jul 31, 2006 11:24 pm
by Memblers
Sounds odd. I'm not sure what's going on then. Posting the new code (or editing the previous post with it) might shed some light (include the full NMI routine also).

Often a good thing to do in cases like this is step through the code with a debugger and see what happens there.

Posted: Tue Aug 01, 2006 5:52 am
by lynxsolaris
Memblers wrote:Sounds odd. I'm not sure what's going on then. Posting the new code (or editing the previous post with it) might shed some light (include the full NMI routine also).

Memblers,

Here is the code in its entirety:

Code: Select all


 .inesprg   1
 .ineschr   1
 .inesmap   0
 .inesmir   1
 
 
 
 .zp
text_flag = $00
 
 .bss
 
 
 .code
 .org $8000
 .bank 0
 
reset: 
 sei
 cld
 
 ldx #$40
 stx $4017
 
 ldx #$ff
 txs
 inx
 stx $2000
 stx $2001
 ldx #$99
 stx text_flag
 
 jsr wait_vblank
 jsr wait_vblank
 
 ;load color palette first
 lda #$3F
 sta $2006
 lda #$00
 sta $2006
 
 ldx #$00
loop_pal:
 lda color_pal,X
 sta $2007
 inx
 cpx #$10
 bne loop_pal
 
 

 lda #$20
 sta $2006
 lda #$84
 sta $2006
 
 
 ; text demo will display "A(0B) B(0C)"

 
 lda #$0B
 sta $2007
 
 lda #$00
 sta $2006
 sta $2006
 
 lda #%10001000
 sta $2000
 lda #%00011000
 sta $2001

wait_again:
 lda text_flag
 cmp #$00
 bne wait_again
 lda #$99
 sta text_flag
 
 
 lda #$00
 sta $2000
 sta $2001



 lda #$20
 sta $2006
 lda #$85
 sta $2006
 
 lda #$0C
 sta $2007
 
 lda #$00
 sta $2006
 sta $2006
 

 
 lda #%10001000
 sta $2000
 lda #%00011000
 sta $2001
 
 text_end:
  jmp text_end 
 
 
 
 
wait_vblank:
 bit $2002
 bpl wait_vblank
 rts
 
 color_pal:
  .db $0D,$30,$22,$16,$0D,$00,$10,$30,$0D,$00,$16,$30,$0D,$3C,$1B,$09
 
nmi:
 lda #%10010000
 sta $2000
 dec text_flag

 int:
  rti
  
 .bank 1
 .org $fffa
 .dw reset,nmi,int
 
 .bank 2
 .org $0000
 .incbin "text.chr"
Thanks for your help.

Posted: Tue Aug 01, 2006 10:46 am
by Bregalad
Your NMI routine modify the content of the accumulator. You should save at least your accumulator on the stack and I'd reccomand to save X and Y too, in case you modify the NMI routine to use code with the use of X and Y registers, then restore all these register at the end of the NMI routine, in order to not have the accumulator randomly changed in the main code by the interrupt, that can happen ANYTIME you enabled it.

The cmp #$00 instruction is useless, because when loading the value in the accumulator, the Z flag is automatically set if the value loaded is zero, so you can just delete this line and this won't affect anything, saving you two bytes.

I have no idea why you write twice to $2006 after writing one name table byte, you'd better write twice to $2005 in order to proprely reset the scrolling registers.
Also, I'd put a value lower than $99 in your wait timer, because $99 is several second long and you would want less time as far as I understand.
I'd also do a decent programm that writes the content of a text buffer to the screen with VBlank interval and a decent programm that fill the buffer with $0b, $0c, $00 (for example, assuming that $00 marks the end) and call for the first routine. This would structure your code in a decent way and make your life easier.

Posted: Tue Aug 01, 2006 1:35 pm
by lynxsolaris
Bregalad wrote: The cmp #$00 instruction is useless, because when loading the value in the accumulator, the Z flag is automatically set if the value loaded is zero, so you can just delete this line and this won't affect anything, saving you two bytes.
Yeah, that was dumb of me.
I have no idea why you write twice to $2006 after writing one name table byte, you'd better write twice to $2005 in order to proprely reset the scrolling registers.
I've been doing this because its the only way I could get my character to show up @ all..... I MUST be doing something wrong. $2005x2 writes aren't working for me. With just writes to $2005 my "A" (or "B") isn't displayed.
Also, I'd put a value lower than $99 in your wait timer, because $99 is several second long and you would want less time as far as I understand.
I put it that high just so I'd actually see it happen. See the code I posted in this thread is just test code. I wouldn't actually do it exactly like that in my real code. But my problem here is that "B" is never displayed. It's like I never get past the wait_again loop.
I'd also do a decent programm that writes the content of a text buffer to the screen with VBlank interval and a decent programm that fill the buffer with $0b, $0c, $00 (for example, assuming that $00 marks the end) and call for the first routine. This would structure your code in a decent way and make your life easier.
Yeah, again, just test code :)
Your NMI routine modify the content of the accumulator. You should save at least your accumulator on the stack and I'd reccomand to save X and Y too, in case you modify the NMI routine to use code with the use of X and Y registers, then restore all these register at the end of the NMI routine, in order to not have the accumulator randomly changed in the main code by the interrupt, that can happen ANYTIME you enabled it.
Do you think this is why I'm having problems?

Posted: Tue Aug 01, 2006 1:49 pm
by Bregalad
I'm not sure, because when NMI is SUPPOSED to hit, you're just looping if a is nonzero, and if A contain the value written to $2000 instead, the branch happen anyways, so I'm unsure if your problems are caused by this or not, but anyway you must save all register you modify in your NMI routine, just because it doesn't always hit when it is supposed too, and that shouldn't crash your programm.
I don't see any other error, but maybe by doing a more decent structured programm that doesn't write the hard-wired letter "A" and "B", but that can write any message and another that order it to write "AB", then you must have less problems figuring your bugs. I'd highly reccomand directly tring to code the routine you want to be coded, even for test purposes.

Oh by the way you souldn't use $0d as black, but $0e or $0f because $0d will output noncorrect color voltage to the TV. This shouldn't cause problems with emulators, tough.

Finally, you should "bit $2002" in your NMI, to clear the VBlank flag, to avoid another NMI to be triggered as soon as the first one exits.

Posted: Tue Aug 01, 2006 2:24 pm
by Quietust
Bregalad wrote:Finally, you should "bit $2002" in your NMI, to clear the VBlank flag, to avoid another NMI to be triggered as soon as the first one exits.
That is incorrect - the only thing that would cause a repeat NMI in such a circumstance is if you were to clear and then re-set the NMI enable flag in $2000. Remember, NMIs are edge-triggered, unlike IRQs (which are level-triggered).

Posted: Tue Aug 01, 2006 2:32 pm
by Bregalad
Remember, NMIs are edge-triggered, unlike IRQs (which are level-triggered).
For some reasong I was under the beliving of the other way arround.
So yeah, I still have the habbit to use $2002 at the begining of VBlank, but this may not be necessary.

Posted: Tue Aug 01, 2006 2:47 pm
by Disch
I would still recommend doing the "bit $2002" in NMI. If nothing else it will reset the $2005/$2006 toggle and clear the VBlank flag so that if you do happen to disable and re-enable NMIs in the same VBlank, you won't run into a problem.

Posted: Tue Aug 01, 2006 3:56 pm
by lynxsolaris
Quietust wrote: That is incorrect - the only thing that would cause a repeat NMI in such a circumstance is if you were to clear and then re-set the NMI enable flag in $2000. Remember, NMIs are edge-triggered, unlike IRQs (which are level-triggered).
Disch wrote: I would still recommend doing the "bit $2002" in NMI. If nothing else it will reset the $2005/$2006 toggle and clear the VBlank flag so that if you do happen to disable and re-enable NMIs in the same VBlank, you won't run into a problem.

so bit $2002 in NMI or no?
Bregalad wrote: you must save all register you modify in your NMI routine, just because it doesn't always hit when it is supposed too, and that shouldn't crash your programm
I haven't done much with pushing or pulling off the stack yet. If I save my values in NMI do I need to restore them once out of NMI?

Does anyone else have any suggestions for me? Thanks for the help thus far.

Posted: Tue Aug 01, 2006 6:08 pm
by Disch
lynxsolaris wrote:so bit $2002 in NMI or no?
You have nothing to lose by doing it other than the 4 cycles it takes. And while it isn't necessarily manditory, it sure can't hurt, and possibly will prevent future potential problems.

So yes, do it.

Q was only saying that the NMI won't reoccur on RTI if you don't do it -- he never said it shouldn't be done. (He was merely making a technical correction -- not really offering advice).
lynxsolaris wrote:I haven't done much with pushing or pulling off the stack yet. If I save my values in NMI do I need to restore them once out of NMI?
Well if you don't restore them, what's the point of saving them? :P

The reason A/X/Y registers are often backed up and restored in NMI/IRQ routines is because they are not automatically backed up by the interrupt. If the interrupt cut into code that was executing -- that code would could get REALLY screwed up if A/X/Y suddenly changed on it.

For example, consider the following:

Code: Select all

something_the_program_is_doing:
  LDA #$05
  CLC
  ADC #$05
  STA somewhere
  RTS

nmi_routine:
  LDA #$00
  RTI
'somewhere' would be expected to have $0A written to it ($05 + $05). But what would happen if the NMI tripped right before that CLC? The executed code would flow something like the following:

Code: Select all


LDA #$05
; **nmi occurs here, go to nmi vector**
LDA #$00
RTI  ; return to previous routine
CLC
ADC #$05  ; 0 + 5 = $05, NOT $0A like it should be
STA somewhere  ; somewhere is officially screwed
If such an interruption occurs during program critical calculations, it's very possible for such an occurance to completely crash and burn the program. However if you save and restore A/X/Y in interrupts, such problems are avoided because none of the registers change and the code being interrupted has no idea its been interrupted (a good thing).

Though now... you're typically enabling NMIs and just waiting around for them to happen because your program isn't complicated. So you probably don't NEED to backup your registers -- however it's one of those things like the $2002 thing. It can't hurt, and it costs nothing aside from a handful of CPU cycles.

So yeah... do it. Every NMI, every IRQ. Backup and restore A/X/Y.

Posted: Tue Aug 01, 2006 6:25 pm
by tokumaru
lynxsolaris wrote:I haven't done much with pushing or pulling off the stack yet. If I save my values in NMI do I need to restore them once out of NMI?
Just a quick note here: you have to restore them before leaving the NMI, like, the last thing before the RTI. The way you said it looks like you want to restore them after leaving the NMI, wich you can't, because you don't know where you'll be after returning.

Posted: Tue Aug 01, 2006 7:40 pm
by lynxsolaris
tokumaru wrote:
lynxsolaris wrote:I haven't done much with pushing or pulling off the stack yet. If I save my values in NMI do I need to restore them once out of NMI?
Just a quick note here: you have to restore them before leaving the NMI, like, the last thing before the RTI. The way you said it looks like you want to restore them after leaving the NMI, wich you can't, because you don't know where you'll be after returning.
Nah, I figured that much out on my own :) As you just said, there isn't any way to tell where in the code NMI will occur. I was hoping that some of the tips above would help with my issue. Unfortunately, it didn't.

Posted: Tue Aug 01, 2006 9:29 pm
by lynxsolaris
Ok, I've changed some of the code around. Here is the updated code:

Code: Select all

 .zp 
text_flag = $00 

 .bss

 .code
 .org $8000
 .bank 0

reset:
 sei
 cld

 ldx #$40
 stx $4017

 ldx #$ff
 txs
 inx
 stx $2000
 stx $2001

 ldx #$02
 stx text_flag

 
 lda #$3F
 sta $2006
 lda #$00
 sta $2006

 ldy #$00
loop_pal:
 lda pal_colors,Y
 sta $2007
 iny
 cpy #$10
 bne loop_pal


 lda #$21
 sta $2006
 lda #$84
 sta $2006

 lda #$0B
 sta $2007

 lda #$00
 sta $2006
 sta $2006

 lda #%10001000
 sta $2000
 lda #%00011000
 sta $2001

wait_again:
 lda text_flag
 bne wait_again

 lda #$00
 sta $2000
 sta $2001

 lda #$21
 sta $2006
 lda #$85
 sta $2006

 lda #$0C
 sta $2007

 lda #$00
 sta $2006
 sta $2006
 
 ldx #%10001000
 stx $2000
 ldx #%00011000
 stx $2001


nmi:
 pha
 txs
 tya
 pha
 sec
 lda text_flag
 sbc #$01
 sta text_flag
 
 lda #$00
 sta $2005
 sta $2005
 lda #%10001000
 sta $2000
 
 bit $2002
 pla 
 tay
 tsx
 pla
 rti

int:
 rti

pal_colors:
 .db $0E,$30,$22,$16,$0E,$00,$10,$30,$0E,$00,$16,$30,$0E,$3C,$1B,$09

 .bank 1
 .org $fffa
 .dw reset,nmi,int

 .bank 2
 .org $0000
 .incbin "text.chr"

"A" still shows and "B" does not. This is what appears in the debug window of nintendulator:

Code: Select all


Invalid opcode $FF (ISB) encountered at $0000
Invalid opcode $03 (SLO) encountered at $0103
Invalid opcode $03 (SLO) encountered at $0903

and when the code is running (according to CPU Trace) it seems to just be sitting in

Code: Select all

wait_again:
 lda text_flag
 bne wait_again
It seems to be that something is wrong in my NMI and my subtraction of the text_flag var.

Any ideas?

Posted: Tue Aug 01, 2006 9:45 pm
by Quietust
lynxsolaris wrote: It seems to be that something is wrong in my NMI and my subtraction of the text_flag var.

Any ideas?
Yes.

Code: Select all

nmi:
 pha
 txs
 tya
 pha
 sec
 lda text_flag
 sbc #$01
 sta text_flag
 
 lda #$00
 sta $2005
 sta $2005
 lda #%10001000
 sta $2000
 
 bit $2002
 pla
 tay
 tsx
 pla
 rti 
You do NOT want to be doing txs/tsx in your save/restore registers code - doing so modifies the stack pointer, making it very easy to corrupt the interrupt's return address if you're not very careful (which, in this case, you are not).

Your NMI routine should read as follows:

Code: Select all

nmi:
 pha
 txa
 pha
 tya
 pha

  (stuff)

 pla
 tay
 pla
 tax
 pla
 rti 

Posted: Wed Aug 02, 2006 1:33 am
by Bregalad
Also, why did your remove your two wait for VBlank at startup ? There really was nothing wrong with it, but it is wrong that your removed it.
You must do your bit $2002 at the start of the NMI to reset the $2005/$2006 flip-flop.
As Quitetust stated, this is not a good idea to save X on the stack POINTER because this will effectivly save the X register, but will totally corrupt the stack, so Y will backup anywhere in the stack in function of the old value of X, and restore correctly, but the A register and the return adress WON'T return correctly, causing your programm to crash.

I think you misunderstood txs and tsx as being phx and plx, wich just doesn't exist.

On a side not, you should do a routine that fills at least one whole Name Table and Attribute Table (PPU $2000-$23ff) with known values. Typically, $00 is your "blank" tile, you should fill it wihth $00, using color $00 for the whole screen. Most emulators will do it automatically, but NOT the real hardware. Remember that is not because you didn't write the number of a tile in your programm that it will be blank on the screen.

Now you shouldn't have that much problems from here. If it still doesn't work, use FCE Ultra to trace your code.