The thing is I'm not even sure this is simple... We've seen so many newbies stumbling with this method of reading the controller... it's very confusing because of the spaghetti code it generates.bunnyboy wrote:Newbies need simplicity to learn first, being "professional" is overwhelming instead of teaching.
First time Homebrewer
Moderator: Moderators
Comment a controller reading routine and it shouldn't be hard at all. You need to have an idea of simple instructions, X indexing, and memory locations and loops and you can read a controller correctly. If you're doing stuff on the screen from input, you should be able to understand the routine to read it. The $4016 hardcoded reads don't make it easier, it just makes people think "Well what was different from last time? It's the same thing."
Glad I could help, and it sounds like you're doing some exploring which is nice to do.I know you basically said the same thing as the tut but I got your way more. Thanks ill keep you posted if I have more trouble.
Re: Controller Reading.
I'll explain the other way. He's mentioned C, so I assume he's done some stuff in it. Bit shifts may already be in his repertoire, and if not it may not be as huge a leap for him as others.
We've got the benefit of being a forum, so we can tailor our teaching to the person.
Tokumaru, I actually wouldn't mind seeing a tutorial by you. You know this stuff better than I do AND provide shorter (and easier to understand) explanations than I do. Else one day I really will write my own. And I'll teach things in a different order than this post, I guess.
Camronas, this way is worth reading about even if you have already got the other way working. Also this turned into a long post (I warned of this!), and it may help to read a little at a time to let it sink in.
A quick note about this post: I haven't had time to thoroughly check it for errors. I promise I'll come back and fix it if I find some later, but just be aware there may be parts where there are nine bits in an LDA instruction or something. Hopefully nothing more serious than that, but I wrote this all in one go with not a lot of proofreading...
Edit: Also, shoot, I just realized I never mentioned how to USE the buttons variable once this code is finished. I'll add a quick note at the end.
That is correct. There is no difference between how you get the bit for each new button.I cant see the difference between the two function to be able to see what needs to change, this code works though.
$4016 is a special address for interfacing with NES hardware. Every time you read from it, its value is updated to the state of the next button. So even in this string of code that looks like it would be redundant:
Code: Select all
lda $4016
lda $4016
The order is always:
A
B
Select
Start
Up
Down
Left
Right
What this code does:
Code: Select all
LDA #$01
STA $4016
LDA #$00
STA $4016 ; tell both the controllers to latch buttons
I will now pause on controller reading to review some things about the 6502 that are important to understand for the other controller method.
There is a byte internal to the 6502 CPU. Each bit represents a different thing that is either true (1) or false (0). These bits are called the processor status flags.
Code: Select all
From Assembly in One Step
bit -> 7 0
+---+---+---+---+---+---+---+---+
| N | V | | B | D | I | Z | C | <-- flag, 0/1 = reset/set
+---+---+---+---+---+---+---+---+
The easy one to explain is Z. It is set if the result of the last instruction was zero, and cleared if the result was not zero.
Code: Select all
lda #$00; Z is set.
lda #$FF; Z is cleared.
lda #$XX;where XX is anything but 00. Z is cleared
Code: Select all
lda #$01
sec;We set the carry before a subtract
sbc #$01;1-1=0.
;The result of the last operation was 0. Z is set.
Code: Select all
ldx #$01;X is another register that holds a value like A. There is also Y.
dex;Subtracts one from X.
1-1 = 0; Z is set.
As you know a byte is eight bits. But ROR and ROL actually work on NINE bits. Say, WHAT?
The ninth bit is C in the processor status byte. So let's say we have a this byte in A: #%10000000
It looks like this to ROL and ROR:
Code: Select all
C = The carry bit. If cleared, it is zero. If set it is 1.
C10000000C
Let's look at this code:
Code: Select all
clc; Clears the carry flag. C is 0.
lda #%1000000R;(R is 0. I have marked it R for a reason)
ROR a;Rotates the bits in A to the right.
;A now has this byte: #%01000000
;What about the carry? It gets the bit that was on the very right of A. (marked R in the example).
;R was 0. C is now 0.
;The carry is now clear.
Code: Select all
sec;Sets the carry flag. C is 1.
lda #%10000000;No need to mark R now, right?
ROR a
;A now has this byte: #%11000000
;The carry was set instead of cleared when we started, so a 1 was moved into A's leftmost bit instead of a zero.
;Because a zero was in A's rightmost bit, the carry is now clear.
Code: Select all
clc
lda #%10000000
rol a
;A = #%00000000
;Carry is set.
Now for two that are a little different. LSR and ASL. LSR shifts bits right, but puts a ZERO into the left most bit of the byte instead of what was in C. The right most bit of A is still moved into C, however.
ASL shifts bits left but puts a ZERO in the right most bit of the byte instead of what was in C. The left most bit of A is still moved into C.
Similar enough, right? Here are some examples just in case.
Code: Select all
sec
lda #%10000000
ASL a
;A now has #%00000000. The zero flag was set! Don't forget about that.
;C has 1.
Code: Select all
clc
lda #%10000000
ASL a
;A now has #%00000000.
;C has 1.
LSR is similar.
Code: Select all
;State of carry doesn't matter
lda #%00000010
lsr a
;A is #%00000001
;Carry is clear.
Code: Select all
;State of carry doesn't matter
lda #%00000001
lsr a
;A is #%00000000
;Carry is set.
Let's construct a simple endless loop using a branch.
Code: Select all
label:
lda #$00
beq label;This jmps to label if Z is set. It continues down if Z is clear.
Code: Select all
label:
lda #$FF
bne label;This jmps to label if Z is clear. It continues down if Z is set.
Now let's construct a regular loop. Sort of like the for loop in C.
Code: Select all
ldx #$02
label:
lda $4016
dex
bne label
clc;Just extra code.
From the explanation earlier, you may realize we actually read two button states just now. But we didn't store them anyplace we could use them later.
Code: Select all
LatchController:
LDA #$01
STA $4016
LDA #$00
STA $4016 ; tell both the controllers to latch buttons
sta buttons;A variable in RAM. All buttons are now unpressed.
ldx #$08;Why not read 8 button states?
buttonreadloop:
asl buttons;We can ASL bytes other than A. It works the same just using RAM instead of A.
lda $4016
and #%00000001;We only care about this one bit.
ora buttons
sta buttons
dex
bne buttonreadloop
We loop again. We shift buttons so that the state of the A button is now in the part of the bit marked A. #%000000A0. It puts a 0 in place for the next button to set if pressed because of how asl works. We load the state of the button through $4016. We isolate the bit. We ora, so now A contains the state of both buttons. Then we store A in buttons so the actual buttons variable contains the state of both buttons.
We keep looping until we have all eight buttons.
If that was hyper confusing, give it some time to digest. If you need an alternate explanation on an instruction (ROR, ASL, ETC), let me know. Once you get those, the controller code should "click" after a little thought, but if not ask away! As always, sorry for writing so much.
Edit: To use the buttons variable: Load it, AND out the bits you don't want to check. and #%1000000 would give you the A button, because all the others will end up zero.
Then you can use BEQ to branch passed the code that requires A, because if A was pressed the result of the beq would not be zero.
-
Dr. Floppy
- Posts: 47
- Joined: Mon May 09, 2011 7:02 pm
This is awesome!Kasumi wrote:Code: Select all
LatchController: LDA #$01 STA $4016 LDA #$00 STA $4016 ; tell both the controllers to latch buttons sta buttons;A variable in RAM. All buttons are now unpressed. ldx #$08;Why not read 8 button states? buttonreadloop: asl buttons;We can ASL bytes other than A. It works the same just using RAM instead of A. lda $4016 and #%00000001;We only care about this one bit. ora buttons sta buttons dex bne buttonreadloop
Is there an expanded version of this to control for redundant reads over several frames (as Tokumaru mentioned)? Or that infernal DPCM glitch that causes phony reads from the Right button?
First I'd like to point out that this routine is pretty inefficient... the following one is shorter, faster, and even reads Famicom controllers that go on the expansion port, which the code above doesn't:Kasumi wrote:Code: Select all
LatchController: LDA #$01 STA $4016 LDA #$00 STA $4016 ; tell both the controllers to latch buttons sta buttons;A variable in RAM. All buttons are now unpressed. ldx #$08;Why not read 8 button states? buttonreadloop: asl buttons;We can ASL bytes other than A. It works the same just using RAM instead of A. lda $4016 and #%00000001;We only care about this one bit. ora buttons sta buttons dex bne buttonreadloop
Code: Select all
ldx #$09 ;strobe the controllers and initialize the counter
stx $4016
dex
stx $4016
buttonreadloop:
lda $4016 ;get the state of the button
and #%00000011 ;keep only the 2 lowest bits
cmp #$01 ;if any of those 2 bits are set, the carry gets set
rol buttons ;put the state of the button with the others
dex ;move on to the next button
bne buttonreadloopNext, there's no need to clear the button states, because we will insert 8 new bits into it, effectively kicking all the old bits out.
Then we proceed to the loop. There we read the state of a button, and since there are Famicom controllers that return the button states in the second bit of $4016, you want to keep the 2 lowest bits, not only the first. Then we use another "trick": by comparing the value to 1, we can make the carry flag reflect the state of the button no matter if it came in bit 0 (regular controller) or bit 1 (expansion port Famicom controller). Think about it: if a a button is pressed and a regular controller is used, our value will be 1 (%00000001), if an expansion port controller is used, the value will be 2 (%00000010). Both values are larger than 0, so when we compare it to 1 we are making sure that it's at least equal to or larger than 1, in which case the carry gets set. If the value was smaller than 1 (which would happen only if no button was pressed), the carry would be clear.
With the state of the button in the carry flag, we are ready to put it in a variable, along with the other button states. There's no need to load it from memory or anything, you can directly put the new bit in the variable with a rotate instruction.
You can handle that by saving the previous state of the controller and doing some bit operations between it and the new state. Here's an example:Dr. Floppy wrote:Is there an expanded version of this to control for redundant reads over several frames (as Tokumaru mentioned)?
Code: Select all
lda buttons ;make a copy of the old state
sta oldbuttons
jsr readcontroller ;read the new state
lda oldbuttons ;get the old state
eor #$ff ;turn buttons that were down into 0 and the others into 1
and buttons ;erase from the current state the buttons that were down last time
sta newbuttons ;save the result in another variableWe usually handle this by reading the controller multiple times, until 2 consecutive reads return the same result. Something like this:Or that infernal DPCM glitch that causes phony reads from the Right button?
Code: Select all
jsr readcontroller ;read the controller
validatebuttons:
ldy buttons ;make a copy of what was read (use Y because A and X are used in the subroutine)
jsr readcontroller ;read again
cpy buttons ;compare the previous read with the new one
bne validatebuttons ;if they differ, go back and try againThat's a very good idea tepples... It's faster (we get rid of the DEX inside the loop) and frees one register. With this, the subroutine could look like this:
Code: Select all
readcontroller:
lda #$01
sta buttons ;initialize the buffer with a flag
sta $4016
lda #$00
sta $4016
buttonreadloop:
lda $4016
and #%00000011
cmp #$01
rol buttons
bcc buttonreadloop ;loop if the flag wasn't shifted out yet
rtsHeh. But the code becomes a byte smaller if you DO use X, but only for the setup.
dex vs. lda #$00. You all know you need that byte! 
And considering this probably wouldn't run when something else uses X, you might as well.
Optimization is fun.
It's also amazing how much I still have to learn, because Tepples' trick would not have come to me. I would have had to read specifically about it.
Maybe we need a topic like this thread around here.
Code: Select all
readcontroller:
ldx #$01
stx buttons ;initialize the buffer with a flag
stx $4016
dex
stx $4016
buttonreadloop:
And considering this probably wouldn't run when something else uses X, you might as well.
Optimization is fun.
Maybe we need a topic like this thread around here.
I have used it before, and also the opposite trick, which you use to detect when a byte is empty, rather than full: when you shift the first bit out, insert a one at the other end. That bit will prevent the number from becoming 0 before all the bits have been shifted out. When the bit buffer finally becomes 0 you'll know that the flag has been shifted out, and all meaningful bits have been used up.Kasumi wrote:Tepples' trick would not have come to me.
I never thought about using the trick for reading the controller though, and that's the perfect kind of situation for it.
I love that topic. A lot of the tricks shown there are useful on the NES too.Maybe we need a topic like this thread around here.
YEP!Camronas wrote:My god this is confusing lol as I'm sure it was for everyone else when they started
Also Camronas, here's some assembly tuts that I found very useful when I started learning. Just sent these to some friends the other night who are starting to learn.
http://bbitmaster.com/neshackingtutorial1.txt
http://bbitmaster.com/neshackingtutorial2.txt
These do a good job of teaching the bare, bare basics of 6502 and gearing it toward the NES in general. Also, have yo downloaded the 6502 simulator yet?
http://exifpro.com/utils.html
This is an awesome little program and I encourage you to learn how to use it to write little test programs, also how to debug those programs and see what's happening in memory as your program counter rolls through your opcodes. I still find it useful now to write little tests for pseudocode that I'm pondering. It gives me a chance to test it without plugging it strait into my large program and potentially doink something up.
I guess I should post this here rather than create a new topic. The input code from the back and forth above doesn't seem to like the famicom 4 player adapter in FCEUX. When it's set as attached in the drop down, up on player 2's controller also triggers right. Is there some reason for this I'm not getting?
Edit: It seems it was using my player 4 keybinding which happened to be on the same key.
Either way as someone who would like to support both the 4 score and the famicom 4 player adapter without button interference from multiple players at once is there some other way to do this since my old input code apparently wouldn't have liked the famicom either?
Edit: It seems it was using my player 4 keybinding which happened to be on the same key.
Complicating factors:
I recommend two different reading loops, one for ordinary operation:
- When one or two players are playing, Famicom controllers 3 and 4 should control players 1 and 2. But when more than two players are playing, Famicom controllers 3 and 4 should control players 3 and 4.
- The Four Score can be switched on or off while the power is on, so detection in the init code might not be enough.
- The Four Score is slower to read, but it provides fairly reliable detection of DMC DMA bit deletions.
I recommend two different reading loops, one for ordinary operation:
- Toggle strobe to 1 then 0.
- Read D0 and D1 from $4016 and $4017 eight times.
- Repeat steps 1 and 2, compare, and use the previous frame's keypresses if they differ.
- If a 3- or 4-player game has not been started, OR player 3 on top of player 1 and player 4 on top of player 2.
- Toggle strobe to 1 then 0.
- Read D0 from $4016 and $4017 24 times.
- Check the signature for each of the two ports; if it differs, use the previous frame's keypresses.