PSX Controllers '/////////////////////////////////////////////////// '// Playstation 2 Controller Interface // '// Created by: Isaac Hayes // '// February 1, 2007 // '// // '// PIC Used: 16F88 in INTRC Mode // '// Programming Language: MikroBasic // '// // '// Summary: // '// This program uses Mikrobasic SPI functions // '// to communicate with a Playstation2 controller // '// and output the status of the "X" and "O" // '// buttons on pins RA0 and RA1. // '// Modified for dual controller read// '/////////////////////////////////////////////////// program PICNonSPI symbol psxCLK = PORTB.4 symbol psxCMD = PORTB.3 symbol psxDAT1 = PORTB.1 symbol psxDAT2 = PORTB.2 symbol psxATT = PORTB.0 dim psxOut as byte dim psxIn1 as byte dim psxIn2 as byte dim psx1ID as byte dim psx2ID as byte dim i as byte dim psx1Status as byte dim psx1LeftB as byte dim psx1RightB as byte dim psx1RJoyX as byte dim psx1RJoyY as byte dim psx1LJoyX as byte dim psx1LJoyY as byte dim psx2Status as byte dim psx2LeftB as byte dim psx2RightB as byte dim psx2RJoyX as byte dim psx2RJoyY as byte dim psx2LJoyX as byte dim psx2LJoyY as byte '//////////////////////////////////// '// Sub Procedures // '//////////////////////////////////// '// Send and Receive data simultaneously from 2 controllers sub procedure psxTxRx(dim byref byteOut, byteIn1 as byte, bytein2 as byte) byteIn1=0 'Reset Receive byte byteIn2=0 'Reset Receive byte for i = 0 to 7 'Read data bits LSB to MSB psxCMD=testbit(byteOut,i) 'Prepare first bit to send psxCLK=0 'Generate clock pulse delay_us(25) 'Used to regulate communication speed if psxDAT1 = 0 then Setbit(byteIn1,i) 'Low Data line indicates set bit end if if psxDAT2 = 0 then Setbit(byteIn2,i) 'Low Data line indicates set bit end if psxCLK=1 'End clock pulse Delay_us(25) 'Regulate speed next i end sub '///////////////////////////////////// '// Program Start // '///////////////////////////////////// main: OSCCON=%01111110 'Set INTRC to 8Mhz ANSEL=0 'Set I/O to digital TRISA=0 'PortA all outputs PORTA=0 'PortA all off TRISB=%00000010 'PortB all output except SDI Pin (R1) PORTB=%00010001 'PortB SCK and psxAtt are high, all else low PortA.0=1 'Flashes LED on RA0 for debugging purposes Delay_ms(500) PortA.0=0 Delay_ms(500) PortA.0=1 Delay_ms(200) PortA.0=0 Delay_ms(200) '///////////////////////////////////// '// main Loop // '///////////////////////////////////// do psxATT=0 'psxAtt low, Activates Controller Delay_us(50) psxATT=1 'Release controller by setting psxATT high Delay_us(2) psxOut=0x01 'Start Signal psxTxRx(psxOut,psxIn1,psxin2) Delay_us(2) psxOut=0x42 'Request Status psxTxRx(psxOut,psxIn1,psxin2) psx1ID=psxIn1 'Simultaneously receive controller ID psx2ID=psxIn2 'Simultaneously receive controller ID Delay_us(2) psxOut=0 'Clear psxOut psxTxRx(psxOut,psxIn1,psxin2) 'Get psxStatus psx1Status=psxIn1 psx2Status=psxIn2 Delay_us(2) psxTxRx(psxOut,psxIn1,psxin2) 'Get Left side buttons psx1LeftB=psxIn1 psx2LeftB=psxIn2 Delay_us(2) psxTxRx(psxOut,psxIn1,psxin2) 'Get Right side buttons psx1RightB=psxIn1 psx2RightB=psxIn2 Delay_us(2) psxTxRx(psxOut,psxIn1,psxin2) 'Get Right joystick X-axis byte psx1RJoyX=psxIn1 psx2RJoyX=psxIn2 Delay_us(2) psxTxRx(psxOut,psxIn1,psxin2) 'Get Right joystick Y-axis byte psx1RJoyY=psxIn1 psx2RJoyY=psxIn2 Delay_us(2) psxTxRx(psxOut,psxIn1,psxin2) 'Get Left joystick X-axis byte psx1LJoyX=psxIn1 psx2LJoyX=psxIn2 Delay_us(2) psxTxRx(psxOut,psxIn1,psxin2) 'Get Left joystick Y-axis byte psx1LJoyY=psxIn1 psx2LJoyY=psxIn2 Delay_us(2) PortA.0=psxRightB.6 'RA0 equals status of "X" Button PORTA.1=psxRightB.5 'RA1 equals status of "O" Button Delay_ms(10) loop until false 'Loop Forever end. Basic program structure: -First the psxATT line is pulled low to activate the controller. -Next 0x01 is sent to the controller to signal the start of a communications cycle -Third we send byte 0x42 to the controller to request the status. While we send it we will simultaneously receive the controllers ID which will be sent because of the earlier start signal -Next we receive the status byte telling us whether the controller is in analog or digital mode (digital mode just means it won't send the joystick bytes) -The next six byte receives will be button data in this order: Left hand buttons, Right hand buttons, Right joystick X-axis, Right joystick Y-axis, Left joystick X-axis, Left joystick Y-axis. -Finally we pull psxATT high to signal that this communications cycle is complete. I'll post a chart from Jon Williams' article in an edit soon, for now just refer to his article. Joystick data is a byte between 0 and 255 for each axis with 128 being center. For example with the Left joystick X-axis byte, 0 would be fully left and 255 would be fully right. Below is my code that I wrote in Mikrobasic for a 16F88 to turn on LEDs on ports RA0 and RA1 in correspondance with the "X" and "O" buttons. You can download the project with all source code and schematics here. '/////////////////////////////////////////////////// '// Playstation 2 Controller Interface // '// Created by: Isaac Hayes // '// February 1, 2007 // '// // '// PIC Used: 16F88 in INTRC Mode // '// Programming Language: MikroBasic // '// // '// Summary: // '// This program uses Mikrobasic SPI functions // '// to communicate with a Playstation2 controller // '// and output the status of the "X" and "O" // '// buttons on pins RA0 and RA1. // '// // '/////////////////////////////////////////////////// program PICNonSPI symbol psxCLK = PORTB.4 symbol psxCMD = PORTB.2 symbol psxDAT = PORTB.1 symbol psxATT = PORTB.0 dim psxOut as byte dim psxIn as byte dim psxID as byte dim i as byte dim psxStatus as byte dim psxLeftB as byte dim psxRightB as byte dim psxRJoyX as byte dim psxRJoyY as byte dim psxLJoyX as byte dim psxLJoyY as byte '//////////////////////////////////// '// Sub Procedures // '//////////////////////////////////// '// Send and Receive data simultaneously sub procedure psxTxRx(dim byref byteOut, byteIn as byte) byteIn=0 'Reset Receive byte for i = 0 to 7 'Read data bits LSB to MSB psxCMD=testbit(byteOut,i) 'Prepare first bit to send psxCLK=0 'Generate clock pulse delay_us(25) 'Used to regulate communication speed if psxDAT = 0 then Setbit(byteIn,i) 'Low Data line indicates set bit end if psxCLK=1 'End clock pulse Delay_us(25) 'Regulate speed next i end sub '///////////////////////////////////// '// Program Start // '///////////////////////////////////// main: OSCCON=%01111110 'Set INTRC to 8Mhz ANSEL=0 'Set I/O to digital TRISA=0 'PortA all outputs PORTA=0 'PortA all off TRISB=%00000010 'PortB all output except SDI Pin (R1) PORTB=%00010001 'PortB SCK and psxAtt are high, all else low PortA.0=1 'Flashes LED on RA0 for debugging purposes Delay_ms(500) PortA.0=0 Delay_ms(500) PortA.0=1 Delay_ms(200) PortA.0=0 Delay_ms(200) '///////////////////////////////////// '// main Loop // '///////////////////////////////////// do psxATT=0 'psxAtt low, Activates Controller Delay_us(50) psxOut=0x01 'Start Signal psxTxRx(psxOut,psxIn) psxOut=0x42 'Request Status psxTxRx(psxOut,psxIn) psxID=psxIn 'Simultaneously receive controller ID psxOut=0 'Clear psxOut psxTxRx(psxOut,psxIn) 'Get psxStatus psxStatus=psxIn psxTxRx(psxOut,psxIn) 'Get Left side buttons psxLeftB=psxIn psxTxRx(psxOut,psxIn) 'Get Right side buttons psxRightB=psxIn psxTxRx(psxOut,psxIn) 'Get Right joystick X-axis byte psxRJoyX=psxIn psxTxRx(psxOut,psxIn) 'Get Right joystick Y-axis byte psxRJoyY=psxIn psxTxRx(psxOut,psxIn) 'Get Left joystick X-axis byte psxLJoyX=psxIn psxTxRx(psxOut,psxIn) 'Get Left joystick Y-axis byte psxLJoyY=psxIn Delay_us(50) psxATT=1 'Release controller by setting psxATT high PortA.0=psxRightB.6 'RA0 equals status of "X" Button PORTA.1=psxRightB.5 'RA1 equals status of "O" Button Delay_ms(10) loop until false 'Loop Forever end. The end EDIT: My old file server has since been stopped but MikroE was kind enough to add this to their projects section and host a zip file that contains all the code and schematics for this. You can find it HERE Last edited by Ikefu on 06 Apr 2009 17:59, edited 2 times in total. Here’s the critical PS2 routines in Hi-Tech C on an 18F4550 PIC. I’ve only run the “Official” Lynxmotion PS2 controller; and a Pelican I bought at Circuit City. (The Lynxmotion is better). Both are wireless. The information on timing I’d read and implemented previously (change data on low-going clock edge) for the PS2 was not working for me (CLK low BEFORE setting up CMD bit). I’d get the ID, but that’s about all, perhaps an occasional glimpse of the following data. Taking the CLK line low AFTER setting CMD works for me. As there are conditional statements in the ShiftInOut() routine, I choose to keep them as balanced as possible so that waveforms are more observable on a 'scope. You can put a call to the ShiftStringOut() routine in a tight loop (PS2_SPItest()) and trigger on the SEL line (low going edge). Take out the 50 uS delay to speed things up (easier viewing on the 'scope). I just send the data out to the comm port in a printf(). Hope this helps! Alan KM6VV [code]/----------------------------------------------------------------------/ /* DualShock SPI bit defines */ #define DAT PORTBbits.RB0 #define CMD PORTBbits.RB1 #define SEL PORTBbits.RB2 #define CLK PORTBbits.RB3 #define DELAY 2 /* changed from 1 (Lynxmotion) to 2 for Pelican */ #define ClockHi 1 #define ClockLow 0 /* Mode Numbers * 41 --> Digital mode * 73 --> analog mode * F3 --> DS2 native mode * * data comes back [0x73, 0x5A, 0xFF, 0xFF, 0x80, 0x80, 0x80, 0x80] / /----------------------------------------------------------------------*/ void ShiftStringOut(char *StrOutPtr, char *StrInPtr, unsigned char Length) /* * Routine to shift out a number of PS2 SPI command bytes and * simultaneously read back responses. * * call with pointer to string of CMD bytes (StrOutPtr) and number of * command bytes in Length. * * Returns data in StrInPtr. * 08/09/07 alm */ { unsigned int i; SEL = 0; /* Select Controller / delayMicrosec(20); / initial “wakeup” */ for(i = 0; i < Length; i++) { *StrInPtr++ = ShiftInOut(StrOutPtr++); delayMicrosec(40); / between cmd bytes */ } SEL = 1; /* Deselect Controller */ delayMicrosec(50); /* between cmd delay (need?) */ } /----------------------------------------------------------------------/ unsigned char ShiftInOut(unsigned char SendChr) /* * Routine to send commands and receive data from PS2 via SPI * Shift out (LSB first) a byte to SPI * and simultaneously shift in (LSB first) a byte * * Call with byte to send. * Returns byte read. * * 09/02/07 data changes BEFORE negative going clock edges * * Observed 312K clock rate, 1 uS clock pulse width */ { unsigned char i, mask; unsigned char RcvChr; RcvChr = 0; CMD = 1; for(i = 0, mask = 1; i < 8; i++, mask <<= 1) { if(SendChr & mask) /* output a data bit */ CMD = 1; else CMD = 0; CLK = ClockLow; /* generate 1 uS clock pulse */ * * delayMicrosec(DELAY); * * CLK = ClockHi; /* Clock the Bit */ * * if(DAT) /* input a data bit */ * RcvChr |= mask; * else * RcvChr &= ~mask; /* for balance of inst time */ * * delayMicrosec(DELAY); * } * CMD = 1; return(RcvChr); } /--------------------------------------------------------------------/ void PS2_SPItest(void) /* * Test Loop Function for PS2 routines */ { int i, j, loc; unsigned char data[9]; unsigned char cmd[9] = {0x01, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; do { i = 9; /* end of the list */ ShiftStringOut(cmd, data, i); /* print the data, start with mode (data[0] is ignored) / / use last index to stop at */ for(loc = 0, j = 1; j < i; j++) loc += sprintf(&buff[loc], "%02X ", data[j]); sprintf(&buff[loc], "\r\n"); PutString(buff); for(i = 0; i < 40; i++) /* 10 mS */ delayMicrosec(250); } while(1); } /----------------------------------------------------------------------/ [/code]