BLE HID to USB HID Bridge for Raspberry Pi Pico W.
This firmware connects to Bluetooth LE HID devices (mainly mice) and translates their input into USB HID, allowing them to be used with a USB switch between computers without re-pairing.
I built this to pair with the fantastic
Full Scroll Dial by Engineer Bo
so I could easily switch it between computers with a USB switch. The idea is
inspired by the excellent
hid-remapper, but without support
for actually remapping inputs and instead focusing on providing native-like
input feel and explicit support for a few tricky devices. It's since evolved to
help bridge OS support gaps by emulating supported devices on OSes that wouldn't
otherwise support them, like the Full Scroll Dial on macOS, or the Magic
Trackpad on Windows.
Notably, it fully supports the high resolution scrolling events emitted by the Full Scroll Dial and translates them to USB HID with no noticeable loss of scrolling precision versus using the dial natively via Bluetooth. Additionally, the reporting rate (both for input via Bluetooth and output via USB HID) is enough to ensure fairly minimal added latency compared to connecting directly to the target system via Bluetooth.
Moreover, it also supports high-resolution scrolling on macOS without any
software running on the host. macOS traditionally does not support
high-resolution scrolling for third-party input devices, accepting only
line-based scrolling events to which it applies an aggressive acceleration
curve. bt2usb works around this limitation by emulating an Apple Magic
Trackpad 2 and synthesizing touch scroll events. This allows accurate
pixel-level scrolling without any custom driver, and since bt2usb can
detect when connected to macOS hosts, will automatically switch between
standard HID and Magic Trackpad emulation, even on a USB switch.
- High reporting rate for native-like input performance
- Explicit support for high-res scrolling devices, particularly the Full Scroll Dial
- macOS support for high-res (smooth, pixel-level) scrolling via Magic Trackpad 2 emulation
- Supports up to 3 concurrently connected devices
Input devices will be mapped automatically depending on their profile and detected host OS, for reference:
| BT Input device | Windows & Linux output | macOS output |
|---|---|---|
| Full Scroll Dial | Generic mouse with high-res scroll | Emulated Magic Trackpad 2 |
| Generic mouse | Generic mouse | Generic mouse (movement, clicks) + emulated MT2 (scrolling) |
| Magic Trackpad 2* | Generic Precision Touchpad (PTP) | Magic Trackpad 2 (emulated passthrough) |
* Both Lightning and USB-C variants of the Magic Trackpad 2 are supported
I've tested a number of input devices successfully. The Generic (default)
profile should be sufficient for most standard Bluetooth HID mice.
Known to work:
- Full Scroll Dial
- Logitech MX Master 3
- Keychron M3 8K
- Magic Trackpad 2 (Lightning and USB-C)
Not currently supported:
- Keyboards
- Game controllers
We might consider supporting keyboards and game controllers in the future.
- A Raspberry Pi Pico W with the CYW43439 wireless chipset. Other RP2040 boards with different wireless chipsets will not work. The Pico 2W is currently not supported.
- A USB cable
No other hardware is required, aside from the BLE device you want to convert to USB.
For development and full debugging support you'll want a debug probe. Raspberry
Pi sells an official debug probe, but you can also build your own
using another Pico running the debugprobe
firmware.
- Keyboard inputs (Classic and BLE)
- Pico 2W builds
- Download the UF2 file from the releases
page or build your
own with:
just release - Hold the BOOTSEL button on the Pico W while plugging it into USB
- The Pico will appear as a USB mass storage device (drive letter like
RPI-RP2) - Drag and drop
bt2usb/bt2usb.uf2onto the drive - The Pico will automatically reboot and start running the firmware
If you have a debug probe connected:
just dev # Development build with RTT logging
just run release # Release buildOnce flashed, the Pico should expose both a USB HID input, as well as a HID interface for configuration. To configure it, you can use either the web interface or the CLI tool.
You can open the web UI locally in any Chromium-based browser by downloading web/index.html. Alternatively, you can browse to https://timothyb89.org/bt2usb/ for a publicly-hosted version.
- Select the "Connect" button at the top of the page to open the "BT2USB Bridge" device.
- Once connected, use the "Start Scan" button to search for pairable devices.
- When the desired device is found, the appropriate profile will be auto-selected (the Full Scroll Dial defaults to 16-bit mode). Select "Connect" to begin pairing.
It should connect to the device and begin forwarding input events.
Grab bt2usb-cli from the releases page and ensure it's in your $PATH, then:
# Ensure you can communicate with the device
$ bt2usb-cli status
# On Linux, you might need to install udev rules:
$ bt2usb-cli setup-rules
# Scan for pairable devices with interactive pairing
$ bt2usb-cli scan
# For BT Classic devices (e.g. Magic Trackpad 2), use --classic
$ bt2usb-cli scan --classic
# Connect to a device (if not done above)
$ bt2usb-cli connect <address>
# If needed, change the profile (e.g. Full Scroll Dial 16-bit mode, profile 3)
$ bt2usb-cli set-profile <address> <profile>
# Mark the device as active (enables auto connect)
$ bt2usb-cli set-auto-connect <address>
# See `help` for all commands
$ bt2usb-cli help
macOS does not support generic trackpads or devices that provide high resolution scrolling input. We work around that by emulating a Magic Trackpad 2 and synthesizing scroll events on macOS hosts, which is the only input device that macOS supports that can both provide high-res scroll events and do so over USB rather than Bluetooth.
To ensure we emulate the correct device for a given host system, on startup, the firmware first runs an OS probe, then reboots with the proper configuration for the detected host OS. This means you will notice a quick device reconnect whenever it's attached.
The OS probing process is designed to work properly with a USB switch, and will probe again whenever the USB connection is interrupted.
As a side effect of MT2 emulation, the emulated MT2 mouse device doesn't support traditional scrolling, only gestures. This means that any connected mice (that emit low-res scroll events) need to have their scroll events translated to synthesized touch events as well. We have two modes for this:
bt2usb-cli set-config smoothing 0(default) - smoothing is disabled, mouse wheel events result in effectively unsmoothed linear motion.bt2usb-cli set-config smoothing 1- smoothing is enabled, mouse wheel events are given mild acceleration and deceleration smoothing (with no acceleration), still approximately linear.
The Magic Trackpad 2 is a Bluetooth Classic device and needs to be scanned for and connected to separately from BLE devices.
# Scan and connect interactively
$ bt2usb-cli scan --classic
Once connected it will emulate an appropriate device for the detected host OS
(see bt2usb-cli status). That means:
-
On Windows: emulates a generic Precision Touchpad (PTP) with full gesture support
-
On macOS: emulates a Magic Trackpad 2.
It's technically not full passthrough as the USB and BT protocols aren't identical. All multi-finger gestures should work, and built-in force feedback for clicks works too. The hard-press force touch dictionary lookup doesn't work. Custom settings and third-party apps (like BetterTouchTool) aren't tested and probably won't work as expected.
This project uses just as a command runner. Install it with:
cargo install justThen run the setup command to download firmware and check for required tools:
just setup# Build for development with debug probe
just dev
# Build release UF2 for drag-and-drop flashing
just release
# Download CYW43 firmware files (run once, or after clean)
just download-firmware
# Flash with debug probe (probe-rs)
just run release- Install prerequisites:
rustup target add thumbv6m-none-eabi
cargo install elf2uf2-rs probe-rs- Download CYW43 firmware:
# Windows PowerShell
$fw_dir = "bt2usb/cyw43-firmware"
$base_url = "https://raw.githubusercontent.com/embassy-rs/embassy/main/cyw43-firmware"
New-Item -ItemType Directory -Force -Path $fw_dir
foreach ($file in @("43439A0.bin", "43439A0_btfw.bin", "43439A0_clm.bin")) {
Invoke-WebRequest -Uri "$base_url/$file" -OutFile "$fw_dir/$file"
}- Build and generate UF2:
cargo build --package bt2usb --release
elf2uf2-rs target/thumbv6m-none-eabi/release/bt2usb bt2usb/bt2usb.uf2The simplest way to view debug logs is to use the CLI:
$ bt2usb-cli logs
This streams bt2usb's defmt logs over the existing USB HID interface
and does not require a debug probe. However, it can only stream logs after the
interface has already been initialized and won't include logs from early in the
boot process.
If you need earlier logs, you'll need a debug probe (either the official Raspberry Pi debugging probe or one built using another Pico).
Logs will stream as soon as you flash the device. However, probe-rs will
disconnect after the device reboots during the init process after it finishes
probing the host OS.
The easiest way around that is to either reattach probe-rs, or use our builtin helper:
$ probe-rs attach --chip RP2040 ../target/thumbv6m-none-eabi/release/bt2usb
# or
$ cargo build --release --features=probe && cargo run -- logs --probe
The helper has a more aggressive retry/reconnect loop than probe-rs, and the
firmware is embedded directly in the CLI binary to decode defmt messages
without having to specify the binary path. (Note that you will need to rebuild
the CLI if you change logs in the firmware, as it needs an accurate reference
for defmt's interned strings.)
Alternatively, once the device is running, you can force a specific OS to assume for the next boot, and then trigger a software reset:
# Set an OS override
$ bt2usb-cli set-os <win/mac/linux>
# ...in another session using a CLI built with the `probe` feature
$ bt2usb-cli logs --probe
# Then restart the device
$ bt2usb-cli restart
🚨 AI SLOP WARNING 🚨
This was largely made to experiment with AI tooling, and the code was predominantly written by LLMs. The functionality has been reviewed but should be used with care. This isn't a particularly dangerous project and probably doesn't have the capability to break anything other than the Pico you flash it to, if that, but you've been warned.
MIT OR Apache-2.0