PS2 Controller on an AVR

Forward.

When i originally wrote this page there was no existing C code for interfacing PS2 controllers to AVR microcontrollers. Since then though there are 2 other options i would recommend before my code.

1. JadeKnight on the TrossenRobotics forum did what i had always meant to do and tidied up my code. He/she still uses my SPI bit-bang code for the PS2 communication but everything else is much tidier.

If you want a simple PS2 controller library for your AVR grab this one. It should work for any AVR (providing you have at least 4 spare I/O pins).

You can find his code here:

http://forums.trossenrobotics.com/datacenter/drivers-libraries-firmware-7/axon-ps2-controller-interface-library-144/

2. Webbot has written the extremely comprehensive WebbotLib which has a bolt on for loads of different things. He's recently added PS2 controllers to the list.

WebbotLib is all or nothing so if you only want to add PS2 controller support to an existing project then 1. probably is a better option.

If on the other hand you want an all singing, all dancing solution for one of the larger AVRs you should definitely check out WebbotLib.

(ATMega168, ATMega32, ATMega328P, ATMega640, ATMega1280, ATMega2560 and ATMega2561 is the official list at present in May 2010 but it should work with any AVR with enough memory.)

http://webbot.org.uk

The Original Document:

Why do this?

As an input device for my radio controlled aircraft i decided on a PS2 controller because it was the simplest way i could think of to get a nice user interface to my project. Cheap too.

Background reading before you start.

I'm not going to go into the specifics of exactly how to control a PSx controller here as there are several good resources on interfacing them on the internet. At the time of writing these are the pages i found useful:

http://www.geocities.com/digitan000/Hardware/22/e22_page.html

http://www.gamesx.com/controldata/psxcont/psxcont.htm

http://www.curiousinventor.com/guides/ps2

http://www.webx.dk/robot-crawler/ps-joy.htm

Before trying to duplicate my results make sure you understand what the Data, Command and Clock lines do on a PSx controller. Also make sure you understand how a PSx controller packet is formed and know where to look up the various control codes that can be sent to and from it. (All this is explained in the links above.)

How i did it.

The AVR code included at the bottom can read all the button and analouge inputs from a PS2 DualShock controller and display them on a bank of 8 status LEDs.

Writing the firmware turned out to be fairly straight forward. Anyone who is used to programming AVRs should be able to modify and use my code. As usual with my HowTos though, i will not be providing any support. Try the guys at http://www.societyofrobots.com/robotforum/index.php if you need help.

The only thing that had me scratching my head for a while was the delay routine was using an empty for loop to create the microsecond delay and GCC optimisation was causing this delay to vanish. (Obviously. i guess i was having a dumb moment...)

The symptom this caused was "ghosting" of keys. ie. one key press affected more than one bit in the data stream. It was solved by turning off optimisation in the Makefile.

I beleive ghosting can also be caused by too weak a pull up resistor on the "command" and "clock" lines but i did not experience this symptom.

Hardware.

The hardware i use for this is an AVR ATmega8 from Atmel on a home made PCB.

I make my PCBs using the toner transfer method but you could just as easily build on on perf board using a DIL ATmega8. As many people will recoginse the PCBs are done in Eagle and are double sided.

There are a few headers on the board that need a little explanation. TX_ANT and TX_CON are for the RF module i'm using to turn this board into my RX transmitter. The I2C header is just that. It's so i can connect this board to an i2c buss if i ever need to connect additional perifarals.

The PS2 controller.

I got my first controller from Ebay. It was not a genuine Sony but worked fine with some limitations.

The analogue controls on the fake controller are not analogue for their entire range. Maybe 25% movement from center position do not register. Also the movement is not registered all the way to the end of the movement, stopping 25% of the way from the end.

I've borrowed and tried a few different controllers and all the fakes opereate in a similar manner.

A friend nicely gave me a proper Sony PS2 controller (thx Phily!) which exhibits none of these problems. The proper controller is analogue movement all the way through the movement of the sticks.

So in short, the fake PS2 controllers are fine as long as you don't care about the analogue controls. If you want analogue control, get a Sony controller.

AVR firmware.

The code below will put PS2 DualShock controllers in analoge "Red" mode. It could be modified fairly easily to work with PS1 controllers or some of the other types of PS2 controller.

The firmware allows the user to select which register is displayed on the 8 LEDs. You can toggle between registers by pressing the "Select" button on the controller.

So this code is not 100% complete. I've left it up to the user to include timer functions (delay_us and delay_ms) as well as headers and Makefile etc. I used AVRlib's timer functions for this. (Remember to disable GCC optimisation for AVRlib's delay_us....)

This firmware works on AVRs clocked at 8MHz.

#define PSclock 2 // PD2

#define PSdata 3 // PD3

#define PSacknolage 4 // PD4

#define PScommand 5 // PD5

#define PSattention 6 // PD6

int main(void)

{

// set the baud rate of the UART

// (needed for transmitting over radio module).

//uartSetUp(2400);

// set port pins to output for status leds:

sbi(DDRC, PC0); // led.

sbi(DDRC, PC1); // led

sbi(DDRC, PC2); // led

sbi(DDRC, PC3); // led

sbi(DDRD, PD7); // led

sbi(DDRB, PB0); // led

sbi(DDRB, PB1); // led

sbi(DDRB, PB2); // led

// PSx controller I/O pin setup:

sbi(DDRD, PD2); // clock. output. (blue)

cbi(DDRD, PD3); // data. input. (brown)

sbi(PORTD, PD3); // enable pullup resistor

cbi(DDRD, PD4); // acknolage. input. (green)

sbi(PORTD, PD4); // enable pullup resistor

sbi(DDRD, PD5); // command. output. (orange)

sbi(DDRD, PD6); // attention. output. (yellow)

// enable interupts

sei();

// watchdog timer reset and enable

//wdt_reset();

//wdt_enable(0x02);

timerInit();

// this loop continues to put PSx controller into analouge mode untill the

// controller responds with 0x73 in the 2nd byte.

// (PS2 controller responds with 0x73 when in analouge mode.)

// the status LEDs will continue to count upwards untill a controller is found.

// if everything is working correctly this should happen on the first pass of

// this loop but occasionally errors occur and a 2nd or 3rd itteration happen.

unsigned char chk_ana = 0, cnt = 0;

while(chk_ana != 0x73){

// put controller in config mode

sbi(PORTD, PScommand);

sbi(PORTD, PSclock);

cbi(PORTD, PSattention);

gameByte(0x01);

gameByte(0x43);

gameByte(0x00);

gameByte(0x01);

gameByte(0x00);

sbi(PORTD, PScommand);

delay_ms(1);

sbi(PORTD, PSattention);

delay_ms(10);

// put controller in analouge mode

sbi(PORTD, PScommand);

sbi(PORTD, PSclock);

cbi(PORTD, PSattention);

gameByte(0x01);

gameByte(0x44);

gameByte(0x00);

gameByte(0x01);

gameByte(0x03);

gameByte(0x00);

gameByte(0x00);

gameByte(0x00);

gameByte(0x00);

sbi(PORTD, PScommand);

delay_ms(1);

sbi(PORTD, PSattention);

delay_ms(10);

// exit config mode

sbi(PORTD, PScommand);

sbi(PORTD, PSclock);

cbi(PORTD, PSattention);

gameByte(0x01);

gameByte(0x43);

gameByte(0x00);

gameByte(0x00);

gameByte(0x5A);

gameByte(0x5A);

gameByte(0x5A);

gameByte(0x5A);

gameByte(0x5A);

sbi(PORTD, PScommand);

delay_ms(1);

sbi(PORTD, PSattention);

delay_ms(10);

// poll controller and check in analouge mode.

sbi(PORTD, PScommand);

sbi(PORTD, PSclock);

cbi(PORTD, PSattention);

gameByte(0x01);

chk_ana = gameByte(0x42); // the 2nd byte to be returned from the controller should = 0x73 for "red" analouge controller.

gameByte(0x00);

gameByte(0x00);

gameByte(0x00);

gameByte(0x00);

gameByte(0x00);

gameByte(0x00);

gameByte(0x00);

sbi(PORTD, PScommand);

delay_ms(1);

sbi(PORTD, PSattention);

delay_ms(10);

// keep increasing counter to be dispalyed untill PSx controller confirms it's in analouge mode.

display(cnt++);

if (cnt > 254){ cnt=0;}

}

short int temp, data0, data1, data2, data3, data4, data5, i ,debounceSelect;

// main program loop:

while (1){

sbi(PORTD, PScommand); // start communication with PSx controller

sbi(PORTD, PSclock);

cbi(PORTD, PSattention);

gameByte(0x01); // bite 0. header.

temp = gameByte(0x42); // bite 1. header. (should possibly put test on this byte to detect unplugging of controller.)

gameByte(0x00); // bite 2. header.

data0 = gameByte(0x00); // bite 3. first data bite.

data1 = gameByte(0x00); // bite 4.

data2 = gameByte(0x00); // bite 5.

data3 = gameByte(0x00); // bite 6.

data4 = gameByte(0x00); // bite 7.

data5 = gameByte(0x00); // bite 8.

delay_us(1);

sbi(PORTD, PScommand); // close communication with PSx controller

delay_us(1);

sbi(PORTD, PSattention); // all done.

if(!(data0 & _BV(0)) && !debounceSelect) // capture one unique press of the "select" button

{

debounceSelect = 1;

}

else if ((data0 & _BV(0)) && debounceSelect)

{

if(i++ >= 5) i=0;

debounceSelect = 0;

}

// this switch decides which data register to show on status LEDs depending on how many times

// the "select" button on the PS2 controller has been pressed.

switch(i)

{

case 0:

display(data0);

break;

case 1:

display(data1);

break;

case 2:

display(data2);

break;

case 3:

display(data3);

break;

case 4:

display(data4);

break;

case 5:

display(data5);

}

// this section calls my R/C transmit code (not included here).

//tx_channel(0, data2);

//tx_channel(1, data5);

} // while(1)

} //main

// PSx controller communication function.

// send a byte on the command line and receive one on the data line.

// needs Attention pin to have gone low before called to activate controller.

int gameByte(short int command)

{

short int i ;

delay_us(1);

short int data = 0x00; // clear data variable to save setting low bits later.

for(i=0;i<8;i++)

{

if(command & _BV(i)) sbi(PORTD, PScommand); // bit bang "command" out on PScommand wire.

else cbi(PORTD, PScommand);

cbi(PORTD, PSclock); // CLOCK LOW

delay_us(1); // wait for output to stabilise

if((PIND & _BV(PSdata))) sbi(data, i); // read PSdata pin and store

//else cbi(data, i);

sbi(PORTD, PSclock); // CLOCK HIGH

}

sbi(PORTD, PScommand);

delay_us(20); // wait for ACK to pass.

return(data);

}

// put 1 byte on the 8 LEDs. obviously you need to change the output pins to

// match your board.

void display(short int LEDs)

{

if(LEDs & _BV(0)) cbi(PORTC, PD0);

else sbi(PORTC, PD0);

if(LEDs & _BV(1)) cbi(PORTC, PD1);

else sbi(PORTC, PD1);

if(LEDs & _BV(2)) cbi(PORTC, PD2);

else sbi(PORTC, PD2);

if(LEDs & _BV(3)) cbi(PORTC, PD3);

else sbi(PORTC, PD3);

if(LEDs & _BV(4)) cbi(PORTD, PD7);

else sbi(PORTD, PD7);

if(LEDs & _BV(5)) cbi(PORTB, PD0);

else sbi(PORTB, PD0);

if(LEDs & _BV(6)) cbi(PORTB, PD1);

else sbi(PORTB, PD1);

if(LEDs & _BV(7)) cbi(PORTB, PD2);

else sbi(PORTB, PD2);

return;

}

What's next?

Well you need to find something cool to control.

I use this method in my DIY RC system documented here:

DIY RC System

mrdunk(at)gmail.com