Lab Guide: Using the Gamepad

This year students are using a USB gamepad for sending manual commands to their projects.  The gamepad plugs into a computer running a Python script.  The Python script reads the gamepad’s status packet over USB, and then sends it over the serial port to a microcontroller (and then you can do whatever you want with it).

The Python Script

The script was written by River Allen in 2010.  It requires three packages (all of which are cleverly hidden on the lab USB key):

  • Python 2.x: We have successfully used Python 2.6 and 2.7.  Other versions might not work.  Install this before the next two packages.
  • Pygame: This is what connects to the gamepad.
  • Pyserial: Serial port code for talking to the Arduino.

These all work on Linux; pyserial doesn’t explicitly claim to be Mac compatible, but who knows.  If you have multiple Python installations, make sure pygame and pyserial are installed into the Python 2.x folder.

Configuration

You can set the serial port information in the file globalConfig.py.  Select the correct serial port and baud rate for transmitting to your Arduino (note that the script’s serial port setting is 0-indexed, so if your Arduino is on COM6 then the value in globalConfig.py should be 5).  The baud rate is whatever you implemented in your Arduino program, when you called Serial.begin(…).

Using the Script

Plug the Arduino and gamepad into your computer and run the Python script on the computer.  If it’s working, then the script will just sit there and not print anything.  If it’s not working then it will error out.

An error indicating that the pygame or pyserial could not be located means that either they are not installed, or they are installed into the wrong folder.  It can be fixed by reinstalling the packages (not repairing them: remove them if they are already installed) and re-installing them into the Python 2.x directory (this is for Windows, I have no idea how parallel installations work on other OSes).

An error from pygame indicating an invalid joystick device number means that the gamepad is not plugged in.

An error from pyserial indicating that it could not open the COM port means that the Arduino is not plugged in, or the serial port is configured incorrectly in globalConfig.py, or the Arduino program isn’t calling Serial.begin(…).

The Serial Packet Format

The Python script sends the button data over the serial port in a 20-byte packet.  The packet consists of 18 bytes of data and two end-of-line characters to indicate the end of the packet.

The first 12 bytes correspond to the gamepad buttons.  The first 10 button numbers are written on the gamepad itself.  For example, buttons 1-4 are the four right-hand thumb buttons, and they correspond to the first four bytes of the serial packet.  Buttons 5-8 are the trigger buttons and fill out the next four bytes.  There are two buttons labelled 9 and 10 in the middle of the gamepad.  Buttons 11 and 12 are respectively triggered by clicking the left and right analog joysticks.  Each of these 12 bytes are boolean values: 1 if the button is pressed, and 0 if it is not pressed.

The 13th and 14th bytes hold signed data for the left analog joystick (horizontal and vertical, respectively).  Negative values in byte 13 mean the joystick is held to the left, and positive values mean the joystick is held to the right.  Negative values in byte 14 mean the joystick is held down and positive values mean the joystick is held up.  Similarly, bytes 15 and 16 indicate the position of the right analog joystick.

The 17th and 18th bytes hold data for the hat (also called the D-pad).  In byte 17, a value of 1 means the right button is pressed and a value of -1 means the left button is pressed.  In byte 18, a value of 1 means the up button is pressed and a value of -1 means the down button is pressed.

The following table summarizes the serial packet format.  Byte #1 is the first byte to arrive on the serial port, and byte #18 is the last.

Byte NumberDescription (possible values)Byte NumberDescription (possible values)
1Button 1 (0 or 1)10Button 10 (0 or 1)
2Button 2 (0 or 1)11Left Joystick Button (0 or 1)
3Button 3 (0 or 1)12Right Joystick Button (0 or 1)
4Button 4 (0 or 1)13Left Analog Joystick Horizontal (-128 to 127)
5Button 5 (0 or 1)14Left Analog Joystick Vertical (-128 to 127)
6Button 6 (0 or 1)15Right Analog Joystick Horizontal (-128 to 127)
7Button 7 (0 or 1)16Right Analog Joystick Vertical(-128 to 127)
8Button 8 (0 or 1)17Hat Horizontal (-1 for left, 1 for right, 0 for off)
9Button 9 (0 or 1)18Hat Vertical (-1 for down, 1 for up, 0 for off)

Reading the Packet in Arduino

The packet is transmitted over the serial port, so your program just has to read the data using the Arduino library’s Serial object:

uint8_t data[20];
Serial.begin(38400);
uint8_t i = 0;
while (Serial.available() < 20);    // wait until a full packet has been buffered (infinite loop risk alert)
for (i = 0; i < 18; i++)
    data[i] = Serial.read();

This code assumes that nothing else will be transmitting to the Arduino over the serial port.  If you want to transmit something else, you will have to come up with a protocol.

Keying the Packet

Some students have trouble transmitting the data reliably: they found that unexpected bytes were added to the end of the packet.  They solved this problem by sending a key byte such as 0xFE before sending the data, to indicate the packet start to the Arduino.  N.b. 0xFF cannot be used as the key byte because the hat bytes transmit 0xFF to indicate that left or down is pressed.

Suggested Improvements

If you feel like contributing to our body of knowledge, I can think of a couple improvements that you might consider making to the script:

  • Console logging.  It would be nice to have a startup message to indicate that the script is working properly, and maybe some other log messages (e.g. when a button is pressed).
  • Configurable transfer rate.  We could configure how often the packet is sent in globalConfig.py.
  • Serial port autodetect.  There might be a script floating around that supports this, maybe it’s hard to do reliably and efficiently.
  • Get rid of the end-line terminator characters.  We don’t need them for binary data and they add UART overhead.