Help with some code for reading keypresses

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

Help with some code for reading keypresses

Post by electro »

Hi,

This code was cut and pasted from "Rwin's" "Jumpy" program which just displayed a little character that jumped up and down and could be moved left and right with the keypad.

I was only interested in the key reading section. I left out the sub routines. That's where I'll be adding code which responds to the keypresses.

My next step is to add in the subroutines such as; if the left key is pressed write to one of the sound channels, if the right key pressed then write to another sound channel, etc.

Code: Select all




;;---CODE START---;; I copied parts of this from -'Rwin's "Jumpy" rom dated 04/11/2002

	.inesprg 1
	.ineschr 0 

	.bank 1
	.org $FFFA
	.dw 0 ; no VBlank routine
	.dw keypress 
	.dw 0

	.bank 0
	.org $8000

keypress:

	jsr resetjoy ; goto to the routine which resets the joypad #1 status

	lda $4016 ; checks if "a" button is pressed
	bne abutton ; goto jump routine if "a" is pressed

	lda $4016 ;	
	lda $4016 ;
	lda $4016 ;
	lda $4016 ;
	lda $4016 ;
	lda $4016 ; checks if left is pressed
	bne lft	  ; if left is pressed goto the routine for moving the sprite to the left
		
	lda $4016 ; checks if right is pressed
	bne rt    ; if right is pressed goto the routine for moving the sprite to the right


	jmp keypress ; Jump to the begin of the main loop


	resetjoy:
	
	lda #$01  ; Writing #$01 to $4016 (=joypad1) followed by writing #$00 
	sta $4016 ; to $4016 resets status of joypad 1
	lda #$00
	sta $4016
	rts


lft: 	; this is where some other code will respond to left arrow keypress

	jmp keypress

rt:	; right arrow keypress response

	jmp keypress

abutton:	; a button sub routine

	jmp keypress


Where we have;

lda $4016
lda $4016
(continued 6 times, 8 times total in the "keypress" section)

One "lda $4016" for each of the 8 buttons of the keypad?

Is this storing a zero in the accumulator each time?
Is it clearing the bit for each button?

I'm not a programmer, I apologize for any really confused questions I might ask (they'll probably be a lot of them.)

Thanks in advance,
T

[/code]
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: Help with some code for reading keypresses

Post by Disch »

electro wrote: One "lda $4016" for each of the 8 buttons of the keypad?
Yes. One button at a time.
Is this storing a zero in the accumulator each time?
No. Due to some bits of $4016 being open bus, what you actually get is $40 if the button is not pressed, and $41 if the button is pressed. Note that *neither* of these are zero, so a BNE following your LDA will not work. You'll need to get the status of the low bit somehow.

One way to do this is to mask out the low bit with an AND operation

Code: Select all

LDA $4016
AND #$01      ; only want low bit
BNE wherever  ; jump if button pressed
Another way is to shift the low bit into the carry flag by way of LSR:

Code: Select all

LDA $4016
LSR A         ; low bit moves into C flag
BCS wherever  ; jump if C set (button was pressed)
Is it clearing the bit for each button?
kinda.... I'm not sure I'm understanding what you're asking here... but anyway what happens is this:

Strobing the joypad (writing 1, then 0 to $4016) takes a "snapshot" of what buttons the user is currently pressing. Once this snapshot is taken, it won't be changed until another snapshot is taken. When you read $4016, you're not really getting the realtime state of the buttons, you're getting that snapshot.

Once you read a button status from $4016, you cannot read that paticular button state from $4016 again unless you take another snapshot and start the reading process all over.

This makes $4016 not useful at all for quick access to button status. Often in a game you'll need to check button status in several areas, and doing over half a dozen $4016 reads each time isn't very practical. Because of this... games generally keep a variable in RAM where they dump the button states to. Then they can read any button's state any time without hassling with $4016. Though you'd still need to strobe $4016 and read from it every once in a while to get fresh info -- games do this once every frame.

A way this might be accomplished:

Code: Select all


; assuming button_state is a variable in RAM
;   (this routine is not optimized -- it's just to show the idea
;   there are tricks and stuff to speed it up but I figure it's better
;   for you to just grasp the concept first)

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

  ; now we're going to loop 8 times (once for each button), and
  ; rotate each button's status 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
If you call that routine once per frame... 'button_state' will be refreshed with the status of all the buttons -- each bit being one of the buttons. You can then use an AND to mask out which button you're interested in:

Code: Select all

  LDA button_state
  AND #%10000000  ; check high bit (right)
  BNE right_is_down

  LDA button_state
  AND #%00001000
  BNE start_is_down

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

Post by electro »

Thanks a lot for your help. This is a lot fancier than what I've seen.

I think I'm understanding most of what you described. I have to digest it.

When you say "what you actually get is $40 if the button is not pressed, and $41 if the button is pressed."

Those hex numbers are being stored in the accumulator?

If so then I can understand why we're doing the other stuff such as;
AND #01 to get the low bit of a button press.

I still have to go over the ROR stuff.

Thanks again, very very educational. Appreciate it.

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

Post by tepples »

electro wrote:When you say "what you actually get is $40 if the button is not pressed, and $41 if the button is pressed."

Those hex numbers are being stored in the accumulator?
Yes. You get some of the bits from the controller circuitry, and you get some of the bits left over from the last byte of the instruction.
I still have to go over the ROR stuff.
ROR shifts the entire byte to the right by 1 bit, puts the old carry as the new bit 7, and puts the old bit 0 as the new carry. ROL does the opposite. LSR is a shortcut for CLC ROR, and ASL is CLC ROL.
User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro »

tepples wrote:
electro wrote:When you say "what you actually get is $40 if the button is not pressed, and $41 if the button is pressed."

Those hex numbers are being stored in the accumulator?
Yes. You get some of the bits from the controller circuitry, and you get some of the bits left over from the last byte of the instruction.
I still have to go over the ROR stuff.
ROR shifts the entire byte to the right by 1 bit, puts the old carry as the new bit 7, and puts the old bit 0 as the new carry. ROL does the opposite. LSR is a shortcut for CLC ROR, and ASL is CLC ROL.
Thank you. I see.

Code: Select all


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 



So in this loop (above) we're shifting the low bit of 01000000 or 01000001 into the carry bit using LSR.
Then shifting the carry bit using ROR into the button_state variable.

*After ROR does its thing the carry bit is now the high bit?

Doing that eight times, once for each button.

I think I understand now, or starting to.

But how did you guys know it was actually a $40 for no press and a $41 for a press?

Thanks, extremely helpful.

T[/code]
Last edited by electro on Thu Jan 31, 2008 6:45 am, edited 3 times in total.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

electro wrote:The AND #01 I've been seeing in people's code makes sense now since we only want the low bit. But how did you guys know it was actually a $40 for no press and a $41 for a press?
Some of us ran tests on NES hardware. Others watched (especially unlicensed) games run in emulators, looking for why they expected exactly $41 to continue. Then the NES reverse engineering community investigated further, and found that the three bytes on the data bus prior to this were $A5 $16 $40 (LDA $4016), resulting in a hypothesis of bus capacitance (the $40). Further tests confirmed this behavior: reading the controller port with an indexed mode with a page crossing (e.g. LDX #$96 LDA $3F80,X) returned something other than $40 or $41 in register A.
Are there any tutorials or other material on reading the gamepads?
You could try the wiki.
User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro »

Code: Select all


 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 

Just wondering if I understand this correctly.

If at the start of the loop the accumulator had #%01000001 (#$41) in it.
After LSR we would now have: #%00100000 (#$20) and the carry flag would be set?

Then, after ROR, the carry bit gets put into the "high bit" position (position 7), while also shifting all the bits over again to produce this: #%10010000 (#$90), stored in the button_state variable after the first run through the loop)?

Also, after ROR, would the carry flag then be automatically cleared since the low bit is a zero (after the first run through the loop)?

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

Post by tepples »

electro wrote:Just wondering if I understand this correctly.

If at the start of the loop the accumulator had #%01000001 (#$41) in it.
After LSR we would now have: #%00100000 (#$20) and the carry flag would be set?
Correct so far.
Then, after ROR, the carry bit gets put into the "high bit" position (position 7), while also shifting all the bits over again to produce this: #%10010000 (#$90), stored in the button_state variable after the first run through the loop)?
Almost. What was already in button_state on the previous run through the loop? Or to put it another way, "shifting all the bits" of what?
Also, after ROR, would the carry flag then be automatically cleared since the low bit is a zero (after the first run through the loop)?
It depends on what was in button_state before the first run.
User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro »

tepples wrote: Almost. What was already in button_state on the previous run through the loop? Or to put it another way, "shifting all the bits" of what?

I thought that "ROR button_state" meant that we perform ROR on the number in the accumator after "LSR A", and then store the result in the button_state variable.

In other words I thought we were performing ROR on this number:
#%00100000, which would have produced this if the C flag was set:
#%10010000 ?.

Then, I thought that number would be stored the button_state variable.

(I guess that would have been:
LDA #$41
LSR A
ROR A
STA button_state

(Which would make no sense...)

Ok, I think I see where I went wrong. In the "joyloop", the "ROR button_state" is perfroming ROR on the number stored in the button_state variable, not the contents of the accumulator after "LSR A" ?

In the "LSR A" of "joyloop", we're just using the low bit to set or clear the C flag. In the case of $41 the C flag would be set after "LSR A", and then using ROR the bit in the C would be added into the high bit position of what was already in the button_state variable, (and the bits of what ever was in the button_state variable would have also been shifted to the right) ?

Thanks for the help.

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

Post by tepples »

electro wrote:I thought that "ROR button_state" meant that we perform ROR on the number in the accumator after "LSR A", and then store the result in the button_state variable.
The 6502 instruction:

Code: Select all

  ROR button_state
What you thought was happening:

Code: Select all

  ROR A
  STA button_state
What is actually happening is closer to this:

Code: Select all

  PHA
  LDA button_state
  ROR A
  STA button_state
  PLA
Ok, I think I see where I went wrong. In the "joyloop", the "ROR button_state" is perfroming ROR on the number stored in the button_state variable, not the contents of the accumulator after "LSR A" ?
Correct.
In the "LSR A" of "joyloop", we're just using the low bit to set or clear the C flag. In the case of $41 the C flag would be set after "LSR A", and then using ROR the bit in the C would be added into the high bit position of what was already in the button_state variable, (and the bits of what ever was in the button_state variable would have also been shifted to the right) ?
Yes.
User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro »

Thanks for the help, really appreciate it.

I understand this now:

Code: Select all

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

  ; now we're going to loop 8 times (once for each button), and 
  ; rotate each button's status 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 
And think I understand what's happening here now as well:

Code: Select all

LDA button_state 
  AND #%10000000  ; check high bit (right) 
  BNE right_is_down 

  LDA button_state 
  AND #%00001000 
  BNE start_is_down 
So the "AND" is a kind of comparison operation?

In the first instance using the "mask" above, regardless of any other bits in button_state, if there is a 1 in the 7th position (of button_state) than we will branch to the subroutine "right is down"?

Same with second instance, if there is a 1 in the 3rd poistion we can branch to "start_is_down"?

Thanks again, extremely helpful stuff.

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

Post by tepples »

electro wrote:And think I understand what's happening here now as well:

Code: Select all

LDA button_state 
  AND #%10000000  ; check high bit (right) 
  BNE right_is_down 

  LDA button_state 
  AND #%00001000 
  BNE start_is_down 
So the "AND" is a kind of comparison operation?
The AND instruction performs a bitwise logical conjunction, the same operation as the & operator in the C language.

This code:

Code: Select all

  LDA #%00111100
  AND #%00001111
leaves %00001100 ($0C) in register A.
In the first instance using the "mask" above, regardless of any other bits in button_state, if there is a 1 in the 7th position (of button_state) than we will branch to the subroutine "right is down"?
Yes. When there is a 0 in bit 7, the AND results in %00000000 ($00) in A. Like other ALU results of zero, this puts 1 in P.Z, so BNE (branch if P.Z is 0) does nothing.
User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro »

Now I guess I need to know what the binary number is for each individual key on the keypad, so that I can then "mask" them with the "AND" operation?

I was given "right" and "start".

Code: Select all

  LDA button_state 
  AND #%10000000  ; check high bit (right) 
  BNE right_is_down 

  LDA button_state 
  AND #%00001000 
  BNE start_is_down




Then setting up my button_state variable in RAM, would that be an equate?

Like button_state = a memory address in RAM?

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

Post by tepples »

electro wrote:Now I guess I need to know what the binary number is for each individual key on the keypad, so that I can then "mask" them with the "AND" operation?
Yes. You can come up with these by looking at the order that keypresses come out of a standard controller. And it's best to assign names for constants like these:

Code: Select all

KEY_START = %00001000
KEY_RIGHT = %10000000
Then you can use these constants in code:

Code: Select all

  AND #KEY_RIGHT
Then setting up my button_state variable in RAM, would that be an equate?

Like button_state = a memory address in RAM?
That will work. Some might prefer a "reserve uninitialized space" command, such as this, in CA65 syntax:

Code: Select all

.segment "ZEROPAGE"
button_state: .res 1
User avatar
electro
Posts: 132
Joined: Tue Jan 29, 2008 11:12 am
Location: New York

Post by electro »

Great. Thanks for the link, very helpful.

Code: Select all

key_a = %00000001 ; A button press

key_b = %00000010 ; B

key_select = %00000100 ; select

key_start = %00001000 ; start

key_up = %00010000 ; up arrow

key_down = %00100000 ; down 

key_left = %01000000 ; left arrow

key_right = %10000000 ; right
Wondering now how I might read combinations of buttons. Would I need to store another variable in ram called button_state2 ?

Well, that's for later. I should just stick to trying to get this working.

Thanks again,
T
Post Reply