Close

Slight Change to Protocol

A project log for Universal Controller

One controller to rule them all! A PS2 controller crossed with an AVR and XBee / Bluetooth, to use in multiple physical computing projects.

the-big-oneThe Big One 04/24/2014 at 01:000 Comments

We made a few tweaks to the protocol today, in an effort to make things easier to implement on the client side, with fewer special cases.  In addition, we created a client.h file in lib/universal_controller/client.h, which provides various #defines to simplify implementing the client side code.

The new protocol is as follows:

Sending button / stick events (from controller to client): 


Bit order:
[TAAVVVVV] (analog stick event)
[TN0SBBBB] (button event)

Bit 8 (T, MSB): 0 == analog stick message, 1 == digital button message

Analog stick message:
 Bit 7 (A)  : 0 == Left stick, 1 == Right Stick
 Bit 6 (A)  : 0 == X axis, 1 == Y axis
 Bit 5::1(V): Analog stick value: For the Y axis, 0x00 is all the way forward and 
              0x1F is all the way back; for the X axis, 0x00 is all the way left 
              and 0x1F is all the way right.

Digital button message:
 Bit 7 (N)  : 0 == Normal button press / release event, 1 == No Buttons Pressed event.
              When bit 7 is 1, the remainder of the message is ignored.  By convention
              we fill buts 6::1 with zeros in this state, meaning that the 'No button
              pressed' message will always be 0xC0.
 Bit 6      : Unused, set to 0
 Bit 5 (S)  : 0 = Released, 1 = Pressed
 Bit 4::1(B): Button number.  Use lib/universal_controller/client.h for defines.

Receiving client commands (from client to controller):

Bit order:
[CCXXXXXX]
   C: Command (see below)
   X: Command-specific message, left padded with zeros

 Commands:
   00: Enable / Disable Buttons
     [000AAAAX]
      A: Button address.  Specify a button to modify (0x00 - 0x0F).
      X: New value.  1 = enabled, 0 = disabled
   01: Enable / Disable Analog Sticks
     [0100000X]
       X: New value.  1 = enabled, 0 = disabled
   10: Set poll frequency (non-changes will be re-sent after this time)
     [10XXXXXX]
       X: New poll value, to be shifted 8 bits left and set in OCR1A.
          For instance, to set OCR1A = 0x0F00 (the default value), you would send the
          value 001111 (0x0F) as shifting this 8 bits gives 0x0F00.
          0x0F (expanded to 0x0F00) results in an idle poll time of about 500ms.
          0x3F (expanded to 0x3F00) results in an idle poll time of about 2s.
          0x01 (expanded to 0x0100) results in an idle poll time of about 10ms.
          0x00 results in completely disabling idle polling (only send change events).
   11: Set maximum analog repeat frequency (you can't send analog stick changes faster than this).
     [11XXXXXX]
       X: New poll value, to be shifted 2 bits left and set in OCR0A.  Analog values cannot be sent until          this compare ISR fires.  Set this high enough to not be sending un-needed data constantly, 
          but low enough that you can get good response times from the controller.
          0x3F (expanded to 0xFC) is the maximum, and results in an analog repeat time of about 32ms.
          0x10 (expanded to 0x40) is the default, and results in an analog repeat time of about 8ms.
          0x00 results in completely disabling the timer and sends all analog events, no matter how fast.

The client.h file includes the following defines:

//Bit 8: 0 == analog stick message, 1 == digital button message
#define CONTROLLER_MESSAGE_TYPE_MASK	0x80
#define CONTROLLER_MESSAGE_TYPE_ANALOG	0x00
#define CONTROLLER_MESSAGE_TYPE_BUTTON	0x80

//Bit 7 (Analog): Left / Right stick
#define CONTROLLER_ANALOG_STICK			0x40
#define CONTROLLER_ANALOG_STICK_LEFT	0x00
#define CONTROLLER_ANALOG_STICK_RIGHT	0x40

//Bit 6 (Analog): X / Y axis
#define CONTROLLER_ANALOG_AXIS			0x20
#define CONTROLLER_ANALOG_AXIS_X		0x00
#define CONTROLLER_ANALOG_AXIS_Y		0x20

//Bit 5::1 (Analog): Value
#define CONTROLLER_ANALOG_VALUE_MASK	0x1F

//No Buttons Pressed message
#define CONTROLLER_BUTTON_NONE			0xC0

//Bit 5 (Button): Press / Release
#define CONTROLLER_BUTTON_PRESS_MASK	0x10
#define CONTROLLER_BUTTON_RELEASE		0x00
#define CONTROLLER_BUTTON_PRESS			0x10

//Bit 4::1 (Button): Button address
#define CONTROLLER_BUTTON_VALUE_MASK	0x0F

#define CONTROLLER_BUTTON_VALUE_SELECT		0x00
#define CONTROLLER_BUTTON_VALUE_LEFT3		0x01
#define CONTROLLER_BUTTON_VALUE_RIGHT3		0x02
#define CONTROLLER_BUTTON_VALUE_START		0x03
#define CONTROLLER_BUTTON_VALUE_PADUP		0x04
#define CONTROLLER_BUTTON_VALUE_PADRIGHT	0x05
#define CONTROLLER_BUTTON_VALUE_PADDOWN		0x06
#define CONTROLLER_BUTTON_VALUE_PADLEFT		0x07
#define CONTROLLER_BUTTON_VALUE_LEFT2		0x08
#define CONTROLLER_BUTTON_VALUE_RIGHT2		0x09
#define CONTROLLER_BUTTON_VALUE_LEFT1		0x0A
#define CONTROLLER_BUTTON_VALUE_RIGHT1		0x0B
#define CONTROLLER_BUTTON_VALUE_TRIANGLE	0x0C
#define CONTROLLER_BUTTON_VALUE_CIRCLE		0x0D
#define CONTROLLER_BUTTON_VALUE_CROSS		0x0E
#define CONTROLLER_BUTTON_VALUE_SQUARE		0x0F

This simplifies (at least makes more clear) implementing code.  For instance, this is a slightly simplified version of what is currently in use for Stubby, to read the left stick and the X button (assume 'b' is a uint8_t, and is the incoming message over the serial port):

	if ((b & CONTROLLER_MESSAGE_TYPE_MASK) == CONTROLLER_MESSAGE_TYPE_ANALOG){
		if ((b & CONTROLLER_ANALOG_STICK) == CONTROLLER_ANALOG_STICK_LEFT){
			uint8_t value = b & CONTROLLER_ANALOG_VALUE_MASK;
			if ((b & CONTROLLER_ANALOG_AXIS) == CONTROLLER_ANALOG_AXIS_X){
				x_value = value;
			}
			else if ((b & CONTROLLER_ANALOG_AXIS) == CONTROLLER_ANALOG_AXIS_Y){
				y_value = value;
			}
		}
	}
	else if ((b & CONTROLLER_MESSAGE_TYPE_MASK) == CONTROLLER_MESSAGE_TYPE_BUTTON){
		if ((b & CONTROLLER_BUTTON_PRESS_MASK) == CONTROLLER_BUTTON_PRESS){
			cross_button_pressed = 0x01;
		}
	}

Discussions