This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Joystick read without using temp RAM intermediate.

Joystick read without using temp RAM intermediate.
by on (#36699)
Thought I'd share my fast joy read routines. Last night I realized that
cpx can be used for both reading the joy value and setting the
carry bit. The trick is to store the upper 7 bits of the joypad register in
x.

Only thing, the carry inverts the on/off, so a final eor is used to invert them back.

Tested and works so far on FCE, Nestopia, JNES.. Not sure abotu real NES yet?

I unrolled the loop, but could use y for loop count to trade space for speed.

Crash

;=====================================
;=== NES JoyPad Registers
;=====================================
JOY_PAD0 = $4016
JOY_PAD1 = $4017

JOY_A = %10000000
JOY_B = %01000000
JOY_SELECT = %00100000
JOY_START = %00010000
JOY_UP = %00001000
JOY_DOWN = %00000100
JOY_LEFT = %00000010
JOY_RIGHT = %00000001


;=====================================
; read joypad values and store into a, and
; stor into ram vale R_JOY0 or R_JOY0 + 1
;=====================================
.macro readJoy whichJoy
ldx #$01 ;strobe joypads 1 & 3
stx JOY_PAD0 + whichJoy
dex
stx JOY_PAD0 + whichJoy

lda JOY_PAD0 + whichJoy
tay
and #$fe ;save out other bits as refernce val for compare
tax ;store compare val in x
tya
and #$01 ;restore an accum first bit

cpx JOY_PAD0 + whichJoy
rol A
cpx JOY_PAD0 + whichJoy
rol A
cpx JOY_PAD0 + whichJoy
rol A
cpx JOY_PAD0 + whichJoy
rol A
cpx JOY_PAD0 + whichJoy
rol A
cpx JOY_PAD0 + whichJoy
rol A
cpx JOY_PAD0 + whichJoy
rol A
eor #$7f ;invert 7 other bits
sta R_JOY0 + whichJoy
.endmacro
Re: Joystick read without using temp RAM intermediate.
by on (#36702)
CrashTest wrote:
Not sure abotu real NES yet?

Well, that has to be verified, because I don't know if you can trust that the other bits (1 to 7) will remain the same for all reads...

by on (#36704)
Clever. Are you sure the open bus value won't change on you in mid-read? I tested it and it works on my NES, though it's hard to rule out those other bits changing only rarely. Is controller reading really a significant bottleneck in game performance? Remember that it doesn't need to be done during vblank. This might be useful for the DMC-fortified controller read. I'll have to investigate that next.

Apparently you need to OR bits 0 and 1 to read some/all Famicom controllers, so I modified it to OR them together (I could only test in an emulator, as I don't have a Famicom). Fortunately, that didn't change the nifty unrolled portion of your original code. I also modified the macro to not rely on any predefined symbols, and not to write its result to memory. Finally, it puts the controller bits in the standard order, with A at the bottom (the Famicom handling made that just as efficient as the other order). And it preserves the Y register.
Code:
; Quickly reads 8 bits from JOY into A.
; Supports NES and Famicom controllers.
; Controller must have been strobed already.
; Preserved: Y
.macro read_raw_joy JOY
        ; Read first bit and other bits in register
        lda JOY
        tax             ; save other bits
        and #$03        ; carry if bits 0 or 1 set
        cmp #$01
        txa             ; X = state of other bits
        and #$FC
        tax
        ror a           ; first bit
       
        ; Compare first value with new value to put
        ; inverted state of (bit 0 OR bit 1) into carry
        cpx JOY
        ror a
        cpx JOY
        ror a
        cpx JOY
        ror a
        cpx JOY
        ror a
        cpx JOY
        ror a
        cpx JOY
        ror a
        cpx JOY
        ror a
        eor #$FE        ;  undo inversion
.endmacro

; Quickly reads first controller into A.
; Preserved: Y
read_joy:
        ; Strobe both controllers
        lda #$01
        sta $4016
        lsr a
        sta $4016
       
        ; Read first controller
        read_raw_joy $4016
       
        ; Could read second controller here:
        ;read_raw_joy $4017
       
        rts

EDIT: how about this, for reading a single controller? It reads the other bits while the strobe line is set, which means the controller's shift register isn't clocked. So no need for special handling of the first bit. Nice!
Code:
; Strobe controller
ldx #$01
stx $4016
lda $4016   ; read other bits; won't clock register
dex
stx $4016
and #$FC    ; X = other bits
tax

cpx $4016
ror a
... (repeated 8 times)
eor #$FF    ; uninvert all bits

by on (#36707)
Thats pretty useful to be able to preserve y like that, and to get the standard ordering. Thanks for verifying on a real NES too! :)

I think I agree overall joy reading isn't such a bottleneck, even for reading 4 controllers maybe you'd save a few scanlines at most with this approach.

I just figured I'd get the approach out there and contribute to the scene a little. :)

by on (#36720)
I have to admit this is an awesome use of the CPX instruction, but you have to store the result in memory sooner or later anyway, and very often to and the result with the inverted controller read of last frame in order to detect "edges". So I don't see much advantages over doing it the standard way. I can read the controller and preserve both X and Y the standard way.

Also I'm pretty sure that chances the highest bits of $4016 reads changes are very low. I guess some games (any title anyone ?) relies on them to be $4x (from the last byte of lda $4016 instruction), which is a bad thing, but they do it that way anyway.

by on (#36727)
The main point of CrashTest's routine is that it's about 2x faster than the fastest controller read routine we had before, not that it doesn't use a byte of memory during reading; not using a byte simply means it's faster. I've tried it in the DMC-fortified read and it halves the time to around 370 clocks. It's quite an improvement, and could even be a few clocks faster if it assumed a value for the upper 6 bits.

by on (#36739)
On the Famicom, bit 2 of $4016 has the status of controller #2's microphone, which can change in the middle of reading. Also, anything plugged into the expansion port can screw things up. On the NES, however, this technique should work just fine.

I would suggest getting the status of the upper bits before sending the strobe signal:
Code:
lda $4016
and #$FC
tax
lda #1
sta $4016
lsr
sta $4016
cpx $4016
; etc.

In theory, reading $4016 while the strobe bit is set will cause the A button status to be returned. Whether the controller's shift register is clocked or not may depend on the type of controller (standard controllers don't get clocked, but I don't think it can be guaranteed that all NES controllers behave the same way since games aren't supposed to read while strobing).

by on (#36740)
The Famicom microphone/expansion port kills it for me; who wants to make a game that doesn't work on the Famicom, when it would otherwise? Also good point about reading the other bits before asserting strobe, since it makes no difference for this purpose.

by on (#36764)
blargg wrote:
The Famicom microphone/expansion port kills it for me; who wants to make a game that doesn't work on the Famicom, when it would otherwise? Also good point about reading the other bits before asserting strobe, since it makes no difference for this purpose.


I think to make it work on Famicom you'd need something like your DMC fortified approach. Anyway you slice it, the probability of having least least 2/3 or 3/4 reads be equal is probably reliable enough. Also, it might be worth using one approach for $4016 and a different approach for $4017 depending on the ports reliability/ other uses.

by on (#36772)
Do we know what the physical dynamics are of the microphone in the Famicom? The critical period of this accelerated routine is 58 cycles (if i did the math right), or 31kHz -- do we have to worry about audio input to the mic at those frequencies?

by on (#36789)
blargg wrote:
The Famicom microphone/expansion port kills it for me; who wants to make a game that doesn't work on the Famicom

The people who make those PAL-only demos that need all 70 lines of vertical blanking time, for one thing.

by on (#36793)
The Famicom microphone could change the state of a bit at any time, regardless of the frequency of the sound. Having it fail any of the time, even if rare, wouldn't be acceptable for many games. Reading no fewer than three times can compensate for up to two sources of change in the reading, one of which is always the controller itself changing between reads. If you want to handle the DMC as well, you have three sources of possible change, which seems to require at least five reads if each is assumed to change only one reading each.

This should still be useful for high-performance one-player games, especially those that use DMC.

by on (#36795)
So if you're going to have a DMC fortified reading routine anyway and still want to be fast enough, one should use that unrolled loop ? That sound quite usefull so that you save the result to RAM only after all 3 of the needed reading.
Anyway I bet it's shorter to read the controller the traditional way one time rather than the super-fast unrolled way 3 or 4 times.

I'm not sure how the microphone works, but I guess '1' means there is some noise and '0' means no noise, right ? If that's so, it's state probably won't change repeatedly within a few microseconds, so I guess it's safe to assume that it's state won't change more than one time when reading the controller 3/4 times.

by on (#36796)
Bregalad wrote:
So if you're going to have a DMC fortified reading routine anyway and still want to be fast enough, one should use that unrolled loop?

One would use CrashTest's fast routine for reading player 1 of a high-performance game, REGARDLESS of DMC, since it's about twice as fast as the normal read routine. If using DMC, one must read the controller up to four times regardless of which routine is used; even a "standard" controller read routine suffers from this.
Bregalad wrote:
Anyway I bet it's shorter to read the controller the traditional way one time rather than the super-fast unrolled way 3 or 4 times.

And it's even faster to use CrashTest's routine once rather than the traditional way.
Quote:
I'm not sure how the microphone works, but I guess '1' means there is some noise and '0' means no noise, right ? If that's so, it's state probably won't change repeatedly within a few microseconds, so I guess it's safe to assume that it's state won't change more than one time when reading the controller 3/4 times.

Even one change by the microphone requires that the controller be read up to four times (WITHOUT any DMC playing; adding that in could require even more reads). But then if you have four reads, there's more of a window for microphone alteration; ~92*4=368 clocks, which comes out to 0.0002 seconds. A sound at 2432 Hz would cause a change every 0.0002 seconds, and such sounds are common. So that pretty much rules out using this for the second controller on a Famicom with the microphone.

by on (#36849)
blargg wrote:
The Famicom microphone could change the state of a bit at any time, regardless of the frequency of the sound.


I think the question I was trying to ask is what's the frequency response of the microphone? 30kHz sounds aren't that common; and the probability of getting a correct read depends on the relative prevalence of a specific frequency and the length of the read. If the microphone's dynamics have a fairly sharp roll-off at 10kHz (fairly standard in audio equipment), the odds of the zero-crossing happening during the read is low. (Enough lower? I don't know. I'd now like to get my hand on a Famicom controller set and play with this)

Quote:
A sound at 2432 Hz would cause a change every 0.0002 seconds, and such sounds are common. So that pretty much rules out using this for the second controller on a Famicom with the microphone.


Except that you don't have to rely on the same comparison value for the entire protected read -- just for the duration of each individual pass. So it's not the entire 368 cycles that you need to worry.

by on (#36853)
When making multiple passes, at least two must match so we know what value to ultimately return, and this applies to whatever read routine is used: traditional or CrashTest's. If there are two possible sources of changes in reading (controller itself changing, DMC), then as many as four reads need to be made. If we throw in the microphone, I believe that the worst-case is 5 reads. But since we're now doing so many reads spread over time, the microphone could corrupt TWO of them (the first and fifth). So then the worst-case can be 6 reads, or more (I haven't seriously analyzed this).

by on (#36858)
According to this shematic, the microphone input goes to a low pass filter made with a 10k resistor and 0.1uF capacity, whose constant of time equals 1ms. So the bit read cannot change faster than 1ms no matter what causes it to change.

by on (#36864)
Except it is fed to the input of a logic gate, which is a non-linear element, thus the spectrum of input doesn't tell us that of output. It could go just above the threshold, then just below it, and cause two changes within any amount of time. This is almost comical now; I don't see why having this work with the Famicom microphone is so important. What we really need is someone to run some test code on the Famicom with a mic and get actual behavior, though that still wouldn't tell us rare behavior. Maybe I should just ignore the subject further? :)

by on (#36866)
Well I guess you're right. The gate isn't a shmitt trigger so what you describe is likely to be happening.
I find it's stupid to make code that is working on one type of machine but not another for stupid details like that. If you made code that runs on NTSC, then I'd assume it would run on all licenced NTSC NESes, NES2 and Famicoms in the world, not rely on unstable things. PAL only code can use tricks that don't work on the FC I guess, but I wouldn't made PAL only code myself.

by on (#36871)
Two last thoughts --

What's the average DC value on the microphone pin? If the mic output is zero-centered, then the digital input won't read true until it's loud enough (~1.3Vpp). (On the other hand, it could be biased to require less noise to trigger).

Also, the schematic implies that the Famicom APU output also mixes back in, which worries me more.

In other words, yes, it would be nice if someone with a Famicom would help.

by on (#36875)
I've got a Famicom and can run tests, but I don't know what specifically we're testing for. If you guys didn't know, there's a slide pot on the mic to control volume so that will surely affect the results. Because the mic is pretty sensitive, most Famicom players will have the mic always turned off so ambient noise doesn't overwhelm the game's sound, on my main system I have entirely disconnected player 2 for this reason. So I don't think dealing with the mic bit is a big deal, I'd just assume it's off.

by on (#36877)
lidnariq wrote:
Also, the schematic implies that the Famicom APU output also mixes back in, which worries me more.

It's the other way arround : What is recoreded by the microphone is mixed with the output of the 2A03 sound.
Also I'd bet the microphone is very cheap and low quality.

by on (#36919)
Here's a test ROM that finds the worst-case number of good reads between corrupt ones. Run it on a Famicom and hold the B button on controller 2 the whole time (that's the one with the Mic, right?). Try making various sounds into the mic with various volume settings. If you're causing sufficient corruption, you'll see hex values printed, showing the worst-case number of good reads between two reads corrupted by the mic. Your goal is to get this as low as possible (unless you're already getting zero, in which case you have "won" already). Post some typical values, and lowest ones you got. Buildable ca65 source included.

fast_joy_famicom_test.zip

by on (#36953)
I tested 3 second player controllers and I couldn't get any of them to print anything but 00. Perhaps the test is broken? I didn't try shouting into the controller, but whistling loud did nothing, I'm pretty sure the mic bit should be picking that up.

by on (#36955)
So you ran the test ROM, then held B on the second controller and it just kept printing 0 twice a second? If not, the source code is pretty simple.

I noticed I wasn't initializing $4000 and was relying on the default initialization on the NES to 0. I added explicit initialization of $4000-$4002 to 0. I tested and it wouldn't have printed anything if the default initialization was wrong, so this probably wasn't the cause of your issue. Just fixing this on the slim chance it was the cause: fast_joy_famicom_test2.zip

by on (#36957)
Try it with both controllers. Didn't someone say that even though the mic is on the second player controller, it still shows up on $4016?

by on (#36960)
Argh, you're right, the mic affects the FIRST controller ($4016). OK, so hold B on the FIRST controller with this third version and try to get the lowest value with the mic. Third time's a charm? fast_joy_famicom_test3.zip

by on (#36967)
Tried it again, made loud noises into the controller for a few minutes, only 3 times did it print non 00s spaced about 5 lines apart. The values were 78, 08, and 01. The 78 happened when I made a loud sprinkler noise on about full volume, the other two were just random noise (everything sounds like noise through the mic) then after those three I couldn't get it to happen again after 15 lines so I gave up.

I'll make a little test to just visualize the mic and see what happens.

Edit: the test definitely confirms that my console is working. When the volume is off-1/2, it never turns on. It starts turning on around 2/3 volume if you're very loud, and at full volume when talking softly. At full volume just the noise of pressing buttons will be detected.

Because the mic bit will go away with just a slide of the pot (and the game will be unbearable with the mic on picking up interference anyway) I still really don't think it'd be unreasonable to ask users of any homebrew to turn off their microphone (not that it will affect many reads anyway). There are probably some commercial FC games affected by this as well since I have a cart that suffers from DMC glitches nonstop! (how did that get by?) I say if you need the fast read routine, go for it, just maybe put a warning message to original Famicom users.

by on (#36977)
Wait, you were ignoring the times it printed 00? That's a valid value too. That means that the mic was corrupting two reads in a row during that half second of testing. You say after "15 lines", do you mean 15 lines of 00s? If you were getting 00 often while making noise into the mic, then it shows that this fast read routine cannot be used AT ALL on the Famicom with the mic enabled and DMC playing.

by on (#36984)
Ah, I assumed 00 was no bad reads. Yes there were 15 lines of 00s when I stopped testing earlier. When it starts (I'm holding B on P1), nothing prints unless I make noise, like it should, and when it does print (by having the mic turned on at all), it prints 00 the vast majority of the time regardless of volume. I also discovered how to get really high/bad readings all the time: there's a specific position on the volume where it's supposed to be off, but instead picks up exceptionally loud interference that oscillates the mic bit. So depending on how good the slider is, the mic may accidentally turn on during play... Fortunately it's very easy to distinguish when it's truly off.

Maybe it'd be best to leave fast pad reading to a new hardware mapper. It could also detect edges and probably DMC fortify in hardware.
Code:
lda #1
sta $4016
lda #0
sta $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda mapper_pad_0_rising_edges
---------------------
48 cycles

by on (#36986)
Is it feasible to build a circuit that toggles the mic input such that you can adjust this rate via a PC or simple variable resistor?


If so, what would be the "optimal" frequency and duty-cycle to induce the worse interference when reading the joystick?


Would a test like this even be useful or produce any results that would induce anyone to make a code change in their game?

by on (#36995)
kyuusaku wrote:
Ah, I assumed 00 was no bad reads. Yes there were 15 lines of 00s when I stopped testing earlier. When it starts (I'm holding B on P1), nothing prints unless I make noise, like it should, and when it does print (by having the mic turned on at all), it prints 00 the vast majority of the time regardless of volume.

Yes, that confirms everything's working. Assuming my code is properly checking these things (and I fed it some test data to be sure), I'll have to claim my vindication of previous speculation. :)
Quote:
So depending on how good the slider is, the mic may accidentally turn on during play... Fortunately it's very easy to distinguish when it's truly off.

And the slider is on the second controller, correct, so it won't be bumped on a one-player game?
Quote:
Maybe it'd be best to leave fast pad reading to a new hardware mapper.
Code:
lda #1
sta $4016
lda #0
sta $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda $4016
lda mapper_pad_0_rising_edges

Interesting approach, letting the NES clock the controller while the mapper listens in. I wonder if any of the unofficial instructions could cause a double read of $4016 (and no writes)...