• entries
    222
  • comments
    607
  • views
    588067

PlayStation Controllers

Sign in to follow this  
benryves

1650 views

PlayStation controllers are relatively comfortable, and I have a number of them knocking about.


From top to bottom - PS2 IR remote control and receiver; Guitar Hero wireless Kramer and receiver; black PS2 DualShock 2 analogue joypad; a pair of grey standard PS1 digital joypads.


As I've learned in the past, a decent gamepad can help with certain games. Of course, what's much more fun than playing the games is trying to work out how these controllers work.

The byte-level protocol is very simple; the PlayStation pulls a select line low (used to grab the attention of the controller) then pulses the clock eight times, writing a bit at a time onto one line and reading another bit at a time from another. This means that the controller and PlayStation end up sending and receiving a byte simultaneously. Finally, the PlayStation checks to see if controller pulls the acknowledge line low to indicate that it received the data; if no acknowledgement is received it assumes that there is no controller on the port it is currently accessing.

All electrical connections are unidirectional, and so a controller can be easily connected to a standard PC's parallel port. There are a number of diagrams floating around the internet using similar pin connections, so I followed one of those.



I cut up a pound-shop parallel cable for the PC end and a controller extension cable for the PlayStation end. PlayStation controllers require power; a lot of diagrams I've seen refer to a 9V and 5V supply, some 7.6V and 3.3V. A voltmeter informs me that it's the latter option. Rather than try and draw power from the parallel port, I'm using a generic power supply set to 7.5V. To derive the 3.3V I'm using a 5V regulator followed by two 1A rectifier diodes in series - the diodes provide a voltage drop of 0.7V across each, resulting 3.6V.

I wrote an application in C# that attempted to swap bytes back and forth between the PC and the controller, and was getting good results. I was not, however, having any luck polling the acknowledgement line. It didn't appear to ever go low - my guess was that the program simply couldn't poll the parallel port rapidly enough. Not that this is a slur on C#, of course, but to access the parallel port in the first place I need to use an unmanaged library.

The solution was therefore to write an unmanaged library myself that would handle the PlayStation protocol side of things, which I could then wrap up and add nice functionality to via a C# managed library.

#include "Windows.h"

// inpout32.dll function declarations.
short Inp32(short portAddress);
void Out32(short portAddress, short data);

/// Gets the state of the data line.
/// Base address of the parallel port.
/// The state of the data line.
extern "C" __declspec(dllexport) bool GetData(short portAddress) {
return (Inp32(portAddress + 1) & (1 << 6)) != 0;
}

/// Gets the state of the acknowledge line.
/// Base address of the parallel port.
/// The state of the data line.
extern "C" __declspec(dllexport) bool GetAcknowledge(short portAddress) {
return (Inp32(portAddress + 1) & (1 << 5)) != 0;
}

/// Sets the state of the command line.
/// Base address of the parallel port.
/// The state to set the command line to.
extern "C" __declspec(dllexport) void SetCommand(short portAddress, bool state) {
Out32(portAddress, (Inp32(portAddress) & ~0x01) | (state ? 0x01 : 0x00));
}

/// Sets the state of the select line.
/// Base address of the parallel port.
/// The state to set the select line to.
extern "C" __declspec(dllexport) void SetSelect(short portAddress, bool state) {
Out32(portAddress, (Inp32(portAddress) & ~0x02) | (state ? 0x02 : 0x00));
}

/// Sets the state of the clock line.
/// Base address of the parallel port.
/// The state to set the clock line to.
extern "C" __declspec(dllexport) void SetClock(short portAddress, bool state) {
Out32(portAddress, (Inp32(portAddress) & ~0x04) | (state ? 0x04 : 0x00));
}

/// Begins a data transfer by pulling select low.
/// Base address of the parallel port.
extern "C" __declspec(dllexport) void BeginTransfer(short portAddress) {
SetSelect(portAddress, false);
}

/// Ends a data transfer by releasing select high.
/// Base address of the parallel port.
extern "C" __declspec(dllexport) void EndTransfer(short portAddress) {
SetSelect(portAddress, true);
}


/// Exchanges a byte between the PlayStation controller and the PC.
/// Base address of the parallel port.
/// The data to exchange.
/// True if the transmission was acknowledged, false if it timed out.
extern "C" __declspec(dllexport) bool ExchangeByte(short portAddress, unsigned char* data) {

DWORD TimeoutStart = GetTickCount();

for (int i = 0; i < 8; ++i) {
SetClock(portAddress, false);
SetCommand(portAddress, (*data & (1 << i)) != 0);
SetClock(portAddress, true);
if (GetData(portAddress)) {
*data |= (1 << i);
} else {
*data &= ~(1 << i);
}
}

while (GetAcknowledge(portAddress)) {
if ((GetTickCount() - TimeoutStart) > 10) return false;
}

while (!GetAcknowledge(portAddress)) {
if ((GetTickCount() - TimeoutStart) > 10) return false;
}

return true;

}

/// Exchanges a block of data between the PlayStation controller and the PC.
/// Base address of the parallel port.
/// The command byte to send.
/// The data to exchange (input and output).
/// The size of data in bytes.
/// The size of the received packet.
extern "C" __declspec(dllexport) int SendPacket(short portAddress, unsigned char* command, unsigned char* data, int numberOfElements) {

// Start by sending 0x01.
unsigned char ToExchange = 0x01;
if (!ExchangeByte(portAddress, &ToExchange)) return 0;

// Next send the command byte.
// Controller will respond with packet size and mode.
if (!ExchangeByte(portAddress, command)) return 0;

// Check for end-of-header.
ToExchange = 0x00;
if (!ExchangeByte(portAddress, &ToExchange) || ToExchange != 0x5A) return 0;

// Fix the "numberOfElements" to only try and fetch the number of bytes that are in the packet.
numberOfElements = min(numberOfElements, (*command & 0xF) * 2);

for (int i = 0; i < numberOfElements; ++i) {
if (!ExchangeByte(portAddress, &data)) return i + 1;
}

return numberOfElements;
}

I'm not much of a C++ programmer, so I hope the above isn't too offensive.

Polling a standard digital joypad or a dual analogue is pretty straightforwards - send 0x42 to the device, and it'll return the status of each button as a bitfield 2 bytes in length. If the controller is in analogue mode, it'll then go on to return a further four bytes; one byte per axis, two axes per joystick.


Download library and demo.


Standard disclaimer applies; if you're going to hook anything up to your PC, I cannot be held responsible for any damages incurred. Be careful!

I'm still having some problems with data transfer. The controller doesn't always send back enough data (least reliable with a DualShock 2); this could be because I'm running its clock too fast. Introducing delays doesn't seem to help. This is most noticable in the demo program when a DualShock 2 is used in analogue mode; the analogue light flickers on and off.

I also haven't successfully managed to get the DualShock 2 to enter escape mode - this mode is used to access some of the more exotic commands, including commands to control the force feedback motors or to append extra data to the packet sent back when the controller is polled, such as the status of the analogue buttons.
Sign in to follow this  


2 Comments


Recommended Comments

This is awesome, although I value my PC and consoles too much to hook up my own circuits to them [smile].

Also what's happening with Cogwheel? Doesn't look like there's much to go on if you're stuck, the only open source emulator I could find was SMS Plus.

Share this comment


Link to comment
I redid the GUI and tidied up the back-end of Cogwheel quite a lot recently; the problem is - as always - dumping pixels to the screen in a timely manner. SlimDX could well be the answer in this instance.

I still can't work out for the life of me how to synchronise sound and video either. I can either get perfect sound with choppy video (timing based on sound-card buffer refill requests) or perfect video with slurring sound.

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now