Welcome to the Trouble Documentation. This page is for everyone who wants to use Trouble and understand how Trouble works.
Introduction
TrouBLE is a Bluetooth Low Energy (BLE) Host implementation written in Rust, with a future goal of qualification.
A BLE Host is one side of the Host Controller Interface (HCI). The BLE specification defines the software of a BLE implementation in terms of a controller
(lower layer) and a host
(upper layer).
These communicate via a standardized protocol, that may run over different transports such as as UART, USB or a custom in-memory IPC implementation.
The advantage of this split is that the Host can be reused for different controller implementations. This means that you can write BLE applications that can work on different hardware due to the HCI interface.
Trouble uses the bt-hci
crate for the HCI interface, which means that any controller implementing the traits in bt-hci
can work with Trouble. At present, the
following controllers are available:
The examples show how you can use Trouble with the different controllers.
The APIs available in Trouble are documented in rustdoc.
Concepts
A few BLE concepts frequently used in Trouble are explained here.
Central
A BLE central is a device that can scan and connect to other BLE devices. Usually the central is a more powerful device like a phone or a PC, but there are no restrictions on this, and battery powered embedded devices may also act as a central.
Peripheral
A BLE Peripheral is a device that advertises its presence and may be connected to. Common examples include heart rate monitors, fitness trackers, and smart sensors. Peripherals may use GATT (Generic Attribute Profile) to expose services and characteristics, but can also support l2cap connection oriented channels.
Communication Process
-
The Peripheral advertises its presence using advertising packets.
-
The Central scans for nearby Peripherals and initiates a connection.
-
Once connected, both the central and peripheral may open an l2cap channel and/or a GATT server/client.
Addresses
Every BLE device is identified by a unique Bluetooth Device Address, which is a 48-bit identifier similar to a MAC address. BLE addresses are categorized into two main types: Public and Random.
Public Address
A Public Address is globally unique and assigned by the IEEE. It remains constant and is typically used by devices requiring a stable identifier.
Random Address
A Random Address can be static or dynamic:
-
Static Random Address: Remains fixed until the device restarts or resets.
-
Private Random Address: Changes periodically for privacy purposes. It can be Resolvable (can be linked to the original device using an Identity Resolving Key) or Non-Resolvable (completely anonymous).
Random addresses enhance privacy by preventing device tracking.
Getting started
This example walks through the various configuration options you can use for a trouble application and how you can create a basic instance of Trouble. For more advanced examples, see the examples.
A trouble application needs two pieces of configuration:
-
Cargo features specified on the
trouble-host
crate. -
Generics specified on the
HostResources
type.
Cargo features
The following features enable/disable features in the host:
-
central - enables the central BLE role, allowing the devices to create connections.
-
scan - extends the central BLE role allowing the device to scan for devices.
-
peripheral - enables the peripheral BLE role, allowing the device to advertise its presence.
-
gatt - enables GATT client and server support.
-
derive - enables macros for defining GATT services.
-
security - enables support for the security manager for pairing/bonding.
-
controller-host-flow-control - enables controller-host flow control (not supported by all controllers).
-
connection-metrics - enable additional connection metrics that increases the per-connection RAM requirements.
The following features configure queue sizes and memory pools (N is any number supported in the features list):
-
connection-event-queue-size-N - per-connection queue size of events (disconnects, gatt data).
-
l2cap-rx-queue-size-N - per-l2cap channel queue size of inbound data.
-
l2cap-tx-queue-size-N - per-l2cap channel queue size of outbound data.
-
l2cap-rx-packet-pool-size-N - memory pool of packets shared by all l2cap channels for inbound data.
-
l2cap-tx-packet-pool-size-N - memory pool of packets shared by all l2cap channels for outbound data.
-
gatt-client-notification-max-subscribers - GATT client max notification subscribers.
-
gatt-client-notification-queue-size-N - GATT client queue size for inbound notifications.
A common question is why the above settings are not const generics, and the reason is that it would obfuscate the API too much, and they generally do not need to be changed from the defaults.
HostResources
The HostResources
type holds references to all packets, connections, channels and other data used by Trouble. The following
generic parameters can be set:
-
CONNS - max number of BLE connections supported.
-
CHANNELS - max number of L2CAP channels supported (not including GATT).
-
L2CAP_MTU - max size of L2CAP payloads (affects GATT MTU as well).
-
ADV_SETS - max number of advertising sets (the default of 1 is appropriate for most applications).
The following instance would allow you to have up to 4 connections and 2 l2cap channels with an MTU of 128:
let mut resources: HostResources<4, 2, 128, 1> = HostResources::new();
Creating the stack
With the resources defined, an instance of Trouble can be created using the trouble_host::new()
function:
let stack: Stack<'_, MyController> = trouble_host::new(controller, &mut resources);
The MyController
is the type for your particular hardware, for instance SoftdeviceController
from the nrf-sdc
crate.
The Stack
is a builder allowing you to set some properties of the host before creating it:
-
set_random_address
for specifying a BLE random address (see xref:_random_address). -
set_random_generator_seed
for specifying the random seed used by the security manager (if enabled).
Once properties are set, you can build the host:
let Host {
central,
peripheral,
runner,
} = stack.build();
The presence central
and peripheral
fields depends on whether the corresponding cargo features are enabled.
The runner
is responsible for running the BLE host stack, and should be passed to an async task that typically runs forever. It will only
consume CPU resources when there are work to do (accepting connections/channels, creating connections/channels, sending/receiving data).
For instance, an embassy
application would use it like this:
Summary
Now that BLE stack is created, you can go create some awesome BLE applications. The examples cover the following:
-
Scanning for peripherals
-
Central connecting to peripherals and using the GATT client.
-
Central connecting to peripherals and creating L2CAP connection oriented channels.
-
Peripheral advertising and providing a GATT service.
-
Peripheral advertising and accepting L2CAP connection oriented channels.
Performance
To achieve the best possible performance, some tuning is involved. Note this often has a trade-off of throughput, latency and power consumption, so the ideal configuration depends on your application.
Background
In order to understand what configurations need to be set and how, we will require some understanding of the Bluetooth hardware and lower layers of the software stack.
A Bluetooth controller, the actual physical radio, sends packets of data called the Protocol Data Unit (PDU). In Bluetooth communication, there are a number of different PDUs used with different physical channels for different purposes such as advertising and data transfer. These PDUs have different structures. For our example, we only need to focus on the Data Physical Channel PDU. As the name suggests, this is used in the transfer of data.
The structure of the Data Physical Channel PDU is shown below.
-
Data Physical Channel PDU. Obtained from bluetooth.com image::PDU.png[Data Physical Channel PDU]
Since Bluetooth 4.2, the maximum size of the PDU payload, plus the MIC if included, has increased from 27 to 251 bytes. The maximum size of the PDU to be used in a connection, also known as the Maximum Transmission Unit (MTU), is negotiated between the central and peripheral device upon establishing a connection. The length of this payload is described in the PDU header.
A data physical channel PDU payload can be a Logic Link (LL) Data PDU or a LL Control PDU. These are differentiated by the LLID header bits.
For the purpose of these examples, we shall focus further on the LL data PDU. The payload of an LL data PDU contains Logical Link Control and Adaptation Protocol (L2CAP) data.
In a connection oriented channel, the L2CAP data has the following structure.

The L2CAP PDU length describes the length of the information payload. The maximum size of the information payload is 65533 bytes. This limit setting is known as the L2CAP MTU. Notably, this can be larger than the maximum size of the PDU payload. L2CAP payloads larger than the maximum size of the PDU payload will be fragmented into chunks no longer than the PDU MTU before being sent over the air.

The primary goal of Bluetooth Low Energy (BLE) is to minimize energy consumption. BLE achieves this efficiency by keeping the radio off for the majority of the time, and only activating it when necessary. For instance, after a connection is established, data transfer occurs periodically during synchronized time windows called connection events. These events are separated by a negotiated time interval known as the connection interval. During each connection event, the central and peripheral radios briefly wake up to exchange data packets and then return to an idle state until the next event. This scheduled radio activity significantly reduces power usage compared to keeping the radio active continuously.

Since Bluetooth 5.0, Bluetooth controllers can double the symbol transmission rate from 1Mbps, known as 1M PHY, to 2Mbps, known as 2M PHY.
By understanding these configurations, we can adjust them to prioritize throughput over energy conservation.
Default configurations
A default BLE configuration optimised for backwards compatibility and energy conservation, like the ble_l2cap
examples,
would have a PDU size of 27 and a high connection interval.
The Bluetooth packet timeline blow depicts multiple packets being sent in one connection event. Blue represents useful data being sent.

The Bluetooth packet timeline below depicts bluetooth packets sent in different connection events. Notice the white gaps in both figures. To maximise throughput, we need to increase the ratio of blue in these diagrams.

The following sections describe how these configurations can be modified to maximise throughput.
Data Length Extension
Since Bluetooth 4.2, the maximum PDU size has increased from 27 to 251. For better throughput, a higher PDU should be used as 1. there will be fewer header bytes per packet of data 2. more data is sent in one go, reducing the waiting time required between sending consecutive packets.
To set this we use the HCI command LeSetDataLength
with tx_octets = 251
and tx_time = 2120
.
Note
|
This option is only available on the 1M and 2M phy. |
L2CAP MTU
Choosing the correct value for the L2CAP MTU can have a significant impact on throughput. If an L2CAP payload is larger than the PDU by 1 byte, this will be fragmented into two packets, one of size PDU and the other of size 1. The following diagram shows the packets when PDU = 251 and L2CAP MTU = 252.

Hence, it is important to choose an L2CAP MTU value that can neatly fit into a number of PDU packets. If the PDU length is set to 251, then the L2CAP MTU should be set to some multiple of this.
One other thing to note is that an L2CAP payload has 6 bytes of header. This means that for a smaller L2CAP MTU, more bytes are used for headers than service data.
The maximum size of the L2CAP MTU is 65533, however, this may be limited by the controller resources, see the next section.
HCI controller configuration
The size of the PDU and the maximum number of fragments an L2CAP PDU can be broken into may be limited by the HCI firmware. For example, in the serial-hci example, the HCI UART Bluetooth dongle firmware’s default has 3 27-byte long buffers. Hence, L2CAP PDUs greater than $ 27 \times 3 $ cannot be handled by the HCI firmware. These firmware configurations may be improved. If this is required for your target, check the target’s example readme for more information on how to do this.
To calculate the maximum L2CAP MTU allowed by the controller, we can multiply the size and number of available buffers. At its max, the HCI UART Bluetooth dongle firmware can have 20 251-byte long buffers. Hence, the largest possible L2CAP MTU supported by this firmware is 5020 bytes.
However, it can be beneficial not set the L2CAP MTU to this maximum. If the L2CAP MTU is set to the maximum buffer space available to the controller, the host will only be able to send a new packet once the all the buffers are free. This might cause the controller to switch off the radio until the next connection event once it has exhausted all the buffers, before receiving the next batch from the host. See diagram below.

However, if we allow a few extra buffers, the host will be abel to send more data to the controller before it exhausts the data from the previous message. This will cause the radio to stay awake.

The change from 1M PHY in the above figures is denoted by the doubling of the packet height.
Service Data Unit size
If it can be controlled, such as when having a lot of available data to send, we can improve our throughput by sending data that neatly fits in L2CAP payloads.
Since an L2CAP payload has 4 bytes of header + 2 bytes for a connection oriented channel payload, this can be achieved by sending data that is a multiple of the L2CAP MTU - 6.
Connection Interval
If the L2CAP MTU is set such that it allows the host to constantly keep some controller buffers occupied, the controller will keep sending data until just before a new connection event is scheduled.
In this scenario, it makes sense to use a longer connection interval, however, this would increase latency and possibly lower throughput in noisy environments as when connections are dropped, the radio will idle until the next connection event.
Frequently Asked Questions
These are a list of unsorted, commonly asked questions and answers.
Please feel free to add items to this page, especially if someone in the chat answered a question for you!