Modified Wireless PS2 Joypad

Designing a Wireless Joypad for Robotic Control

I was thinking about designing a wireless joypad to control my robot, and I finally came up with an idea to repurpose a cheap Gigaware PS2 Joypad from RadioShack, which cost only $9.99. This joypad includes:

  • 2 Joysticks
  • 1 8-way D-Pad (Direction Pad)
  • 8 Action Buttons
  • 4 Extra Buttons (Select, Start, Macro, Mode)

I began by searching the web for ways to interface this PS2 controller with an Arduino and discovered an Arduino-compatible library called the PSX Library. Unfortunately, it didn’t work with my setup, likely because the library was designed for PlayStation 1 controllers, whereas mine was PS2-compatible. So, I decided to create my own library instead.

We developed the sperm into a clinging clot, then the clot into a lump, then the lump into bones, then clothed the bones with flesh, then We brought it into being as a new creation. So Blessed is Allah, the Best of Creators.

Quran 23:13-14
Gigaware PS2 Joypad Marked

First, I removed the PS2 connector and the long cable, as this project would be wireless. I didn’t need any of the internal circuitry of the joypad, just the push buttons, LEDs, and joystick potentiometers. I figured out the schematic of the two joysticks:

Gigaware Joystick Schematic
Joystick schematic for the four potentiometers.

The approximate value for each potentiometer is 5.2K ohms. These four points would serve as the Arduino’s analog inputs (Pins 1 to 4). Due to the limited space inside the joypad, I used wire wrap wires to solder the connectors for the buttons, potentiometers, and LEDs. I managed to solder only 8 out of the 16 buttons because the others were too compact to handle. These 8 buttons and 2 LEDs would connect to 10 digital I/O pins on the Arduino.

Here is the picture of the modified joypad:

Modified PS2 Joypad

And here are the Arduino, and the RF transmitter and receiver modules:

Arduino RF Transmitter Receiver

I found a few reference articles on interfacing RF modules with microcontrollers:

I followed the first reference, and it worked without any issues. The example module used a 4-byte packet transmission protocol at 1200 bps, but I modified the code for the Arduino to work at 2400 bps since my RF modules were a bit faster, capable of 4800 bps.

Each joystick on my joypad has two axes (X and Y), giving 4 analog inputs to the microcontroller. Each input has a resolution of 1024 (0 to 1023), requiring 2 bytes (integer) for each. Therefore, 2 joysticks need 8 bytes (4 x 2 bytes). The 8 push buttons can be represented with 1 byte (1 bit per button). The data transmission packet also includes 1 synchronization byte, 1 address byte, and 1 checksum word (2 bytes), making the complete packet length 13 bytes (8 + 1 + 1 + 1 + 2).

Wireless Joypad Transmission Data Packet
Transmission Data Packet

I kept the code clean and well-commented. Since there are two separate circuits, the transmitter (joypad itself) and the receiver (Arduino Duemilanove), I wrote two separate scripts. However, I made the WirelessDataPacket class common for both.

Below is the source code for the transmitter module:

Transmitter Sketch
#include <stdio.h>

int r_sync = 0xAA;  // synchro signal
int r_addr = 0x44;  // receiver address

char buffer[36];

// pin configuration between arduino and joypad
int pinLeftStickX  = 1;  // Analog Pin 1
int pinLeftStickY  = 2;  // Analog Pin 2
int pinRightStickX = 3;  // Analog Pin 3
int pinRightStickY = 4;  // Analog Pin 4

int pinButtonL1 = 9;     // L1 Button
int pinButtonL2 = 10;    // L2 Button
int pinButtonR1 = 11;    // R1 Button
int pinButtonR2 = 12;    // R2 Button

int pinButtonSelect = 8; // select button
int pinButtonStart = 5;  // start button
int pinButtonMacro = 7;  // macro button
int pinButtonMode = 6;   // mode button

int pinLEDRed = 2;       // red LED pin
int pinLEDYellow = 4;    // yellow LED pin

int releasedButtonMacro = true; // either the macro button is pressed(false) or released(true)
int releasedButtonMode = true;  // either the mode button is pressed(false) or released(true)

// status
boolean macro = false;
boolean mode  = false;

//-----------------------------
// multiple bytes manipulation
//-----------------------------
int getHiByte(int intData) {
  return (intData >> 8);
}

int getLoByte(int intData) {
  return (intData & 0xFF);
}

int mergeHiLo(int hi, int lo) {
  return ( (hi << 8) | lo );
}

//----------------------------------
// Each data packet has 4 bytes:
// sync | address | data | checksum
//----------------------------------
class WirelessDataPacket {
  public:
  int address;
  int leftStickX;
  int leftStickY;
  int rightStickX;
  int rightStickY;
  int button;
  int checksum;
  
  WirelessDataPacket(int addr) {
    address = addr;
  }
  
  int sendPacket() {            
    checksum = address + leftStickX + leftStickY + rightStickX + rightStickY + button;
    
    Serial.print(r_sync, BYTE);                  // send sync byte
    Serial.print(address, BYTE);                 // send address byte
    Serial.print(getLoByte(leftStickX), BYTE);   // send leftStickX Low Byte
    Serial.print(getHiByte(leftStickX), BYTE);   // send leftStickX High Byte
    Serial.print(getLoByte(leftStickY), BYTE);   // send leftStickY Low Byte
    Serial.print(getHiByte(leftStickY), BYTE);   // send leftStickY High Byte
    Serial.print(getLoByte(rightStickX), BYTE);  // send rightStickX Low Byte
    Serial.print(getHiByte(rightStickX), BYTE);  // send rightStickX High Byte
    Serial.print(getLoByte(rightStickY), BYTE);  // send rightStickY Low Byte
    Serial.print(getHiByte(rightStickY), BYTE);  // send rightStickY High Byte
    Serial.print(button, BYTE);                  // send button byte
    Serial.print(getLoByte(checksum), BYTE);     // send checksum byte
    Serial.print(getHiByte(checksum), BYTE);     // send checksum byte
    
    return 0;
  }
  
  // wait until a new byte (8 bit) arrives, then return it
  int receiveByte() {
    while (Serial.available() == 0);
    return Serial.read();
  }

  // wait until a new byte pair (16 bit) arrives, then return it
  int receiveBytePair() {
    int loByte, hiByte;
    loByte = receiveByte();
    hiByte = receiveByte();
    return mergeHiLo(hiByte, loByte);
  }
  
  int receivePacket() {
    int addr;
    
    addr = receiveByte();
    leftStickX = receiveBytePair();
    leftStickY = receiveBytePair();
    rightStickX = receiveBytePair();
    rightStickY = receiveBytePair();
    button = receiveByte();
    checksum = receiveBytePair();

    // checksum verification
    if (checksum == (addr + leftStickX + leftStickY + rightStickX + rightStickY + button)) {
      // if receiver address matches
      if (addr == address) {
        sprintf(buffer, "L(%04d, %04d) R(%04d, %04d) B(%04d)", leftStickX, leftStickY, rightStickX, rightStickY, button);
        Serial.println(buffer);
        delay(300);
        Serial.flush();
      }
    }    
  }
};

WirelessDataPacket WDP(r_addr);

int getButtonValue() {
  int value = 0;
  if (digitalRead(pinButtonL1) == LOW) {
    value = value | (1<<7);
  }
  if (digitalRead(pinButtonL2) == LOW) {
    value = value | (1<<6);
  }
  if (digitalRead(pinButtonR1) == LOW) {
    value = value | (1<<5);
  }
  if (digitalRead(pinButtonR2) == LOW) {
    value = value | (1<<4);
  }
  if (digitalRead(pinButtonSelect) == LOW) {
    value = value | (1<<3);
  }
  if (digitalRead(pinButtonStart) == LOW) {
    value = value | (1<<2);
  }
  if (digitalRead(pinButtonMacro) == LOW) {
    if (releasedButtonMacro) {          // if previously released
      macro = ~macro;                   // toggle the macro
      digitalWrite(pinLEDRed, ~macro);  // toggle the red LED
    }
    if (macro) {
      value = value | (1<<1);
    }
    releasedButtonMacro = false;
  } else {
    releasedButtonMacro = true;
  }
  if (digitalRead(pinButtonMode) == LOW) {
    if (releasedButtonMode) {               // if previously released
        mode = ~mode;                       // toggle the mode
        digitalWrite(pinLEDYellow, ~mode);  // toggle the yellow LED
    }
    if (mode) {
      value = value | (1<<0);
    }
    releasedButtonMode = false;
  } else {
    releasedButtonMode = true;
  }
  
  return value;
}

void setup() {
  pinMode(pinButtonL1, INPUT); digitalWrite(pinButtonL1, HIGH);
  pinMode(pinButtonL2, INPUT); digitalWrite(pinButtonL2, HIGH);
  pinMode(pinButtonR1, INPUT); digitalWrite(pinButtonR1, HIGH);
  pinMode(pinButtonR2, INPUT); digitalWrite(pinButtonR2, HIGH);
  pinMode(pinButtonSelect, INPUT); digitalWrite(pinButtonSelect, HIGH);
  pinMode(pinButtonStart, INPUT); digitalWrite(pinButtonStart, HIGH);
  pinMode(pinButtonMacro, INPUT); digitalWrite(pinButtonMacro, HIGH);
  pinMode(pinButtonMode, INPUT); digitalWrite(pinButtonMode, HIGH);
  pinMode(pinLEDRed, OUTPUT); digitalWrite(pinLEDRed, HIGH);
  pinMode(pinLEDYellow, OUTPUT); digitalWrite(pinLEDYellow, HIGH);
  Serial.begin(2400);  // opens serial port, sets data rate to 9600 bps
}

void loop() {
  WDP.leftStickX  = analogRead(pinLeftStickX);
  WDP.leftStickY  = analogRead(pinLeftStickY);
  WDP.rightStickX = analogRead(pinRightStickX);
  WDP.rightStickY = analogRead(pinRightStickY);
  WDP.button      = getButtonValue();
  WDP.sendPacket();
}
Expand

And below is the source code for the receiver:

Receiver Sketch
#include <stdio.h>
 
int r_sync = 0xAA;  // synchro signal
int r_addr = 0x44;  // receiver address

char buffer[36];

//-----------------------------
// multiple bytes manipulation
//-----------------------------
int getHiByte(int intData) {
    return (intData >> 8);
}

int getLoByte(int intData) {
    return (intData & 0xFF);
}

int mergeHiLo(int hi, int lo) {
    return ( (hi << 8) | lo );
}


//----------------------------------
// Each data packet has 4 bytes:
// sync | address | data | checksum
//----------------------------------
class WirelessDataPacket {
  public:
  int address;
  int leftStickX;
  int leftStickY;
  int rightStickX;
  int rightStickY;
  int button;
  int checksum;
  
  WirelessDataPacket(int addr) {
    address = addr;
  }
  
  int sendPacket() {            
    checksum = address + leftStickX + leftStickY + rightStickX + rightStickY + button;
    
    Serial.print(r_sync, BYTE);                  // send sync byte
    Serial.print(address, BYTE);                 // send address byte
    Serial.print(getLoByte(leftStickX), BYTE);   // send leftStickX Low Byte
    Serial.print(getHiByte(leftStickX), BYTE);   // send leftStickX High Byte
    Serial.print(getLoByte(leftStickY), BYTE);   // send leftStickY Low Byte
    Serial.print(getHiByte(leftStickY), BYTE);   // send leftStickY High Byte
    Serial.print(getLoByte(rightStickX), BYTE);  // send rightStickX Low Byte
    Serial.print(getHiByte(rightStickX), BYTE);  // send rightStickX High Byte
    Serial.print(getLoByte(rightStickY), BYTE);  // send rightStickY Low Byte
    Serial.print(getHiByte(rightStickY), BYTE);  // send rightStickY High Byte
    Serial.print(button, BYTE);                  // send button byte
    Serial.print(getLoByte(checksum), BYTE);     // send checksum byte
    Serial.print(getHiByte(checksum), BYTE);     // send checksum byte
    
    return 0;
  }
  
  // wait until a new byte (8 bit) arrives, then return it
  int receiveByte() {
    while (Serial.available() == 0);
    return Serial.read();
  }

  // wait until a new byte pair (16 bit) arrives, then return it
  int receiveBytePair() {
    int loByte, hiByte;
    loByte = receiveByte();
    hiByte = receiveByte();
    return mergeHiLo(hiByte, loByte);
  }
  
  int receivePacket() {
    int addr;
    
    addr = receiveByte();
    leftStickX = receiveBytePair();
    leftStickY = receiveBytePair();
    rightStickX = receiveBytePair();
    rightStickY = receiveBytePair();
    button = receiveByte();
    checksum = receiveBytePair();

    // checksum verification
    if (checksum == (addr + leftStickX + leftStickY + rightStickX + rightStickY + button)) {
      // if receiver address matches
      if (addr == address) {
        sprintf(buffer, "L(%04d, %04d) R(%04d, %04d) B(%04d)", leftStickX, leftStickY, rightStickX, rightStickY, button);
        Serial.println(buffer);
        delay(300);
        Serial.flush();
      }
    }    
  }
};

WirelessDataPacket WDP(r_addr);

void setup() {
  Serial.begin(2400);  // opens serial port, sets data rate to 9600 bps
  pinMode(13, OUTPUT);
}

void loop() {
  WDP.receivePacket();
}
Expand

Everything worked well using the 4-byte transmission protocol from the reference. However, when I switched to the 13-byte packet, the transmission slowed significantly, and more than 50% of the data was lost.

Here’s how I calculated the speed:

  • 2400 bps = 300 bytes per second (2400 / 8)
  • 300 Bps = 23 packets per second (300 / 13)

The Arduino uses a 16 MHz crystal, which can produce a 0.1% error (ref: AVR BAUD Rate Calculator). Considering hardware and software factors, a 5% error is acceptable. However, in my case, the error rate was so high that the transmission broke up and became too slow. I believe this can be improved by optimizing the baud rate of the RF modules and employing better programming techniques, which I have yet to explore.