Earlier this month, I modified my right side Shimano Di2 shifter and Zwift Click v2 remote controls to use with Zwift on my Tacx Neo 2T smart bike trainer.
There are other online smart training apps which I sometimes use including Rouvy and MyWhoosh which now support virtual shifting via keyboard controls.
As I had not used the left Shimano Di2 shifter I decided to modify this to act as a Bluetooth keyboard and map the buttons to the keyboard commands required for each application.
My original plan was to have this completely self-contained within the shifter body using a small button cell to power the circuit, but after disassembling the Shimano Di2 shifter, I found the space available was too small for the Bluetooth module and a battery holder. Software issues with power management also ruled out this idea.
Please note, these modifications will remove all original functionality from your Shimano Di2 shifters.
Seeed Studio XIAO nRF52840 module
After looking at various microcontroller options, I decided to use the Seeed Studio XIAO nRF52840 module, which has built-in Bluetooth Low Energy (BLE) support and is small enough to fit inside the Di2 shifter body.
Features:
- Bluetooth 5.0 with onboard antenna
- Nordic nRF52840, ARM® Cortex®-M4 32-bit processor with FPU, 64 MHz
- Ultra-Low Power: Standby power consumption is less than 5μA
- Battery charging chip: Supports lithium battery charge and discharge management
- Onboard 2 MB flash
- Ultra Small Size: 21 x 17.8mm
- 11 digital I/O
- Single-sided components, surface mounting design

Parts Used:
| Part | Price Each | Link |
|---|---|---|
| Shimano Di2 road shifter | ||
| Seeed XIAO nRF52840 | £9.50 | The Pi Hut |
| Heat Shrink Tubing to cover the wire connections and cable join inside the shifter. A suitable kit is available from Amazon 560 Pcs Heat Shrink Tubing Kit | £6.43 | Amazon |
| USB power supply | ||
| Thin multicore core cable such as alarm cable | ||
| Small Cable Tie | ||
| USB C to USB A cable 100cm |
Tools required
- Soldering Iron and solder.
- Wire cutters.
- An electric drill and 4mm drill bit.
Accessing the Switches in the Shimano Di2 Shifter
These modifications are easier to do with the shifter removed from the handlebars. This is based on the Shimano Ultegra ST-6870 Di2 model shifters, but other models should be similar to modify.
In my first Shimano Di2 shifter mod, I removed the switch body and lever from the shifter. In this version, I decided to leave these in place and cut the wire where it enters the Di2 module.
First, remove the rubber shifter hood by pulling it forward over the brake and gear lever. These can be very tight, so gently warming them with a hair dryer can make it easier to remove.
Modifying the Shifter Switches and extending the cable
The Di2 controller box needs to be removed from the cable using wire cutters unclip the Di2 controller and cut the cable where it enters the controller box. This cable needs to be extended by approx 8cm to solder to the Bluetooth module.
I pulled the trimmed cable back through the shifter body to allow more room to work on the cable.
Carefully remove the outer insulation on the switch wire which reveals three wires and a strainer cord.
On my Di2 switch, the wires are connected as follows:
- Grey - Common
- Purple - Rear button
- Yellow - Front Button
Using thin multicore wire, cut three pieces approximately 8cm long and solder these to the existing switch wires. I used some spare alarm cable which had cores with matching colours, but any thin multicore cable will be suitable.
Slide the smaller heat shrink over the solder joints and gently heat to shrink to fit. Then slide the outer cover heat shrink over the joints and shrink to fit to protect everything.
The wires can now be routed back through the shifter body.
Shifter body modifications
The Di2 shifter body has three slots designed to support the Di2 cables; these need to be removed to allow the USB connector and cable to be installed flush with the shifter cover.
Using a small file, carefully remove the cable guides so the USB connector can be fitted.
Using a 4mm drill, create two holes as shown in the photo to allow the installation of a cable tie to secure the USB connector.
Connecting the XIAO nRF52840 module
The three wires need to be soldered to the pads on the XIAO nRF52840 module. Solder the wires as follows:
- Grey - GND
- Purple - D1
- Yellow - D0
Using a thin cable tie, feed the tie through one of the holes you drilled into the shifter body and back through the second hole.
Connect the USB cable to the XIAO nRF52840 module and carefully install it into the space where the Di2 module was installed. Using the cable tie, secure the connector so it is central in the space.
The shifter modifications are now complete and the Bluetooth module is ready to be programmed.
Programming the XIAO nRF52840 module
There are various applications available for programming the XIAO nRF52840 module. I initially tried to use the nRF Connect SDK from Nordic Semiconductor , but had numerous issues with compiling the code and connecting to the module.
I then decided to use the Arduino IDE and Adafruit libraries for the board, as I am familiar with the IDE and its ease of use.
To install and setup the software required to program the XIAO nRF52840 module, follow the instructions on wiki.seeedstudio.com/XIAO_BLE/ to install the Arduino IDE and install the necessary Arduino Libraries.
You also need to install Python to use the XIAO nRF52840, see this forum post for more information. Download and install from python.org/downloads/.
Code
The software is based on the Adafruit blehid_keyboard example and modified to include button inputs using interrupts and additional code to debounce the button presses.
Open the Arduino IDE, select the Seeed XIAO nRF52840 board and Com port and start a new Sketch.
First we add the bluefruit include and define the Bluetooth objects.
#include <bluefruit.h> // Define bluetooth objects BLEDis bledis; BLEHidAdafruit blehid;
Create buttons assigned to inputs D0 and D1.
// Buttons const uint8_t button1Pin = D1; const uint8_t button2Pin = D0;
Define button key press character.
// MyWhoosh Settings // K = Shift Up // I = Shift Down char button1Char = 'K'; char button2Char = 'I'; // Rouvy Settings // + = Shift Up // - = Shift Down //char button1Char = '+'; //char button2Char = '-';
Add variables and functions for debounce code.
// ISR flags
volatile bool button1Flag = false;
volatile bool button2Flag = false;
// Button pressed state for state-machine debounce
bool button1Pressed = false;
bool button2Pressed = false;
// Time from last button press
volatile uint64_t lastPressTime = 0;
const uint64_t minimumDelay = 100;
// ----------------------------
// Minimal ISR — set flags to true
// ----------------------------
void button1ISR() { button1Flag = true; }
void button2ISR() { button2Flag = true; }
Track if BLE has been started for this power cycle.
bool bleStarted = false;
Function to start BLE advertising & HID
void startBLE()
{
if(bleStarted) return;
bleStarted = true;
Bluefruit.begin();
Bluefruit.setTxPower(4);
Bluefruit.setName("Di2Keyboard");
bledis.setManufacturer("Bluetooth Di2");
bledis.setModel("Di2 Keyboard");
Bluefruit.autoConnLed(true);
// Start services
bledis.begin();
blehid.begin();
// Advertising
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD);
Bluefruit.Advertising.addService(blehid);
Bluefruit.Advertising.addName();
// Automatic reconnect
Bluefruit.Advertising.restartOnDisconnect(true);
// Advertising interval ~100–200ms
Bluefruit.Advertising.setInterval(160, 320);
Bluefruit.Advertising.start(0);
}
Function to send the keycommand via Bluetooth.
void sendKey(char key)
{
if (!Bluefruit.connected()) return;
blehid.keyPress(key);
delay(10);
blehid.keyRelease();
}
Setup function to assign pins and add interupts.
void setup()
{
pinMode(button1Pin, INPUT_PULLUP);
pinMode(button2Pin, INPUT_PULLUP);
// Attach interrupts on falling edge
attachInterrupt(button1Pin, button1ISR, FALLING);
attachInterrupt(button2Pin, button2ISR, FALLING);
startBLE();
}
Main loop where we check if Bluetooth is connected and handle button presses.
void loop()
{
if(!Bluefruit.connected())
{
// Start BLE if not connected
if((button1Flag || button2Flag) && !bleStarted)
{
startBLE();
}
}
// --------------------------
// Handle button 1 press (state-machine debounce)
// --------------------------
if(button1Flag) {
button1Flag = false; // clear ISR flag
if(!button1Pressed && digitalRead(button1Pin) == LOW) {
if (millis() - lastPressTime > minimumDelay){
sendKey(button1Char);
button1Pressed = true; // mark as pressed
lastPressTime = millis();
}
}
}
// --------------------------
// Handle button 2 press (state-machine debounce)
// --------------------------
if(button2Flag) {
button2Flag = false;
if(!button2Pressed && digitalRead(button2Pin) == LOW) {
if (millis() - lastPressTime > minimumDelay){
sendKey(button2Char);
button2Pressed = true;
lastPressTime = millis();
}
}
}
// --------------------------
// Reset pressed state when button released
// --------------------------
if(button1Pressed && digitalRead(button1Pin) == HIGH) button1Pressed = false;
if(button2Pressed && digitalRead(button2Pin) == HIGH) button2Pressed = false;
}
You can use the Arduino IDE to program the module via the USB cable.
The full code for this project can be downloaded from GitHub at github.com/briandorey/xiao-nrf52840-to-shimano-di2-keyboard-controller.
The Di2BluetoothKeyboard folder contains the code in Di2BluetoothKeyboard.ino and needs to be compiled and programmed to the XIAO nRF52840 module to use it as a Bluetooth keyboard.
To configure the controller to use Rouvy, remove the "//" from the start of lines 25 and 26 and add "//" to lines 19 and 20.
To configure the controller to use MyWhoosh, remove the "//" from the start of lines 19 and 20 and add "//" to lines 25 and 26.
Future upgrades could include adding a button to switch between keymaps for different applications.
Paring the controller
Power on the XIAO nRF52840 module by connecting it to a USB power source. The module will enter pairing mode for 60 seconds, during which time you can pair it with your computer or tablet.
On your computer or tablet, search for Bluetooth devices and select "Di2Keyboard" from the list of available devices. Once paired, the controller will be ready to use.
You can check the keyboard commands are being sent by opening Notepad or a text editor and pressing the shifter buttons which should send the keyboard presses to the computer.
Installation onto the bike
Remove any bar tape on the side of the handlebar where the shifter is to be installed and mount the Di2 shifter onto your handlebar.
The wire needs to be routed under the bar tape to the middle of the handlebar, where it will be connected to USB power bank or other USB power supply.
Refit the shifter's rubber hood.
Using the controller
When using Rouvy or MyWhoosh, press the front button on the Di2 shifter to shift up and the rear button to shift down.
Enjoy your modified Shimano Di2 shifter with Bluetooth keyboard functionality!
Comments