Reading the controller?

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
tokumaru
Posts: 11862
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru » Thu Feb 07, 2008 9:33 pm

electro wrote:Maybe BEQ to a subroutine that clears the $4015 register?
Remember, you can't branch to a subroutine, ever. This was a very serious mistake in your first attempts to make a NES ROM. The only way to call a subroutine is by using the JSR instruction. You may use the branch instructions to decide if the subroutine will be called or not, but never directly branch to it. The JSR instruction is the only one that puts the current value of the program counter in the stack, so that the subroutine can return to the place where it was called after it finishes.

I know you have no previous programming experience, but keep in mind that computers do not give a damn about english, and it really doesn't know what a subroutine is. This is a word, made up by humans, to make the commands easier to understand and remember. But as far as the CPU is concerned, what each command does is what matters, not what they are called.

For example, when you look at this page, you can see that there is a description of what the JSR instruction actually does, and you can see that "Jump to Subroutine" is just there to help us remember that instruction. By reading that description, you can see that I lied about JSR being the only way to call a subroutine. For some crazy reason, you may want to place a fake return address on the stack, and then simply JMP or branch to the subroutine. When the subroutine ends, it will fetch that address you placed on the stack before, even though it isn't the location where the routine was called. This kind of trick is a bit too advanced for you right now (you don't need to worry about tricking the CPU yet), but this is just an example.

Well, I'm saying all that because, with no programming background, you may end up relying too much on the name of the instructions. So this is just a heads uo, to let you know that computers are much more dumb than you think, and if you master the exact meaning of the instructions you give to them, you'll write better, more reliable, and faster code.

User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro » Fri Feb 08, 2008 10:07 am

I'm glad you pointed this out to me, looking at the previous code it makes sense now.

Will do some more reading.

Thanks again for that.

T

User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg » Fri Feb 08, 2008 10:47 am

A CPU provides the most basic nuts and bolts to build things from. Anything higher level than individual instructions is just a mental aid for us to understand and design programs; the CPU knows nothing about them. Every instruction is a distinct operation with well-defined inputs and effects, nothing more. Put another way, a program of random instructions (note I didn't say random bytes) is just as valid to the CPU as a carefully-coded one, even though the random ones do things that for most purposes would be nonsense.

User avatar
never-obsolete
Posts: 381
Joined: Wed Sep 07, 2005 9:55 am
Location: Phoenix, AZ

Post by never-obsolete » Fri Feb 08, 2008 12:07 pm

For some crazy reason, you may want to place a fake return address on the stack, and then simply JMP or branch to the subroutine. When the subroutine ends, it will fetch that address you placed on the stack before, even though it isn't the location where the routine was called. This kind of trick is a bit too advanced for you right now (you don't need to worry about tricking the CPU yet), but this is just an example.
this trick is useful if you need a JSR ($aaaa). i've used it for menus and for virtual functions of objects (enemies and whatnot). kinda crazy thinking about oop in assembly.

Code: Select all

	lda CursorCoorY		; get address
	asl
	tay
	lda modeptrs, Y
	sta JmpIndLO
	lda modeptrs + 1, Y
	sta JmpIndHI
	lda >levelEdFuncReturn - 1	; save the return address
	pha
	lda <levelEdFuncReturn - 1
	pha
	jmp (JmpIndLO)
levelEdFuncReturn:
	; go on about business
though i've seen some comercial games push an address to the stack and RTS to it for the same effect.
. That's just like, your opinion, man .

User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg » Fri Feb 08, 2008 1:10 pm

No need to push the new return address, just JSR to a JMP (...) instruction:

Code: Select all

   lda CursorCoorY      ; get address 
   asl 
   tay 
   lda modeptrs,Y 
   sta JmpIndLO 
   lda modeptrs+1,Y 
   sta JmpIndHI 
   jsr jmp_JmpIndLO
   ; go on about business 

jmp_JmpIndLO:
   jmp (JmpIndLO)
And further, you could just push the address on the stack (modeptrs table entries must have 1 subtracted from them):

Code: Select all

   jsr ind_jmp
   ; go on about business 
   
ind_jmp:
   lda CursorCoorY      ; get address 
   asl 
   tay 
   lda modeptrs+1,Y 
   pha
   lda modeptrs,Y 
   pha
   rts

modeptrs:
   .word first-1, second-1 ...
Finally, if you split the table, you can eliminate more:

Code: Select all

   jsr ind_jmp
   ; go on about business 
   
ind_jmp:
   ldy CursorCoorY      ; get address 
   lda modeptrs_hi,Y 
   pha
   lda modeptrs_lo,Y 
   pha
   rts

modeptrs_lo:
   .byte <(first-1), <(second-1) ...
modeptrs_hi:
   .byte >(first-1), >(second-1) ...

User avatar
Laserbeak43
Posts: 188
Joined: Fri Sep 21, 2007 4:31 pm
Contact:

Post by Laserbeak43 » Mon Feb 25, 2008 4:27 pm

have only gotten through page one, but this is a beautiful thread so far!! love it!

but i do have one question(for now):

Code: Select all

Clear_Sound: 
   sta $4000,x      ; store accumulator at $4000 offset by x 
   inx         ; increment x 
   cpx #$0F      ; compare x to $0F 
   bne Clear_Sound      ; branch back to Clear_Sound if x != $0F 

what is the point of clearing the sound if you want x to equal $0F?
is that some kind of shift/mask trick?
Noob sticky!!
Please document this part of the NESdevWiki!! XD
YOU NEED A RETROMACHINESHOP!!

User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg » Mon Feb 25, 2008 4:59 pm

That code writes register A to memory locations $4000 through $400E. Here are comments describing the instructions. This is really basic stuff. Don't skip the basics in your learning process.

Code: Select all

Clear_Sound: 
   sta $4000,x      ; Store A at memory location $4000+X
   inx              ; X = X + 1
   cpx #$0F         ; If X is not equal to $0F, jump to Clear_Sound
   bne Clear_Sound

User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro » Mon Feb 25, 2008 5:08 pm

Laserbeak43 wrote:have only gotten through page one, but this is a beautiful thread so far!! love it!

but i do have one question(for now):

Code: Select all

Clear_Sound: 
   sta $4000,x      ; store accumulator at $4000 offset by x 
   inx         ; increment x 
   cpx #$0F      ; compare x to $0F 
   bne Clear_Sound      ; branch back to Clear_Sound if x != $0F 

what is the point of clearing the sound if you want x to equal $0F?
is that some kind of shift/mask trick?
Ditto. I never learned so much from a thread in any other forum before.

T
Last edited by electro on Tue Feb 26, 2008 7:56 am, edited 1 time in total.

User avatar
Laserbeak43
Posts: 188
Joined: Fri Sep 21, 2007 4:31 pm
Contact:

Post by Laserbeak43 » Mon Feb 25, 2008 6:14 pm

blargg wrote:That code writes register A to memory locations $4000 through $400E. Here are comments describing the instructions. This is really basic stuff. Don't skip the basics in your learning process.

Code: Select all

Clear_Sound: 
   sta $4000,x      ; Store A at memory location $4000+X
   inx              ; X = X + 1
   cpx #$0F         ; If X is not equal to $0F, jump to Clear_Sound
   bne Clear_Sound
it's more the other way around for me. i learn more in a community. always have.
but anyway, it's true. learn the basics. but this still doesn't explain to me why you want to jump to Clear_Sound if x is NOT equal to $0F. if it is does this mean it IS clear? cause i'm under the impression that clear would be $00.
Noob sticky!!
Please document this part of the NESdevWiki!! XD
YOU NEED A RETROMACHINESHOP!!

tepples
Posts: 22054
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples » Mon Feb 25, 2008 6:25 pm

It's trying to clear (set to zero) all registers from $4000 through $400E. Once all these registers have been cleared, X will equal $0F, and the loop ends.

User avatar
Laserbeak43
Posts: 188
Joined: Fri Sep 21, 2007 4:31 pm
Contact:

Post by Laserbeak43 » Mon Feb 25, 2008 8:42 pm

tepples wrote:It's trying to clear (set to zero) all registers from $4000 through $400E. Once all these registers have been cleared, X will equal $0F, and the loop ends.
oh! duh! x serves ONLY as a counter. i was expecting it to do more. lol. i tend to make things more complicated than they are! sorry :P and thanks XD
Noob sticky!!
Please document this part of the NESdevWiki!! XD
YOU NEED A RETROMACHINESHOP!!

User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg » Mon Feb 25, 2008 9:26 pm

The best way to understand code is to run it in your mind, or with a simulator if you have one. Then you can see the big picture. For example, this is clearly a loop:

Code: Select all

lda #0              ; A = $00
ldx #0              ; X = $00
Clear_Sound: 
sta $4000,x         ; Store 0 at memory location $4000
inx                 ; X = $01
cpx #$0F
bne Clear_Sound     ; X is not equal to $0F, so this is taken
Clear_Sound: 
sta $4000,x         ; Store 0 at memory location $4001
inx                 ; X = $02
cpx #$0F
bne Clear_Sound     ; X is not equal to $0F, so this is taken
...
sta $4000,x         ; Store 0 at memory location $400E
inx                 ; X = $0F
cpx #$0F
bne Clear_Sound     ; X is equal to $0F, so this is NOT taken
; done
Loops are useful when you need to process a lot of values, where doing each manually would take lots of code. The combination of conditional branches (decisions) and loops can allow a lot of functionality with a small amount of code. Loops are also useful when the number of values processed depends on some value while the program is running, rather than a fixed value as the above loop does.

User avatar
Laserbeak43
Posts: 188
Joined: Fri Sep 21, 2007 4:31 pm
Contact:

Post by Laserbeak43 » Tue Feb 26, 2008 6:20 am

yeah i'm quite familiar with loops. just not in asm :lol: which actually looks a lot simpler!! maybe that's why i've missed it? oh well. as always, thanks for the explanation! :D
Noob sticky!!
Please document this part of the NESdevWiki!! XD
YOU NEED A RETROMACHINESHOP!!

User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro » Tue Feb 26, 2008 8:06 am

That's a great explanation of the loop that clears the sound registers.

This is awesome.

Thanks.

T

P.S. I meant to say above that I have never learned so much in a forum before. I had to edit my original comment, I think the way I worded it, it sounded the opposite of what I meant!

tepples
Posts: 22054
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples » Tue Feb 26, 2008 8:44 am

tokumaru wrote:
electro wrote:Maybe BEQ to a subroutine that clears the $4015 register?
Remember, you can't branch to a subroutine, ever.
Ever?

Post Reply