In this section, we highlight the BTstack components that need to be adjusted for different hardware platforms.

Time Abstraction Layer

BTstack requires a way to learn about passing time. btstack_run_loop_embedded.c supports two different modes: system ticks or a system clock with millisecond resolution. BTstack’s timing requirements are quite low as only Bluetooth timeouts in the second range need to be handled.

Tick Hardware Abstraction

If your platform doesn’t require a system clock or if you already have a system tick (as it is the default with CMSIS on ARM Cortex devices), you can use that to implement BTstack’s time abstraction in include/btstack/hal_tick.h>.

For this, you need to define HAVE_EMBEDDED_TICK in btstack_config.h:

#define HAVE_EMBEDDED_TICK

Then, you need to implement the functions hal_tick_init and hal_tick_set_handler, which will be called during the initialization of the run loop.

void hal_tick_init(void);
void hal_tick_set_handler(void (*tick_handler)(void));
int  hal_tick_get_tick_period_in_ms(void);

After BTstack calls hal_tick_init() and hal_tick_set_handler(tick_handler), it expects that the tick_handler gets called every hal_tick_get_tick_period_in_ms() ms.

Time MS Hardware Abstraction

If your platform already has a system clock or it is more convenient to provide such a clock, you can use the Time MS Hardware Abstraction in include/btstack/hal_time_ms.h.

For this, you need to define HAVE_EMBEDDED_TIME_MS in btstack_config.h:

#define HAVE_EMBEDDED_TIME_MS

Then, you need to implement the function hal_time_ms(), which will be called from BTstack’s run loop and when setting a timer for the future. It has to return the time in milliseconds.

uint32_t hal_time_ms(void);

Bluetooth Hardware Control API

The Bluetooth hardware control API can provide the HCI layer with a custom initialization script, a vendor-specific baud rate change command, and system power notifications. It is also used to control the power mode of the Bluetooth module, i.e., turning it on/off and putting to sleep. In addition, it provides an error handler hw_error that is called when a Hardware Error is reported by the Bluetooth module. The callback allows for persistent logging or signaling of this failure.

Overall, the struct btstack_control_t encapsulates common functionality that is not covered by the Bluetooth specification. As an example, the btstack_chipset_cc256x_in-stance function returns a pointer to a control struct suitable for the CC256x chipset.

HCI Transport Implementation

On embedded systems, a Bluetooth module can be connected via USB or an UART port. BTstack implements three UART based protocols for carrying HCI commands, events and data between a host and a Bluetooth module: HCI UART Transport Layer (H4), H4 with eHCILL support, a lightweight low-power variant by Texas Instruments, and the Three-Wire UART Transport Layer (H5).

HCI UART Transport Layer (H4)

Most embedded UART interfaces operate on the byte level and generate a processor interrupt when a byte was received. In the interrupt handler, common UART drivers then place the received data in a ring buffer and set a flag for further processing or notify the higher-level code, i.e., in our case the Bluetooth stack.

Bluetooth communication is packet-based and a single packet may contain up to 1021 bytes. Calling a data received handler of the Bluetooth stack for every byte creates an unnecessary overhead. To avoid that, a Bluetooth packet can be read as multiple blocks where the amount of bytes to read is known in advance. Even better would be the use of on-chip DMA modules for these block reads, if available.

The BTstack UART Hardware Abstraction Layer API reflects this design approach and the underlying UART driver has to implement the following API:

void hal_uart_dma_init(void);
void hal_uart_dma_set_block_received(void (*block_handler)(void));
void hal_uart_dma_set_block_sent(void (*block_handler)(void));
int  hal_uart_dma_set_baud(uint32_t baud);
void hal_uart_dma_send_block(const uint8_t *buffer, uint16_t len);
void hal_uart_dma_receive_block(uint8_t *buffer, uint16_t len);

The main HCI H4 implementations for embedded system is hci_h4_transport-_dma function. This function calls the following sequence: hal_uart_dma_init, hal_uart_dma_set_block_received and hal_uart_dma_set_block_sent functions. this sequence, the HCI layer will start packet processing by calling hal_uart-_dma_receive_block function. The HAL implementation is responsible for reading the requested amount of bytes, stopping incoming data via the RTS line when the requested amount of data was received and has to call the handler. By this, the HAL implementation can stay generic, while requiring only three callbacks per HCI packet.

H4 with eHCILL support

With the standard H4 protocol interface, it is not possible for either the host nor the baseband controller to enter a sleep mode. Besides the official H5 protocol, various chip vendors came up with proprietary solutions to this. The eHCILL support by Texas Instruments allows both the host and the baseband controller to independently enter sleep mode without loosing their synchronization with the HCI H4 Transport Layer. In addition to the IRQ-driven block-wise RX and TX, eHCILL requires a callback for CTS interrupts.

void hal_uart_dma_set_cts_irq_handler(void(*cts_irq_handler)(void));
void hal_uart_dma_set_sleep(uint8_t sleep);

H5

H5, makes use of the SLIP protocol to transmit a packet and can deal with packet loss and bit-errors by retransmission. Since it can recover from packet loss, it's also possible for either side to enter sleep mode without loosing synchronization.

The use of hardware flow control in H5 is optional, however, since BTstack uses hardware flow control to avoid packet buffers, it's recommended to only use H5 with RTS/CTS as well.

For porting, the implementation follows the regular H4 protocol described above.

Persistent Storage APIs

On embedded systems there is no generic way to persist data like link keys or remote device names, as every type of a device has its own capabilities, particularities and limitations. The persistent storage APIs provides an interface to implement concrete drivers for a particular system.

As an example and for testing purposes, BTstack provides the memory-only implementation btstack_link_key_db_memory. An implementation has to conform to the interface in Listing below.

typedef struct {
    // management
    void (*open)();
    void (*close)();

    // link key
    int  (*get_link_key)(bd_addr_t bd_addr, link_key_t link_key);
    void (*put_link_key)(bd_addr_t bd_addr, link_key_t key);
    void (*delete_link_key)(bd_addr_t bd_addr);
} btstack_link_key_db_t;