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
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro » Mon Feb 04, 2008 7:11 am

I have some code that was mostly given to me in another post, which I learned tremendously from. However now having some problems actually putting the pieces together correctly.

If using the emulators, would the arrow keys on my PC automatically replace the arrow keys from a real nes gamepad? Should I be using a PC joystick?

I compiled with nesasm and using Nintendulator. (But also tried Jnes, Nestopia.)

I wanted to use the code to test the reading of game pad keys and assumed the emulator made use of the arrow keys on the PC.

In the code I just wanted to press the right arrow and make some noise.

I left in the subroutine lables for each of the buttons, however I just wanted to test one button (the right arrow). That's why the other key_press subroutines are empty.

Code: Select all

;Nes Gamepad demo program
;-------------------

	.inesprg 1
	.ineschr 0  

	.bank 1        ; don't know if I need this.
	.org $FFFA
	.dw 0 
	.dw updatejoy ; should my code start here?
	.dw 0 

	.bank 0
	.org $8000

;;Equates for masking

key_a		EQU	%00000001 ; A button press 
key_b		EQU 	%00000010 ; B 
key_select  	EQU 	%00000100 ; select 
key_start  	EQU 	%00001000 ; start 
key_up  		EQU 	%00010000 ; up arrow 
key_down  	EQU 	%00100000 ; down 
key_left  		EQU 	%01000000 ; left arrow 
key_right  	EQU 	%10000000 ; right 
			
button_state          EQU       $00 ; my button_state variable

	
updatejoy: 

  LDA #1 		  ; first, strobe the joypad 
  STA $4016 
  LDA #0 
  STA $4016 

  ; now we're going to loop 8 times (once for each button), and 
  ; rotate each button's status (in the carry flag) into our button_state variable 

    LDX #$08              ; set X to 8 (the number of times we want to loop) 
  joyloop: 
    LDA $4016            ; get button state 
    LSR A                   ; shift it into the C flag 
    ROR button_state  ; rotate C flag into our button_state variable 
    DEX                      ; decrement X (our loop counter) 
    BNE joyloop          ; jump back to our loop until X is zero 

  RTS 

  LDA button_state 
  AND key_right  ; masking using AND. Check right arrow press 
  BNE right_is_pressed ; branch to subroutine

  LDA button_state 
  AND key_left  ; check left arrow press
  BNE left_is_pressed 

  LDA button_state 
  AND key_down  ; check down arrow press 
  BNE down_is_pressed 

  LDA button_state 
  AND key_up  ; check up arrow press
  BNE up_is_pressed 

  LDA button_state 
  AND key_start  ; check start button press
  BNE start_is_pressed 

  LDA button_state 
  AND key_select  ; check select button
  BNE select_is_pressed 

  LDA button_state 
  AND key_b  ; check b button press 
  BNE b_is_pressed 

  LDA button_state 
  AND key_a  ; check a button press
  BNE a_is_pressed 

right_is_pressed: ; play sound when right arrow is pressed.

	lda #$FF   ;
	sta $4000  ;

	lda #%11011011  ; 
	sta $4001  ; 

	lda #$A5
	sta $4002

	lda #$AB
	sta $4003

	lda #%00000001
	sta $4015



	jmp updatejoy ; is this jmp right?

left_is_pressed:

rts

down_is_pressed:

rts

up_is_pressed:

rts 

start_is_pressed:

rts

select_is_pressed:

rts

b_is_pressed:

rts

a_is_pressed:

rts
Thanks,
T

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

Post by tepples » Mon Feb 04, 2008 7:59 am

electro wrote:If using the emulators, would the arrow keys on my PC automatically replace the arrow keys from a real nes gamepad?
Yes. You might want to configure the controls with a known-working rom to make sure that the directions work before you try it with your own creation.
Should I be using a PC joystick?
Yes, but watch the brand. Some gamepads like to press diagonal too easily. To start with, I would recommend using a PlayStation controller through a PS1 to USB adapter. I get the authentic Nintendo feel by using an N64 controller through an N64 to USB adapter, but it's harder to find those adapters than it was in 1999 when I bought mine.

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

Post by tokumaru » Mon Feb 04, 2008 12:03 pm

The RTS's are pretty much fucking everything up...

If you start at updatejoy, which is a subroutine, when the RTS is executed, where do you thing the code will return to? That's right, there is no place to return to, because the subroutine was not called (code execution just started there), thus a return address was not placed on the stack. The RTS instruction does not care about this though, it just fetches from the stack what it assumes to be a return address and jumps to it. Since there wasn't a return address on the stack, a junk value was fetched and the program crashed, because the CPU tried to execute random data instead of an actual program.

You also have RTS's after the code that handles each button, but again you didn't JSR there, you just branched with BNE. So, again, no return address was placed on the stack. You can only use RTS if you JSR'ed to the location where the RTS instruction is.

Another serious problem is that you don't do any sort of initialization, you simply try to read the joypad as soon as the program starts. Actually, all the logic is pretty much wrong. The following is an example of a more correct program structure:

Code: Select all

start:
	;INITIALIZATION CODE HERE

;The following is the main loop

loop:
	jsr updatejoy

	;Check the state of the right key
	lda button_state
	and key_right
	beq right_not_pressed
	jsr right_is_pressed
right_not_pressed:

	;Check the state of the left button
	lda button_state
	and key_left
	beq left_not_pressed
	jsr left_is_pressed
left_not_pressed:

	jmp loop ;Go back and keep reading the joypad forever

;Now come all the subroutines used by the main code:

updatejoy:
	(...)
	rts

right_is_pressed:
	(...)
	rts

left_is_pressed:
	(...)
	rts

down_is_pressed:
	(...)
	rts

up_is_pressed: 
	(...)
	rts
Of course, the above will not compile, it's just to show what the correct program structure to achieve what you want would be.

First of all, you need to perform some basic initialization: disable interrups, turn rendering off (since you're not using the PPU), initialize the stack, and so on. The wiki has some basic initialization code, IIRC, so you can take a look there.

Then you need a loop. The player pressing buttons is something that happens over time, so you must constantly read the joypad. A part of the code that repeats is called a loop. Inside this loop, you must perform the actions that repeat, in your case, that's reading the joypad and acting accordingly to the buttons that are pressed, calling the different routines.

Now the subroutines are proper subroutines, called with JSR and finished with RTS. The part that checks for each individual button was modified to skip over the call to the subroutine in case the button was NOT pressed. This is a common assembly "trick".

Programs are usually composed by the main code, in this case starting at "start", and by supporting subroutines, which are pieces of code that can be called by the main program. You'll eventually get this.

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

Post by electro » Mon Feb 04, 2008 12:16 pm

I think I see what you're saying. (I haven't really seen good examples of code that demonstrate these things as well as it's being demonstrated here.) Very cool.

I'll check out the "initialization" info on wiki. That was something I have to get straight.

I appreciate the layout you provided. I'll digest it and repost.

Thanks again,
T

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

Post by electro » Mon Feb 04, 2008 7:11 pm

I see what you've done to fix the code. It makes sense to me.

I'm trying now to find good examples of setting up the "initialization".

Can anyone point me to an example somehwere of the initialization, which I can study? (I would prefer to study some examples, then come back with some semi-intelligent questions to post here.)

I use nesasm. Would the initialization be different for each assembler?

Right now I'm just interested in keypad and sound, no video (yet).

Thanks again,
T

P.S. I really did learn a great deal from the previous posts here and in my other thread. KNowing what's happening with the instructions, and then putting together the correct "structure", 2 different things!

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

Post by tokumaru » Mon Feb 04, 2008 9:13 pm

electro wrote:Can anyone point me to an example somehwere of the initialization, which I can study?
Didn't you find the wiki page? Here it is, in case you didn't. You can probably just copy it all, although you should try to understand what the coe is doing.
I use nesasm. Would the initialization be different for each assembler?
Simple code like that doesn't change much from assembler to assembler... You may find some diference regarding the labels, because some assemblers support local labels (I believe that in the wiki code these are the labels starting with "@"), temporary labels, and so on, while others don't. If something goes wrong, changing the names of the labels to simple ones (no special characters or anything) and keeping them unique should solve the problem.

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

Post by electro » Mon Feb 04, 2008 9:36 pm

Thanks for the link.

I have never seen any of this "init" stuff in any of the example code I've looked at. No wonder they didn't work correctly.

What kind of surprises me is that this "init" stuff is extremely important part of an nes code, yet it seemed to be the most difficult thing to find clear examples of.

I love nesdev wiki. (Have you ever wondered what the word "wiki" is? "Wiki" ?! New words for the 21st century. Like "23 skadoo" was in 1924.

I'll really be studying it.

Thank you again for your great help, it's very much appreciated.

T

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

Post by electro » Thu Feb 07, 2008 6:10 am

Here's my latest mess:

Code: Select all

;Nes Gamepad demo program
;-------------------

	.inesprg 1
	.ineschr 0  

	.bank 1
	.org $FFFA
	.dw 0 
	.dw start 
	.dw 0 

	.bank 0
	.org $8000

start:

key_a		EQU	%00000001 ; A button press 
key_b		EQU 	%00000010 ; B 
key_select  	EQU 	%00000100 ; select 
key_start  	EQU 	%00001000 ; start 
key_up  		EQU 	%00010000 ; up arrow 
key_down  	EQU 	%00100000 ; down 
key_left  		EQU 	%01000000 ; left arrow 
key_right  	EQU 	%10000000 ; right 
			
button_state          EQU       $00


    sei        ; ignore IRQs
    cld        ; disable decimal mode
    ldx #$40
    stx $4017  ; disable APU frame IRQ
    ldx #$ff
    txs        ; Set up stack
    inx        ; now X = 0
    stx $2000  ; disable NMI
    stx $2001  ; disable rendering
    stx $4010  ; disable DMC IRQs

vblankwait1:  
    bit $2002
    bpl vblankwait1

    txa
clrmem:
    sta $000,x
    sta $100,x
    sta $200,x
    sta $300,x
    sta $400,x
    sta $500,x
    sta $600,x
    ;sta $700,x  ; Remove this if you're storing reset-persistent data
    inx
    bne clrmem
   
vblankwait2:
    bit $2002
    bpl vblankwait2

loop: 
   jsr updatejoy 

    
   ;Check the state of the right key 
   lda button_state 
   and key_right 
   beq right_not_pressed 
   jsr right_is_pressed 

right_not_pressed: 

   ;Check the state of the left button 
   lda button_state 
   and key_left 
   beq left_not_pressed 
   jsr left_is_pressed 

left_not_pressed: 

   jmp loop ;Go back and keep reading the joypad forever 


updatejoy:

    LDA #1 		  ; first, strobe the joypad 
    STA $4016 
    LDA #0 
    STA $4016

    LDX #$08              ; set X to 8 (the number of times we want to loop) 
    joybuttons: 
    LDA $4016            ; get button state 
    LSR A                   ; shift it into the C flag 
    ROR button_state  ; rotate C flag into our button_state variable 
    DEX                      ; decrement X (our loop counter) 
    BNE joybuttons          ; jump back to our loop until X is zero 

 
 
    rts

 
right_is_pressed: ; play sound when right arrow is pressed.

	lda #$FF   ;
	sta $4000  ;

	lda #%11011011  ; 
	sta $4001  ; 

	lda #$A5
	sta $4002

	lda #$AB
	sta $4003

	lda #%00000001
	sta $4015

	rts

left_is_pressed:

              lda #$FF   ;
	sta $4000  ;

	lda #%11011011  ; 
	sta $4001  ; 

	lda #$A5
	sta $4002

	lda #$AB
	sta $4003

	lda #%00000001
	sta $4015

	rts
It complies but does not work correctly. (Suppose to play a sound when the right or left keypad button is pressed.

(I left the other keypad buttons out for testing purposes).

Thanks for any feedback.

T

Roth
Posts: 399
Joined: Wed Aug 03, 2005 3:15 pm
Contact:

Post by Roth » Thu Feb 07, 2008 6:29 am

Can I recommend splitting this thread from this post...

http://nesdev.com/bbs/viewtopic.php?p=3 ... ght=#30612

... and making it a new topic, with an appropriate name about controller coding? There is some great info here, and it would be alot easier for any newer people to find.

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

Post by blargg » Thu Feb 07, 2008 6:35 am

Best way to debug things is start small and expand. When you have a bug, it could be anywhere, so you have to narrow it down systematically. Insert this debug code at various points, first just after NES initialization, then in a controller handler routine. If it doesn't beep, then it's not getting there.

Best way to debug things is start small and expand. Insert this debug code at various points, first just after NES initialization, then in a controller handler routine. If it doesn't beep, then it's not getting there.

Code: Select all

beep:
	lda #$01   ; Enable square 1
	sta $4015
	lda #$9F   ; Envelope
	sta $4000
	lda #$21   ; Start tone
	sta $4003

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

Post by electro » Thu Feb 07, 2008 6:44 am

blargg wrote:Best way to debug things is start small and expand. When you have a bug, it could be anywhere, so you have to narrow it down systematically. Insert this debug code at various points, first just after NES initialization, then in a controller handler routine. If it doesn't beep, then it's not getting there.

Best way to debug things is start small and expand. Insert this debug code at various points, first just after NES initialization, then in a controller handler routine. If it doesn't beep, then it's not getting there.

Code: Select all

beep:
	lda #$01   ; Enable square 1
	sta $4015
	lda #$9F   ; Envelope
	sta $4000
	lda #$21   ; Start tone
	sta $4003
Great idea. I'll try that.

I'll also post as the other guy suggested above.

Before I do, I was wondering if I have to clear the sound registers, or reset the frame counter/clock divider at the init of my code?

(I have been reading a little over at nullsleep's nes page).

Thanks again,
T

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

Post by electro » Thu Feb 07, 2008 6:56 am

Tried what you suggested, inserting the beep debugger at various places. It works if placed directly after my init stuff, and also in my main loop.

It doesn't work anywhere in my subroutines.

This is a great thing to have learned (the debugger thing).

Thanks again,
T

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

Post by electro » Thu Feb 07, 2008 9:24 am

Here is the latest code. This was all pieced together from code examples by other members here. Although it was all cut and pasted, etc. I still learned what each piece of code is doing, thanks to their help.

The init stuff is something I still have to go over more. Anyway with the help from blargg pointing out that in my masks I was missing the "#". So instead of doing immediate addressing (loading a number), it was reading;

Code: Select all

AND keypress ; absolute adressing. Looks for the "keypress" address in memory, that's not good!
Should be;

Code: Select all

AND #keypress ; immediate addressing. Masks with the designated binary number for the particular keypress.
Here's the code which compiled and was loaded into Jnes (I still will also test in Nintendulator an others).

Code: Select all

;Nes Gamepad demo program
;-------------------

	.inesprg 1
	.ineschr 0  

	.bank 1
	.org $FFFA
	.dw 0 
	.dw start 
	.dw 0 

	.bank 0
	.org $8000

start:

key_a		EQU	%00000001 ; A button press 
key_b		EQU 	%00000010 ; B 
key_select  	EQU 	%00000100 ; select 
key_start  	                EQU 	%00001000 ; start 
key_up  		EQU 	%00010000 ; up arrow 
key_down  	EQU 	%00100000 ; down 
key_left  		EQU 	%01000000 ; left arrow 
key_right   	EQU 	%10000000 ; right 
			
button_state              EQU       $00

    sei        ; ignore IRQs
    cld        ; disable decimal mode
    ldx #$40
    stx $4017  ; disable APU frame IRQ
    ldx #$ff
    txs        ; Set up stack
    inx        ; now X = 0
    stx $2000  ; disable NMI
    stx $2001  ; disable rendering
    stx $4010  ; disable DMC IRQs

vblankwait1:  
    bit $2002
    bpl vblankwait1

    ; We now have about 30,000 cycles to burn before the PPU stabilizes.
    ; Use it to clear RAM.  X is still 0...
    txa
clrmem:
    sta $000,x
    sta $100,x
    sta $200,x
    sta $300,x
    sta $400,x
    sta $500,x
    sta $600,x
    sta $700,x  ; Remove this if you're storing reset-persistent data
    inx
    bne clrmem
   
vblankwait2:
    bit $2002
    bpl vblankwait2

; *** CLEAR SOUND REGISTERS ***
	lda #$00		; clear all the sound registers by setting
	ldx #$00		; everything to 0 in the Clear_Sound loop
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

	lda #$10		; load accumulator with $10
	sta $4010		; store accumulator in $4010
	lda #$00		; load accumulator with 0
	sta $4011		; clear these 3 registers that are 
	sta $4012		; associated with the delta modulation
	sta $4013		; channel of the NES



loop: 
   jsr updatejoy 

   ;Check the state of the right key 
   lda button_state 
   and #key_right 
   beq right_not_pressed 
   jsr right_is_pressed 


right_not_pressed: 
    
   ;Check the state of the left button 
   lda button_state 
   and #key_left 
   beq left_not_pressed 
   jsr left_is_pressed 

left_not_pressed: 

   jmp loop ;Go back and keep reading the joypad forever 


updatejoy:

    LDA #1 		  ; first, strobe the joypad 
    STA $4016 
    LDA #0 
    STA $4016

    LDX #$08              ; set X to 8 (the number of times we want to loop) 
    joybuttons: 
    LDA $4016            ; get button state 
    LSR A                   ; shift it into the C flag 
    ROR button_state  ; rotate C flag into our button_state variable 
    DEX                      ; decrement X (our loop counter) 
    BNE joybuttons          ; jump back to our loop until X is zero 

    rts

 
right_is_pressed: ; play sound when right arrow is pressed.


	lda #$01   ; Enable square 1
	sta $4015  ; this register gets written to first to enable sound channel. Then come the other registers.

	lda #$9F   ; Envelope
	sta $4000
	lda #$21   ; Start tone
	sta $4003
	
	rts
	

	
left_is_pressed:

                lda #$01   ; Enable square 1
	sta $4015  ; this register gets written to first to enable sound channel. Then come the other registers.
	lda #$9F   ; Envelope
	sta $4000
	lda #$21   ; Start tone
	sta $4003

	rts
It doesn't work in Nintenduator, but does work in Nestopia and Jnes.

Now I have to figure out how to stop the sound immediately when the button is up. Now the way it is the sound plays for a set duration.

I guess the "pseudo" code would be something like;

If button_press = #0
then goto "stop sound"

Or would it be some kind of timing thing? (Like play sound for T=X?)

Maybe BEQ to a subroutine that clears the $4015 register?
(In other words clear the $4015 register when the button is up?)

Thanks,
T

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

Post by blargg » Thu Feb 07, 2008 11:05 am

Stop a channel by clearing its length counter. One way is to clear that bit of $4015. Another way is to enable the length counter and load it with the appropriate duration when starting the note, then let it count down to 0 normally. Set bit 4 of $4000 to enable the length counter, then write the appropriate value to $4003 to load the length counter (as well as set the high 3 bits of the period). Yet another is to use the volume envelope by clearing bit 4 of $4000 and setting the decay rate in the low 4 bits. Also, you should write $08 to $4001 (and $4005) to disable the sweep, otherwise they'll silence low notes (really, any value with bit 3 set and bit 7 clear).

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

Post by electro » Thu Feb 07, 2008 5:10 pm

blargg wrote:Stop a channel by clearing its length counter. One way is to clear that bit of $4015. Another way is to enable the length counter and load it with the appropriate duration when starting the note, then let it count down to 0 normally. Set bit 4 of $4000 to enable the length counter, then write the appropriate value to $4003 to load the length counter (as well as set the high 3 bits of the period). Yet another is to use the volume envelope by clearing bit 4 of $4000 and setting the decay rate in the low 4 bits. Also, you should write $08 to $4001 (and $4005) to disable the sweep, otherwise they'll silence low notes (really, any value with bit 3 set and bit 7 clear).
Thank you. I'll digest this some more and re-post.

T

Post Reply