Pages such as these:
http://www.gamesx.com/controldata/psxcont/psxcont.htm
**broken link removed**
describe the controller protocol far better than I could. There is no initialization of the controller, at least for the original digital controllers, just plug and respond to the serial commands as needed. There are commands sent from the console for the newer dualshock and dualshock 2 controllers, but these should be ignored by the digital controllers that dont support those features.
I know PSX controllers can be emulated by MCU's; the first link describes the project of someone who did it used a HC68 motorola mcu. After some hard google-fu, I managed to track down the hex and even the source code. The source shows a very simple SPI interface. If anyone wants to see it, let me know and I'll post it up.
____________________
I started from scratch and have tried to tackle this using bitbanging, but again I'm failing. The I had the first run writing values to the eeprom so I could get an idea what was going on, and it appears that the ATT (attention, pulled low to tell the controller that it's beginning to communicate with it, and released back to high when communication is done) is getting released before the second byte is fully transfered. The first byte IS being read properly. That's leading me to believe that the problem is with the ACK line (pulled low by the controller for a short while after each byte is transfered), prolly being too short or too long. The code you see below is an attempt to have the MCU figure out the best delay to hold the line down for; if the ATT line is released to high by the console unexpectedly, increment the delay time by 10 cycles. It cycles through without any value (10 instruction cycles through 2500 instruction cycles) without ever working properly.
The documents linked above give the time for ACK to be pulled down as 'at least one clock cycle', and 'at least 2us'. The clock signal stays high during this time, so I cant use it to count time. I *BELIEVE* that the PIC is running at 48MHz, at least that's my intention. If so, that's 12 MIPS, or 12 instructions per us. Running Delay10TCYx(3); should be the perfect amount of delay, but it is failing for all values from 1-250.
So I have one question outside of the general 'I dont have a clue what's wrong.' problem. With a 18LF4550 I/P with a 20Mhz crystal and 22pf caps, being powered by around 3.3v from the playstation, is the processor in fact running at 48MHz/12 MIPS with these config settings?
Code:
/* includes */
#include <p18cxxx.h>
#include <delays.h>
#include "upcb.h" // contains the mLED_* defines and the EEpromPut function
#define ACK_Delay() {Delay10TCYx(0x3);}
#define ACK_Start() {LATCbits.LATC6=0;TRISCbits.TRISC6=0;}
#define ACK_End() {TRISCbits.TRISC6=0;}
#define SPI_Load(DATA) {do{ \
SSPCON1bits.WCOL=0;\
SSPBUF=DATA;\
}while(SSPCON1bits.WCOL);} // if collision do it again
#define PSX_ATT PORTAbits.RA5
#define PSX_CLK PORTBbits.RB1
#define PSX_OUT LATCbits.LATC7
#define PSX_OUT_HIGH() {TRISCbits.TRISC7=1;}
#define PSX_OUT_LOW() {PSX_OUT=0;TRISCbits.TRISC7=0;}
#define PSX_IN PORTBbits.RB0
void PSX_Init(void);
void PSX_ACK_2(unsigned char delay);
unsigned char PSX_Xfer_Bit(unsigned char data);
unsigned char PSX_Xfer_Byte(unsigned char data);
void PSX_ACK(void);
void main(void)
{
unsigned char holder, holder2, holder3, holder4, holder5, eeptr, tmpdelay;
ADCON1 |= 0x0F; // Default all pins to digital
holder=holder2=holder3=holder4=holder5=eeptr=0;
tmpdelay=1;
mInitAllLEDs(); /* set RA0 and RA1 as outputs */
PSX_Init();
while(1)
{
while(!PSX_ATT); //in case we got here from a unsynced attempt or unrecognized command,
// let's wait until the ACK is back up before waiting for it to go down
while(PSX_ATT); //Wait for it to drop to begin
if (0x01 == (holder=PSX_Xfer_Byte(0xFF))) // first byte should be 0x01 (since PSX_Xfer_byte knows PSX is LSB first,
{ // we can check for the actual sent values
mLED_1_Toggle();
PSX_ACK_2(tmpdelay); //send an acknowledgement
if (0x42==(holder2=PSX_Xfer_Byte(0x41))) //second byte from us is the controller ID (0x41 being a digital)
{ // byte we're hoping to receive is 0x42, for query status
mLED_2_Toggle();
PSX_ACK(); //send ack
PSX_Xfer_Byte(0x5A); //send the 'here comes the data' byte. We dont care what it sends us
if (PSX_ATT) continue; //just in case we got dropped
PSX_ACK();
PSX_Xfer_Byte(0b11101111); //send the first byte which shows 'Up' is pressed
if (PSX_ATT) continue;
PSX_ACK();
PSX_Xfer_Byte(0xFF); // all other buttons not pressed
//No ACK'ing the last byte
}
else
{
if (holder2 == 0xAA)
{
if (tmpdelay >250)
{
mLED_2_On();
}
else
{
tmpdelay++;
}
}
}
}
if (eeptr < 250)
{
if (holder2 == 0x42)
{
EEpromPut(eeptr, holder);
eeptr++;
EEpromPut(eeptr, holder2);
eeptr++;
EEpromPut(eeptr, tmpdelay);
eeptr++;
}
}
else
{
mLED_2_On();
}
}
}//main
void PSX_Init(void)
{
TRISAbits.TRISA5=1; //SS line - input
TRISCbits.TRISC7=0; //SDO - output
TRISBbits.TRISB1=1; //Clock - input
TRISBbits.TRISB0=1; //SDI - input
TRISCbits.TRISC6=0; //Line we're using for Ack
}
void PSX_ACK(void)
{
ACK_Start();
ACK_Delay();
ACK_End();
}
void PSX_ACK_2(unsigned char delay)
{
ACK_Start();
Delay10TCYx(delay);
ACK_End();
}
unsigned char PSX_Xfer_Byte(unsigned char data)
{
unsigned char holder;
holder=0;
if (PSX_ATT) return (0xAA); //Check in case ATT is released back to high
//PSX communication is LSB first
if (PSX_Xfer_Bit(data & 0b00000001)) holder |= 0b00000001; if (PSX_ATT) return (0xAA);
if (PSX_Xfer_Bit(data & 0b00000010)) holder |= 0b00000010; if (PSX_ATT) return (0xAA);
if (PSX_Xfer_Bit(data & 0b00000100)) holder |= 0b00000100; if (PSX_ATT) return (0xAA);
if (PSX_Xfer_Bit(data & 0b00001000)) holder |= 0b00001000; if (PSX_ATT) return (0xAA);
if (PSX_Xfer_Bit(data & 0b00010000)) holder |= 0b00010000; if (PSX_ATT) return (0xAA);
if (PSX_Xfer_Bit(data & 0b00100000)) holder |= 0b00100000; if (PSX_ATT) return (0xAA);
if (PSX_Xfer_Bit(data & 0b01000000)) holder |= 0b01000000; if (PSX_ATT) return (0xAA);
if (PSX_Xfer_Bit(data & 0b10000000)) holder |= 0b10000000; if (PSX_ATT) return (0xAA);
return(holder);
}
unsigned char PSX_Xfer_Bit(unsigned char data)
{
while (PSX_CLK) //Wait for clock to drop
{
if (PSX_ATT) return (0x00); //Check in case ATT is released back to high
}
// clock just dropped, so send the bit
if (data)
{
PSX_OUT_HIGH();
}
else
{
PSX_OUT_LOW();
}
//wait for clock to raise back so we can read the data
while (!PSX_CLK)
{
if (PSX_ATT) return (0x00);
}
//clock has raised, time to read the bit
if (PSX_IN)
{
return(0xFF);
}
else
{
return(0x00);
}
}
/* Config settings */
#pragma config PLLDIV = 5
#pragma config CPUDIV = OSC1_PLL2
#pragma config USBDIV = 2
#pragma config FOSC = HSPLL_HS
#pragma config IESO = OFF
#pragma config PWRT = OFF
#pragma config BOR = ON
#pragma config VREGEN = ON
#pragma config WDT = OFF
#pragma config WDTPS = 1
#pragma config MCLRE = ON
#pragma config LPT1OSC = OFF
#pragma config PBADEN = OFF
#pragma config CCP2MX = OFF
#pragma config STVREN = OFF
#pragma config LVP = OFF
#pragma config ICPRT = OFF
#pragma config XINST = OFF
#pragma config DEBUG = OFF
#pragma config CP0 = OFF
#pragma config CPB = OFF
#pragma config WRT0 = OFF
#pragma config WRTB = OFF
#pragma config EBTR0 = OFF
#pragma config EBTRB = OFF
EDIT: DOH! Im a dumbass. ACK_Start and ACK_Edn both set the TRIS to 0. One should be 1. One moment while I edit and test.
Ninja edit 2: SUCCESS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Ken is jumping up just like he should. This is so awesome. The same bug was in the SPI version, so I should try it too here soon.
A little more tweaking is needed for what I need, but this proof of concept is a BIG BIG BIG help. If anyone wants to try, just take the bitbang code above and change:
#define ACK_End() {TRISCbits.TRISC6=0;}
to
#define ACK_End() {TRISCbits.TRISC6=1;}
WOOHOO!