What's an elegant way to read controller input for NES (6502) assembly on ca65?

1.4k views Asked by At

I'm starting to learn 6502 assembly for a potential NES game project on my off time, and I'm having some trouble setting up the reading of controller input. My background is in C, so I'm familiar with memory and how it works, but the flow control in assembly still escapes me.

Because I'm new, I figured I should start easy and use the button-by-button method described in https://www.vbforums.com/showthread.php?858965-NES-6502-Programming-Tutorial-Part-5-Controller-Commands . This works fine, but it is really repetitive and wordy.

Is there a more elegant way of doing this that isn't completely above my abilities? I don't know enough to integrate code from other sources without some help.

https://wiki.nesdev.com/w/index.php/Controller_reading_code looks promising, but I don't really understand enough of it to use it.

Thank you for your time.

1

There are 1 answers

2
Tommy On BEST ANSWER

Controllers on the NES are serial devices, each containing an internal shift register. To read the controllers:

  • set a 1 to b0 of $4016; that'll cause the controllers to begin continuously sampling their inputs and reloading their 8-bit shift register;
  • set a 0 to b0 of $4016; that'll cause the controllers to stop sampling their inputs, and stop reloading their shift registers;
  • for controller 1, each read from $4016 will return the least significant bit from the shift register in b0 and cause the register to shift;
  • for controller 2, $4017 does the equivalent read-and-shift.

Inputs are returned in the order A, B, Select, Start, Up, Down, Left, Right.

So, the first part of that contract can't really be neatened much. You're unavoidably going to see something like:

; Get current controller inputs into their shift registers.
LDA #1
STA $4016
LDA #0
STA $4016

Assuming you're interested in all eight inputs, and only controller 1 for the sake of example, there's then definitely going to be at least eight reads from $4016. Since it is also the strobe for resetting what's in the shift register, they had better be reads only — no writes or read-modify-writes.

Also it is unfortunately not true that bits 1 to 7 are 0. So e.g. you can't just ORA from $4016 and get results a bit at a time. And none of the unofficial opcodes seem that useful in doing a convenient load-and-AND in this situation.

So if you wanted to accumulate the results into A then things aren't going to get that elegant.

Of the links you provide:

The vbforums.com suggestion is to load from $4016 eight times, test the lowest bit each time, and react appropriately in that loop. It uses AND #1 (to set Z) and BEQ (to test it) to test the bit after loading, whereas LSR (to move bit 0 into carry) and BCC (to test carry) would probably me more elegant in the sense that it's a little more compact.

The NesDev Wiki link instead rolls each single bit of joypad input through carry and into another byte, so you end up with an 8-bit value in memory that is equal to what was once in the controller's shift register, and you can test and manipulate that at your leisure.

If your concern is simply 'repetitive and wordy' then I think the problem might be your tools, not the hardware — look for a macro assembler. Many of them are a step or two above C's preprocessor, so you might end up being to code the same sequence of steps as something more like:

MACRO nextBit source destination {
    lda source        ; Read next bit from controller.
    lsr               ; Move bit to carry.
    ror destination   ; Roll bit from carry to top of local state.
} ENDMACRO

; Get controllers to reload their shift registers.
lda #1
sta $4016
lda #0
sta $4016

; Copy shift registers to local memory.
FOR n, 0, 8
    nextBit $4016 controller1State
    nextBit $4017 controller2State
NEXT

That's the macro syntax used by a particular real-life assembler for the BBC Micro, and almost certainly not the macro syntax your assembler uses because there's very little standardisation. But there'll be a good macro compiler for the NES, and the job of a macro compiler is to allow you to spell out repetitive parts without copying and pasting.