As you can see from the well placed and stylish ON/OFF labels, I have converted the selector switch into an On/Off switch for my system. The extended leads are easily pluggable into the breadboard system, so my task has been completed.
Next, I have to actually read the damn thing. Fortunately, there is tons of information on how the NES controllers work, and even some sample code on the Parallax forums that I could investigate and play with.
After a little experimenting to see what did what, I ended up with the following gamepad driver code:
CON IO_JOY_CLK = %00001000 IO_JOY_SHLDn = %00010000 IO_JOY_DATAOUT0 = %00100000 IO_JOY_DATAOUT1 = %01000000 NES_LATCH_DELAY = $40 NES_RIGHT = %00000001 NES_LEFT = %00000010 NES_DOWN = %00000100 NES_UP = %00001000 NES_START = %00010000 NES_SELECT = %00100000 NES_B = %01000000 NES_A = %10000000 NES0_RIGHT = %00000000_00000001 NES0_LEFT = %00000000_00000010 NES0_DOWN = %00000000_00000100 NES0_UP = %00000000_00001000 NES0_START = %00000000_00010000 NES0_SELECT = %00000000_00100000 NES0_B = %00000000_01000000 NES0_A = %00000000_10000000 NES1_RIGHT = %00000001_00000000 NES1_LEFT = %00000010_00000000 NES1_DOWN = %00000100_00000000 NES1_UP = %00001000_00000000 NES1_START = %00010000_00000000 NES1_SELECT = %00100000_00000000 NES1_B = %01000000_00000000 NES1_A = %10000000_00000000VAR long cogon, cog long nes_bits_parm ' local storage for NES state/bitsPUB start : okay'' Start the NES gamepad reading process'' returns false if no cog available'' stop okay := cogon := (cog := cognew(@NES_Read_Gamepad_ASM_Entry, @nes_bits_parm)) > 0PUB stop'' Stops driver - frees a cog if cogon~ cogstop(cog)PUB read'' Reads the NES state and sends it back to caller return(nes_bits_parm)PUB button(nes_button)'' Return TRUE/FALSE if sent button is down return(nes_button & nes_bits_parm)DATorg $000NES_Read_Gamepad_ASM_Entry ' step 1: set I/Os, or DIRA, #(IO_JOY_CLK | IO_JOY_SHLDn) ' JOY_CLK and JOY_SH/LDn to outputs and DIRA, #(!(IO_JOY_DATAOUT0 | IO_JOY_DATAOUT1)) & $1ff ' JOY_DATAOUT0 and JOY_DATAOUT1 to inputs NES_Latchbits ' step 2: set latch and clock to 0 and OUTA, #(!(IO_JOY_CLK | IO_JOY_SHLDn)) & $1ff ' JOY_CLK = 0, JOY_SH/LDn = 0 ' initialize counter and delay to let NES settle mov _counter, CNT add _counter, #NES_LATCH_DELAY waitcnt _counter, #NES_LATCH_DELAY ' step 3: set latch to 1 or OUTA, #(IO_JOY_SHLDn) ' JOY_SH/LDn = 1 ' initialize counter and delay to let NES settle mov _counter, CNT add _counter, #NES_LATCH_DELAY waitcnt _counter, #NES_LATCH_DELAY ' step 4: set latch to 0 and OUTA,#(!(IO_JOY_SHLDn) & $1ff) ' JOY_SH/LDn = 0 ' clear gamepad storage word xor _nes_bits, _nes_bits ' step 5: read 8 bits, 1st bits are already latched and ready, simply save and clock remaining bits mov _index, #8NES_Getbits_Loop shl _nes_bits, #$1 ' ' shift results 1 bit to the left each time mov _nes_gamepad0, INA ' read all 32-bits of input including gamepads mov _nes_gamepad1, _nes_gamepad0 ' copy all 32-bits of input including gamepads ' now extract bits from inputs and _nes_gamepad0, #(IO_JOY_DATAOUT0) and _nes_gamepad1, #(IO_JOY_DATAOUT1) ' shift bits into place, so that gamepad0 bits fall into bit 0 of 16-bit result and gamepad1 bits fall into bit 8 of 16-bit result ' then continuously shift the entire result until every buttons has been shifted into position from both gamepads shr _nes_gamepad0, #5 shl _nes_gamepad1, #2 ' finally OR results into accumulating result/sum or _nes_bits, _nes_gamepad0 or _nes_bits, _nes_gamepad1 ' pulse clock... or OUTA, #%00001000 ' JOY_CLK = 1 ' initialize counter and delay to let NES settle mov _counter, CNT add _counter, #NES_LATCH_DELAY waitcnt _counter, #NES_LATCH_DELAY and OUTA,#%11110111 ' JOY_CLK = 0 ' initialize counter and delay to let NES settle mov _counter, CNT add _counter, #NES_LATCH_DELAY waitcnt _counter, #NES_LATCH_DELAY djnz _index, #NES_Getbits_Loop ' END NES_getbits_loop ' invert bits to make positive logic xor _nes_bits, _MAXINT ' mask lower 16-bits only and _nes_bits, _NES_GAMEPAD_MASK ' finally write results back out to caller wrlong _nes_bits, par ' now access main memory and write the value ' continue looping... jmp #NES_Latchbits_MAXINT long $ffff_ffff ' 32-bit maximum value constant_NES_GAMEPAD_MASK long $0000_ffff ' mask for NES lower 16-bits _nes_bits long $0 ' storage for 16 NES gamepad bits (lower 16-bits)_nes_gamepad0 long $0 ' left gamepad temp storage_nes_gamepad1 long $0 ' right gamepad temp storage_index long $0 ' general counter/index_counter long $0 ' general counter
Using this, I set up a simple tester program that would let me see the state of all the buttons visually. I downloaded the nice PropTerminal program, which talks to a Propeller hooked up through a USB port and reports any textual video information to the screen. That way I didn't need a real TV to hook up with.
Here is the result of the gamepad test (Note: I'm holding down the B and Up buttons on the first controller).
Not bad for a day's work. So I finally have some reliable input. Next step is graphical output to a TV, and from there, any game I want to make is up for grabs.
I have a few ideas of the first few games I want to make. I'm thinking of Pong as my first, since it's such a classic. After that, it's not as clear. I could go with Pac Man or Defender or Robotron, or maybe even asteroids. There are simply tons of cool old games that would be a blast to recreate. If anyone has any ideas for a great game, let me hear 'em!
On a final note, I started thinking seriously about the next step the project will take. The breadboard is nice for quick prototyping, but I need something more stable and more permanent. I found EAGLE, a freeware schematic diagramming application that seems to be highly recommended. Couple a board design from there and the cheap pricing of Batch PCB and I may be able to go with a custom PCB for a tidier solution.
Thoughts or comments? Let me hear 'em!