During our recent research, we experimented with different Bluetooth USB dongles. There are tons of options, and sometimes, it’s challenging to determine what chipset a dongle actually contains, what Bluetooth features it supports, and whether it works on Linux. Inspired by the recent ESP32 Bluetooth research, we wondered whether we could turn our Raspberry Pi Pico Ws into a functioning Bluetooth dongle. We had a few lying around, and the advantage here is that we know exactly which Bluetooth controller it uses – the Infineon CYW43439. It’s also very easy to get one. You can just buy the Pico W for a few bucks, even cheaper than some Bluetooth dongles. You also have a controller family that has been researched quite a bit in the internalblue project. However, there was one disadvantage. We did not find any code that exposes the CYW43439’s HCI interface via USB. So we had to write that on our own.
You can find the code here, if you don’t care about the background and just want to use your Pico W as a Bluetooth dongle.
A Little Bit of Background on HCI
But first, let’s quickly get into HCI. Those that are already familiar with HCI can skip to the next section.
HCI – the Host Controller Interface in Bluetooth is the communication interface between the Bluetooth Host and the Bluetooth Controller, i.e., between your operating system and the Bluetooth dongle, or built-in Bluetooth chip. This communication can use different transports. A common transport for USB Bluetooth dongles is USB HCI. Another very common transport is UART. Many embedded devices, and historically smartphones, use this transport.
The Bluetooth standard specifies these transports in their HCI chapter. Some operating systems support a number of these transports. So, a USB HCI or UART HCI device should work regardless of the chip or dongle vendor. At least on Linux. macOS doesn’t let you use UART-based HCI devices for their operating system Bluetooth stack, but USB HCI seems to be supported. Regardless of operating system support, this still allows you to use other Bluetooth tooling, such as btstack, Google’s Python-based Bumble, or internalblue. Btstack and Bumble allow you to run a separate Bluetooth stack, fully independent from your OS stack, with the external dongle. For research and development, this is great.
Things like this also exist as a finished product, e.g., the Ezurio BT851. But if you already have a Pico W lying around, this is the cheaper option. And in our opinion, it’s also more fun to build something!
Pico HCI UART
As with other systems, the Raspberry Pi Pico W uses HCI to communicate with the CYW43439 controller. So our idea was to expose this HCI interface via UART (using USB CDC, basically a virtual serial interface) using the Pico’s USB interface. We decided to implement HCI over UART instead of HCI USB because it’s easier to implement and was enough for our purposes (It also allows us to use it on an Android phone, but that’s a story for a later time… 😉). The Pico does not use UART to communicate with the Bluetooth controller but rather an SPI interface. As the CYW43439 is a combo chip that also does Wi-Fi, the SPI transport handles both, Bluetooth and Wi-Fi.
However, this is not a big issue. Regardless of the transport, the Pico and the controller still use HCI. We can build something like a translator between the SPI-based HCI and our desired UART HCI. This is made even more convenient by the two API functions cyw43_bluetooth_hci_read
and cyw43_bluetooth_hci_write
, which are part of the Pico SDK’s CYW driver.
Essentially, we have to read from our UART CDC device and send the data to the controller using the write function. On the other hand, we use the read function and write this data to the CDC interface. There is a bit of additional reassembly and protocol handling, but apart from this, it’s relatively simple.
Using Pico UART HCI
- Install the Pico SDK
- Build the project.
git clone https://github.com/auracast-research/pico-uart-hci.git
cd pico-uart-hci
mkdir build
cd build
cmake -DPICO_BOARD=pico_w ..
make
- Flash the
uf2
file to the Pico W. - Attach the Pico as Bluetooth controller (Linux).
# start bluetooth if it's not running
sudo systemctl start bluetooth
# attach the HCI UART
sudo hciattach /dev/ttyACM0 any
# list HCI devices
hciconfig
hci0: Type: Primary Bus: UART
BD Address: 28:CD:C1:XX:XX:XX ACL MTU: 1021:8 SCO MTU: 64:10
DOWN RUNNING
RX bytes:722 acl:0 sco:0 events:40 errors:0
TX bytes:438 acl:0 sco:0 commands:40 errors:0
# power on device
sudo hciconfig hci0 up
Now you can use the Pico as Bluetooth controller. For example with bluetoothctl
$ bluetoothctl
Agent registered
[bluetoothctl]> power on
Changing power on succeeded
[bluetoothctl]> scan on
SetDiscoveryFilter success
Discovery started
[CHG] Controller 28:CD:C1:XX:XX:XX Discovering: yes
[NEW] Device XX:XX:XX:XX:XX:XX XX-XX-XX-XX-XX-XX
[...]
Limitations
One limitation we faced was that SCO does not work. SCO is a protocol that some audio applications require. More details about the issue can be found in the pico-sdk repo. Apparently, this is a combination of an issue with the controller firmware, and the way the controller is wired to the Pico SoC.
Cheers,
Dennis and Frieder.