Zerk Zone

Pages for the Creations of Ryan Armstrong

Arduino Leonardo Custom Joystick

Posted on June 28th, 2015 @ 2:35 pm by Zerker

As hinted at in the previous post: I made a joystick. While you can buy dedicated PCBs for custom joysticks (and I would potentially recommend this for most people), I felt like doing mine a bit more manually with an Arduino instead. And here’s how it looks!

JoyFront

To be fair, I didn’t exactly start from scratch. The initial USB stack patch came from a post on Imaginary Industries, but I modified it quite a bit since then to have just the USB descriptor I wanted, and to be a bit more intelligent with the struct handling and USB report data packing. I also used this reference for the SNES connection scheme.

The Hardware

The joystick hardware is not particularly complicated. Here’s a picture of the inside of the stick:
JoyInside
The main parts I used are:

Each button and joystick input is tied to one end of each screw terminal, then connected to the Arduino via a shorter solid-core wire that is less prone to wear. The button wires are stranded 22 guage wire, and the connects are quite secure once attached to the screw terminals. As per standard convention, all buttons inputs are configured with pull-ups (using Arduino internal pull up mode), with the other side of the button connected to ground. When the button is not pressed, the Arduino will read high; when it is pressed, it will be pulled down to ground. Since ground is common among all buttons, the wire is simply daisy-chained to each from a single screw terminal.

The Joystick has a built-in PCB with a five-wire connector. It already connects the common ground for each switch on the joystick to the black wire, then provides a signal wire for each direction. These are connected to the Arduino in the same manner as the buttons. Unfortunately, the joystick I picked came with a strange offset mounting plate, which wasn’t ideal for my application. I inserted two small pieces of wood to offset the joystick slightly so I could fully tighten the screws without bowing the plastic.

The rear of the joystick has two ports: USB and DA15:
JoyRear

My original plan was to support both SNES and Genesis through the DA15 connector, but I ran into problems with Genesis support (e.g. powering the Arduino stopped the genesis from pulsing the select line), so it just supports the 5 pins for the SNES. If anyone wants to just make a USB/SNES controller, I recommend getting a 5-pin round connector instead, which would be much easier to drill a hole for.

The breadboard is a remnant of attempting to support the Genesis and is really just providing a relay for the pins that don’t fit directly into the Arduino. And the pins are only needed because I used stranded wire for my DA15 pins. For simple SNES support, I recommend instead just using solid core wire and running it direct to the Arduino.

Software

Here is the download for my Arduino project (99 kB). It also includes some Oscilloscope traces when testing the design, as well as two modified USB stack files for the Arduino libraries. The two files are HID.cpp and USBAPI.h, and need to be merged into arduino-[version]/hardware/arduino/avr/cores/. I do not recommend blindly copying the files into this folder; instead you should use a comparison utility to merge in the appropriate changes. This is especially important if these files are modified in future Arduino library versions. You should, of course, back up the existing files as well.

Note that this approach will also work with the Arduino Due, but you won’t be able to support the SNES without a line-level converter. The Due’s USB stack can be found in arduino-[version]/hardware/arduino/sam/cores/arduino/USB/. If you are making a joystick with the Due, take extra care doing the comparison, as there are other differences between the Leonardo and Due libraries that you don’t want to change. The joystick code is fairly isolated, but let me know if you are trying this and have problems.

As far as what the code does, it’s somewhat braindead. It checks the discrete state of every button and input, then changes the appropriate bit or X/Y value in the USB data. Then it sends the USB report to the PC and repeats.

The SNES code is only slightly more complicated. It stores the button states in the main loop, then uses an interrupt on the SNES latch line. When the SNES triggers that signal, it stores the button/direction values into a fake ‘shift register’ and shifts the first bit. On each subsequent clock pulse (also an interrupt), it shifts the next bit. This is more or less the same approach as mentioned by Kalshagar on his SNES cabinet post.

It may also be possible to use either SPI or I2C to have the microprocessor handle the clocking automatically. However, you will still require an ISR for the initial value, and you will also need to handle the case where the buffer empties, since the Leonardo only has an 8-bit communication buffer.

Finally, this should also mostly support NES games if you wire up an SNES to NES adaptor. However, NES support isn’t perfect; see below.

Limitations

As per my previous post, the Super Gameboy won’t work. Also, there are some minor problems with a few NES games. Instead of using the Y and B buttons as per the typical data sequence, it actually responds to the Y and A buttons. After scoping this, the problem is that some NES games appear to start clocking the data very soon after the latch command. This means the Arduino doesn’t have enough time to get the first data value ready (SNES B button/NES A button) until it is already too late! However, we are partially saved in this case, because the previous output of the SNES A button is actually the 9th bit in the previous message, and ends up being the value on the data line between data sequences. If the NES samples the data line before the Arduino is ready, this is the value it reads.

Here’s how it looks on a scope. First, a first-party Nintendo game without this problem. Latch and Clock lines:

B button:

Next is a a problem game. Here is the latch/clock sequence (note the scale is different to show all pulses; the clock pulses are a bit further apart)

And here’s the the B button pressed. Note it misses all the clock pulses.

And the A button for reference:

There’s no real easy solution to this, however. Maybe there’s a way to edit the Arduino libraries to reduce the ISR preamble. However, this is a rather dangerous approach, as it could result in data loss for the background task, and unexpected behaviours.

Comments (6)

Filed under: Hardware |

6 Comments »

  1. Great work!!!
    I tried to get my Arduino Leonardo to work as a joystick controller on Recalbox.
    After several implementations or joystick examples I stumbled over this and it worked nearly perfect!
    I only had to set a small delay to avoid bouncing.

    THX

    Comment by DsChAeK — August 29, 2015 @ 9:47 am

  2. You’re welcome.

    Regarding the delay, I did some initial testing for bouncing on a scope and thought the frame rate was too slow for bouncing to actually have any effect. However, I suspect the joystick test application wasn’t actually polling the joystick as fast as possible. I will re-test with some other applications and update accordingly; I did notice some erroneous double-taps when trying to play Scott Pilgrim vs the World on PS3.

    Comment by Zerker — August 29, 2015 @ 11:03 am

  3. So I did some more re-testing, and it appears Scott Pilgrim is just based on the way the game detects the Joystick as an analog axis; it expects if you slam the joystick from zero to fully extended, you want to run/dodge. Since a digital joystick will always do that, it triggers it every time. Re-doing the descriptor as a HAT switch would fix things for that game, but would cause problems elsewhere.

    As for bouncing in general, every OS I tested the stick against samples the joystick every 1 ms. When I tested the joystick and buttons with a scope, I found the worst-case bounce was ~10 us with the parts I used, which is an order of magnitude faster than the frame itself. I’m really surprised you had any issues with bouncing. If you have a scope, I’d be interested in what sorts of bounce times you are seeing, as well as average frame times. The code I uploaded should let you measure frame time if you uncomment #define DEBUGSCOPE and hook up the scope to the appropriate discretes.

    Comment by Zerker — September 6, 2015 @ 12:58 pm

  4. Well, I’m more a software kind of guy so I don’t have deep hardware knowledge, and I really don’t have a scope at home. :)
    I got myself this: https://shop.pimoroni.com/products/picade and wanted it to work with Recalbox which only supports gamepads in game. Pimoroni uses an arduino leonardo compatible board but their firmware only supports key strokes. So I modified their original firmware (https://github.com/pimoroni/Picade-Sketch/blob/master/Picade/) and used your code. You can have a look here: https://github.com/DsChAeK/Picade. Without the delay items got skipped when stepping through my romlist up and down.
    I’m sure the bouncing is because I blindly used their implementation for the joystick part… ;)
    I never got any problems in games, but I do have to play more now that thing is working.

    Comment by DsChAeK — September 6, 2015 @ 2:43 pm

  5. Came across your post when I searched around for that sub feedthrough of yours with an arduino.

    I am currently building a controller also, except I’m using an Uno and the Unojoy Library. The arduino still has some pins for LEDs that my buttons use, the lights dim and turn on with some custom delay functions , but I am wondering if using a delay function will impact gameplay response?

    Love the color on your fightstick btw…

    Comment by Keanu — February 6, 2016 @ 10:48 am

  6. Thanks! Using a delay function could impact gameplay response, depending on how much you delay by. You should target sending one USB report every frame of your display. Targeting 60 fps gameplay, this means you main loop should not take more than 16 ms. If you have access to an oscilloscope, you can test this by having a start/end of frame debug discrete. If not, you can probably guess based on what you coded, but there will be some overhead for the USB communication.

    If you really get stuck with the two competing tasks, you can always move the USB operation into a timer interrupt that runs every 16 ms exactly.

    Comment by Zerker — February 6, 2016 @ 11:10 am

RSS feed for comments on this post. TrackBack URL

Leave a comment