Multiplayer Bluetooth controllers adapter for retro video game consoles

Public Chat
Similar projects worth following
BlueRetro is a multiplayer Bluetooth controllers adapter for various retro game consoles. Lost or broken controllers? Reproduction too expensive? Need those rare and obscure accessories? Just use the Bluetooth devices you already got! The project is open source hardware & software under the CERN-OHL-P-2.0 & Apache-2.0 licenses respectively. It's built for the popular ESP32 chip. All processing for Bluetooth and HID input decoding is done on the first core which makes it easy for other projects to use the Bluetooth stack within their own project by using the 2nd core. Wii, Switch, PS3, PS4, PS5, Xbox One & generic HID Bluetooth devices are supported. Parallel 1P (NeoGeo, Supergun, JAMMA, etc), Parallel 2P (Atari 2600/7800, Master System), NES, Genesis, SNES, CD-i, 3DO, Saturn, PSX, PC-FX, JVS (Arcade), N64, Dreamcast, PS2 & GameCube are supported with simultaneous 4+ players using a single adapter. Soon PCE / TG16...

Project documentation

General documentation


There are various ways available to enjoy retro gaming. Those of us that prefer playing with the original systems face various challenges regarding the controllers. Controllers are the part that degrades the most and often with old console not all of your original controllers (if any) are in good working condition. If you have more than one console, it can quickly get expensive to buy NOS or replica controllers. Adapters for USB & Bluetooth exist on the market but are compatible with one or two systems only.

When it comes to 4+ retro multiplayer party, it’s hard to have enough working controller and it quickly becomes a cable mess if you end up playing more than one console. Most systems included only 2 ports and 3+ players games required a multitap accessories that are now often hard to find and expensive. Existing adapters solutions are all limited for single player use, one need to buy multiple adapters for each player.

Some early games got really awkward control style by today's standard and it is often not even possible to edit the buttons mapping. Even when possible, it's often too much trouble doing so while your friends are waiting to play. Controllers & adapters including configurability are not simple enough to be used in a party.

Mouse, trackball & keyboards are often rare and expensive to get accessories for most systems. And let not forget that some controller design got questionable ergonomic! Very few, if any, adaptive option existed back in the day for people with limited mobility.


Across all current and last generation console and PC accessories, we already own a large quantity of Bluetooth HID devices. BlueRetro is a Bluetooth controller adapter for retro video game consoles that enable to use all those devices. Any Bluetooth HID (BR/EDR/BLE) devices can be connected and used including gamepad, mouse, keyboard, trackball, etc. This includes the Xbox adaptive controller.

The BlueRetro core can be used with every retro gaming system supported. This has the benefits of not requiring to repair your Bluetooth controllers between different system uses. No reconfiguration is required since the adapter auto detect the system it is plug on. Only the cable adapter need switched between system.

Up to 7 controllers can be connected simultaneously on a single adapter for multiplayer games. This means the adapter also emulate the multitap accessory for most systems. Rumble accessories/function is supported. Keyboard, trackball & mouse console accessories are supported too.

The adapter is highly configurable via a Web Bluetooth (BLE) interface for mobile & desktop. Buttons and axes can be mapped to any buttons or axis direction (Ex. mapping axis direction to a button, buttons on an axis direction, inverting an axis direction, trigger to axis, buttons to trigger, etc.). Various scaling and response curve options is available for axes and trigger. Presets configuration for various games are available. Using presets, you can change every player mapping at once with a few clicks on your mobile phone.

A secondary objective is for the code to be easily used on other ESP32 project to add Bluetooth HID device input.

To help this the Bluetooth and HID decoding is all done within...

Read more »

View all 9 components

  • CD-i interface

    Jacques Gagnon6 days ago 0 comments

    I added CD-i support with version v0.15! Regular pad, mouse & keyboards (K, X & T type) are supported. Any mix of devices is supported for up to 2 players! Refer to wiki for cable schematic and config documentation.

    The low level protocol is UART based. It is well documented in various specifications but the most up-to-date one is contained in Chapter 9. Input devices of the Technical Documentation for CDI 605 / 605T Users that can be found on ICDIA.

    CD-i pinout for dual interface front port

    The spec defines RTS and RXD pin as standard RS-232 levels: 

    - logical 1: -15V < signal level < +0.8V
    - logical 0: +2.4V < signal level < + 15V

    But from what I saw in various schematics the logical 1 level is always 0V and the logical 0 is 5V. So when interfacing with a CD-i interface level shifters are enough (no need for MAX232) as long the UART is configured to be inverted.

    Pointing devices like gamepad and mouse defined speed of 1200 & 9600 baud in the original spec but only 1200 devices ever got released. Newer CD-i players dropped 9600 support on the front ports and the latest spec actually removed 9600 as a possible baud rate for mouse and gamepad.

    For mouse, gamepad & type 'T' keyboard data signaling is 7 bits with 2 stop bits LSB first. For type 'K' & 'X' keyboards signaling is 8 bits with 1 stop bit LSB first.

    Unlike most other game system the CD-i inputs are not polled. As long the RTS line is released devices are free to send data when state change.

    Devices are identified once at boot time by holding the RTS line low. Once RTS line is released devices are required to send their 1 byte identification followed by their initial status data.

    T is also used for 3rd keyboard type.

    This was a problem for BlueRetro as devices per CD-i spec are required to answer the ID request within 500 ms. However BlueRetro take around 700 ms to read its configuration at boot. To work around this BlueRetro need to be powered externally and powered before powering the CD-i. This give the extra time required to load the config and to be ready to answer the ID request.

    IDs request at boot for gamepad and 'K' keyboard.
    IDs request at boot for mouse and 'T' keyboard.

    Controller & Mouse

    Status update sent on buttons press and release.
    On axes movements update are continuously sent if value is not neutral (0).

    'K' & 'X' type keyboards

    Key A pressed and released
    Key A pressed and released while holding shift key
    Caps Lock press followed by Key A press and release

    'T' type keyboard

    Later CD-i model like my CD-i 450 only had a single physical front port including 2 serial port. Only the secondary port was compatible with 8 bits UART configuration that keyboards (K & X) and modem required. This posed a problem for CD online application as this made impossible to use the previous keyboard simultaneously with the modem. To answer this issue Philips defined the 'T' keyboard spec that piggy back on the tablet 'T' ID and redefine the data format to include the same data as the 'K' type keyboard.

  • PC-FX interface

    Jacques Gagnon04/02/2021 at 20:52 0 comments

    I added PC-FX support with version v0.14! Regular pad and mouse are supported. Any mix of devices is supported for up to 2 players! Refer to wiki for cable schematic and config documentation.

    The low level protocol is pretty much like SPI mode 0 for clock and data line. You got the /LATCH line that can be inverted to be use as a CS. An /OE signal can mute the output from the peripheral when high. The console poll the peripherals 5 times in a row every frame. This look like how PC Engine controllers are polled as well for the multitap. Maybe a multitap was plan for the PC-FX as well?

    Sequential 2P polling with 5 consecutive poll each

    Data line is inverted and LSB is sent first. Clock may sometimes cycle while LATCH is held low, these cycle must be ignored.

    Controller poll

    The two controllers are often polled simultaneously.

    Simultaneous 2P polling

    Getting the ESP32 SPI timing was a bit tricky, In my mind this should be SPI Mode 2 but somehow that was very unreliable. Using Mode 0 timing is rock solid however.

    Regular controller

    RX: FFFFFF0F (LSB first)
        ││││  ├┘
        ││││  └ ID?
        │││└ Left, Down, Right, Up
        ││└ 1, Mode2, 1, Mode1
        │└ IV, III, II, I
        └ Run, Select, VI, V


    RX: FFFFFF2F (LSB first)
        ├┘├┘ │├┘
        │ │  │└ ID?
        │ │  └ Buttons (1, 1, Right, Left)
        │ └ X axis (8 bits) (Left: -, Right: +, Two's complement, inverted, LSB first)
        └ Y axis (8 bits) (Up: -, Down: +, Two's complement, inverted, LSB first)

  • 3DO interface

    Jacques Gagnon03/29/2021 at 00:45 0 comments

    I just added 3DO support with version v0.13! Regular pad, flightstick and mouse are supported. Any mix of devices is supported for up to 8 players! Refer to wiki for cable schematic and config documentation.

    The 3DO interface is quite unique as 8 players are supported via a single port! 3DO peripherals for the most part include an input port on them allowing to daisy-chain controller back to back. No extra port on console or multitap are required!

    The low level protocol is pretty much like SPI mode 3. You got the CLK, a data output and a data input. Base on my test the console data output is not used by the controller. The big difference with SPI is that there is no chip select signal. Frame start is signaled by holding the CLK line high for 500 us.

    Data from daisy-chained peripherals are simply appended to the transmission back to back, first controller transmitted first. 3DO BIOS and some games will always query for 200 bytes of data. Some games only query what they need. Extra read data is always 0xFF.

    This is one of the easiest systems to implement in BlueRetro. The only challenge was to generate a chip select signal for the ESP32 SPI hardware. The ESP32 SPI slave hardware is unfortunately not very flexible. I loop back my generated CS GPIO output into the ESP32 SPI CS input.

    See generated CS signal in yellow base on CLK signal Start

    Regular controller

    RX: 8000
        │││└ R, L , 0, 0
        ││└ B, C, P, X
        │└ Up, Right, Left, A
        └ 8, 0, 0, Down


            ┌ Y axis (10 bits) (Up: -, Down: +, Two's complement)
    RX: 49000000
        ├┘│  └┬┘
        │ │   └ X axis (10 bits) (Left: -, Right: +, Two's complement)
        │ └ Buttons (Left, Middle, Right, 0)
        └ ID


    I didn't RE this one my self, base on:

               ┌ X axis (10 bits) (Left: -, Right: +, Two's complement)
    RX: 017B08802008020000
        └┬───┘  └┬┘└┬┘│││
         └ IDs?  │  │ ││└ P, X, L, R
                 │  │ │└ Up, Down, Right, Left
                 │  │ └ Trigger, A, B, C
                 │  └ Z axis (10 bits) (Two's complement)
                 └ Y axis (10 bits) (Up: -, Down: +, Two's complement)

  • 2021-03-12 Update

    Jacques Gagnon03/12/2021 at 18:08 0 comments

    Quick updates :)

    Just released v0.11.1 to fix a regression from v11 where pull-up on RTC shared pins didn't work anymore. This might give you a lot of problems if you have unused pins left float on your ESP32 module.

    Also, v0.12 [edit now out!] will come soon with SNES mouse and Famicom Hori trackball support.

    Then finally v1.0 will come probably in early April with N64 memory card support!!

    I'm talking about this for months but the HW Beta is really happening soon! I fully expect to be ready shipping around ~20 DevKit boards and cables in early May 2021. I got plenty available for the beta so subscribe here:

    If you rather build your own around an ESP-DevkitC WROOM module check these instructions.

    I also started to experiment with some concept for BlueRetro v2 (slim version) that I fully intend to launch a crowd funding camping for, probably this fall:

  • ESP32 RTOS + Bare Metal: Best of Both Worlds?

    Jacques Gagnon02/27/2021 at 13:51 0 comments

    Ever since I finished working on the latency tests & improvement, I've been working on trying to free up the 2nd core from its FreeRTOS duty by running it bare metal as originally demonstrated by @Daniel  with #Bare metal second core on ESP32. I highly recommend reading the project logs for more detail. I will focus on describing how to refactor a complex application to use this hack in this log.


    My original goal was to free myself from a workaround I have been using since the beginning of the project for bit-banged interface (Dreamcast, NES/SNES and Genesis). The issue with Big-Banging with an ESP32 running meaningful code on both core (this is an important nuance!) like the Bluetooth controller task on Core0 and wired interface on Core1 is that the wired task will get interrupted either by the FreeRTOS tick interrupt on its own core or by some event that requires core cooperation like Flash or DPORT access or something else on the first core. To be honest I haven't made an in-depth analysis of the source of interruption but they are either cause by interrupt on Core1 or some event on Core0. 

    Original Issue

    I can't use easily any of the ESP32 peripheral for NES/SNES and Genesis since the games themselves are bit-banging the protocol. So the way controllers are polled vary greatly from game to game and that would probably make using the I2S peripheral hard. Also the high amount of output lines for Genesis overall and for NES/SNES multitaps make it impossible to use a single SPI slave and we only got 2 on the ESP32. I can see how I could implement Dreamcast's maple protocol using 2 SPI peripheral but again to support 4 players there is not enough SPI hardware available.

    Some examples of what happens when we get interrupted while bit-banging:

    Two edges of the Genesis select signal are miss and the output is not updated.
    Two edges of the SNES clock is missed that would result in buttons output being shifted to wrong cycle
    Multiples edges of the Dreamcast input is loss result in corrupted packet receive.

    If you search a bit online on this subject people will often recommend having a task on Core1 doing a loop without any yield but disabling interrupt on that Core and disabling the Core1 idle task watchdog. This only work if Core0 is not doing anything significant. If you are running the Bluetooth controller task, you will quickly get watchdog timeout on Core0!!

    So my original fix was to use the DPORT access locking functions (esp_dport_access_stall_other_cpu_start / end)  which does two things. First it disables interrupts on the current core by entering into a critical section and second it generates an interrupt on Core0 than essentially make the Core0 loop doing nothing in a high level interrupt (level 4) until we release the DPORT access lock. The function exists to work around a silicon bug for pre-V3 ESP32 but here I use it only for its locking property we don't really care about DPORT access. But the problem with the DPORT locking is that it is sometimes too long to get the lock. The critical section depends on a mutex and the locking function wait for the Core0 to confirm it's "stall".

    While this work around work pretty good for the maple bus, it not 100% perfect for the genesis drivers. I got fewer glitches but I still got some which is unacceptable when playing a game.

    My hope was that removing FreeRTOS from the 2nd core would remove the need to stall the first core. It didn't exactly turn that way...

    Updating Example to Latest ESP-IDF

    The original code from GitHub was base on ESP-IDF v4.0.2. A lot of rework happened since then in the master branch to add support for S2, S3 and C3 chips. Beside the things that moved around one of the problems that prevented it from working is that some initialization code for region protection that used to be in-lined was now a function located on flash. Using the older inline version fixed that issue since anything running on bare metal Core1...

    Read more »

  • 2021-02-27 Update - Release v0.11

    Jacques Gagnon02/25/2021 at 12:34 0 comments

    New release v0.11 is now available!

    The major change in this release is that I reworked the whole adapter so that the second core does not run FreeRTOS anymore. So it's essentially bare metal. That was a lot of work and I will make a separate log about it soon!

    The second big change is that I added Kconfig support and in conjunction with the GitHub action CI I can now offer multiple builds! Two types of build are now available: the SD card version as before (now named BlueRetro_universal_sd.bin) and also internal flash (SPIFFS). For each type I also provide the regular universal version with system auto detection. But in addition system hard-coded versions are available. A total of 24 variants are available.

    That should be very helpful for people doing DIY version that misses the SD card slot or are hardwired to a specific system. (You don't need to set pin I39 or ground the auto detect pins with the hard-coded build.)


    • Output buffer init overruns
    • Fix PS5 Dual Sense L & R trigger indexes
    • Remove left over auto parallel init
    • Fixup player 2 SEGA mouse init for Genesis
    • Update outputs only if a mapping exists

  • 2021-01-28 Update

    Jacques Gagnon01/28/2021 at 13:11 0 comments

    Already a month in the new year!

    No new release yet but I figured I'll make a quick update since I feel I still got some weeks of work ahead before anything new.

    One of the biggest challenge doing BlueRetro is systems protocols that requires to use software bit-bang. In a chip as complex as the ESP32 running FreeRTOS on two cores you don't control what the CPUs are doing as much as with a PIC or an AVR micro. If you loose the CPU while TX or RX bit bang your data is now corrupted.

    So in this context its always better to try to use one of the HW peripheral and abuse them to talk a non-standard protocols. For example the N64 & GC driver use the RMT hardware (for IR remote) to implement Nintendo's 1-wire protocols in a very robust way. But its not always possible to do so, so its important to be able to bit-bang reliably.

    I've been experimenting for the better part of the month on trying to restrict FreeRTOS on a single core and run the 2nd one bare metal base on the work of @Daniel with #Bare metal second core on ESP32 . Results are promising so far, I made a small demo here:

    The big challenge in doing so is that the core can't touch the flash anymore and all instructions and data need to be in RAM. I got the PSX driver working which is one of the more complex one with its 3 interrupts. I still got a lot of rework to do to get everything working with that design but it's really worth it as it will end my fight with the GPIOs and make the bit-bang interfaces 100% reliable.

  • 2020-12-26 Update - Latency tests & Release v0.10

    Jacques Gagnon12/26/2020 at 21:56 0 comments

    New release v0.10 is now available!


    • Fixup PSX NHL2000 multitap support
    • Add Saturn Keyboard support
    • Add PSX Lightspan Keyboard support
    • Add SEGA Mouse support for Genesis & Saturn
    • Add PSX SCPH-1110 Analog Joystick support


    • Add debug for latency tests
    • Reduce latency for WiiU Pro, PS3 & PS4 controllers


    • Update to latest ESP-IDF master

    Latency tests

    I finally got around doing the latency tests. It's a bit of a rabbit hole tbh but I think I managed to keep it simple while getting meaningful numbers. I ditched my original idea of running the test by measuring the delay between button input on the controller and the RGB line black to white transition. It's too much work for nothing. Once the buttons data is within the shared memory the adapter is simply waiting for the console to query for it. At that point any significant delay would make the transmission fail. The fact it work well as show by doing protocol traces tell us their is no significant latency pass that point.

    So I settle to simply running the adapter in the 1P parallel mode and hooking up GPIO26 of BlueRetro's ESP32 to an Arduino running a simple sketch using a pin to toggle a controller button and measuring the delay between that and the input pin (Same thing used by MiSTer tests) . I also configured the adapter using a special preset that maps all buttons to the pin GPIO26. I hacked a few controllers to expose one of the buttons on a 3.5mm TRS connector.

    I published the full results here:

    There are two measurements of interest here. First there is the HID report interval which is very helpful to figure out if the Bluetooth connection is good. It's very easy to get the same numbers from a wireless trace to help compare between various systems. I made a simple python script using Scapy to be able to get the stats.

    The second is the latency between the input on the controller and the output from BlueRetro. PS4 & PS5 controllers are the winners using BlueRetro with around ~5.4 ms of latency.

    1. PS4/PS5: ~5.4 ms
    2. XB1: ~8.8 ms
    3. Switch Pro: ~12.4 ms
    4. PS3: ~12.8
    5. WiiU Pro: ~13.2 ms
    6. 8bitdo: ~16.2 ms
    7. Wiimote: ~16.3 ms

    At first I had the PS4 controller configure for 4 ms  report interval, while this was good with Linux, it didn't behave as well with the ESP32. Somehow using 0 ms config yield much better consistency in the report interval and obviously better latency. Both PS3 & WiiU controller had very bad consistency in their report interval. Base on this retropie thread I tried to set those controller as master of the connection and this improved the latency a lot! I guess the ESP32 clocking of the baseband don't play too nice with those controllers somehow.

  • 2020-12-08 Update - PSX Lightspan Keyboard & Saturn Keyboard support

    Jacques Gagnon12/08/2020 at 13:39 0 comments

    I added support for both the PSX Lightspan Keyboard & Saturn Keyboard in BlueRetro. See pre-release v0.9.1.

    N64, DC & GC keyboard only send the raw scan code and let the console software determine the key press and release. Saturn and PSX keyboard are base on PS/2 keyboard protocol running on a different low level interface. PS/2 keyboard send Make and Break scan code to tell explicitly to the console if a key was pressed or released.

    More detail here:

    This page document well how the Saturn keyboard work:

    The Saturn keyboard mostly use the standard AT Keyboard Scan Codes (Set 2) but some common 2 bytes scan code are reduced to to only 1 byte using non standard code.

    The PSX keyboard is only supported by the Lightspan Online Connection CD that was dump only a few year ago. It is not yet known if this use the PS/2 Keyboard/Mouse adapter prototype (SCPH-2000) or if it's a standalone keyboard.

    In any case base on some information extracted from the Lightspan software by nocash @PSXDEV forum, the ID & size byte expected is 0x96 which tell use the frame is 12 bytes. I was able to figure out how to emulate basic keyboard functionality. The first byte sent after the 0x5A header is the size of the scan code and the scan code follow in network byte order. I could validate scan code up to 3 bytes. It doesn't look to be possible send multiple scan code at once.

    Also the Lightspan softwate probably don't support the longer scan code like Print Screen and Pause. If this is base on the SCPH-2000 adapter the rest of the bytes are probably for the mouse. Scan code reduced to one byte like the Saturn 0x8x ones are also supported as well as the two bytes regular variant.

    I didn't observe any data being sent to the keyboard in my traces.

    It would be very easy for someone who own a SCPH-2000 PS/2 adapter to make a dump of the protocol using a special BlueRetro passthrough PSX cable running my SPI Full-Duplex sniffer firmware to make a trace like this one.

  • 2020-11-30 Update - Release v0.9 PSX & PS2 support

    Jacques Gagnon11/30/2020 at 01:25 0 comments

    Release v0.9 finally add support for PSX & PS2 controller emulation!! In addition mouse & keyboard support is added for PSX, N64, GC & DC.

    See release v0.9 on GitHub!


    • PS5 Dual Sense Rumble & LED support


    • PS2 DualShock 2 support
    • PSX Multitap support
    • PSX Mouse support
    • N64 Mouse support
    • N64 Randnet Keyboard support
    • Dreamcast Mouse support
    • Dreamcast Keyboard support
    • GameCube ASCII/Sammy Keyboard support
    • Disable Auto parallel for now as it interfere with auto detect

    Consult the following documentation for building cables:

    Consult the following documentation each system usage & web-config specifics:

View all 48 project logs

View all 3 instructions

Enjoy this project?



BBsan wrote 09/05/2020 at 07:22 point


This is a really great project!!

I‘m trying to build some kind of a „lite“ Version using a Devkit v4 without SD card for use only with my Gamecube.  Unfortunately I wasn‘t able to find any documentation about how to pair my PS3 Controller... Could you briefly explain it somewhere? Thanks!!!😊 

  Are you sure? yes | no

Jacques Gagnon wrote 09/07/2020 at 15:54 point

Sorry missed your message, at boot on the serial console the log will display your ESP32 BDADDR (MAC). Search for line "local_bdaddr". Then connect the PS3 ctrl to a PC via USB and use a tool call "sixaxis pair tool" to set the ESP32 Address. Drop by the  chat ( if you need more help! Thanks for giving BlueRetro a try!!

  Are you sure? yes | no

BBsan wrote 09/07/2020 at 17:28 point

Thanks a lot! This worked really well 😊 I just have two problems now with my SIXAXIS:

1. Once it starts vibrating it doesn't stop anymore.

2. R2 doesn't seems the be mapped on GC output as it seems.

But wow - what a nice project!!! 😊😊 

  Are you sure? yes | no

Jacques Gagnon wrote 09/07/2020 at 16:48 point

  Are you sure? yes | no

Arnaldo Pirrone wrote 07/26/2020 at 10:18 point

Hi there,

Is the support for original Xbox planned? (maybe using the same original gamepads)

  Are you sure? yes | no

Jacques Gagnon wrote 07/26/2020 at 11:15 point

Yes but it's very far on my list, it's an USB interface so it will require a special adapter cable with some uC with a USB PHY.

  Are you sure? yes | no

PixJuan wrote 07/24/2020 at 10:23 point

I was wondering if you know of any 3d model of joystick connector, because it would hurt me to cut one from a 30 year old Joystick :-(

  Are you sure? yes | no

Jacques Gagnon wrote 07/24/2020 at 10:27 point

Just buy cord extension on AliExpress they are around 1 to 3$ each.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates