/* Name: snes.c * Project: Multiple NES/SNES to USB converter * Author: Raphael Assenat * Copyright: (C) 2007 Raphael Assenat * License: Proprietary, free under certain conditions. See Documentation. * Tabsize: 4 */ #define F_CPU 12000000L #include #include #include #include #include #include "gamepad.h" #include "leds.h" #include "snes.h" #define REPORT_SIZE 8 #define GAMEPAD_BYTES 8 /* 2 byte per snes controller * 4 controllers */ /******** IO port definitions **************/ #define SNES_LATCH_DDR DDRC #define SNES_LATCH_PORT PORTC #define SNES_LATCH_BIT (1<<4) #define SNES_CLOCK_DDR DDRC #define SNES_CLOCK_PORT PORTC #define SNES_CLOCK_BIT (1<<5) #define SNES_DATA_PORT PORTC #define SNES_DATA_DDR DDRC #define SNES_DATA_PIN PINC #define SNES_DATA_BIT1 (1<<3) /* controller 1 */ #define SNES_DATA_BIT2 (1<<2) /* controller 2 */ #define SNES_DATA_BIT3 (1<<1) /* controller 3 */ #define SNES_DATA_BIT4 (1<<0) /* controller 4 */ /********* IO port manipulation macros **********/ #define SNES_LATCH_LOW() do { SNES_LATCH_PORT &= ~(SNES_LATCH_BIT); } while(0) #define SNES_LATCH_HIGH() do { SNES_LATCH_PORT |= SNES_LATCH_BIT; } while(0) #define SNES_CLOCK_LOW() do { SNES_CLOCK_PORT &= ~(SNES_CLOCK_BIT); } while(0) #define SNES_CLOCK_HIGH() do { SNES_CLOCK_PORT |= SNES_CLOCK_BIT; } while(0) #define SNES_GET_DATA1() (SNES_DATA_PIN & SNES_DATA_BIT1) #define SNES_GET_DATA2() (SNES_DATA_PIN & SNES_DATA_BIT2) #define SNES_GET_DATA3() (SNES_DATA_PIN & SNES_DATA_BIT3) #define SNES_GET_DATA4() (SNES_DATA_PIN & SNES_DATA_BIT4) /*********** prototypes *************/ static void snesInit(void); static void snesUpdate(void); static char snesChanged(void); static void snesBuildReport(unsigned char *reportBuffer); // the most recent bytes we fetched from the controller static unsigned char last_read_controller_bytes[GAMEPAD_BYTES]; // the most recently reported bytes static unsigned char last_reported_controller_bytes[GAMEPAD_BYTES]; // indicates if a controller is in NES mode static unsigned char nesMode=0; /* Bit0: controller 1, Bit1: controller 2...*/ static void snesInit(void) { unsigned char sreg; sreg = SREG; cli(); // clock and latch as output SNES_LATCH_DDR |= SNES_LATCH_BIT; SNES_CLOCK_DDR |= SNES_CLOCK_BIT; // data as input SNES_DATA_DDR &= ~(SNES_DATA_BIT1 | SNES_DATA_BIT2 | SNES_DATA_BIT3 | SNES_DATA_BIT4 ); // enable pullup. This should prevent random toggling of pins // when no controller is connected. SNES_DATA_PORT |= (SNES_DATA_BIT1 | SNES_DATA_BIT2 | SNES_DATA_BIT3 | SNES_DATA_BIT4 ); // clock is normally high SNES_CLOCK_PORT |= SNES_CLOCK_BIT; // LATCH is Active HIGH SNES_LATCH_PORT &= ~(SNES_LATCH_BIT); nesMode = 0; snesUpdate(); /* Snes controller buttons are sent in this order: * 1st byte: B Y SEL START UP DOWN LEFT RIGHT * 2nd byte: A X L R 1 1 1 1 * * Nes controller buttons are sent in this order: * One byte: A B SEL START UP DOWN LEFT RIGHT * * When an additional byte is read from a NES controller, * all bits are 0. Because the data signal is active low, * this corresponds to pressed buttons. When we read * from the controller for the first time, detect NES * controllers by checking those 4 bits. **/ if (last_read_controller_bytes[1]==0xFF) nesMode |= 1; if (last_read_controller_bytes[3]==0xFF) nesMode |= 2; if (last_read_controller_bytes[5]==0xFF) nesMode |= 4; /* The last controllers are always in NES mode. But * it is accessible only of the third is in NES mode * too. */ if (last_read_controller_bytes[7]==0xFF) nesMode |= 8; SREG = sreg; } /* * Clock Cycle Button Reported =========== =============== 1 B 2 Y 3 Select 4 Start 5 Up on joypad 6 Down on joypad 7 Left on joypad 8 Right on joypad 9 A 10 X 11 L 12 R 13 none (always high) 14 none (always high) 15 none (always high) 16 none (always high) * */ static void snesUpdate(void) { int i; unsigned char tmp1=0; unsigned char tmp2=0; unsigned char tmp3=0; unsigned char tmp4=0; SNES_LATCH_HIGH(); _delay_us(12); SNES_LATCH_LOW(); for (i=0; i<8; i++) { _delay_us(6); SNES_CLOCK_LOW(); tmp1 <<= 1; tmp2 <<= 1; tmp3 <<= 1; tmp4 <<= 1; if (!SNES_GET_DATA1()) { tmp1 |= 1; } if (!SNES_GET_DATA2()) { tmp2 |= 1; } if (!SNES_GET_DATA3()) { tmp3 |= 1; } if (!SNES_GET_DATA4()) { tmp4 |= 1; } _delay_us(6); SNES_CLOCK_HIGH(); } last_read_controller_bytes[0] = tmp1; last_read_controller_bytes[2] = tmp2; last_read_controller_bytes[4] = tmp3; last_read_controller_bytes[6] = tmp4; for (i=0; i<8; i++) { _delay_us(6); SNES_CLOCK_LOW(); // notice that this is different from above. We // want the bits to be in reverse-order tmp1 >>= 1; tmp2 >>= 1; tmp3 >>= 1; tmp4 >>= 1; if (!SNES_GET_DATA1()) { tmp1 |= 0x80; } if (!SNES_GET_DATA2()) { tmp2 |= 0x80; } if (!SNES_GET_DATA3()) { tmp3 |= 0x80; } if (!SNES_GET_DATA4()) { tmp4 |= 0x80; } _delay_us(6); SNES_CLOCK_HIGH(); } /* Force extra bits to 0 when in NES mode. Otherwise, if * we read zeros on the wire, we will have permanantly * pressed buttons */ last_read_controller_bytes[1] = (nesMode & 1) ? 0x00 : tmp1; last_read_controller_bytes[3] = (nesMode & 2) ? 0x00 : tmp2; last_read_controller_bytes[5] = (nesMode & 4) ? 0x00 : tmp3; last_read_controller_bytes[7] = (nesMode & 8) ? 0x00 : tmp4; } static char snesChanged(void) { static int first = 1; if (first) { first = 0; return 1; } return memcmp(last_read_controller_bytes, last_reported_controller_bytes, GAMEPAD_BYTES); } static char getX(unsigned char nesByte1) { char x = 128; if (nesByte1&0x1) { x = 255; } if (nesByte1&0x2) { x = 0; } return x; } static char getY(unsigned char nesByte1) { char y = 128; if (nesByte1&0x4) { y = 255; } if (nesByte1&0x8) { y = 0; } return y; } static unsigned char snesReorderButtons(unsigned char bytes[2]) { unsigned char v; /* pack the snes button bits, which are on two bytes, in * one single byte. */ v = (bytes[0]&0x80)>>7; v |= (bytes[0]&0x40)>>5; v |= (bytes[0]&0x20)>>3; v |= (bytes[0]&0x10)>>1; v |= (bytes[1]&0x0f)<<4; return v; } static void snesBuildReport(unsigned char *reportBuffer) { /* last_read_controller_bytes[] structure: * * [0] : controller 1, 8 first bits (dpad + start + sel + y|a + b) * [1] : controller 1, 8 snes extra bits (4 lower bits are buttons) * * [2] : controller 2, 8 first bits * [3] : controller 2, 4 extra snes buttons * * [4] : controller 3, 8 first bits * [5] : controller 3, 4 extra snes buttons * * [6] : controller 4, 8 first bits * [7] : controller 4, 4 extra snes buttons */ if (reportBuffer != NULL) { reportBuffer[0]=getX(last_read_controller_bytes[0]); reportBuffer[1]=getY(last_read_controller_bytes[0]); reportBuffer[2]=getX(last_read_controller_bytes[2]); reportBuffer[3]=getY(last_read_controller_bytes[2]); reportBuffer[4] = snesReorderButtons(&last_read_controller_bytes[0]); reportBuffer[5] = snesReorderButtons(&last_read_controller_bytes[2]); if (nesMode & 0x04) { // Two last controllers are in NES mode. reportBuffer[6] = last_read_controller_bytes[4]; reportBuffer[7] = last_read_controller_bytes[6]; } else { // Third controller is in SNES mode. Use the two // last bytes for it. Sorry, no fourth controller. reportBuffer[6] = last_read_controller_bytes[4]; reportBuffer[7] = last_read_controller_bytes[5]; } } memcpy(last_reported_controller_bytes, last_read_controller_bytes, GAMEPAD_BYTES); } const char snes_usbHidReportDescriptor[] PROGMEM = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x04, // USAGE (Joystick) 0xa1, 0x01, // COLLECTION (Application) 0x09, 0x01, // USAGE (Pointer) 0xa1, 0x00, // COLLECTION (Physical) 0x09, 0x30, // USAGE (X) 0x09, 0x31, // USAGE (Y) 0x09, 0x32, // USAGE (Z) 0x09, 0x36, // USAGE (SLIDER) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x04, // REPORT_COUNT (4) 0x81, 0x02, // INPUT (Data,Var,Abs) 0xc0, // END_COLLECTION 0x05, 0x09, // USAGE_PAGE (Button) 0x19, 1, // USAGE_MINIMUM (Button 1) 0x29, 32, // USAGE_MAXIMUM (Button 32) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 1, // REPORT_SIZE (1) 0x95, 32, // REPORT_COUNT (32) 0x81, 0x02, // INPUT (Data,Var,Abs) 0xc0 // END_COLLECTION }; Gamepad SnesGamepad = { report_size: REPORT_SIZE, reportDescriptorSize: sizeof(snes_usbHidReportDescriptor), init: snesInit, update: snesUpdate, changed: snesChanged, buildReport: snesBuildReport }; Gamepad *snesGetGamepad(void) { SnesGamepad.reportDescriptor = (void*)snes_usbHidReportDescriptor; return &SnesGamepad; }