Integrating a Nintendo Switch Joystick into an Arduino Game Controller

by Cory on October 28, 2020 12:49 AM (Edited November 7, 2020 1:25 AM)

First thing's first:

I "rebuilt" my thumbstick breakout board with a solid piece of Amazon cardboard. I also had to redo the FPC connector soldering on the PCB after I accidentally broke it off (it's on the underside, so not visible in the picture, unfortunately). I wasn't sure if I could properly solder surface mount components with random junk in my room but it turns out that I do! I have a flux pen and a copper solder wick. The technique I went for was to load up the pins with a big glob of solder. You might notice that now all the pins are connected together in one big solder blog and be confused at this point since I just fucked everything up. FEAR NOT! The next step is to get a piece of solder wick, use the soldering iron to press it on top of the glob, and wick up the excess solder. Viola! The join looks professional now.

Here's what it looks like connected to the rest of the controller.

Once all the hardware was in place, I sat down to do some hard investigating into why the Nintendo Switch joystick wasn't working right. If this was a movie, this part would cut to a montage right now. Instead let me explain what I did. Like a nerd. Using multiple diagrams and animated gifs.

The symptoms I was seeing was that the joystick wasn't being recognized at all by retropie even though I could see it using the jscal, which is a utility that comes pre-installed on the retropie image. There's a menu in the retropie frontend where you can configure the controller buttons and moving the analog stick doesn't register as any button presses. It turns out this issue was caused by numerous small problems related to calibration.

The first is that you need to know that retropie uses SDL2 as its input layer. So the values you see in jscal are complete bullshit. Confusingly, the utility that you should use isn't installed with the image and it's called sdl2-test. You need to download it from GitHub from the Raspberry pi and compile it too. Also you should note that there is sdl-test and sdl2-test programs in this repository and you need sdl2-test because retropie is using SDL2.

Also, peep this:

Windows has a calibration utility that let's you view the values your joystick is putting out. In the above gif, I'm outputting the raw joystick values. As you can see, at 5V input voltage, the thumbstick axes range from about 170-200 to 850. We'll need to know this for later in the Arduino program. By the way, you can view my Arduino code in this repo.

This leads me to the second issue, which is that my mapping was wrong. Most modern joysticks expect a range from -32768 to +32767, with some value near 0 being the neutral position. Why? Because that's the range of values you can express with a signed 16 bit integer. My arduino code mapped the joystick raw values to a 0 to 16384 range. This was purely arbitrary and I figured that would be okay because I could calibrate this later (like you can in Windows). But that's a huge pain in the ass*. So I just changed it to map to -32767 to 32767 to be done with it.

*sdl2-test simply lets you view the information of the joystick. The corresponding jscal program is called evdev-joystick and this comes pre-installed with retropie. Once you run evdev-joystick, you need to do this whole thing with saving the coefficient output to a file and then setting up a script to rerun evdev-joystick on startup to recalibrate the joystick. UGH. Not recommended.

I'm still trying to understand why, but mapping from 0 to 16384 really screwed things up. Here's what a properly calibrated joystick (using the full +/- 32767 range looks like):

Note how the program shows you the full expected range and the "#" symbol goes all the way to the edges.

If I map the 200 <-> 850 range to 0 <-> 16374 using the Arduino, I get something that looks like this:

Note two things here. The first is that the neutral position is at ~4000. I don't know why this is. But this causes "lopsided" behavior where one direction moves the symbol more than in the other direction. The second issue is that this doesn't even register as movement in retropie! Because it's expecting the full 16 bit range, retropie is only registering this as neutral, slight tilt, or half tilt (and when treating the axis as a binary push button, this is just under the halfway cutoff so it doesn't register as pressed at all).

So that explains why jscal shows me movement but it doesn't register in retropie.

But wait, there's more! Once I fixed this and mapped it to the full range, I was seeing weird behavior sometimes, like when I press down and having it register as pressing up. This was because my mapping was still incorrect. I saw something like this:

Notice how both axes seem to wrap around the other sides when I push the joystick too far, like a game of mario bros. This is because the input range I used for the mapping is too small. Recall earlier that I said the raw values were between 170-200 and ~850. It is important to go back and find the max values in either direction and write these down. Since the joystick values are also noisy, you'll get +/-10 jitter. 

So when I modified the input range to add some leeway, this stopped happening. For instance, I used something like 190 <-> 850 for the X axis and 130 <-> 830 for the Y axis.

After all that, I ran the joystick configuration again and tried playing some GBA games and with a joystick that ended up feeling completely natural! Now that's satisfying.

This Thought is part of Emulators

Cory tries to make the perfect handheld emulator

back to the