This is the first in a series of “Under the Hood” posts, in which we will go into the technical details of the SwaraLink Platform and Bluetooth Low Energy in general.
One of the most common points of confusion in Bluetooth Low Energy comes up when product developers are trying to optimize their system throughput. The Bluetooth 5.x specification introduced the “2Mbps” feature, so you would think that the throughput in BLE can go up to 2Mbps, but unfortunately this just the data rate at the physical layer, also known as the “PHY” layer. In reality, the throughput at the application layer (i.e. the data that you care about as a product developer) is much less than this for a few reasons:
- There are several layers of the protocol stack that add overhead data that is sent over the air. In addition to your application layer data, there are layers such ATT, L2CAP, and LL that include header bytes in every packet.
- The BLE protocol uses half-duplex communication between two connected devices. This means that at any given point in time, a device is either receiving or transmitting; it’s never doing both at the same time. Even though your application may be sending data in one direction, the other layers of the stack must communicate with each other.
- BLE doesn’t use the radio 100% of the time. BLE communications occur periodically during “connection events”, and in between these connections events the radio is idle, and no data is being transmitted or received.
We won’t go into all of the low-level details of how the BLE protocol impacts throughput, as this has already been well documented in some excellent posts by Novel Bits (1), Punch Through (2), and Memfault (3) (see reference links at the bottom of this post; note that some of the information in those posts are outdated, but conceptually the methods that they describe are still very much valid). We also have some of the basics documented in our Bluetooth Low Energy Developer’s Checklist document, which is available for download here.
With the SwaraLink Bluetooth Low Energy Platform, we’ve been able to achieve a maximum application layer throughput of ~650kbps with iPhones, and ~1.2Mbps with some newer Android phones, by more-or-less using the same techniques discussed in those posts. The key steps to achieving this high throughput are the following:
- Request a minimum and maximum 15ms connection interval (shortest allowed by iOS) and peripheral latency value of 0
- Use 2Mbps PHY (if supported by central device)
- Set LL PDU length to 251 bytes or max allowed by central device)
- Set ATT_MTU size to (LL_PDU – 4) bytes
- Fragment your data in chunks of (ATT_MTU – 3) bytes and send data using ATT notifications (peripheral-to-central) as fast as possible, until stack buffers are full. Repeat this process as buffers free up
The information that’s often missing, however, is what actual APIs to use to implement these steps. Below, we’ve provided a reference of the key API from some chip vendors / stacks, along with some implementation notes. We will try and keep the tables below updated with additional chip vendors and based on the latest stack APIs over time.
Note: this article focuses on maximizing throughput when data is going in the peripheral-to-central direction. Many of these steps will also help you optimize when data is going in the opposite direction, but instead of ATT Notifications you will need to use ATT Write Command (aka “Write Without Response”) packets. There is some additional complexity involved when sending large numbers of ATT Write Commands, that we will try to address in a future post.
Requesting Connection Parameter Updates
Device Family / SDK | API Functions | Notes |
Nordic nRF52 nRF5 SDK 17.x | sd_ble_gap_conn_param_update | When the parameters have been fully updated, the Soft Device will generate the BLE_GAP_EVT_CONN_PARAM_UPDATE event |
Silicon Labs EFR32BG22, GSDK 4.x | sl_bt_connection_set_parameters | When the parameters have been fully updated, the stack will generate the sl_bt_evt_connection_parameters event. Note that Silicon Labs stack will also generate this event for other connection updates (e.g. security), so be sure to actually check the values. |
STMicro STM32WB, STM32CubeWB 1.14.1 | aci_l2cap_connection_parameter_update_req | When the parameters have been fully updated, the stack will generate the ACI_L2CAP_CONNECTION_UPDATE_RESP_EVENT event. |
Additional Notes:
- When the above APIs are called, the peripheral will send a request for new values. Be sure to wait for the corresponding event to know when the actual parameters have been updated.
- We recommend waiting for 5 seconds after the connection has been established before sending the update request
- Be sure to follow Apple’s guidelines for valid parameters, which can be found in Apple’s Accessory Design Guidelines Document (4), (see reference links below).
PHY Updates
Device Family / SDK | API Functions | Notes |
Nordic nRF52 nRF5 SDK 17.x | sd_ble_gap_phy_update | When the PHY has been fully updated, the Soft Device will generate the BLE_GAP_EVT_PHY_UPDATE event. |
Silicon Labs EFR32BG22, GSDK 4.x | sl_bt_connection_set_default_preferred_phy sl_bt_connection_set_preferred_phy | The “_set_default_preferred_phy” API will configure the stack to automatically respond with what was set. The “_set_preferred_phy” API can be used at any time to request a PHY update. When the PHY has been fully updated, the stack will generate the sl_bt_evt_connection_phy_status event |
STMicro STM32WB, STM32CubeWB 1.14.1 | hci_le_set_default_phy hci_le_set_phy | The “_set_default_phy” API will configure the stack to automatically respond with what was set. The “_set_phy” API can be used at any time to request a PHY update. The ST stack does not generate an event when the PHY is successfully updated. |
Additional Notes:
- Newer iPhones and Android phones will try and update the PHY to 2Mbps upon connection
- Using a 2Mbps PHY will impact your range, and in a poor RF environment may be less reliable than a 1Mbps
LL PDU Length Updates
Device Family / SDK | API Functions | Notes |
Nordic nRF52 nRF5 SDK 17.x | sd_ble_gap_data_length_update | Nordic’s API is a bit strange in that you have to specify the max Tx and Rx lengths, as well as the max Tx and Rx “times”. We recommend using the defined valued of BLE_GAP_DATA_LENGTH_AUTO for the time parameters. When the PDU length has been updated, the Soft Device will generate the BLE_GAP_EVT_DATA_LENGTH_UPDATE event. |
Silicon Labs EFR32BG22, GSDK 4.x | sl_bt_gatt_server_set_max_mtu | Silicon Labs’ SDK set the default PDU MAX to 251. When a connection is opened, the PDUs is determined by the max size of the central. |
STMicro STM32WB, STM32CubeWB 1.14.1 | hci_le_set_data_length | Peripheral requests Tx length and time for connection. We recommend using 0x0148 for the time parameter. |
Additional Notes:
- We recommend that the peripheral sends the LL_PDU request as soon as possible after the connection is established, rather than waiting for the central to send the request
- The actual used PDU length will be the lesser of the value set by the peripheral and the value set by the central. Beware that not all mobile devices (including some relatively recent phones from major brands) support a 251-byte data length! Be sure that your embedded software is designed to work in the event that the mobile only supports a smaller value
ATT_MTU Size Updates
Device Family / SDK | API Functions | Notes |
Nordic nRF52 nRF5 SDK 17.x | sd_ble_gatts_exchange_mtu_reply | The Soft Device will generate a BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST when the central sends the request. This function should be called in response to the request. The updated ATT_MTU size takes effect as soon as the reply has been sent |
Silicon Labs EFR32BG22, GSDK 4.x | sl_bt_gatt_server_set_max_mtu | Stack will generate a sl_bt_evt_gatt_mtu_exchanged_id event once the MTU has been udpated. |
STMicro STM32WB, STM32CubeWB 1.14.1 | aci_gatt_exchange_config | ST’s approach for updating MTU size is a bit strange. Firstly, the max mtu size must be set with SHCI_C2_BLE_Init(), this API sets several BLE configuration values, before calling aci_gatt_exchange_config(). The ACI_ATT_EXCHANGE_MTU_RESP_VSEVT_CODE event is then generated afterward. |
Additional Notes:
- We recommend setting an ATT_MTU value equal to (LL_PDU length – 4) bytes. If you followed our recommendations to perform the LL_PDU length update immediately upon connection, you should know the optimal ATT_MTU value by the time that the ATT_MTU exchange request is received.
- We recommend that the peripheral waits until the central has sent an ATT_MTU request and replies to it, rather than initiating the request.
- The actual used ATT_MTU size will be the lesser of the value set by the peripheral and the value set by the central. Beware that not all mobile devices (including some relatively recent phones from major brands) support a 247-byte ATT_MTU length! Be sure that your embedded software is designed to work in the event that the mobile only supports a smaller value or it doesn’t support a 251-byte LL_PDU length. Setting it to a larger value might actually reduce your throughput.
Sending ATT Notifications
Device Family / SDK | API Functions | Notes |
Nordic nRF52 nRF5 SDK 17.x | sd_ble_gatts_hvx | To maximize throughput, you can call this function over and over until the Soft Device returns the NRF_ERROR_RESOURCES value. This means that all of the transmit buffers are full. As notifications have been transmitted and buffers free up, the Soft Device will generate BLE_GATTS_EVT_HVN_TX_COMPLETE events to let you know. To maximize throughput, you should try to queue up more notifications as soon as buffers are freed. |
Silicon Labs EFR32BG22, GSDK 4.x | sl_bt_gatt_server_send_notification | To maximize throughput, you can call this function over and over until the stack returns the SL_STATUS_NO_MORE_RESOURCE value. This means that all of the transmit buffers are full. The Silicon Labs stack does not generate an event to let you know when transmit buffers are freed up. As a workaround you can do the following: Call sl_bt_system_get_counters() to know if there have been any Tx packets sent out. If there has, attempt to send notifications again. Repeat this until all the data has been sent. |
STMicro STM32WB, STM32CubeWB 1.14.1 | aci_gatt_update_char_value | In order to maximize throughput, this API call be called repeatedly before the BLE_STATUS_INSUFFICIENT_RESOURCES error is returned. The stack will generate the following event when Tx buffers have freed up: ACI_GATT_TX_POOL_AVAILABLE_VSEVT_CODE. To maximize throughput, you should try to queue up more notifications as soon as buffers are freed |
Additional Notes:
- Some stacks will have configuration options that enable you to configure how much memory is allocated for the transmit buffers. Increasing this buffer size will generally improve your throughput, though iPhones and Android phones will have their own buffer limitations, so at a certain point you will stop benefiting from increasing the transmit buffer size
- As stated before, for optimal throughput we recommend sending your data in chunks of size (ATT_MTU size – 3) bytes. This will pack the data most efficiently.
An Easier Option: The SwaraLink BLE Platform
All of the above information is based on many years of experience working with Bluetooth Low Energy systems. We hope that you find it to be useful on your BLE journey.
If you’re looking for an alternative to the above, consider developing with the SwaraLink BLE Platform. With our platform, the process for optimizing throughput is much simpler. Our platform has a concept of Peripheral Priorities, of which your options are to Increase Throughput, Reduce Power, or Improve Range.
At runtime, your application can specify Priority 1 (highest), Priority 2 (second highest), and Priority 3 (third highest). If there is a strong need for one of these priorities, all three priorities can be set to the same option. So for example, in order to maximize throughput you would set priorities 1, 2, and 3 all with the “Increase Throughput” option.
Under the hood, the SwaraLink Platform will manage all of the connection settings to ensure that your system is optimized for throughput.
Below are the relevant APIs to optimize throughput with the SwaraLink platform
SwaraLink Library | API Functions | Notes |
SWLCentral (mobile) | SWLCentral.setPeripheralPriorities | Set the priority 1, 2, and 3 parameters all to “Increase Throughput”. After connection has been updated, the SWLCentral library will generate the SWLCentral.evtCurrentPeripheralPriorities event to let you know that the update has completed. |
swl_periph (embedded) | swl_periph_set_peripheral_priorities | Set the priority 1, 2, and 3 parameters all to SWL_PRIO_INCREASE_THROUGHPUT. After connection has been updated, the swl_periph library will generate the SWL_PERIPH_EVT_PERIPH_PRIO_UPDATE event to let you know that the update has completed. |
And that’s it! At SwaraLink we are trying to make it easier for developers to create High-Quality Bluetooth Low Energy products, and we believe that optimizing your system for throughput can have a major impact on the end user experience.
If you’d like to learn more about the SwaraLink Bluetooth Low Energy Platform, feel check out our tutorial and demo here. We would love to hear your feedback!
How has your experience been with optimizing throughput on BLE? We’d love to hear from you! Feel free to reach out to us at info@swaralink.com, via the form on our contact page, or via our LinkedIn page.
References: