AVR based PS2 digital controller

Started by busslave, November 18, 2012, 11:55:53 PM

Previous topic - Next topic

busslave

November 18, 2012, 11:55:53 PM Last Edit: December 14, 2012, 11:38:22 PM by busslave
Hi everybody.
I'm trying to emulate a PS2 digital controller with an ATmega328P.
I read somewhere else that AVRs don't make good SPI slaves, because SPDR is not buffered in one direction. Still, I would like to give it a try.

Here is my testbed:

ATmega328P pin   | PS2 slim pin
PB1   15   |   9 ACK
~SS    16   |   6 ATT
MOSI 17   |   2 COMMAND
MISO 18   |   1 DATA
SCK    19   |   7 CLOCK
GND         |       GND
(Grounds are common. I read somewhere that some SPI signals need this ground reference. The MISO line, maybe? )

Here are the links to the references I've based my code on:
http://www.gamesx.com/controldata/psxcont/psxcont.htm
http://store.curiousinventor.com/guides/PS2

Also, I've read the "Using the SPI in the HT82J30R for PS2 Gamepad Slave Applications" document. According to it, these are the controller signals:


I've noticed that the MOSI-SDO-CMND line is normally low, as opposed to what's represented in "SONY PLAYSTATION® CONTROLLER INFORMATION" (1st link). The MISO-SDI-DATA line is normally high, so I'm assuming an 0xFF byte means "no buttons pressed" (which is also stated in "Interfacing a PS2 (PlayStation 2) Controller", the 2nd link).

My SPI setup looks like this:
void SPI_slave_init() {
DDRB=18;//PB1 (ACK), PB4 (MISO) as outputs.

SPCR|=(1<<SPR1);//Fosc/64. @16MHz==250KHz.
SPCR|=(1<<CPHA);//Setup (put bit to be exchanged on MOSI) on leading edge, sample on trailing edge (perform bitshift).
SPCR|=(1<<CPOL);//Leading edge is falling edge, trailing edge is rising edge.
SPCR|=(1<<DORD);//Byte is transmitted LSB first, MSB last.
SPCR|=(1<<SPE);//Enable SPI.
//MSTR bit is zero, SPI is slave.

PORTB|=(1<<PORTB1);//ACK is normally high.
}


Playstation's command sequence should be as follows:
byte #           1         2         3          4          5
Command   0x01   0x42    0x00    0x00    0x00 <-- PS2 sends to AVR
Data           0xFF    0x41    0x5A    0xFF     0xFF <-- AVR response.

Now, I'm checking in the main loop when the ~SS-ATT-CS line goes low, like this:
void loop() {//Yes, I'm using an Arduino.
uint8_t ss_edge=PINB&_BV(PINB2);

if (ss_edge==0) {//ATT has gone low.
SPCR|=(1<<SPIE);//Enable SPI interrupts.
uint8_t clear_spif=SPSR; clear_spif=SPDR;//Clear SPIF. Needed??
SPDR=0xFF;//Set initial (default) controller data.
byte_num=0;//Volatile uint8_t.
}
}


I'm using the Serial Transfer Interrupt to deal with the data, like so:
ISR(SPI_STC_vect) {
uint8_t ch=SPDR;//Read command sent by the PS2.
if (ch==polling_seq[byte_num]) {
switch (byte_num) {
case 0:
PORTB &= ~(1<<PORTB1);//Pull acknowledge line low for at least 5us.
_delay_us(5);
PORTB |= (1<<PORTB1);//Pull it high again.
//Preload SPDR with the data that will be sent to master
//upon receiving next byte.
SPDR = 0x41;//Digital controller.
byte_num++;
break;

case 1:
PORTB &= ~(1<<PORTB1);
_delay_us(5);
PORTB |= (1<<PORTB1);
SPDR = 0x5A;//"Here comes data" reply.
byte_num++;
break;

case 2:
PORTB &= ~(1<<PORTB1);
_delay_us(5);
PORTB |= (1<<PORTB1);
SPDR = 0xBF;//DOWN d-pad button??
byte_num++;
break;

case 3:
PORTB &= ~(1<<PORTB1);
_delay_us(5);
PORTB |= (1<<PORTB1);
SPDR = 0xBF;//X button??
byte_num++;
break;

case 4:
SPCR&=~(1<<SPIE);//Disable SPI interrupts.
_delay_us(5);
break;

}//End switch.

} else {
SPCR&=~(1<<SPIE);//Disable SPI interrupts.
}

Serial.println(ch);
}


The polling_seq array is used to check if the PS2 is asking for data in the correct order. It's defined as:
volatile uint8_t polling_seq[5]={0x01,0x42,0x00,0x00,0x00} (see table above).

As serial results, I'm getting:
1
66
0
(repeats)...

But I have no way of checking if the PS2 is receiving the correct bytes back.
Since communication stops after 3rd byte, I'm assuming the prologue bytes are received, but not the actual button data bytes.

I've tried to remove the if statement from the ISR routine above. Same results.
I've also tried to tie up the ~SS line to INT0, and enable STC interrupt there. Same thing.
I've moved ~SS detection to the main loop, to avoid another interrupt overhead (actually, it would be worse, because I have many other things to do in the main loop).

Where am I going wrong? :(

busslave

November 25, 2012, 02:55:10 AM #1 Last Edit: November 25, 2012, 03:07:46 AM by busslave
OK. Now I realize the Playstation protocol isn't that simple. :-\
I decided to store my serial results in an array, and they look like this (I converted it to hex):

0x01
0x42
0x00
0x00
0x00

0x01
0x43
0x00
0x01
0x00

0x01
0x45
0x00
0x00
0x00

This last sequence repeats 10 times and then restarts from the 1st sequence.

According to the second link in my previous post, 0x42 (1st sequence) is the main polling command.
I'm replying it with 0x41 (4 means digital, 1 means 16-bit data). I renamed my command_buff array to data_buff, to avoid confusion with the Playstation terminology, so that:

Command always means Playstation-->AVR (MOSI).
Data always means AVR-->Playstation (MISO).

My data_buff is:
data_buff[4]={0x41,0x5A,0xBF,0xFF}

SPDR is set to 0xFF when INT0 goes low.
While transmitting the 0x01 command, PS receives SPDR (0xFF). Next I reply:

0x41 - 16-bit data digital controller (0x79 is analogue?).
0x5A - "Here comes data" command.
0xBF - Should mean "down key pressed".
0xFF - No keys pressed.

Am I getting this right?
Anyway, the second sequence (0x43), "Enter/Exit Config Mode" (also poll all button states, joysticks and pressures), looks like this:

0x01 - SPDR is 0xFF.
0x43 - About to Enter/Exit Config Mode.
0x00 - Always 0x00?
0x01 - Enter Config Mode (0x00 exits).
0x00 - Always 0x00?

I'm responding to it with my
config_buff[4]={0x41,0x5A,0xFF,0xFF}. Once again, the first 0xFF response is already in SPDR.
I also tried with 0x79 instead of 0x41.

Finally, the last command is 0x45, which should mean "Get more status info".
I configured a status info buffer array as such:
status_info_buff[8]={0xF3,0x5A,0x03,0x02,0x00,0x02,0x01,0x00}
The data byte in bold is the dual shock code. 0x01 is for guitar hero. Which one for digital?
Also, PS commands are 0x00 instead of 0x5A.

Notice also that I'm incrementing my arrays index upon Serial Transfer Complete ISR. I'm completely relying on INT0 going low to reset the index to zero. No "index out of bounds" exception is checked.
Regardless, I'm only getting 5 bytes length commands.

Has anyone experimented with digital controllers before?
Any suggestions would be very welcome.

busslave

Here is a simplified version of my current code:

#define MAX_SAMPLES 82
volatile uint8_t data_buff[4]={0x41,0x5A,0xBF,0xFF};//Reply to 0x42 command.
volatile uint8_t config_buff[4]={0x41,0x5A,0xFF,0xFF};//Reply to 0x43 command.
volatile uint8_t status_info_buff[8]={0xF3,0x5A,0x03,0x02,0x00,0x02,0x01,0x00};//Reply to 0x45 command.
volatile uint8_t sampled_seq[MAX_SAMPLES];

void setup() {
Serial.begin(115200);//Debug!!

//SPI_PORT setup.
SPI_DDR |= (1<<ACK_PIN);//output
SPI_PORT |= (1<<ACK_PIN);//set HIGH
SPI_DDR &= ~(1<<ATT_PIN);//input
SPI_PORT |= (1<<ATT_PIN);//pull-up enabled
SPI_DDR &= ~(1<<CMD_PIN);//input
SPI_PORT |= (1<<CMD_PIN);//pull-up enabled
SPI_DDR |= (1<<DATA_PIN);//output
SPI_PORT |= (1<<DATA_PIN);//set HIGH
SPI_DDR &= ~(1<<CLK_PIN);//input
SPI_PORT |= (1<<CLK_PIN); //pull-up enabled

//SPI setup.
PRR &= ~(1<<PRSPI);//Set to 0 to ensure power to SPI module.
SPCR|=(1<<SPR1);//Fosc/64. @16MHz==250KHz.
SPCR|=(1<<CPHA);//Setup @ leading edge, sample @ falling edge.
SPCR|=(1<<CPOL);//Leading edge is falling edge, trailing edge is rising edge.
SPCR &= ~(1<<MSTR);//MSTR bit is zero, SPI is slave.
SPCR|=(1<<DORD);//Byte is transmitted LSB first, MSB last.
SPCR|=(1<<SPE);//Enable SPI.
SPCR|=(1<<SPIE);//Enable Serial Transfer Complete (STC) interrupt.

//INT0 external interrupt setup.
EICRA=(1<<ISC01);//Falling edge triggers interrupt.
EIMSK=(1<<INT0);//Enable INT0 interrupts.
EIFR|=_BV(INTF0);//Clear flag. Needed??

sei();//Enable global interrupts.
}

ISR(SPI_STC_vect) {
sampled_seq[sample_num]=SPDR;

if (byte_num==1) {//Get polling mode.
if (sampled_seq[sample_num]==0x43) {
SPDR = config_buff[byte_num];
} else if (sampled_seq[sample_num]==0x45) {
SPDR=status_info_buff[byte_num];
} else {
SPDR = data_buff[byte_num];
}
}  else {
SPDR = data_buff[byte_num];
}

if (byte_num<4) {
SPI_PORT &= ~(1<<PORTB1);//pull acknowledge line low for at least 5us.
_delay_us(5);
SPI_PORT |= (1<<PORTB1);//High again.
}

byte_num++;
sample_num++;
}

ISR(INT0_vect) {//Triggers on falling edge.
SPDR=0xFF;
byte_num=0;
}

void loop() {
if (sample_num>(MAX_SAMPLES-1)) {
EIMSK&=~(1<<INT0);//Disable INT0 interrupts.
SPCR&=~(1<<SPIE);//Disable SPI interrupts.
for (uint8_t t=0; t<MAX_SAMPLES; t++) {
Serial.println(sampled_seq[t]);
}
sample_num=0;
}
}

public-pervert

Looks really promising!  :D

I hope Micro can help you on that.

busslave

Micro has already helped me with my Saturn IR gamepad.
My guess is that I'm missing something really obvious, like I was then.  :-\

busslave

Actually, the problem runs deeper then I thought.

I'm not sure if I can use AVR's SPI unit at all.

According to the xls spreadsheet I downloaded from CuriousInventor, an example of a DualShock controller would look like this (numbers before the colons are line numbers):

DS command   DS data
MOSI        MISO
51: 01   51: FF
52: 01   52: FF
53: 01   53: FF
54: 01   54: FF
55: 01   55: FF
58: 01   58: FF
59: 42   59: 41 poll
60: 00   60: 5A
61: 00   61: FE
62: 00   62: FF
63: 01   63: FF
64: 43   64: 41 config
65: 00   65: 5A
66: 01   66: FE
67: 00   67: FF
68: 01   68: FF
69: 45   69: F3 query model
70: 00   70: 5A
71: 5A   71: 02
72: 5A   72: 02
73: 5A   73: 00
74: 5A   74: 02
75: 5A   75: 00   
76: 5A   76: 00
77: 01   77: FF

Now, my question is: since there is no way of anticipating the next byte the PS2 will send, what value should I load SPDR with?

I'll try to clarify: let's imagine the PS2 sends command number 55 (0x01). Since command number 56 is also 0x01, I should have preloaded SPDR with 0xFF, because next command byte 56 will also be 0x01. But now, in command 56, I must preload it with 0x41, because the next command byte will be 0x42. Or with 0xF3, if next command byte should be 0x45 (entering query model mode). >:(

Actually, it's not 56, it's 58, I don't know why it skipped 2 numbers.

See my problem here? Probably I'm missing a fundamental issue.



micro

Are you trying to emulate a digital Playstation 1 controller or a Dual Shock 2 controller?

Some years ago I successfully emulated a digital PSX pad with an ATtiny2313 @ 20 MHz or so I believe. The program was written in Basic (Bascom). I didn't use the SPI peripheral. Instead I waited until "ATT" went low then I shifted out the bytes.
The code wasn't written well but it worked. Unfortunately I can't find the source code anymore...

But I remember that it wasn't too hard. I don't know for sure but I think I played dumb and expected the communication to be like:

0x01
0x42
don't care
don't care
don't care
(PSX->controller)

0xFF
0x41
0x5A
button data 1
button data 2
(controller->PSX)

I don't think I even checked what kind of data the PSX was sending to the my microcontroller.

I don't know if this really helps you at all...? ;)
But if you read somewhere that ACK should go low for at least 5 us then you should try 7 or 10 us and not exactly 5 us  :P



busslave

December 05, 2012, 10:16:51 PM #7 Last Edit: December 05, 2012, 10:35:57 PM by busslave
Yes, Micro, it kinda helps.
I'm trying to emulate a digital Playstation 1 controller, but I'm testing it on a PS2.
I'm assuming the PS2 works fine with those. I'll try to get one from my nephew, next weekend.
And maybe a PS1 too.  ;)
But one of my objectives was learning how to program the SPI unit as a slave, I was really not feeling like bit banging in/out pins.

busslave

December 05, 2012, 11:43:59 PM #8 Last Edit: December 06, 2012, 12:11:44 AM by busslave
It's working!   ;D ;D ;D
That was all I needed to know!  8)
Here is the code that worked for me:

/*
PSX_RECEIVER.cpp

Created: 05-12-2012
Author: busslave

Arduino pin | AVR pin | PSX pin

2 INT0 | 4 | 6 ATT
5 PD5 | 11 | IR RECEIVER DATA PIN
9 PB1 | 15 | 9 ACK
10 -SS | 16 | 6 ATT
11 MOSI | 17 | 2 COMMAND
12 MISO | 18 | 1 DATA
13 SCK | 19 | 7 CLOCK
| 4 GND
| 5 VCC

*/

#include "Arduino.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define F_CPU 16000000
#define ARDUINO 101
#define MAX_SAMPLES 82

#define SPI_PORT PORTB
#define SPI_PINS PINB
#define SPI_DDR  DDRB
#define SPI_PINS  PINB

#define ACK_PIN   1 //PB1
#define ATT_PIN   2 //~SS
#define CMD_PIN   3 //MOSI
#define DATA_PIN  4 //MISO
#define CLK_PIN   5 //SCK

#define DATA_LEN 5

volatile uint8_t data_buff[DATA_LEN]={0x41,0x5A,0xBF,0xFF,0xFF};//Reply.
volatile uint8_t command_buff[DATA_LEN]={0x01,0x42,0x00,0x00,0x00};

volatile uint8_t sampled_seq[MAX_SAMPLES];
volatile uint8_t sent_seq[MAX_SAMPLES];

volatile uint8_t curr_byte=0;
volatile uint8_t sample_num=0;
volatile uint8_t next_byte=0;

void setup();
void loop();

void setup() {
Serial.begin(115200);//DEBUG!

//SPI_PORT setup.
SPI_DDR |= (1<<ACK_PIN);//output
SPI_PORT |= (1<<ACK_PIN);//set HIGH
SPI_DDR |= (1<<DATA_PIN);//output
SPI_PORT |= (1<<DATA_PIN);//set HIGH

//SPI setup.
PRR &= ~(1<<PRSPI);//Set to 0 to ensure power to SPI module.
//SPSR|=(1<<SPI2X);//Fosc/32. @16MHz==500KHz.
SPCR|=(1<<SPR1);//Fosc/64. @16MHz==250KHz.
SPCR|=(1<<CPHA);//Setup @ leading edge, sample @ falling edge.
SPCR|=(1<<CPOL);//Leading edge is falling edge, trailing edge is rising edge.
SPCR &= ~(1<<MSTR);//MSTR bit is zero, SPI is slave.
SPCR|=(1<<DORD);//Byte is transmitted LSB first, MSB last.
SPCR|=(1<<SPE);//Enable SPI.
SPCR|=(1<<SPIE);//Enable Serial Transfer Complete (STC) interrupt.
SPDR=0xFF;

sei();//Enable global interrupts.
}

/*
data_buff[DATA_LEN+1]={0x41,0x5A,0xFF,0xBF,0xFF};//Reply
command_buff[DATA_LEN+1]={0x01,0x42,0x00,0x00,0x00};
*/
ISR(SPI_STC_vect) {
uint8_t inbyte=SPDR;
sampled_seq[sample_num]=inbyte;//DEBUG!

if (inbyte==command_buff[curr_byte]) {
SPDR = data_buff[curr_byte];
sent_seq[sample_num] = data_buff[curr_byte];//DEBUG!
curr_byte++;
if (curr_byte<DATA_LEN) {//ACK low.
SPI_PORT &= ~(1<<PORTB1);
_delay_us(10);
SPI_PORT |= (1<<PORTB1);
} else {
SPDR = 0xFF;
curr_byte=0;
}
} else {
SPDR = 0xFF;
curr_byte=0;
}

sample_num++;//DEBUG!
}

void loop() {
if (sample_num>(MAX_SAMPLES-1)) {
for (uint8_t t=0; t<MAX_SAMPLES; t++) {
Serial.println(sampled_seq[t]);
Serial.println(sent_seq[t]);
Serial.println("---");
}
sample_num=0;
}
}


Thanks for the reply, Micro!
BTW, this is a great forum. I'm not a big gamer myself, but the electronic sections are definitely my favourite ones. It's also nicely presented, programed and maintained. Thanks for everybody that keep it up.

public-pervert

This is amazing! Good to hear you got it working, man!

I would love to see a video of this working btw  ;D

micro

Well done!  ;D

Two more things:

- I don't  think you need to configure the SPI clock speed as this has no effect when beeing an SPI slave

- You've configured your ACK pin as output pin. I don't know for sure but I think all PSX controller ports are sharing the same clock, command, data and ack lines. All these lines are held high by the PSX when inactive. It's likely there's an pull-up resistor inside the PSX at least for ACK and DATA coming from the controllers. So when there's really only one ACK line shared by all ports I see problems arising when using 2 controllers at the same time.

It'd be safer to leave the ACK PORT bit as zero and then just set the DDR-bit for driving the ACK line low or clearing the DDR-bit for a high ACK signal.

busslave

Micro:
I see what you mean. I'll do just that as soon as can.

Public-pervert:
It's not quite working yet. I'm using the Saturn IR controller I prototyped previously, so I still need to integrate the IR receive functionality. The code I posted before just implements the SPI slave functionality in ATmega328P.  ;)

busslave

Hi everybody.
I'm glad to say my controller is working fine on the PS2.
On games where one can use the d-pad, that is.
On the PSX it displays a glitch, that causes the pointer (cursor) to switch in and out of existence very rapidly.
I'm currently working on another project, but I'll try to test it more thoroughly later, and post some pictures too.

djsedaw

Hi I know it was a long time ago but did you get this working, I'm trying to use a teensy 2 as a control interface for a ps2 portable project!