Charging the PS3 controller

Technical discussion on the newly released and hard to find PS3.

Moderators: cheriff, emoon

Post Reply
jimparis
Posts: 1145
Joined: Fri Jun 10, 2005 4:21 am
Location: Boston

Charging the PS3 controller

Post by jimparis »

USB is only supposed to provide power to devices that have correctly enumerated. In particular, devices aren't allowed to draw the maximum 500mA unless the host has specifically assigned a device configuration that requested this much power. Many devices either ignore this rule, or follow the USB Battery Charging Specification, which provides a means for configuring higher-current draw using a particular resistor configuration on the D+ and D- lines. This is used by most USB-charged cell phones etc, and so it's easy to get them to charge by just supplying 5v and a resistor. Most USB wall chargers have this resistor, too.

However, the Sixaxis and Dualshock 3 controllers don't follow this convention and require the full enumeration before they charge. This means that they can't charge with most USB chargers, and need to talk to a real USB host and go through the enumeration process before they'll start drawing power.

My friend wanted to build his own controller charging dock, so we came up with a circuit and some code that will perform just enough of the USB enumeration process to get the controller to charge. The PS3 controller is a full-speed USB device, which means you need to talk to it at 12MHz for this to work. We used an ATtiny24A, which is one of the smallest and cheapest MCUs you can get that can drive output pins that fast. The circuit is straightforward:

Image

JP1 is just for in-circuit programming and isn't necessary if you program the chip some other way. U2 is any 3.3v regulator. JP2 is where you supply 5v.

Currently it only supports one controller. It needs to be directly connected to the controller; you can't use a hub. The MCU should be able to handle charging up to 4 controllers at once using the other free pins, but the code needs a bit of love before that will work.

The code is available here: ps3-charger.zip

Code: Select all

/* ATTINY24 code to emulate enough of a USB host to get a PS3
   controller to charge.
 
   Copyright &#40;c&#41; 2010 Jim Paris <[email protected]>
   BSD license
*/

#include <avr/io.h>

FUSES =
&#123;
	.low = 0xff,
	.high = 0xff & FUSE_RSTDISBL & FUSE_SPIEN & FUSE_BODLEVEL0,
	.extended = 0xff & EFUSE_DEFAULT,
&#125;;

/* Connections&#58;
   D+  PA3
   D-  PA7
*/

#define MASK_DP &#40;1 << 3&#41;
#define MASK_DM &#40;1 << 7&#41;
#define MASK_BOTH &#40;MASK_DP | MASK_DM&#41;
#define MASK_NONE 0
#define MASK_OUT MASK_BOTH
#define MASK_IN MASK_NONE
#define FS_J MASK_DP
#define FS_K MASK_DM
#define SE0 0

#define J "out %&#91;_porta&#93;, %&#91;_j&#93;\n"
#define K "out %&#91;_porta&#93;, %&#91;_k&#93;\n"
#define X "out %&#91;_porta&#93;, %&#91;_x&#93;\n"
#define IN "out %&#91;_ddra&#93;, %&#91;_in&#93;\n"
#define OUT "out %&#91;_ddra&#93;, %&#91;_out&#93;\n"
#define D "nop\n" // delay 1 bit time

#define asmblock&#40;str&#41; asm volatile &#40;str \
	&#58; \
	&#58; &#91;_porta&#93; "I" &#40;_SFR_IO_ADDR&#40;PORTA&#41;&#41;, \
	  &#91;_ddra&#93; "I" &#40;_SFR_IO_ADDR&#40;DDRA&#41;&#41;, \
	  &#91;_j&#93; "r" &#40;FS_J&#41;, \
	  &#91;_k&#93; "r" &#40;FS_K&#41;, \
	  &#91;_x&#93; "r" &#40;SE0&#41;, \
	  &#91;_in&#93; "r" &#40;MASK_IN&#41;, \
	  &#91;_out&#93; "r" &#40;MASK_OUT&#41; \
	&#58; "memory"&#41;

/* wait for the next "ms" timer ticks, which occur at 1ms intervals */
void mwait&#40;int ms&#41;
&#123;
	static int initialized = 0;

	if &#40;!initialized&#41; &#123;
		TIMSK1 = 0x00; /* no interrupts */
		OCR1A = 12000 - 1; /* 12000 cycles = 1 ms */
		TCCR1B = _BV&#40;WGM12&#41; | _BV&#40;CS10&#41;; /* no prescale, CTC mode */
		initialized = 1;
	&#125;

        while &#40;ms-- > 0&#41; &#123;
		TIFR1 &= ~_BV&#40;TOV1&#41;;
                while&#40;&#40;TIFR1 & _BV&#40;TOV1&#41;&#41; == 0&#41;
                        continue;
        &#125;
&#125;

/* Send a bus reset */
static void send_reset&#40;void&#41;
&#123;
	/* SE0 for >= 10 ms */
	PORTA = SE0;
	DDRA = MASK_OUT;
	mwait&#40;10&#41;;
	DDRA = MASK_IN;
&#125;

/* Wait for next frame and send SOF.
   Returns 0 if the device is not present. */
static int send_SOF&#40;int count&#41;
&#123;
	while &#40;count--&#41; &#123;
		mwait&#40;1&#41;;
		if &#40;&#40;PINA & MASK_BOTH&#41; != FS_J&#41;
			return 0;
		asmblock&#40;
			OUT
			// send SOF 1337
			K J K J K J K K K J J K J J K K K J K K K K J K K J J J J J J K X X J
			IN
			X
		&#41;;
	&#125;
	return 1;
&#125;

/* Send SET_ADDRESS */
static void send_SET_ADDRESS&#40;void&#41;
&#123;
	asmblock&#40;
		OUT
		// send SETUP
		K J K J K J K K K J J J K K J K J K J K J K J K J K J K K J K J X X J
		// delay a little
		D D D D D D D D
		// send DATA0
		K J K J K J K K K K J K J K K K J K J K J K J K K J J K J K J K K J K
		J K J K J K J K J K J K J K J K J K J K J K J K J K J K J K J K J K J
		K J K J K J K J K J J J K K J J J J J K K J K K J K X X J
		IN
		X
		// device will send ACK now &#40;within 16 bit times&#41;
	&#41;;
	// wait until new frame; an immediate IN will just get NAKed
	send_SOF&#40;1&#41;;
	asmblock&#40;
		OUT
		// send IN
		K J K J K J K K K J K K J J J K J K J K J K J K J K J K K J K J X X J
		// device will send DATA1 now.
		IN
		// delay for the expected 35 bits of data
		X D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D
		// wait 12 more bit times &#40;16 is max turnaround&#41;
		D D D D D D D D D D D D
		OUT
		// send ACK
		K J K J K J K K J J K J J K K K X X J
		IN
		X
	&#41;;
&#125;

/* Send SET_CONFIGURATION */
static void send_SET_CONFIGURATION&#40;void&#41;
&#123;
	asmblock&#40;
		OUT
		// send SETUP
		K J K J K J K K K J J J K K J K K J K J K J K J K J K K J J J J X X J
		// delay a little
		D D D D D D D D
		// send DATA0
		K J K J K J K K K K J K J K K K J K J K J K J K K J K K J K J K K J K
		J K J K J K J K J K J K J K J K J K J K J K J K J K J K J K J K J K J
		K J K J K J K J K J J J J K J J K J J K K J K K J K X X J
		IN
		X
		// device will send ACK now &#40;within 16 bit times&#41;
	&#41;;
	// wait until new frame; an immediate IN will just get NAKed
	send_SOF&#40;1&#41;;
	asmblock&#40;
		OUT
		// send IN
		K J K J K J K K K J K K J J J K K J K J K J K J K J K K J J J J X X J
		// device will send DATA1 now.
		IN
		// delay for the expected 35 bits of data
		X D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D
		// wait 12 more bit times &#40;16 is max turnaround&#41;
		D D D D D D D D D D D D
		OUT
		// send ACK
		K J K J K J K K J J K J J K K K X X J
		IN
		X
	&#41;;
&#125;

int main&#40;void&#41;
&#123;	
	/* Tristate D+ and D- */
	DDRA = MASK_IN;
	PORTA = SE0;

	mwait&#40;100&#41;;

	for &#40;;;&#41; &#123;
		/* if the device doesn't appear present, do nothing */
		while &#40;&#40;PINA & MASK_BOTH&#41; != FS_J&#41; &#123;
			mwait&#40;1&#41;;
		&#125;

		// perform reset
		mwait&#40;100&#41;;
		send_reset&#40;&#41;;

		// perform basic enumeration
		send_SOF&#40;10&#41;;
		send_SET_ADDRESS&#40;&#41;;
		send_SOF&#40;3&#41;;
		send_SET_CONFIGURATION&#40;&#41;;

		// now send empty frames as long as the device is present.
		while &#40;send_SOF&#40;1&#41;&#41;
			continue;
	&#125;
&#125;
Post Reply