Convert Sony PS2 Controller to Arduino RC Controller

by taifur in Circuits > Remote Control

6104 Views, 111 Favorites, 0 Comments

Convert Sony PS2 Controller to Arduino RC Controller

COVER.png
F1.png
F2.png

Robots, cars, robotcars, and all sorts of prank devices require some degree of remote control. Most of the time, it's tempting to implement this yourself using XBee or some other wireless technology. Sometimes it's not a bad idea, but more often than not it's an over-powered and somewhat frustrating way to go. 

Radio Control units, or RC Controllers, have been used by model airplanes, helicopters, rovers and boating enthusiasts for years. But these controllers are not limited to hobby vehicle control. They are equally suitable for use in controlling any electronic project that requires a full-featured wireless remote control.

A typical transmitter will have a few control surfaces, like wheels or joysticks, as well as some switches or dials.

Today, I’ll show you how to make a very common and inexpensive Arduino RC Controller from an old Sony Play Station 2 controller. In this project, we will use low-power 2.4GHz nRF24L01 transceiver modules to make wireless communication between the robot car and the RC remote.

Supplies

412Q3RFHZVL.jpg
nrf-nano.jpg
16340.jpg
5v-1A-power-bank-circuit-b.jpg

Old/New Sony Play Station 2 Controller:

The DualShock 2 Controller is a sixth-generation gamepad and a successor of the DualShock Controller. It was released into the market in 2000 as a video game controller for Sony PlayStation 2. The DualShock 2 Controller is also forward compatible with original models of PlayStation and backward compatible with PlayStation 3 through the third-party peripheral connecting the controller and the console via a USB port.

Noticeable features of the DualShock 2 Controller include stiffer analog sticks, a blue DualShock logo at the top of the controller, and pressure-sensitive analog values. It also cannot work with games that require Sixaxis functionality.

The DualShock 2 Controller has a height of 3.74” (95 mm), width of 6.18” (157 mm), depth of 2.16” (54.9 mm), and approximate weight of 7.4 oz (.21 kg).

nRF Nano (buy one from aliexpress)

This board combines the NRF24L01+ wireless transceiver with the familiar form factor and programming paradigm of the Arduino Nano. For someone who needs wireless capabilities and prefers to keep solder and wiring work to a minimum, this board is a good option.

or Arduino Nano + nRF24L01

16340 Li-ion rechargeable battery (buy from aliexpress.com)

The 16340 battery (or RCR123A) is a cylindrical li-ion cell classified by its 34mm x 16mm dimensions, and is the rechargeable version of a CR123A battery. They usually have a 3.6V or 3.7V voltage, button-top terminal, and most use a LiCoO2 (lithium carbon-oxide) chemistry. While some come with built-in USB charging ports, all 16340 batteries are rechargeable in li-ion smart chargers. 16340 cells are often used in flashlights or other small compact electronics

Power Bank Charger Circuit (buy from aliexpress.com)

This power bank circuit uses two integrated modules. The first module is a lithium-ion battery charger and the second is a DC-DC boost converter module.

Fiber-glass Perf Board (buy from aliexpress)

Opening the PS2 Controller

s1.jpg
s3.jpg
s2.jpg

Remove the six 7.8 mm anchoring screws from the back casing of the controller using a Phillips screwdriver. Pull apart the outer casing by hand, or by using a plastic opening tool, from the bottom of the controller. Remove two vibration motors from inside the controller. We don't need them.

If you want to use the dc vibration motors, you will need to add a motor driver (example: L293D) as you cannot directly connect it to the pins of the Arduino as it requires a lot of currents which may harm the Arduino. 

Cut the data cable of the PS2 Controller keeping 10 cm with the circuit.

Preparing the PCB

a1.png
IMG_0585.JPG
IMG_0586.JPG

For bringing the wireless functionality in the remote we will use Arduino Nano and nRF24L01 wireless transceiver. I will place the Nano board and the nRF24l01 module in a perf board and connect the data and power lines of the controller circuit.

I used an anti-cutter to cut the perf board according to my measurements. The size of the PCB piece I prepared is 10CM X 2CM.

PCB will house the Arduino Nano, nRF24L01 wireless transceiver, and the Li-ion charger and boost converter circuit. I will use female headers to connect the Arduino with the PCB.

Making the Connections

g1.png
g2.jpg
images.jpg

The connection between Controller and Arduino Nano

Connect the controller pins to Arduino according to the image attached. This connection is changeable but you need to change it in the Arduino program also.

The connection between nRF24L01 and Arduino Nano

The connection is exactly the same on both the transmitter and receiver end. 

Connect the GND pin to -ve and VCC pin to 3.3v pin of Arduino. The signals generated by these modules are very sensitive to power supply noises. So, adding a decoupling capacitor (anything from 10uF to 100uF) across the power supply line is always a very good idea.

Then connect the CSN pin to D9, CE to D10, MOSI to D11, MISO to D12, and SCK to D13 pin of the Arduino.

Since the nRF24L01+ module requires a lot of data transfer, it will give the best performance when connected to the hardware SPI pins on the microcontroller. Note that each Arduino board has different SPI pins that must be connected accordingly. Have a look at the table onscreen for a quick understanding.

PCB Connections

h1.jpg
h2.jpg
h3.jpg

For connecting the Arduino with the controller board I directly used the wires cut from the cable.

The power consumption of this module is just around 12mA during transmission, which is even lower than a single LED. The operating voltage of the module is from 1.9 to 3.6V, but the good thing is that the other pins tolerate 5V logic, so we can easily connect it to an Arduino without using any logic level converters.

Three of these pins are for the SPI communication and they need to be connected to the SPI pins of the Arduino, but note that each Arduino board has different SPI pins. The pins CSN and CE can be connected to any digital pin of the Arduino board and they are used for setting the module in standby or active mode, as well as for switching between transmitting or command mode. The last pin is an interrupt pin which doesn’t have to be used.

Battery Charger & Boost Converter

j1.jpg
j2.JPG
IMG_0613.JPG

The converted RC remote will be powered by li-ion battery. Li-ion batteries required a sophisticated charging circuit that protects the battery from overcharge and deep discharge. The output voltage of a li-ion cell is 3.7V which is not enough for the PS2 internal circuit. So, I converted that voltage to 5V using a boost converter circuit. The good news is that the li-ion charger circuit and boost converter circuit are available in a single PCB. That is called power bank circuit, generally used in power banks.

So, I used a power bank circuit to charge the battery and as well as to step up the voltage to 5V. I cut out the USB output port from the PCB as it is not required in our case.

Attaching All the Parts

IMG_0615.JPG
IMG_0616.JPG
IMG_0618.JPG

All the parts like PCB board for connecting Nano, NRF24L01, and charger circuits are ready. Now we need to connect all the components together. The Nano and the NRF module will be connected to the female header we soldered in the PCB board. The power bank circuit will be placed using hot glue. Before that, we need to complete the necessary soldering of the power bank circuit. The power bank circuit has 2 (two) through solder pads for connecting the battery. As we will place batteries inside the controller and the power bank circuit will be outside of the controller I soldered 2 long wires to the battery connector pad of the power bank circuit. The output of the power bank circuit is connected to the 5V input of the circuit through an SPDT switch. The ground is directly connected to the ground of the system.

After making the connections and gluing the power bank circuit with the PCB I placed the final terminal of the circuit PCB on top of the controller PCB. Then I used hot glue to fix them together. I also use some hot glue to organize the connecting wires and make them protected. Finally, I placed the 16340 li-ion battery inside the controller and placed and fixed in place with hot glue. Two li-ion batteries can be used in parallel to get more battery backup or you can use one.

Final Assembling

IMG_0620.JPG
IMG_0632.JPG
IMG_0633.JPG
IMG_0640.JPG

All the circuit placement is completed. It is the right time to re-assemble the controller. We have already the top part with the glued PCB. Now we need to place the bottom part in the right place and use the screws to make it fix with the top part.

After final assembling your controller is ready to remotely control your robot car but before that, you have a final task. That is the programming. You need to upload the Arduino program to read the joysticks and button and send it to your RC car through the nRF24L01 module. In the next step, I will add the Arduino sketch for you.

Uploading the Arduino Sketch

ps2_controller_h6WUIp9OsW.jpg
output.jpg

The following sketch can be uploaded to your nano to transmit the value to the RC car. Your receiver also needs to upload a code that is synchronized with this code.

Arduino PS2X library was used in this Arduino program. The zip file with the library and the code are attached. You can separately download the library from here:

#include <PS2X_lib.h>
#include <RF24_config.h>
#include <RF24.h>
#include <nRF24L01.h>
#include <SPI.h>


/*-------------------Preprocessors-----------------------*/
//RF24
#define  PIN_07_RF_CE	9
#define  PIN_08_RF_CSN	10
#define  RF_PAYLOAD_SIZE_10	10


//PS2X
#define PIN_02_PS2X_ATT  5
#define PIN_03_PS2X_COM  6
#define PIN_04_PS2X_DTA  7
#define PIN_05_PS2X_CLK  8


/*--------Variables/Class Declarations-------------------*/


//NRF24
RF24 radio(PIN_07_RF_CE, PIN_08_RF_CSN);
const uint64_t pipe = 0xE8E8F0F0E1LL; // Define the transmit pipe, NOTE: the "LL" at the end of the constant is "LongLong" type
unsigned int rx_status[2] = { 0, 0};


//PS2X
PS2X ps2x;	
int ps2x_ErrorCheck = 0;	
char vibrate = 0;


//analogs
typedef struct analogs {
	unsigned int leftX;
	unsigned int leftY;
	unsigned int rghtX;
	unsigned int rghtY;
};


//digitals
typedef union digitals{
	struct {
		unsigned int l1 : 1;
		unsigned int l2 : 1;
		unsigned int r1 : 1;
		unsigned int r2 : 1;
		unsigned int up : 1;
		unsigned int dn : 1;
		unsigned int lf : 1;
		unsigned int rt : 1;
		unsigned int tr : 1;
		unsigned int cr : 1;
		unsigned int sq : 1;
		unsigned int cl : 1;
		unsigned int sl : 1;
		unsigned int st : 1;
	}bits;
	unsigned int onoff;
} ;


struct keys {
	analogs analogKeys;
	digitals digitalKeys;
};


keys keyValues;


void setup()
{
	// enable serial monitor for debugging
	Serial.begin(115200);


	//RF24
	radio.begin();
	radio.setAutoAck(true);		
	radio.enableAckPayload();
	radio.enableDynamicPayloads();
	//radio.setPayloadSize(sizeof(keyValues)+1);
	radio.setPALevel(RF24_PA_LOW); //try to set to RF24_PA_LOW if there are some issues, RF24_PA_MAX is the default
	radio.stopListening();
	radio.openWritingPipe(pipe);
	radio.setRetries(5, 5);


	//PS2X setup
	ps2x_ErrorCheck = ps2x.config_gamepad(PIN_05_PS2X_CLK, PIN_03_PS2X_COM,
		PIN_02_PS2X_ATT, PIN_04_PS2X_DTA, true, true);   //setup pins and settings:  GamePad(clock, command, attention, data, Pressures?, Rumble?) check for error
	switch(ps2x_ErrorCheck){
	case 0:
		Serial.println("Found Controller, configured successful");
		Serial.println("Try out all the buttons, X will vibrate the controller, faster as you press harder;");
		Serial.println("holding L1 or R1 will print out the analog stick values.");
		Serial.println("Go to www.billporter.info for updates and to report bugs.");
		break;
	case 1:
		Serial.println("No controller found, check wiring, see readme.txt to enable debug. visit www.billporter.info for troubleshooting tips");
		break;
	case 2:
		Serial.println("Controller found but not accepting commands. see readme.txt to enable debug. Visit www.billporter.info for troubleshooting tips");
		break;
	case 3:
	default:
		Serial.println("Controller refusing to enter Pressures mode, may not support it. ");
		break;
	}


	memset(&keyValues, 0, sizeof(keyValues));  // intialize struct to 0
}


void loop()
{
	// PS2X read gamepad values:
	gameController_Reading();
	//RF24
	if (radio.write(&keyValues, sizeof(keyValues)) )
	{
		Serial.println("...tx success");
		if (radio.isAckPayloadAvailable())
		{
			radio.read(rx_status, sizeof(rx_status));
			Serial.print("received ack payload is : ");
			Serial.println(rx_status[0]);
		}
		else
		{
			Serial.println("status has become false so stop here....");
		}
	}


}


//Read the controllers inputs
void gameController_Reading()
{
	ps2x.read_gamepad(false, false);  //read controller but set rumble motor to off


	//analog readings
	keyValues.analogKeys.leftX = ps2x.Analog(PSS_LX);	// left analog stick
	keyValues.analogKeys.leftY = ps2x.Analog(PSS_LY);
	keyValues.analogKeys.rghtX = ps2x.Analog(PSS_RX);	// right analog stick
	keyValues.analogKeys.rghtY = ps2x.Analog(PSS_RY);


	// digital readings
	if (ps2x.NewButtonState()) {
		// use masking to toggle the value
		if (ps2x.ButtonPressed(PSB_L1))			keyValues.digitalKeys.bits.l1 = toggleButton(0x0001);
		if (ps2x.ButtonPressed(PSB_L2))			keyValues.digitalKeys.bits.l2 = toggleButton(0x0002);


		if (ps2x.ButtonPressed(PSB_R1))			keyValues.digitalKeys.bits.r1 = toggleButton(0x0008);
		if (ps2x.ButtonPressed(PSB_R2))			keyValues.digitalKeys.bits.r2 = toggleButton(0x0010);


		if (ps2x.ButtonPressed(PSB_PAD_UP))		keyValues.digitalKeys.bits.up = toggleButton(0x0040);
		if (ps2x.ButtonPressed(PSB_PAD_DOWN))	keyValues.digitalKeys.bits.dn = toggleButton(0x0080);
		if (ps2x.ButtonPressed(PSB_PAD_LEFT))	keyValues.digitalKeys.bits.lf = toggleButton(0x0100);
		if (ps2x.ButtonPressed(PSB_PAD_RIGHT))	keyValues.digitalKeys.bits.rt = toggleButton(0x0200);


		if (ps2x.ButtonPressed(PSB_TRIANGLE))	keyValues.digitalKeys.bits.tr = toggleButton(0x0400);
		
		if (ps2x.ButtonPressed(PSB_CROSS))		keyValues.digitalKeys.bits.cr = toggleButton(0x0800);
		if (ps2x.ButtonReleased(PSB_CROSS))		keyValues.digitalKeys.bits.cr = toggleButton(0x0800);


		if (ps2x.ButtonPressed(PSB_SQUARE))		keyValues.digitalKeys.bits.sq = toggleButton(0x1000);
		if (ps2x.ButtonPressed(PSB_CIRCLE))		keyValues.digitalKeys.bits.cl = toggleButton(0x2000);


		if (ps2x.ButtonPressed(PSB_SELECT))		keyValues.digitalKeys.bits.sl = toggleButton(0x4000);
		if (ps2x.ButtonPressed(PSB_START))		keyValues.digitalKeys.bits.st = toggleButton(0x8000);
	}
}


int toggleButton(unsigned int mask) {
	if ((keyValues.digitalKeys.onoff & mask) == 0) {
		return 1;
	}
	else {
		return 0;
	}
}