Chapter 68: BLE Performance Optimization
Chapter Objectives
By the end of this chapter, you will be able to:
- Understand the key metrics for BLE performance: throughput and latency.
- Identify the BLE protocol parameters that significantly impact performance, such as Connection Interval, ATT MTU, and PHY layer.
- Learn how to configure Data Length Extension (DLE) to increase payload size.
- Understand the benefits and trade-offs of different PHY layers (1M, 2M, Coded PHY).
- Implement strategies to optimize BLE applications for either high throughput or low latency using ESP-IDF.
- Measure and analyze BLE performance in your ESP32 applications.
- Recognize common pitfalls that can degrade BLE performance and how to avoid them.
Introduction
Bluetooth Low Energy (BLE) is designed for low power consumption, but many applications also demand efficient data transfer, whether it’s streaming sensor data, sending firmware updates, or controlling responsive peripherals. “Performance” in BLE typically refers to two main aspects: throughput (how much data can be transferred per unit of time) and latency (the delay between sending a request and receiving a response, or the delay in data delivery).
Optimizing BLE performance is a balancing act. Parameters that boost throughput might increase power consumption or latency, and vice-versa. Understanding the underlying mechanisms and the tools available in ESP-IDF is crucial for tailoring your BLE application to meet its specific performance requirements.
This chapter will explore the various factors influencing BLE performance on ESP32 devices. We will cover how to configure connection parameters, utilize features like Data Length Extension (DLE) and different PHY layers, and discuss best practices for designing efficient BLE communication.
Theory
Understanding BLE Performance Metrics
- Throughput:
- Definition: The actual rate of user data transfer over a BLE link, typically measured in kilobits per second (kbps) or kilobytes per second (kBps).
- Factors: Payload size per packet, number of packets per connection event, connection interval, PHY data rate.
- Use Cases: Firmware Over-The-Air (FOTA) updates, streaming audio (though BLE Audio uses specific profiles), transferring large sensor logs.
- Latency:
- Definition: The time delay experienced in the communication link. This can be one-way latency (time for a packet to travel from sender to receiver) or round-trip latency (time for a request and its corresponding response).
- Factors: Connection interval, slave latency, number of retransmissions, processing time on both devices.
- Use Cases: Real-time control systems (e.g., robotics, gaming controllers), interactive applications, time-sensitive alerts.
Performance Metric | Definition | Key Influencing Factors | Typical Use Cases | Measured In |
---|---|---|---|---|
Throughput | The actual rate of user data transfer over a BLE link. | ATT MTU, Data Length Extension (DLE), PHY layer (1M, 2M), Connection Interval, Packets per Connection Event, Notification/Indication choice. | Firmware Over-The-Air (FOTA) updates, streaming sensor logs, transferring images or audio snippets (non-real-time). | kbps (kilobits per second) or kBps (kilobytes per second). |
Latency | The time delay in the communication link (one-way or round-trip). | Connection Interval, Slave Latency, Number of retransmissions, PHY layer, Processing time on devices, Notification/Indication choice. | Real-time control (robotics, game controllers), interactive applications, time-sensitive alerts, responsive user interfaces. | ms (milliseconds). |
Key Factors Affecting BLE Performance
The BLE protocol offers several parameters that can be negotiated between a central and a peripheral device to manage the connection.
Parameter | Change | Impact on Throughput | Impact on Latency | Impact on Power Consumption (Peripheral) | Typical Use Case |
---|---|---|---|---|---|
Connection Interval (CI) | Shorter (e.g., 7.5ms) | ↑ Increases | ↓ Decreases (Improves) | ↑ Increases | High throughput, low latency applications (e.g., gaming, FOTA). |
Longer (e.g., 100ms, 1s) | ↓ Decreases | ↑ Increases (Worsens) | ↓ Decreases | Low power sensor networks, infrequent updates. | |
Slave Latency (SL) | Higher Value (e.g., 10) | ~ No direct impact on peripheral-to-central throughput (Peripheral wakes when it has data) | ↑ Increases for central-to-peripheral data (Peripheral might be sleeping) | ↓ Decreases significantly (Allows peripheral to skip CIs) | Power-sensitive peripherals that mostly send data and infrequently receive critical data. |
Lower Value (e.g., 0) | ~ No direct impact | ↓ Decreases for central-to-peripheral data | ↑ Increases (Peripheral listens at every CI if SL=0) | Applications requiring responsive bidirectional communication. | |
Supervision Timeout | Shorter (e.g., 200ms) | ~ No direct impact on typical performance | ~ No direct impact (Affects link loss detection) | Faster detection of lost connections. | |
Longer (e.g., 4s) | ~ No direct impact on typical performance | ~ No direct impact (More tolerant to temporary interference) | Environments with intermittent connectivity. | ||
ATT MTU | Larger (e.g., 247 bytes) | ↑ Significantly Increases (More data per GATT op) | ~ Can slightly improve for large data transfers (Fewer ops) | ~ Minimal direct impact, efficient use of radio time | High throughput applications (FOTA, data streaming). |
Smaller (e.g., 23 bytes default) | ↓ Decreases (More overhead) | ~ Can slightly worsen for large data transfers | ~ Less efficient use of radio time | Legacy devices, very small infrequent data. |
- Connection Interval (CI):
- Definition: The time between the start of two consecutive connection events. During a connection event, the central and peripheral can exchange data packets.
- Range: 7.5 ms to 4.0 seconds, in multiples of 1.25 ms.
- Impact:
- Shorter CI: Increases opportunities for data exchange, potentially improving throughput and reducing latency. However, it also increases power consumption as both devices wake up more frequently.
- Longer CI: Reduces power consumption but decreases throughput and increases latency.
- Configuration:
min_interval
andmax_interval
are proposed by the central or peripheral during connection establishment or update.
- Slave Latency (Peripheral Latency):
- Definition: The number of consecutive connection events the peripheral (slave) is allowed to skip if it has no data to send to the central (master). The central must still send packets at every connection event.
- Range: 0 to 499. A value of 0 means the peripheral must listen at every connection event.
- Impact:
- Allows the peripheral to save power by sleeping for longer periods, even with a short connection interval, if it doesn’t have data to send.
- If the peripheral has data to send, it must wake up at the next connection event.
- Increases latency for data sent from the central to the peripheral if the peripheral is skipping connection events. Data from peripheral to central is not delayed by slave latency itself (as the peripheral wakes when it has data).
- Supervision Timeout:
- Definition: The maximum time between two successfully received data packets before the connection is considered lost.Range: 100 ms to 32.0 seconds, in multiples of 10 ms.Rule: Must be greater than
(1 + slaveLatency) * connectionInterval * 2
.Impact:- Shorter Timeout: Faster detection of lost connections.Longer Timeout: More tolerant to temporary interference or missed packets, but slower detection of actual link loss.
- Definition: The maximum time between two successfully received data packets before the connection is considered lost.Range: 100 ms to 32.0 seconds, in multiples of 10 ms.Rule: Must be greater than
sequenceDiagram participant M as Central Device (Master) participant S as Peripheral Device (Slave) participant T as Time (ms) Note over M,S: Connection Interval (30ms) rect rgb(230, 245, 255) Note over M,S: Connection Event 1 M->>S: Data Packet S->>M: Response Packet M->>S: Data Packet S->>M: Response Packet end Note over T: 30ms rect rgb(230, 245, 255) Note over M,S: Connection Event 2 M->>S: Data Packet S->>M: Response Packet end Note over T: 60ms rect rgb(255, 230, 230) Note over M,S: Connection Event 3 <br>(Skipped - Slave Latency) M->>M: Attempt to send Note over S: Slave in sleep mode end Note over T: 90ms rect rgb(255, 230, 230) Note over M,S: Connection Event 4 <br>(Skipped - Slave Latency) M->>M: Attempt to send Note over S: Slave in sleep mode end Note over T: 120ms rect rgb(230, 245, 255) Note over M,S: Connection Event 5 <br>(Slave Active Again) M->>S: Data Packet S->>M: Response Packet end Note over T: 150ms rect rgb(255, 245, 230) Note over M,S: Supervision Timeout <br>(600ms from last response) Note over M,S: If no response received within <br>timeout period,<br>connection is considered lost end Note over M,S: Connection Parameters:<br>- Connection Interval: 30ms<br>- Slave Latency: 2 (can skip up to 2 events)<br>- Supervision Timeout: 600ms
- ATT MTU (Attribute Protocol Maximum Transmission Unit):
- Definition: The maximum size of an ATT protocol data unit (PDU) that can be exchanged between the client and server. The actual GATT characteristic value that can be sent in a single notification or write operation is
ATT_MTU - 3
bytes (1 byte for ATT opcode, 2 bytes for attribute handle). For notifications, this becomesATT_MTU - 3
. For indications, it’sATT_MTU - 3
. For Write Commands, it’sATT_MTU - 3
. For Write Requests, it’sATT_MTU - 3
. - Range: Default is 23 bytes. Can be negotiated up to a maximum defined by the Bluetooth controller (e.g., 517 bytes for ESP32).
- Impact:
- Larger MTU: Allows more application data to be sent in a single GATT operation, reducing overhead and significantly increasing throughput.
- Negotiation: Initiated by the GATT client after connection using an “Exchange MTU Request.” The server responds with its supported MTU, and the smaller of the two (client’s requested vs. server’s supported) becomes the effective MTU for the connection.
- Definition: The maximum size of an ATT protocol data unit (PDU) that can be exchanged between the client and server. The actual GATT characteristic value that can be sent in a single notification or write operation is
- PHY (Physical Layer):
- BLE 4.x originally defined a single PHY: 1 Mbps (
LE 1M
). - BLE 5.0 introduced two new PHYs:
- LE 2M PHY: 2 Mbps symbol rate. Doubles the on-air data rate compared to 1M PHY, potentially doubling throughput or halving transmission time (reducing power consumption for the same amount of data). Range is slightly reduced compared to 1M PHY.
- LE Coded PHY (S=2 or S=8): Uses Forward Error Correction (FEC) to significantly increase range (up to 4x compared to 1M PHY). Data rate is reduced (500 kbps for S=2, 125 kbps for S=8). S=2 means 2 symbols per bit, S=8 means 8 symbols per bit.
- Impact:
LE 2M PHY
: Best for high throughput, short to medium range.LE 1M PHY
: Standard, good balance of range and throughput.LE Coded PHY
: Best for long-range applications where throughput is less critical.
- Negotiation: Devices can switch PHYs during a connection if both support it.
- BLE 4.x originally defined a single PHY: 1 Mbps (
PHY Layer | Symbol Rate | Typical Data Rate (Uncoded) | Key Advantage | Range Comparison (vs. 1M) | Power Consumption (for same data amount) | Best Suited For |
---|---|---|---|---|---|---|
LE 1M PHY | 1 Msps (Mega symbols per second) | ~1 Mbps | Standard, good balance of range and throughput. Widest compatibility. | Baseline (1x) | Moderate | General purpose BLE applications, legacy device compatibility. |
LE 2M PHY | 2 Msps | ~2 Mbps | Higher throughput, shorter transmission time for same data. | Slightly Reduced (~0.8x – 0.9x) | Lower (due to shorter on-air time) | High throughput applications (FOTA, data streaming) at short to medium range. |
LE Coded PHY (S=2) | 1 Msps | ~500 kbps (effective, due to coding) | Increased range using Forward Error Correction (FEC). | Increased (e.g., ~2x) | Higher (longer on-air time due to coding) | Longer range applications where throughput is secondary, moderate interference. |
LE Coded PHY (S=8) | 1 Msps | ~125 kbps (effective, due to coding) | Significantly increased range using stronger FEC. | Significantly Increased (e.g., up to ~4x) | Highest (longest on-air time due to coding) | Very long range applications, challenging RF environments, low data rate requirements. |
- Data Length Extension (DLE):
- Definition: A BLE 4.2 feature (and part of BLE 5.0) that allows the Link Layer PDU payload to be increased from the original 27 bytes up to 251 bytes. The actual user data within this payload also increases.Impact:
- Significantly increases throughput by sending more application data per Link Layer packet, reducing overhead from headers and inter-frame spacing.Works independently of ATT MTU but complements it. A large ATT MTU requires DLE to be effective, as GATT operations are fragmented into Link Layer packets.
- Definition: A BLE 4.2 feature (and part of BLE 5.0) that allows the Link Layer PDU payload to be increased from the original 27 bytes up to 251 bytes. The actual user data within this payload also increases.Impact:
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%% graph TD subgraph Device_A ["Sending Device (e.g., ESP32 Peripheral)<br><br>"] direction TB APP_DATA["Application Data<br>(e.g., Sensor Reading, File Chunk)"]:::appdata subgraph GATT_Layer [GATT Layer] direction TB GATT_OP["GATT Operation<br>(e.g., Notification, Write Command)"]:::gatt ATT_PDU["ATT PDU<br>(Opcode + Handle + Value)<br>Value size <= (ATT_MTU - 3 bytes)"]:::att end subgraph L2CAP_Layer [L2CAP Layer] direction TB L2CAP_SDU["L2CAP SDU (Service Data Unit)<br>Contains ATT PDU(s)"]:::l2cap L2CAP_PDU["L2CAP PDU<br>(L2CAP Header + SDU/Fragment)"]:::l2cap end subgraph Link_Layer ["Link Layer (LL)"] direction TB LL_SDU["LL SDU (Service Data Unit)<br>Contains L2CAP PDU(s)"]:::ll LL_PDU["LL Data PDU<br>(LL Header + Payload + MIC)<br>Payload size up to 251 bytes with DLE"]:::ll_dle end subgraph PHY_Layer ["Physical Layer (PHY)"] direction TB MOD_SYMBOLS["Modulated RF Symbols<br>(e.g., GFSK at 1M, 2M, or Coded rates)"]:::phy end APP_DATA --> GATT_OP; GATT_OP --> ATT_PDU; ATT_PDU --> L2CAP_SDU; L2CAP_SDU --> L2CAP_PDU; L2CAP_PDU --> LL_SDU; LL_SDU --> LL_PDU; LL_PDU --> MOD_SYMBOLS; end MOD_SYMBOLS -- Transmitted Over Air --> RF_CHANNEL["RF Channel (2.4 GHz)"]; RF_CHANNEL -- Received By --> Device_B_PHY[Receiving Device PHY]; Device_B_PHY --> Device_B_LL[Receiving Device Link Layer<br>Demodulates, Checks MIC, Reassembles]; Device_B_LL --> Device_B_L2CAP[Receiving Device L2CAP<br>Reassembles L2CAP PDUs]; Device_B_L2CAP --> Device_B_GATT[Receiving Device GATT<br>Parses ATT PDU, Delivers to App]; Device_B_GATT --> Device_B_APP[Receiving Device Application]; classDef appdata fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef gatt fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef att fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef l2cap fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef ll fill:#EDE9FE,stroke:#5B21B6,stroke-width:1px,color:#5B21B6; classDef ll_dle fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef phy fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; classDef channel fill:#E5E7EB,stroke:#6B7280,stroke-width:1px,color:#374151; classDef receiving_device fill:#F0F9FF,stroke:#0284C7,stroke-width:1px,color:#075985; class RF_CHANNEL channel; class Device_B_PHY,Device_B_LL,Device_B_L2CAP,Device_B_GATT,Device_B_APP receiving_device;
- Packets per Connection Event:
- The Bluetooth controller in the ESP32 (and the peer device) can send and receive multiple Link Layer packets within a single connection event, as long as the connection event duration allows.
- Impact: Maximizing packets per connection event is crucial for throughput. This is often handled by the controller based on available time in the connection event.
- Notifications vs. Indications:
- Notifications (
Notify
): Asynchronous messages sent from server to client. They are unacknowledged at the GATT layer. Faster, lower overhead, but less reliable (if a packet is lost at the Link Layer and not retransmitted, the GATT layer won’t know). - Indications (
Indicate
): Similar to notifications, but require an acknowledgment (confirmation) from the GATT client. More reliable, but higher latency and lower throughput due to the acknowledgment round trip. - Impact: For high throughput of sensor data, notifications are generally preferred. For critical commands or data where delivery must be confirmed, indications are better.
- Notifications (
Feature | Notifications (Notify) | Indications (Indicate) |
---|---|---|
GATT Operation | Asynchronous message from Server to Client. | Asynchronous message from Server to Client, requiring Client confirmation. |
Acknowledgment | Unacknowledged at the GATT layer. (Link Layer may provide reliability via retransmissions). | Requires an acknowledgment (Confirmation PDU) from the GATT Client. |
Reliability (GATT Layer) | Less reliable; if a packet is lost and not recovered by Link Layer, GATT Client won’t know. | More reliable; Server knows if the Client received the Indication. |
Throughput | Higher (no acknowledgment overhead). Can send multiple notifications without waiting. | Lower (acknowledgment round trip introduces delay; Server typically waits for confirmation before sending next Indication). |
Latency | Lower (no waiting for acknowledgment). | Higher (due to acknowledgment round trip). |
Flow Control | No inherent GATT-level flow control; relies on Link Layer or application-level mechanisms if needed. | Implicit flow control; Server won’t send next Indication until current one is confirmed. |
Typical Use Cases | Streaming sensor data, frequent updates where occasional loss is tolerable or handled by higher layers, telemetry. | Critical commands, data where delivery confirmation is essential, state changes that must be acknowledged. |
ESP-IDF API | esp_ble_gatts_send_indicate(…, need_confirm = false) or implicitly when characteristic property is Notify. Some stacks might have a dedicated notify function. In ESP-IDF, often esp_ble_gatts_send_indicate with need_confirm=false is used for notifications. | esp_ble_gatts_send_indicate(…, need_confirm = true) |
- CPU Load and Task Priorities:
- The ESP32’s application code and the Bluetooth stack (Bluedroid/NimBLE) run as tasks under FreeRTOS.
- Impact: If the application consumes too much CPU time or if Bluetooth tasks are starved (e.g., due to higher priority application tasks running for too long), it can lead to missed connection events, delayed processing of BLE events, and overall performance degradation.
- Optimization: Ensure Bluetooth tasks (
BT Controller
,BTU
,BTC
, etc.) have appropriate priorities (typically higher than general application tasks that are not time-critical). Offload lengthy processing from BLE event callbacks to separate lower-priority tasks.
- RF Environment and Interference:
- BLE operates in the 2.4 GHz ISM band, which is shared with Wi-Fi, microwave ovens, and other devices.
- Impact: Interference can cause packet loss, leading to retransmissions, increased latency, and reduced throughput. Physical obstructions can also weaken the signal.
- Optimization: Proper antenna design and placement. In noisy environments, LE Coded PHY might be necessary for robust communication, albeit at lower speeds. Adaptive Frequency Hopping (AFH) in BLE helps mitigate some interference.
Calculating Theoretical Maximum Throughput
A simplified view:
Throughput ≈ (ApplicationDataPerPacket * PacketsPerSec)
ApplicationDataPerPacket
: Depends on(ATT_MTU - 3)
and how it fits into DLE-enabled Link Layer packets.PacketsPerSec
: Depends onConnectionInterval
and how many LL packets can be sent per CI.
For example, with an ATT_MTU of 247 bytes, DLE enabling ~244 bytes of LL payload (after L2CAP header), and a 2M PHY:
One GATT notification carries 247 – 3 = 244 bytes of app data.
This fits into one LL packet if DLE supports it.
If CI = 7.5ms (133 connection events/sec), and we can send, say, 6 LL packets per CI (bidirectional, so maybe 3 in one direction effectively for a stream):
Throughput ≈ 244 bytes/packet * 3 packets/CI * 133 CI/sec ≈ 97,332 bytes/sec ≈ 778 kbps
This is a rough estimate; real-world throughput is often lower due to protocol overhead, retransmissions, and processing limits.
Practical Examples
Prerequisites:
- Two ESP32 boards (one central, one peripheral) for testing connection parameter updates.
- VS Code with the Espressif IDF Extension.
- ESP-IDF v5.x.
Example 1: Configuring Connection Parameters for Higher Throughput
This example shows how a peripheral can request specific connection parameters aimed at higher throughput. The central device ultimately decides the parameters but usually considers the peripheral’s request.
Peripheral Code Snippet (main/peripheral_main.c):
(Focus on the connection parameter update request part. Assume basic GATT server setup from previous chapters.)
// ... (Includes and basic GATT server setup) ...
#define PERIPH_TAG "BLE_PERIPH_TP"
// Desired connection parameters for high throughput
#define PREFERRED_MIN_CONN_INTERVAL_TP 0x0006 // 7.5ms (6 * 1.25ms)
#define PREFERRED_MAX_CONN_INTERVAL_TP 0x000C // 15ms (12 * 1.25ms)
#define PREFERRED_SLAVE_LATENCY_TP 0 // No slave latency for high throughput
#define PREFERRED_CONN_TIMEOUT_TP 500 // 5s (500 * 10ms)
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
switch (event) {
// ... (other GAP events like advertising start) ...
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(PERIPH_TAG, "Update connection params status = %d, min_int = %d, max_int = %d, conn_int = %d, latency = %d, timeout = %d",
param->update_conn_params.status,
param->update_conn_params.min_int,
param->update_conn_params.max_int,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
case ESP_GAP_BLE_CONNECT_EVT: // This event is for esp_ble_gap_start_advertising() related connections
ESP_LOGI(PERIPH_TAG, "Peripheral ESP_GAP_BLE_CONNECT_EVT, conn_id %d", param->connect.conn_id);
// Request connection parameter update after connection
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
conn_params.min_int = PREFERRED_MIN_CONN_INTERVAL_TP;
conn_params.max_int = PREFERRED_MAX_CONN_INTERVAL_TP;
conn_params.latency = PREFERRED_SLAVE_LATENCY_TP;
conn_params.timeout = PREFERRED_CONN_TIMEOUT_TP;
esp_err_t ret = esp_ble_gap_update_conn_params(&conn_params);
if (ret == ESP_OK) {
ESP_LOGI(PERIPH_TAG, "Requested connection parameter update.");
} else {
ESP_LOGE(PERIPH_TAG, "Failed to request connection parameter update: %s", esp_err_to_name(ret));
}
break;
// ... (other GAP events like disconnect) ...
default:
break;
}
}
// In app_main, after BLE init and GATTS registration:
// esp_ble_gap_register_callback(gap_event_handler);
// ... start advertising ...
Central Code Snippet (main/central_main.c):
A central device can also initiate a connection parameter update. If the peripheral requests it, the central’s stack usually handles the ESP_GAP_BLE_CONN_PARAM_UPDATE_REQ_EVT
(if Bluedroid is configured to pass this to the app, often it’s handled by the stack). The central can accept or reject the parameters. For simplicity, we’ll focus on the peripheral requesting. The central will see the ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT
once parameters are changed.
Build, Flash, Observe:
- Build and flash both peripheral and central code to respective ESP32s.
- Monitor serial output on both.
- When the peripheral connects to the central, it will request a connection parameter update.
- Observe the
ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT
on both devices to see the negotiated parameters. They should be close to the peripheral’s request if the central accepts them.
Tip: A shorter connection interval (e.g., 7.5ms to 15ms) and zero slave latency are good starting points for high throughput.
Example 2: Configuring Connection Parameters for Low Latency
For low latency, the approach is similar: short connection interval, zero slave latency.
Peripheral Code Snippet (main/peripheral_main.c):
Use similar connection parameters as for high throughput, as low latency often benefits from frequent communication opportunities.
// Desired connection parameters for low latency
#define PREFERRED_MIN_CONN_INTERVAL_LL 0x0006 // 7.5ms
#define PREFERRED_MAX_CONN_INTERVAL_LL 0x000A // 12.5ms
#define PREFERRED_SLAVE_LATENCY_LL 0 // No slave latency
#define PREFERRED_CONN_TIMEOUT_LL 200 // 2s
// ... rest of the GAP handler and connection parameter update request is similar to Example 1 ...
The key is that with slave_latency = 0
and a short CI, the peripheral is always listening at each interval, minimizing delays for both incoming and outgoing data.
Example 3: Enabling Data Length Extension (DLE) and 2M PHY
DLE and PHY changes can be initiated by either central or peripheral after connection.
Common Code Snippet (can be in peripheral or central, in gap_event_handler
ESP_GAP_BLE_CONNECT_EVT
or similar post-connection event):
// ... (inside a GAP event, after connection is established, e.g. ESP_GAP_BLE_CONNECT_EVT for peripheral,
// or ESP_GATTC_OPEN_EVT for client after connection) ...
// Assuming `param->connect.conn_id` or `param->open.conn_id` is available as `conn_id`
// and `param->connect.remote_bda` or `param->open.remote_bda` is available as `remote_bda`
// 1. Set preferred PHY (e.g., request 2M PHY)
ESP_LOGI(PERIPH_TAG, "Setting preferred PHY to 2M");
esp_err_t phy_ret = esp_ble_gap_set_prefer_phy(remote_bda, ESP_BLE_GAP_PHY_2M_PREF_MASK, ESP_BLE_GAP_PHY_2M_PREF_MASK, ESP_BLE_GAP_PHY_OPTIONS_PREF_NO_CODED);
if (phy_ret == ESP_OK) {
ESP_LOGI(PERIPH_TAG, "Preferred PHY set request sent.");
} else {
ESP_LOGE(PERIPH_TAG, "Failed to set preferred PHY: %s", esp_err_to_name(phy_ret));
}
// 2. Enable Data Length Extension (DLE)
// For ESP32, max TX/RX octets is 251. Max TX/RX time is 2120us.
// These values are for the Link Layer PDU payload.
uint16_t tx_octets = 251; // Desired TX octets
uint16_t tx_time = 2120; // Desired TX time in microseconds
ESP_LOGI(PERIPH_TAG, "Requesting DLE: TX Octets=%d, TX Time=%dus", tx_octets, tx_time);
esp_err_t dle_ret = esp_ble_gap_set_pkt_data_len(remote_bda, tx_octets);
if (dle_ret == ESP_OK) {
ESP_LOGI(PERIPH_TAG, "DLE request sent.");
} else {
ESP_LOGE(PERIPH_TAG, "Failed to set DLE: %s", esp_err_to_name(dle_ret));
}
// Corresponding event handlers for DLE and PHY updates:
// In gap_event_handler:
// case ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT:
// ESP_LOGI(PERIPH_TAG, "PHY update complete. status = %d, TX_PHY = %d, RX_PHY = %d",
// param->phy_update.status, param->phy_update.tx_phy, param->phy_update.rx_phy);
// break;
// case ESP_GAP_BLE_SET_PKT_DATA_LEN_COMPLETE_EVT:
// ESP_LOGI(PERIPH_TAG, "Set Packet Data Length complete. status = %d, tx_octets = %d, tx_time = %d, rx_octets = %d, rx_time = %d",
// param->pkt_data_len_cmpl.status,
// param->pkt_data_len_cmpl.params.tx_len, param->pkt_data_len_cmpl.params.tx_time,
// param->pkt_data_len_cmpl.params.rx_len, param->pkt_data_len_cmpl.params.rx_time);
// break;
ATT MTU Exchange (typically initiated by GATT Client/Central):
After connection, the GATT client should request an MTU exchange.
Central (GATT Client) Code Snippet (in gattc_profile_event_handler
ESP_GATTC_OPEN_EVT
):
// ... (inside ESP_GATTC_OPEN_EVT, after esp_ble_gattc_open is successful) ...
// param->open.conn_id is the connection ID
// param->open.gatt_if is the GATTC interface
ESP_LOGI(CENTRAL_TAG, "Requesting MTU exchange. conn_id: %d", param->open.conn_id);
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req(param->open.gatt_if, param->open.conn_id);
if (mtu_ret == ESP_OK) {
ESP_LOGI(CENTRAL_TAG, "MTU exchange request sent.");
} else {
ESP_LOGE(CENTRAL_TAG, "Failed to send MTU exchange request: %s", esp_err_to_name(mtu_ret));
}
// Handle MTU exchange response in gattc_profile_event_handler:
// case ESP_GATTC_CFG_MTU_EVT:
// if (param->cfg_mtu.status == ESP_GATT_OK) {
// ESP_LOGI(CENTRAL_TAG, "MTU configured to: %d for conn_id: %d", param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
// // Store this MTU value for calculating max characteristic data size
// } else {
// ESP_LOGW(CENTRAL_TAG, "MTU configuration failed, status: %d for conn_id: %d", param->cfg_mtu.status, param->cfg_mtu.conn_id);
// }
// break;
Build, Flash, Observe:
- Flash peripheral and central.
- Connect them.
- Observe logs for PHY update, DLE update, and MTU configuration events.
- To test throughput, implement a characteristic on the peripheral that can send a large amount of data via notifications. The central should subscribe and measure the time taken to receive a known amount of data.
Warning: Not all peer devices (e.g., older smartphones) support 2M PHY or DLE. The negotiation process will settle on parameters supported by both devices. Always check the status in
ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT
andESP_GAP_BLE_SET_PKT_DATA_LEN_COMPLETE_EVT
.
Variant Notes
- 2M PHY: Available on S3, C3, C6, H2. Limited/variable on original ESP32.
- LE Coded PHY (Long Range): Available on C6, H2. Not on original ESP32, S3, C3.
- DLE & Large MTU: Generally well-supported across all BLE-capable ESP32 variants (original ESP32, S3, C3, C6, H2).
ESP32 Variant | Data Length Extension (DLE) | Max ATT_MTU (Typical) | LE 1M PHY | LE 2M PHY | LE Coded PHY (Long Range) | Bluetooth Version |
---|---|---|---|---|---|---|
ESP32 (Original/Classic) | Yes | ~517 bytes | Yes | Limited/Variable (Check Revision) | No | BLE 4.2 (Some revisions have BLE 5.0 features) |
ESP32-S2 | No Bluetooth Hardware | N/A | ||||
ESP32-S3 | Yes | ~517 bytes | Yes | Yes | No | BLE 5.0 |
ESP32-C3 | Yes | ~517 bytes | Yes | Yes | No | BLE 5.0 |
ESP32-C6 | Yes | ~517 bytes | Yes | Yes | Yes (S=8) | BLE 5.3 |
ESP32-H2 | Yes | ~517 bytes | Yes | Yes | Yes (S=8) | BLE 5.3 |
Always consult the latest ESP-IDF documentation and the specific datasheet for the ESP32 variant and revision you are using, as capabilities can evolve.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
Ignoring Peer Device Capabilities | Feature negotiation (DLE, PHY, MTU) fails or settles on less optimal parameters. Throughput/latency not as expected. Connection may be unstable if unsupported features are forced. |
Check Completion Events: Monitor ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT, ESP_GAP_BLE_SET_PKT_DATA_LEN_COMPLETE_EVT, ESP_GATTC_CFG_MTU_EVT to see actual negotiated parameters.
Graceful Degradation: Design application to work with baseline features if advanced ones are not supported by the peer. Test with Target Peers: Verify performance with the specific central/peripheral devices your application will interact with. |
Unrealistic Connection Interval Expectations | Actual connection interval is longer than requested, especially with smartphone centrals. This impacts latency and maximum possible throughput. |
Provide a Range: When requesting connection parameters, specify a reasonable min/max interval range.
Understand Central Policies: Smartphone OSes manage connection intervals to balance performance and power for multiple apps/connections. They may not always grant the shortest possible interval. Monitor Actual Interval: Check ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT for the actual conn_int. |
Misunderstanding ATT_MTU vs. DLE | Throughput doesn’t improve much even after increasing ATT_MTU, or is unexpectedly low. |
Enable Both: For high throughput, ensure DLE is enabled (allowing >27 byte LL payloads) AND ATT_MTU is increased. A large ATT PDU needs DLE to avoid excessive Link Layer fragmentation.
Verify DLE Status: Check ESP_GAP_BLE_SET_PKT_DATA_LEN_COMPLETE_EVT for effective tx_octets / rx_octets. |
Blocking Operations in BLE Callbacks | Missed connection events, delayed BLE operations, stack instability, watchdog timeouts, poor responsiveness. |
Offload Processing: Keep GAP and GATTS/GATTC event callbacks very short. Offload lengthy computations or I/O operations to separate FreeRTOS tasks.
Use Queues/Events: Pass data or signals from callbacks to worker tasks using FreeRTOS queues or event groups. |
Insufficient Task Priority for Bluetooth Stack | Erratic BLE behavior, connection drops, poor performance, especially under high application load. |
Respect Default Priorities: ESP-IDF Bluetooth tasks (Controller, Host stack like BTU/BTC) have carefully chosen default priorities. Avoid setting application tasks higher unless absolutely necessary and well-understood.
Yield CPU: Ensure high-priority application tasks yield the CPU regularly (vTaskDelay, etc.) to allow Bluetooth tasks to run. |
Not Accounting for Protocol Overhead | Calculated theoretical throughput is much higher than achieved practical throughput. |
Consider All Layers: Remember overhead from L2CAP headers, Link Layer headers, MIC, inter-frame spacing, and GATT opcodes/handles.
Factor in Retransmissions: RF interference can cause packet loss and retransmissions, reducing effective throughput. Bidirectional Traffic: Throughput is shared in bidirectional communication. |
Suboptimal Antenna Design/Placement | Poor range, frequent disconnections, low RSSI, high packet error rate, inconsistent performance. |
Follow Antenna Guidelines: Adhere to manufacturer guidelines for PCB antenna keep-out areas, grounding, and impedance matching.
Avoid Obstructions: Minimize metallic objects or enclosures near the antenna. Orientation: Antenna orientation can impact signal strength. Test in various orientations. |
Exercises
- Throughput Measurement Test:
- Set up two ESP32s, one as a GATT server (peripheral) and one as a GATT client (central).
- On the server, create a characteristic. Implement a function that, when triggered (e.g., by a write to another characteristic or a button press), sends a large amount of data (e.g., 1MB) via notifications on this characteristic as quickly as possible. Use a large ATT_MTU and enable DLE/2M PHY.
- On the client, subscribe to notifications. Measure the time it takes to receive the full 1MB of data. Calculate the throughput.
- Experiment with different connection intervals (e.g., 7.5ms, 15ms, 30ms, 75ms) and observe the impact on throughput and power consumption (if you have measurement tools).
- Latency Measurement Test (Round Trip):
- Using the same two ESP32s:
- Client sends a small “ping” message by writing to a characteristic on the server.
- Server, upon receiving the write, immediately sends a “pong” notification back to the client on a different characteristic.
- Client measures the time from sending the write to receiving the notification.
- Configure connection parameters for low latency (short CI, zero slave latency).
- Measure this round-trip latency multiple times and calculate an average. Observe the impact of different connection intervals.
- Using the same two ESP32s:
- PHY Layer Impact on Range/RSSI:
- If you have ESP32-C6 or ESP32-H2 boards (which support Coded PHY):
- Set up a simple peripheral advertising and a central scanning.
- Have the central connect and then attempt to switch to LE 1M PHY, LE 2M PHY, and LE Coded PHY (S=8).
- Monitor the RSSI (Received Signal Strength Indicator) reported by the central for each PHY at various distances.
- Observe how 2M PHY might have slightly lower RSSI at the same distance compared to 1M, and how Coded PHY maintains a connection at distances where 1M/2M PHY might fail or have very low RSSI. (This is a qualitative test without precise RF measurement tools).
- If you have ESP32-C6 or ESP32-H2 boards (which support Coded PHY):
Summary
- BLE performance optimization involves balancing throughput, latency, and power consumption.
- Key configurable parameters include Connection Interval, Slave Latency, Supervision Timeout, ATT_MTU, PHY layer, and Data Length Extension (DLE).
- Shorter Connection Intervals generally improve throughput and latency but increase power consumption.
- Larger ATT_MTU and enabled DLE are crucial for high throughput by increasing data payload per packet.
- LE 2M PHY offers higher speeds at the cost of slightly reduced range, while LE Coded PHY significantly increases range at lower speeds.
- Notifications are faster but less reliable than indications for data transfer.
- Efficient application design, including proper task prioritization and non-blocking callbacks, is vital.
- ESP32 variants differ in their support for advanced BLE 5.0 features like 2M PHY and Coded PHY.
Further Reading
- Bluetooth Core Specification: https://www.bluetooth.com/specifications/specs/
- ESP-IDF API Reference: https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/ble/gatt_server_service_table
- Espressif Application Notes and Tech Blogs: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/esp_gatts.html
