Home

Awesome

My Nintendo Switch reverse engineering attempts

I'm just going to dump all my discoveries here, and hopefully they would be useful to the Nintendo Switch community.

Questions?

Please make a post in the issue section, contributors have been immensely helpful.

Contributors

Please remember that the information in this repo is the work of a lot more people than just me. Please take a look at the contributers list and appreciate their work. Be sure to credit them as well.

Joy-Con PCB Layout and test points

Alt text

Full-size PDF of Joy-Con pinouts, both left and right.

The "JC" is for the 10-pin Joycon connector, see below for details.

Remarks

SPI Peripherals

Alt text

There are 2 SPI devices on the bus, one 4Mb MX25U4033E flash memory and one LSM6DS3 6-axis MEMS accelerometer and gyroscope.

Here is a capture of SPI lines when the Joy-Con battery is connected, and then attached to the console.

It looks like SCK runs at 12.5MHz when accessing the flash memory, but switches to 6.25MHz when accessing the MEMS chip.

Accelerometer and gyroscope

Upon connection the microcontroller initializes a software reset of the MEMS chip, then set up the accelerometer and gyroscope as follows:

AccelerometerGyroscope
ODR 1.66KHz, full-scale ±8gODR 208Hz, full-scale 2000dps

The accelerometer also has AA filter at 100Hz bandwidth, low-pass filter enabled, slope filter enabled with cut-off frequency at 416Hz.

The Joy-Con then polls LSM6DS3 every 1.35ms(740Hz) for both accelerometer and gyroscope data in all axises, totaling 12 bytes(6 axises, each axis 2 bytes).

Since the Joy-Con polls MEMS data every 1.35ms but only send out controller update every 15ms, there might be some internal averaging to smooth out the data, needs to go through the numbers to find out.

Flash Memory

Well there's a capture of the SPI lines when the Joy-Con is powered up (battery connected), which contains all the address and data Joy-Con reads from the flash memory. I don't have time to go through it right now but of course you can if you want.

The SPI flash can be dumped post-pairing over UART using 19 01 03 38 00 92 00 31 00 00 d4 e6 01 0c 00 01 40 40 00 01 40 40 10 XX XX XX XX YY 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 where XX XX XX XX is a 32-bit little-endian address and YY is the amount to dump up to 0x1c bytes. The resulting data will be at +0x20 in the Joy-Con response.

Joy-Con color, serial and calibration settings are all stored on the SPI flash and are accessed through the above query.

Joy-Con to Console Communication

When attached to the console, the Joy-Con talks to it through a physical connection instead of Bluetooth. There are 10 pins on the connector, I'm just going to arbitrarily name it like this:

Alt text

Alt text

Looking at the pins on both Joy-Con facing towards you, the left most one is Pin 1, and the right most one is Pin 10. I simply removed the rumble motor, burned a hole on the back cover, and routed all the wires out through that.

Capture of the docking of the left and right Joycon.

Alt text

Joy-Con Connector Pinout

Logic analyzer channelJoycon Connector PinFunctionRemarks
-1GND-
-2GND-
03JdetHIGH when connected to console via Bluetooth. Joy-Con will not send serial data post-handshake unless pin is pulled LOW by console.
145VJoy-Con power and charging
25Serial data console to JoyconInverted level (idle at GND)
36JRSTJoy-Con reset signal , high level is reset
-7GND-
48Serial data Joycon to consoleStandard level (idle at 1.8V)
59Power outputJoy can output power to Ring Fit Adventure or starlink toy
610Flow controlJoy-Con will only send data to console when this line is HIGH

Handshake procedure

I took apart 2 left Joy-Con, one grey one red. Below you can see the difference in the response between two Joy-Con.

Console to Joy-ConGREY Joy-Con responseRED Joy-Con responseDifferent?Remarks
A1 A2 A3 A4 19 01 03 07 00 A5 02 01 7E 00 00 0019 81 03 07 00 A5 02 02 7D 00 00 6419 81 03 07 00 A5 02 02 7D 00 00 64SameHandshake start; 1000000bps
19 01 03 07 00 91 01 00 00 00 00 2419 81 03 0F 00 94 01 08 00 00 FA E8 01 31 67 9C 8A BB 7C 0019 81 03 0F 00 94 01 08 00 00 8F 87 01 E6 4C 5F B9 E6 98 00DifferentJoycon MAC
19 01 03 0F 00 91 20 08 00 00 BD B1 C0 C6 2D 00 00 00 00 0019 81 03 07 00 94 20 00 00 00 00 A819 81 03 07 00 94 20 00 00 00 00 A8SameCommand to switch to 3125000bps
19 01 03 07 00 91 11 00 00 00 00 0E19 81 03 07 00 94 11 00 00 0F 00 3319 81 03 07 00 94 11 00 00 0F 00 33Same?; 3125000 bps from now on
19 01 03 07 00 91 10 00 00 00 00 3D19 81 03 07 00 94 10 00 00 00 00 D619 81 03 07 00 94 10 00 00 00 00 D6Same?
19 01 03 0B 00 91 12 04 00 00 12 A6 0F 00 00 0019 81 03 07 00 94 12 00 00 00 00 B019 81 03 07 00 94 12 00 00 00 00 B0Same?
19 01 03 08 00 92 00 01 00 00 69 2D 1F61B Controller status61B Controller statusDifferentHandshake done. Console sends this controller status request command every 15ms from now on.

Pesky checksums

It turns out the last byte of each command sent over serial seems to be a checksum of some sort, and without figuring it out it would be rather difficult testing what each command does because the console will not accept commands with the wrong checksum.

Thanks to ewalt1's effort and contribution, we seems to have a solution to the checksum problem:

The first 4 bytes are a header, with the 4th byte being the length of the remaining packet (not counting the checksum). The next 7 bytes are some type of data, with the 8th byte being the CRC of that data. The CRC used is CRC-8 with a polynomial of 0x8D and an initial value of 0x00.

There's some example code for calculating this CRC using a lookup table in packet_parse/joycon_crc.py.

Note: these checksums are only sent over serial, not over the Bluetooth or USB HID mode.

Joy-Con status data packet

In normal operation the console asks Joy-Con for an update every 15ms (66.6fps), the command for requesting update is:

19 01 03 08 00 92 00 01 00 00 69 2d 1f

Around 4ms later, Joy-Con respond with a 61 bytes long answer.

One sample:

19 81 03 38 
00 92 00 31 
00 00 e9 2e 
30 7f 40 00 
00 00 65 f7 
81 00 00 00 
c0 23 01 e2 
ff 3e 10 0a 
00 d6 ff d0 
ff 23 01 e1 
ff 37 10 0a 
00 d6 ff cf 
ff 29 01 dd 
ff 34 10 0a 
00 d7 ff ce 
ff 

Here is what I figured out:

Byte #Sample valueRemarks
0 to 819 81 03 38 00 92 00 31Header, fixed
16 and 1700 02Button status, see section below
19f7Joystick X value, reversed nibble?
2081Joystick Y value
31 and 324e 05Gyroscope X value
33 and 34cc fbGyroscope Y value
35 and 36eb ffGyroscope Z value
37 and 3841 00Accelerometer X Value
39 and 401b 03Accelerometer Y Value
41 and 4282 f0Accelerometer Z Value

Each accelerometer and gyroscope axis data is 2 bytes long and forms a int16_t, last byte is the higher byte.

Button status

The 16th and 17th byte (on line 5, before 65 f7) are the button status, when a button is pressed the corresponding bit is set to 1.

Alt text

Joystick value

Byte 19 and 20 (f7 81 between 5th and 6th line) are the Joystick values, most likely the raw 8-bit ADC data. Byte 19 is X while byte 20 is Y. Again, bizarrely, the X nibble is reversed, as in the f7 should actually be 7f (127 at neutral position). The Y value is correct though (0x81 is 129).

Joy-Con status data packet

See the bluetooth_hid_subcommands_notes.md file for details about the data transferred during normal operations (button status, joysticks, etc).

Rumble commands

Details on rumble data are in the rumble_data_table.md file.

Touchscreen controller

Alt text

The console itself uses a FT9CJ capacitive touchscreen controller. And according to techinsights it's a custom part by STMicroelectronics for the Nintendo Switch. After looking at the communication it appears to use I2C, which is in line with other touchscreen controller chips. Here is a capture of the I2C bus on power-up.

The 7-bit I2C address of the chip is 0x49 (0x92 write, 0x93 read), and it's polled around every 4ms for update.

Docking station firmware dump

The docking station uses a STM32F048 microcontroller. It's actually labeled as STM32P048 because it uses the FASTROM option where ST pre-programs the flash memory inside the factory. It has 32KB flash memory and 6KB RAM, runs at 48MHz.

It uses SWD debugging and programming interface, and interestingly the programming testpoints are on the PCB and clearly labeled. After connecting a ST-Link programmer to it reveals that the chip is not read-protected at all, so a firmware dump was easily made. I'm not going to post it in the repo, but if you want it just ask.

Ending remarks

I'll update this from time to time when I have new discoveries. Please share if you find this useful.