Loading...
 

PSX Controllers

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]