Atari Jaguar Controller to USB HID Joystick

Pretty much everything about the Atari Jaguar controller is weird. It's overly cheap and overly complicated at the same time, and uncomfortable to hold even in my relatively large hands. Along with a very mushy low-profile d-pad and a reasonable selection of face buttons, Atari went the 5200/Colecovision/Intellivision route and included a full phone keypad, giving the controller an absurd 17 total buttons. Inside the controller, there's just a buffer IC, a few resistors, a bunch of diodes, and a PCB that sort of looks like it was cut out from an old cereal box. The good news for us dorks who are obsessed with making USB converters is that they once again used an off-the-shelf connector (DE-15, the same connector used for VGA on computers) and a very simple protocol. Here are the details, borrowed from the excellent Deathskull Laboratories page:

DE-15 connector
Male on controller, female on console
Pin Function
1 Column 1
2 Column 2
3 Column 3
4 Column 4
5 N/C
6 Row 1
7 +5V
8 N/C
10 Row 2
11 Row 3
12 Row 4
13 Row 5
14 Row 6
15 N/C
Column Pins
Row Pins
4 3 2 1
6 Pause
10 A B C Option
11 Right 1 2 3
12 Left 4 5 6
13 Down 7 8 9
14 Up * 0 #

Button-to-pin mapping

Rigging up a way to get all the pins of the DE-15 connector into a breadboard for prototyping was pretty laborious. I wasn't expecting to ever need more than one of these things, and I'm a cheapskate, so it wasn't worth the trouble and expense of designing a custom PCB and ordering apppropriate connectors for it. Instead, I ordered a pack of five panel mount DE-15 connectors for a whole 93 cents, soldered a wire into each of the 15 pins (that felt like less trouble than keeping track of which pins were unused) then glued it down to a proto board and soldered the other end of each wire to a header pin that was already mounted on the board. After verifying that all the pins were in the right order and properly connected, I entombed the whole thing in hot glue for protection. The narrow spacing of the pins on the connector made the whole process very fiddly and tedious since there wasn't much room to maneuver the tip of my soldering iron, but I will at least give the DE-15 connector points for being asymmetrical, as that makes the pin numbering a lot easier to keep straight.

Despite the large number of pins compared to most controllers, reading input is pretty straightforward. Ten I/O pins are needed: four for the columns as outputs, and six for the rows as inputs. My original plan was to use one of the dirt cheap STM32 "blue pill" development boards, but right before I started work on the code, my programming setup stopped working, so I used a spare Arduino Micro clone instead, with this really nice joystick library by Matthew Heironimus. To read input from the controller, all you need to do is set the selected column pin low, read in each of the six row pins (these are active low), then set the column pin back high again and move onto the next column. I briefly considered just hardcoding reading the whole grid pin by pin, but decided to make it a little more concise and set up a 2D array of the desired USB joystick button mappings. I just arbitrarily picked negative ones for the d-pad directions (so I could check afterwards and set a joystick axis intead) and 99 for unused ones so I could skip them.

#include <Joystick.h>

#define COL1 10
#define COL2 16
#define COL3 14
#define COL4 15
#define ROW1 2
#define ROW2 3
#define ROW3 4
#define ROW4 5
#define ROW5 6
#define ROW6 7

Joystick_ Joystick;

int *buttons[][4] = {

int xdir = 0;
int ydir = 0;

void setup() {
  pinMode(COL1, OUTPUT);
  pinMode(COL2, OUTPUT);
  pinMode(COL3, OUTPUT);
  pinMode(COL4, OUTPUT);
  pinMode(ROW1, INPUT_PULLUP);
  pinMode(ROW2, INPUT_PULLUP);
  pinMode(ROW3, INPUT_PULLUP);
  pinMode(ROW4, INPUT_PULLUP);
  pinMode(ROW5, INPUT_PULLUP);
  pinMode(ROW6, INPUT_PULLUP);

  digitalWrite(COL1, HIGH);
  digitalWrite(COL2, HIGH);
  digitalWrite(COL3, HIGH);
  digitalWrite(COL4, HIGH);
  Joystick.setXAxisRange(-32, 31);
  Joystick.setYAxisRange(-32, 31);

void setButton(int btn, bool on) {
  if (btn >= 0 && btn != 99) {
    if (on) {
    } else {
  } else if (btn != 99) {
    if (btn == -1 && on) {
      xdir = 31;
    } else if (btn == -2 && on) {
      xdir = -32;
    if (btn == -3 && on) {
      ydir = 31;
    } else if (btn == -4 && on) {
      ydir = -32;

void readRow(int colPin, int colNum) {
  digitalWrite(colPin, LOW);

  setButton(buttons[0][colNum], !digitalRead(ROW1));
  setButton(buttons[1][colNum], !digitalRead(ROW2));
  setButton(buttons[2][colNum], !digitalRead(ROW3));
  setButton(buttons[3][colNum], !digitalRead(ROW4));
  setButton(buttons[4][colNum], !digitalRead(ROW5));
  setButton(buttons[5][colNum], !digitalRead(ROW6));
  digitalWrite(colPin, HIGH);

void loop() {
  xdir = 0;
  ydir = 0;

  readRow(COL1, 3);
  readRow(COL2, 2);
  readRow(COL3, 1);
  readRow(COL4, 0);


The DE-15 breakout prior to being slathered in hot glue

I don't use the word "entombed" lightly

All 17 buttons accounted for!