PS2 Controller Buttons sent over RFM12 wireless module

I really believed there would be a conflict using the PS2 controller with the RFM12 modules, since they both use SPI.  But the great thing is that Bill Porter wrote the PS2X library so that the pins are changable, aka software SPI.  So the RFM12B can use hardware SPI, and the PS2 can use its software SPI bit banging.

The only thing I had to modify in the PS2X library was the read_delay variable in the config_gamepad function (I increased it to 3).  After that, the controller gets recognized as a Dual Shock 2 controller, with full analog on all 12 buttons, 2 joysticks, and 4 analog buttons (start, select, L3, R3).

To learn how the RF12 Library from JeeLabs works, I modified the RF12demo sketch to send a packet when I pressed ‘x’ in the serial terminal of the arduino with the PS2 controller.  It then sends the values of all of the buttons (I forgot start and select) to the remote arduino.  The RF12 library is pretty powerful, letting you reconfigure the modules for different channels and everything else.

Regarding backups, I have it pretty much figured out, just not implemented.  I settled on using Windows XP for my main computer, since it is very fast.  A side effect is that I’m finding that both of my USB to serial cables are working perfectly.

I’ve attached the example code below, so feel free to give it a try!  It is mostly the RF12demo sketch, utilizing the PS2X library to get button values.  total sketch size is 10612 bytes.

// Example sketch showing sending PS2 controller values through RF12 wireless module
// This sketch is mostly the RF12demo sketch from JeeLabs (awesome) and using the
// PS2X library from Bill Porter (really great stuff!)
//
#include <JeeLib.h>
#include <util/crc16.h>
#include <util/parity.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <PS2X_lib.h>  //for v1.6

#define COLLECT 0x20 // collect mode, i.e. pass incoming without sending acks

PS2X ps2x; // create PS2 Controller Class

//right now, the library does NOT support hot pluggable controllers, meaning
//you must always either restart your Arduino after you conect the controller,
//or call config_gamepad(pins) again after connecting the controller.
int error = 0;
byte type = 0;
byte vibrate = 0;

//static unsigned long now () {
//    // FIXME 49-day overflow
//    return millis() / 1000;
//}

static void activityLed (byte on) {
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, !on);
#endif
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// RF12 configuration setup code

typedef struct {
byte nodeId;
byte group;
char msg[RF12_EEPROM_SIZE-4];
word crc;
} RF12Config;

static RF12Config config;

static char cmd;
static byte value, stack[RF12_MAXDATA], top, sendLen, dest, quiet;
static byte testbuf[RF12_MAXDATA], testCounter;

static void addCh (char* msg, char c) {
byte n = strlen(msg);
msg[n] = c;
}

static void addInt (char* msg, word v) {
if (v >= 10)
addInt(msg, v / 10);
addCh(msg, '0' + v % 10);
}

static void saveConfig () {
// set up a nice config string to be shown on startup
memset(config.msg, 0, sizeof config.msg);
strcpy(config.msg, " ");

byte id = config.nodeId & 0x1F;
addCh(config.msg, '@' + id);
strcat(config.msg, " i");
addInt(config.msg, id);
if (config.nodeId & COLLECT)
addCh(config.msg, '*');

strcat(config.msg, " g");
addInt(config.msg, config.group);

strcat(config.msg, " @ ");
static word bands[4] = { 315, 433, 868, 915 };
word band = config.nodeId >> 6;
addInt(config.msg, bands[band]);
strcat(config.msg, " MHz ");

config.crc = ~0;
for (byte i = 0; i < sizeof config - 2; ++i)
config.crc = _crc16_update(config.crc, ((byte*) &config)[i]);

// save to EEPROM
for (byte i = 0; i < sizeof config; ++i) {
byte b = ((byte*) &config)[i];
eeprom_write_byte(RF12_EEPROM_ADDR + i, b);
}

if (!rf12_config())
Serial.println("config save failed");
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

char helpText1[] PROGMEM =
"\n"
"Available commands:" "\n"
"  <nn> i     - set node ID (standard node ids are 1..26)" "\n"
"               (or enter an uppercase 'A'..'Z' to set id)" "\n"
"  <n> b      - set MHz band (4 = 433, 8 = 868, 9 = 915)" "\n"
"  <nnn> g    - set network group (RFM12 only allows 212, 0 = any)" "\n"
"  <n> c      - set collect mode (advanced, normally 0)" "\n"
"  t          - broadcast max-size test packet, with ack" "\n"
"  ...,<nn> a - send data packet to node <nn>, with ack" "\n"
"  ...,<nn> s - send data packet to node <nn>, no ack" "\n"
"  <n> q      - set quiet mode (1 = don't report bad packets)" "\n"
;

static void showString (PGM_P s) {
for (;;) {
char c = pgm_read_byte(s++);
if (c == 0)
break;
if (c == '\n')
Serial.print('\r');
Serial.print(c);
}
}

static void showHelp () {
showString(helpText1);
//Serial.println("Current configuration:");
rf12_config();
}

static void handleInput (char c) {
int i = 0;
if ('0' <= c && c <= '9')
value = 10 * value + c - '0';
else if (c == ',') {
if (top < sizeof stack)
stack[top++] = value;
value = 0;
} else if ('a' <= c && c <='z') {
Serial.print("> ");
Serial.print((int) value);
Serial.println(c);
switch (c) {
default:
showHelp();
break;
case 'i': // set node id
config.nodeId = (config.nodeId & 0xE0) + (value & 0x1F);
saveConfig();
break;
case 'b': // set band: 4 = 433, 8 = 868, 9 = 915
value = value == 8 ? RF12_868MHZ :
value == 9 ? RF12_915MHZ : RF12_433MHZ;
config.nodeId = (value << 6) + (config.nodeId & 0x3F);
saveConfig();
break;
case 'g': // set network group
config.group = value;
saveConfig();
break;
case 'c': // set collect mode (off = 0, on = 1)
if (value)
config.nodeId |= COLLECT;
else
config.nodeId &= ~COLLECT;
saveConfig();
break;
case 't': // broadcast a maximum size test packet, request an ack
cmd = 'a';
sendLen = RF12_MAXDATA;
dest = 0;
for (byte i = 0; i < RF12_MAXDATA; ++i)
testbuf[i] = i + testCounter;
Serial.print("test ");
Serial.println((int) testCounter); // first byte in test buffer
++testCounter;
break;
case 'x': //PS2 controller packet.
cmd = 'a';
i = 0;
dest = 0;
sendLen = 18; //16 values
//send status of the following:

// RX
testbuf[i++] = ps2x.Analog(PSS_RX);
// RY
testbuf[i++] = ps2x.Analog(PSS_RY);
// LX
testbuf[i++] = ps2x.Analog(PSS_LX);
// LY
testbuf[i++] = ps2x.Analog(PSS_LY);
// R
testbuf[i++] = ps2x.Analog(PSAB_PAD_RIGHT);
// L
testbuf[i++] = ps2x.Analog(PSAB_PAD_LEFT);
// U
testbuf[i++] = ps2x.Analog(PSAB_PAD_UP);
// D
testbuf[i++] = ps2x.Analog(PSAB_PAD_DOWN);
// Tri
testbuf[i++] = ps2x.Analog(PSAB_TRIANGLE);
// Circle
testbuf[i++] = ps2x.Analog(PSAB_CIRCLE);
// X
testbuf[i++] = ps2x.Analog(PSAB_CROSS);
// Square
testbuf[i++] = ps2x.Analog(PSAB_SQUARE);
// L1
testbuf[i++] = ps2x.Analog(PSAB_L1);
// R1
testbuf[i++] = ps2x.Analog(PSAB_R1);
// L2
testbuf[i++] = ps2x.Analog(PSAB_L2);
// R2
testbuf[i++] = ps2x.Analog(PSAB_R2);
// L3
testbuf[i++] = ps2x.Button(PSB_L3);
// R3
testbuf[i++] = ps2x.Button(PSB_R3);

break;
case 'a': // send packet to node ID N, request an ack
case 's': // send packet to node ID N, no ack
cmd = c;
sendLen = top;
dest = value;
memcpy(testbuf, stack, top);
break;
case 'l': // turn activity LED on or off
activityLed(value);
break;
case 'q': // turn quiet mode on or off (don't report bad packets)
quiet = value;
break;
}
value = top = 0;
memset(stack, 0, sizeof stack);
} else if ('A' <= c && c <= 'Z') {
config.nodeId = (config.nodeId & 0xE0) + (c & 0x1F);
saveConfig();
} else if (c > ' ')
showHelp();
}

void setup() {
Serial.begin(57600);
Serial.print("\n[RF12demo.8]");

if (rf12_config()) {
config.nodeId = eeprom_read_byte(RF12_EEPROM_ADDR);
config.group = eeprom_read_byte(RF12_EEPROM_ADDR + 1);
} else {
config.nodeId = 0x41; // node A1 @ 433 MHz
config.group = 0xD4;
saveConfig();
}
//setup pins and settings:  GamePad(clock, command, attention, data, Pressures?, Rumble?) check for error
error = ps2x.config_gamepad(8,6,7,5, true, true);

showHelp();
}

void loop() {
ps2x.read_gamepad(false, vibrate);

if (Serial.available())
handleInput(Serial.read());

if (rf12_recvDone()) {
byte n = rf12_len;
if (rf12_crc == 0) {
Serial.print("OK");
} else {
if (quiet)
return;
Serial.print(" ?");
if (n > 20) // print at most 20 bytes if crc is wrong
n = 20;
}
if (config.group == 0) {
Serial.print("G ");
Serial.print((int) rf12_grp);
}
Serial.print(' ');
Serial.print((int) rf12_hdr);
for (byte i = 0; i < n; ++i) {
Serial.print(' ');
Serial.print((int) rf12_data[i]);
}
Serial.println();

if (rf12_crc == 0) {
activityLed(1);

if (RF12_WANTS_ACK && (config.nodeId & COLLECT) == 0) {
Serial.println(" -> ack");
rf12_sendStart(RF12_ACK_REPLY, 0, 0);
}
activityLed(0);
}
}

if (cmd && rf12_canSend()) {
Serial.print(" -> ");
Serial.print((int) sendLen);
Serial.println(" b");
byte header = cmd == 'a' ? RF12_HDR_ACK : 0;
if (dest)
header |= RF12_HDR_DST | dest;
rf12_sendStart(header, testbuf, sendLen);
cmd = 0;
}
}

Advertisements

4 thoughts on “PS2 Controller Buttons sent over RFM12 wireless module

  1. I would like to know if a PS3 wireless controller could be used on a PS2, i guess it could be used by the USB port on the PS2 and FMCB loads a USB driver, or by hardware – somehow the PS3 signals get converted to PS2 inputs.

    Any Ideas?

    • Unless the PS2 has the USB drivers to handle PS3 controllers, then I don’t think that you’ll be able to use a PS3 controller on a PS2.

      To convert the PS3 wireless bluetooth to PS2, you would need the equivalent of a Arduino and a USB host shield with Bluetooth dongle. You would then have to convert what you receive via bluetooth, to SPI packets, with the Arduino acting as a SPI slave. That sounds like a lot of work, especially when you can get something like this for $12.

    • I’m not sure that it really matters. All we can see is the USB/Bluetooth interface. Sure, we can take apart the PS3 controller and figure out how it ticks, but unless you want to modify it, it would be better to stay with either USB or the Bluetooth interface.

      For the controllers I use (Nunchuck, PS2), I stay with using the normal interface, since I want it to be easily replaceable if something breaks. A PS2 controller would be easier and simpler to use than a PS3 controller, thanks to Bill Porters Library.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s