22 June 2021

3DS Input Redirect Controller

With some mention of a Discord server member having a modded 3DS firmware, I kinda decided to look into it, and after reading the instructions, it seemed pretty easy to do. I grabbed my secondary 3DS (because I pretty much had nothign to lose on it) and gave it a shot; it really was that easy and non-destructive.

One of the features is being able to control the 3DS with a controller connect to a computer over the network (obviously using a programme on the computer as well), which is pretty nice since the 3DS' directional pad is a little annoying for me to use (I guess I have larger than average hands?). For controllers I have the XBox 360 controller, the Horipad, the Joy Cons, or any of the Bluetooth SNES controllers (8Bitdo or Nintendo) to choose from. The Bluetooth SNES controllers are automatically out for not having a joystick (though probably not a huge deal), the Joy Cons I don't want to have to bother pairing/repairing, the Horipad is wired and connected to the switch dock that I don't want to have to bother with, and so that leaves me with the XBox 360 controller that gives me some disassociative issues because its buttons feel much different than the 3DS'.

With learning CircuitPython, I decided to make my own controller that has a few buttons to allow me to run a predefind macro that speeds up what I was doing a lot by hand; it would also have tactile buttons that'd probably feel a little closer to the 3DS than the XBox 360 controller does.

I think while using the Trinket M0 to do some tests, the programme to send the input to the 3DS didn't seem to register any of the buttons in the gamepad library for the directional pad, and while it sucked, I figured out to just emulate the directional pad by sending a couple joystick movements to make it seem like the joystick was flicked in that direction.

I think the code came first because it was easier to handle? On top of working on this project, I was also learning things about microcontrollers along with CircuitPython. It seems like I was originally planning on using the ATSAMD21E18A-A because it would fit all the inputs as well as having an analogue selector switch (using some resistors and a switch) and four function/macro buttons. Or at least so I thought, because at some point, I realised it took away PA24 and PA25, which were needed for the USB connection to the computer, so I decided to stick the axes of the two joysticks on an I²C ADC (ADS1015) to free up PA24 and PA25. I also had realised at some point that I could use an RTC for the year, month, and day for part of the macro instead of having to manually input it myself, and so I added added it to the I²C line.

I think I started writing the code at this point? (Seriously, there was so much going on that it's hard to remember everything in chronological order.) And with how long the code got, I was uneasy with how large the code was getting and I didn't know how much of the MCU's flash space would be left after all the libraries I needed, so I decided to add an SPI flash chip for expanded storage (it was at this point I hadn't had the Trinket M0 yet). Because of this, I needed four more pins from the MCU that I didn't have, so I opted to "upgrade" to the ATSAMD21G18A-A for more pins. It was likely around this time that I had to look for some other I²C pins because the SPI flash is normally connected to PA08, PA09, PA13, and PA14, and I had PA08 and PA09 planned for the I²C connections. I ended up using PA22 and PA23 for I²C, and then had a couple pins leftover with all the other inputs connected to their own pin. I think I was also just going to use a CircuitPython build for a board that was similar, but after a bit of digging, I became less sure that was a good idea.

I forgot how I ended up with needing a second selection switch, but it seems like I ditched the joystick axes to be able to fit them in? Anyway, I designed the first version of the board after getting some data (button spacing from the Joy Cons), and I think the board sat without traces and airwires for a while before I bucked down and started to do it. I had a bit of problems with placement of the flash and RTC, as well as running the traces for them, but did eventually figure it out. The I²C lines for the RTC was the most annoying because not only did I need to connect the two pins to the MCU, I needed to connect them to separate pull-up resistors as well, but the other problem was getting the trace for the backup battery to the corresponding RTC pin while keeping the trace short and reasonable, and a friend suggested to run the trace in the space between the pull-up resistor pads, which I failed to see (I was getting tired).

Section of the board with the flash, RTC, MCU, part of the backup battery holder (CR2032), and some of the buttons.
If you see the problem with the I²C lines, I'll get to that later.

I think it was sometime after this when I had the Trinket M0, and loading the necessary libraries onto the MCU's flash (the Trinket M0 doesn't have an external flash chip) showed me how much space I had left, which is more than I originally thought. I think I had to strip all the comments and extra whitespace out to really be sure it'd fit? Anyway, since I confirmed that I'd have plenty of space, I could remove the flash chip and shift the pins around a little to have enough analogue pins free for the joystick axes again (even if there's no board space for them).

Sometime I found a guide on building CircuitPython, which includes a link to a guide on adding a new board to CircuitPython, and the latter guide lead me to the configuration files that I'd need to change. Or maybe I came upon it some other way? Regardless, there was the pins.c file with the pin names that board uses to make it easier for the programmer to reference in code, and while I really didn't want to have to make names for it, I did anyway. Later on, I discovered microcontroller.pin that invalidated the need for this.

I forgot to mention, but I think it was around the second board revision (I at least had the Trinket M0) that I found a different version of the input redirect programme for the computer that allowed button assignment, so I was able to give the programme specific buttons for directional pad and allowed me to get rid of the virtual joystick flicking thingy I was doing. I suppose I'll talk about the programme more here. Anyway, the programme (either version) has a window that the user can click on to send the coordinate touched to the 3DS (emulating the 3DS' touchscreen being pressed at that point), but the problem with it was that it wasn't very accurate. The programme allows a specific point to be sent (using L3 or R3 as the trigger) and the point that I sent didn't correspond with what the 3DS says it received (if I sent 205,198, it'd say 208,201). What I tried to do at first for compensation (because I needed the macro to move the mouse and click on the touchscreen emulator) was to get screenshots and scale them down and figure out the translation of points, which kinda worked... Eventually, I found points that corresponded and found that the weird scaling issue is from the horizontal center and below vertical center of the screen. I think at this point I tried poking with the source code (I had to compile the programme anyway) to try to get it to work correctly, but no luck.

I had decided to gather a bunch of points and try to figure out an equation to fix the problem, but I couldn't get anything to work. I think I tried to get an equation from a scatter plot, but I think it kinda worked, but I think I figured it'd be more accurate if I got more points. I then collected literally all the points on the x and y axes to plug into the scatter plot (just one axis though, considering that the scaling is different for both axes), and while it took a bit to get them in, I had something much more accurate (I think I had played around with the dead zones a bit, but don't remember if the final equations included them or not) to work with. After modifying and compiling the code (for the umpteenth time), the coordinates were spot on, except every so often where a pixel would be skipped (it would go from 25 to 27, for example, but be fine after that until the next one), which was good enough because it was much more accurate than it was with the original equation. Because the programme (the one with the button assignment) also allowed the touch emulation window to be resized, it also included some maths to compensate for that, so I had to tinker around with it. I couldn't really get it to work with the window resize, so I ended up just locking the window at a fixed size instead to avoid the hassle.

While the untouched equation for the touchscreen emulation is fine by logic/mathematics, for some reason it just didn't work correctly in Linux, and I kinda did want to try the Windows version (which I think there was a pre-compiled package for), I didn't want to have to reboot to Windows or use Bazett. I might've done a quick test in windows, but I don't remember. I also had tried tinkering with the sent bits before tinkering with the equation and stuff, but that just made the emulation not work most of the time.

It was towards the start of the project that I was planning to have one set of buttons and joysticks and have two microcontrollers so that I could connect to two computers to control two 3DS units with one controller (yeah, it'll do the same thign on both units, but it makes stuff like pokémon trades easier), but after trying to just run the input redirection programme again and just changing the IP address in the second instance (I had applied the mod to my main 3DS at this point), I didn't need to do that. This idea is why I had gotten an ADS1015 breakout from Adafruit, because the Raspberry Pi doesn't have analogue inputs and was also supposed to be the testbed for it. I still did the test, but with connecting the ADS1015 to the Trinket M0 instead, but what I was trying to figure out was if I would be able to connect one joystick axis to two different analogue inputs and get the same values, and it turned out as I expected. (I also had gotten a joystick breakout from Adafruit along with the ADS1015.)

Anyway, another thing I was worried about was how much memory I'd end up needing to run the code and such, but because the Trinket M0 doesn't have enough pins and only has a few pins broken out, I wasn't able to do any testing until I finished my ATSAMD21G18A-A and ATSAMD51J19A-A dev boards. Turns out that there is enough memory to run it if I don't import analogio and add the analogue pins to the programme, but because I had been planning on making another controller that had joysticks, I wanted to keep the MCU the same to make it easier. Obviously the code with the joysticks and stuff ran just fine on the ATSAMD51J19A-A since that MCU has about 6 times the memory than the other, and I went to make another revision to the board using that MCU instead.

Somewhere during this deep dive on these MCUs and CircuitPython (likely between the second and third revisions of the board for this project), I ended up reading about pin muxing (I think for a different concurent project), which I think lead me to save a copy of the IO table from the two MCUs' datasheets before making a spreadsheet with them so that I could sort it by pin or whatever. Before this, I had been relying on the schematics that Adafruit has for their dev boards, but in some cases, it wasn't specific enough. Anyway, I found that for I²C, it was specific pads on the ports that determined which one was SDA and SCL, so while PA22 and PA23 are both I²C pins (on SERCOM 3 or 5), they have to be used specifically. Further in the datasheet gives the specifications that pad 0 is SDA and pad 1 is SCL (there's designations for pad 2 and pad 3, but we're ignoring them for simplicity), and because PA22 is pad 0 and PA23 is pad 1 (again either on SERCOM 3 or 5), PA22 has to be SDA and PA23 has to be SCL. Because I was using the schematic for some Adafruit dev board, it just had PA22 and PA23 labelled as "I²C" without saying which were SDA and SCL, so I had just guessed that it didn't matter or whatever. Turns out that I had the signals swapped in the first and second revisions of the board, but I only ripped the SDA and SCL traces up (and did a pin swap) in the second revsion because that was the more concerning one, even if it wasn't going to be made. I'll be talking more about the pin muxing stuff in a different post for the project that it belongs to.

In my notes, I had the ATSAMD51G and ATSAMD51J as the replacements for the ATSAMD21G if there wasnt' enough memory, but I later found that I wouldn't be able to use the 51G because it's not available in the same package as the 21G along with the fact that it loses one IO pin for a pin that connects to an inductor and such to vary the power input it needs for power savings (if running off of a battery, for example). My only option became the 51J, which was a little larger in size than the 21G, but not much larger (I think the moulded plastic is about 14×14mm compared to 10×10mm?). I wanted to try to keep the routed traces as much as I could, so that I would have a little easier time re-routing, and it made it a bit of a mess while I had to avoid running the design rule check (which would bring up obvious errors). Some inputs did have to shift pins a bit, but otherwise the traces are fairly similar to the second reviison. I also added the connectors for the (Joy Con) joysticks as a placeholder, so that I would be sure I had a path for all the traces (mainly for the left joystick), including the joystick button.

The reasoning for using the Joy Con joysticks for the controller is because of its ease to replace compared to the usual soldered-on variant, and its compact Z-height. While waiting to be able to test the memory consumption of the code, I dug up what I could on the connectors and the joystick measurements, and ended up buying a set of 3rd-party replacement joysticks to be able to measure and confirm the measurements that I found. I had also used it to somewhat mock up an ideal location for the joystick in relation to the directional pad buttons.

One of the design changes between the third revision of the board and the first revision of the board with joysticks was adding a couple more switches to be able to switch the L3 and R3 inputs between the joystick buttons and the discreet buttons (L3 and R3 would be reassigned as the power and home buttons within the input redirection programme) so that I wouldn't have buttons that didn't do anything or were annoying to actuate. The first thing I did was place the left joystick in accordance to my notes (meaning its centre had a negative x coordinate) and then shifting everything over that would need to shift over. I think i calculated the shift for the ABXY buttons to make space for the right joystick, but because some or all of the pads of three of the buttons would extend past the 100mm limiatation of the free version of EAGLE, I had to move them manually by editing the board file (which is literally just a fancy XML file). The next thing I did was move the top of the board and some of the components up to match where the top of the board was in relation to the ABXY buttons. Because of the limits to where I can place the joystick connector and the reachability of the L button, I ended up having to have the L button on the bottom of the board instead of the top. The right joystick was easier to place since there was a bunch of space on top of the board near the MCU (which is under the board).

I think a little later I decided to add an RGB LED for indication, so that I would have an idea of the backup battery state and to know whether or not one of the macros are still running, and I agonised over the placement a bit because of needing to run a trace between it and the MCU and another trace for 5-volt power, but settled near the centre as the best option. Though I already had shifted the pin assignments for the added switches and buttons, I eventually decided to shift the assignments again to make it easier to route the signal trace for the LED so that it wouldn't take such a long path across half the board with having to switch between the top and bottom layers. Luckily, this ended up cleaning the rest of the traces up more.

While doing the inital layout on the first revision of the original board, I was kinda joking around with a friend saying that I could fit a CR2032 and a CR1225 on the board, but I eventually went with the CR2032 because it's more common (I think the CR1225 was actually from the Raspberry Pi clock project where I was considering on making an RTC "backpack" in adition to the display board, which didn't). Anyway, with how much extra board space I gained from the expansion, I mocked up being able to have multiple CR2032 batteries and came up with about 3 or 4 batteries, but with some maths I did back with the first revision of the original board, one CR2032 would last about 9 years, so having it last three or four times that would be a little inane. I also accidentally blocked the path for the backup battery to connect to the RTC because I worried about all the other traces first, but this was fixed with a couple of vias to run part of the trace over the blockage. The trace for the battery check had to take a weird path, since I have the RTC in roughly the same location and the analogue pins being on the opposite side of the MCU.

Battery trace highlighted.

Oh right, after changing MCUs, I forgot I had to change the reset circuit because I was copying the design from some of Adafruit's dev boards (:x), so I had to fiddle with that when I realised it, but I made it work.

While the board is complete and ready to be fabricated, the fact that I only bought enough MCUs for the Griffin Powermate hack and the dev boards and the fact that the silicon shortage had affected suppliers (Mouser, Digi-Key, etc) a little after I bought those MCUs mean that the project is at a standstill until I either wait until there's a stable supply of MCUs or order a small supply of MCUs that will arrive whenever it does. I think this'll be it for now, I don't think there's really much to say about the code and I can't think of anything else about the project to talk about that I haven't already.