Adding full part7-bluetooth write-up

This commit is contained in:
Adam Greenwood-Byrne 2020-07-27 17:54:08 +01:00
parent 46b8b14186
commit b97c6efc57

View file

@ -8,4 +8,125 @@ Controlling the RPi4 solely via a UART-connected laptop is not much fun. Our Bre
In this part, we set up a second UART to communicate with the RPi4's onboard Bluetooth modem. There is nothing simple about Bluetooth, but it is at least simpler than USB, and that's the reason I've chosen to pursue it.
_Write-up coming soon!_
The Broadcom firmware
---------------------
The Bluetooth modem is a Broadcom chip (BCM43455), and it needs to be loaded with proprietary software before it's useful to us. I have taken their _BCM4345C0.hcd_ file from the [Bluez repo](https://github.com/RPi-Distro/bluez-firmware/tree/master/broadcom).
As we don't yet have any filesystem, we won't be able to load this at runtime, so instead we'll need to build it into our kernel. We can use `objcopy` to build a _.o_ file that we can link. We add these lines to _Makefile_:
```c
BCM4345C0.o : BCM4345C0.hcd
$(GCCPATH)/aarch64-none-elf-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@
```
We also need to modify our `kernel8.img` dependencies to include our new _.o_ file:
```c
kernel8.img: boot.o $(OFILES) BCM4345C0.o
$(GCCPATH)/aarch64-none-elf-ld -nostdlib -nostartfiles boot.o $(OFILES) BCM4345C0.o -T link.ld -o kernel8.elf
$(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img
```
If you build the kernel now, you'll see that the new image is much bigger - because it contains the firmware bytes. You can run `objdump -x kernel8.elf`, and you'll also see a few new symbols in there:
* _binary_BCM4345C0_hcd_start
* _binary_BCM4345C0_hcd_size
* _binary_BCM4345C0_hcd_end
We'll use these symbols later to reference the firmware from our C code.
Setting up the UART
-------------------
Look at our new _bt.c_. We'll focus on the `// UART0` section for now. A lot of the techniques will be familiar to you as this hardware also uses the MMIO technique, and we're implementing a lot of the same functions that we did to get our serial debug working.
To use Bluetooth at the same time as our serial debug, we'll need to remap some GPIO pins. Pins 30, 31, 32 and 33 will all need to take on their _alternate function 3_ to give us access to CTS0, RTS0, TXD0 and RXD0. You can read all about this in Section 5.3 of the [BCM2711 ARM Peripherals document](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/rpi_DATA_2711_1p0.pdf).
We already have a function in _io.c_ to do this, and so we add the function definition of `gpio_useAsAlt3` to _io.h_ to give us access to it from here. In `bt_init()`, we go ahead and remap the GPIO pins, then flush the receive buffer just to be safe. The MMIO writes which follow, set us up for 115200 baud and [8-N-1 communication](https://en.wikipedia.org/wiki/8-N-1) - we know the Bluetooth modem can cope with this. Perhaps the most important line to get right was this one:
```c
mmio_write(ARM_UART0_CR, 0xB01);
```
It enables the UART (bit 0 on), enables TX & RX (bits 8 & 9 on), and drives RTS0 low (bit 11 on) - very important as the Bluetooth modem will sit unresponsive until it sees this. **I lost a lot of time figuring this one out.**
We should now be able to talk to the Bluetooth modem over our new UART.
Talking to the Bluetooth modem
------------------------------
The Bluetooth spec is massive, and to implement a full driver would take a long while. I'll settle for "proof of life" for now. We still have a way to travel though...
We talk to the Bluetooth modem using **HCI commands**. I enjoyed reading the [TI HCI docs](http://software-dl.ti.com/simplelink/esd/simplelink_cc13x2_sdk/1.60.00.29_new/exports/docs/ble5stack/vendor_specific_guide/BLE_Vendor_Specific_HCI_Guide/hci_interface.html) as an intro to this.
`bt_reset()` simply calls `hci_Command`, which in turn called `hci_CommandBytes` to write the bytes out to the UART that tell the Bluetooth chip to reset and await firmware. This is a vendor-specific call, so you won't find it documented anywhere. `hci_CommandBytes` then waits for a very specific response before it returns successfully - the "command complete" response.
Loading the firmware
--------------------
Now the device is waiting. We need to send it the firmware bytes we included in our kernel:
```c
void bt_loadfirmware()
{
if (!hciCommand(OGF_VENDOR, COMMAND_LOAD_FIRMWARE, empty, 0)) uart_writeText("loadFirmware() failed\n");
extern unsigned char _binary_BCM4345C0_hcd_start[];
extern unsigned char _binary_BCM4345C0_hcd_size[];
unsigned int c=0;
unsigned int size = (long)&_binary_BCM4345C0_hcd_size;
while (c < size) {
unsigned char opcodebytes[] = { _binary_BCM4345C0_hcd_start[c], _binary_BCM4345C0_hcd_start[c+1] };
unsigned char length = _binary_BCM4345C0_hcd_start[c+2];
unsigned char *data = &(_binary_BCM4345C0_hcd_start[c+3]);
if (!hciCommandBytes(opcodebytes, data, length)) {
uart_writeText("Firmware data load failed\n");
break;
}
c += 3 + length;
}
wait_msec(0x100000);
}
```
First, we send a command to tell the chip that we're about send the firmware. You'll see that we then reference our new symbols, which point us at our firmware bytes. We now know the size of the firmware, and so we iterate over it.
The firmware is simply a sequence of HCI commands following this format:
* 2 bytes of opcode
* 1 byte that tells us the length of the data to follow
* _length_ bytes of data
We check each HCI command succeeds as we go, wait a second and then return. If it runs without error then we've loaded our firmware and we're ready to start some Bluetooth communications!
The Bluetooth spec
------------------
From here, you must tackle the [Bluetooth specification](https://www.bluetooth.com/specifications/bluetooth-core-specification/). You really owe it to yourself to begin with a good understanding of how Bluetooth works - something that is beyond the scope of my write-up!
I just picked some easy Bluetooth LE commands to implement for our "proof of life" (see the _LE CONTROLLER COMMANDS_ section of the spec):
* setLEeventmask
* setLEscanenable
* setLEscanparameters
Read through my implementation alongside the Bluetooth spec - you should see what's going on. Fortunately, these are all we need to start scanning for BLE devices around us. If I can show that I'm seeing other devices, then I've got my proof of life.
Implementing scanning
---------------------
If you look in _kernel.c_, you'll see the main bulk of our scanning code. Exactly as we described above, we first reset the chip, then load the firmware. After that, we set the LE event mask (tell the chip what we want to be notified about) and start scanning for events. `bt_update()` then polls the chip to see if any events are awaiting our attention.
There may be one or more **ad report** in each event, and each contains the 48-bit Bluetooth address of the associated device e.g. `AA:BB:CC:DD:EE:FF` (bd_addr) as well as the data. I found a great article on the [Advertising Data Format from Silicon Labs](https://www.silabs.com/community/wireless/bluetooth/knowledge-base.entry.html/2017/02/10/bluetooth_advertisin-hGsf). Reading this will help you interpret my decode routines.
Right now, I'm just printing the bd_addr (using the convenient `uart_hex` routine added to _io.c_), RSSI (an indicator of signal strength), the ad report type & length, and the manufacturer code if received (`ad_type == 0xff`).
Proof of life!
--------------
And, when I build and run this, I see reports coming in from my neighbouring Bluetooth speaker - magic! _This is our first step towards low-level Bluetooth control of the RPi4._