BLE Timing Variation While Streaming Live Microphone Data

Hi nordic,

I am streaming live microphone PCM data over BLE. The microphone data is continuously captured and stored in a buffer. Whenever 12 KB of audio data is available, I send that data over BLE to the connected device.

However, I am observing that sending the data over BLE takes a large and inconsistent amount of time.

For example, I measured the time taken by the send_data_chunk() function, which sends the data over BLE.
the api i am using is :

// Send data in 244-byte chunks
// ptr_buffer contains the live data 
// no_of_bytes is 12288 
// creating the chunk of 244 among 12288 , and sending over ble.
void send_data_chunk(struct bt_conn *conn, uint8_t *ptr_buffer, uint16_t no_of_bytes)
{
    static uint8_t first_time = 0;
    if (!conn)
    {
        printk("Error: No BLE connection.\n");
    }

    // Calculate chunk size based on negotiated MTU
    uint16_t chunk_size = 247 - 3; // MTU - ATT Header (3 bytes) //current_mtu
    if (chunk_size == 0)
    {
        printk("Error: Invalid chunk size (MTU too small).\n");
    }

    uint16_t data_sent = 0;
    int err = 0;
    uint32_t start_time = k_uptime_get_32();

    while (data_sent < no_of_bytes)
    {
        uint16_t bytes_to_send = MIN(chunk_size, no_of_bytes - data_sent);

        err = bt_gatt_notify(conn, &custom_service.attrs[DATA_NOTIFY_INDEX],
                             ptr_buffer + data_sent, bytes_to_send);

        if (err)
        {
            if (err == -ENOMEM || err == -EAGAIN)
            {
                printk("BLE notification queue full or busy (err %d). Retrying...\n", err);
                k_sleep(K_MSEC(1)); // Sleep briefly to allow BLE stack to process
                continue; // Try sending the same chunk again
            }
            else
            {
                printk("Notification failed (err %d) for chunk at offset %u.\n", err, data_sent);
                // return err; // Other error, stop sending this block
            }
        }
        data_sent += bytes_to_send;
    }

    uint32_t end_time = k_uptime_get_32();
    uint32_t diff_time = end_time - start_time;
    if (diff_time >= 10)
    {
        printk("Total send_data_chunk time for %u bytes: %u ms\n", no_of_bytes, end_time - start_time);
    }
    if (first_time == 0)
    {
        first_time = 1;
        printk("Total send_data_chunk time for %u bytes: %u ms\n", no_of_bytes, end_time - start_time);
    }
 
}

and the prj configurations i am using
CONFIG_BT=y                            
CONFIG_BT_PERIPHERAL=y                
CONFIG_BT_DEVICE_NAME="FYZKS_WDS"      
CONFIG_BT_GATT_CLIENT=y                
CONFIG_BT_ZEPHYR_NUS=y  

CONFIG_BT_USER_DATA_LEN_UPDATE=y        
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251      
CONFIG_BT_BUF_ACL_RX_SIZE=251          
CONFIG_BT_BUF_ACL_TX_SIZE=251          
CONFIG_BT_L2CAP_TX_MTU=247            
CONFIG_BT_USER_PHY_UPDATE=y            
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n


# Connection interval settings
CONFIG_BT_PERIPHERAL_PREF_MIN_INT=6    
CONFIG_BT_PERIPHERAL_PREF_MAX_INT=8    
CONFIG_BT_PERIPHERAL_PREF_LATENCY=0  
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=400  
CONFIG_BT_CTLR_TX_PWR_PLUS_4=y  

Below are the timing logs (internal debugging messages removed for clarity):
Total send_data_chunk time for 12288  bytes: 873 ms
Total send_data_chunk time for 12288 bytes: 887 ms
Total send_data_chunk time for 12288 bytes: 896 ms
Total send_data_chunk time for 12288 bytes: 888 ms
Total send_data_chunk time for 12288 bytes: 903 ms

From these logs, I observe:

  • Sending 12 KB of data takes around 880–900 ms

  • the timing is not constant. Sometimes the transmission completes in around 800 ms, while at other times it takes up to 1800 ms for similar amounts of data.(when flashed the same code again repeatedly whether we are achieving the same time or not )

Parents Reply Children
  • Hi sigurd
    Description:
    I am developing a high-throughput application on the nRF5340 (custom board/DK) aiming for 250–400 kbps.
    While MTU and PHY updates are successful, the Data Length Extension (DLE) update consistently fails, bottlenecking the throughput.

    Environment:

    SoC: nRF5340

    SDK: nRF Connect SDK (v3.1.0)

    Peer Device: Samsung A54 (Android 14)

    Current Status & Test Logs:

    MTU Negotiation: Success (MTU 247 / Payload 244).

    PHY Negotiation: Success (2M PHY enabled).

    DLE Negotiation: Fails. Calling bt_conn_le_data_len_update returns Error -5 (EIO).

    Observed Timings:

    Data Size: 32,000 bytes

    Total Time: 5,587 ms

    Effective Throughput: ~45 kbps

    Analysis: The 45 kbps speed at a 7.5ms interval suggests the link is falling back to 27-byte fragments because DLE is not active.

    Code Implementation:
    I am using an asynchronous queuing model with bt_gatt_notify_cb and a TX callback to manage in_flight packets.
    The in_flight count stays at my limit (55), proving the stack is saturated, but it is processing tiny packets.

    Configuration (prj.conf):

    Makefile
    CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
    CONFIG_BT_BUF_ACL_TX_SIZE=251
    CONFIG_BT_BUF_ACL_RX_SIZE=251
    CONFIG_BT_USER_DATA_LEN_UPDATE=y
    CONFIG_BT_CTLR_PHY_2M=y
    CONFIG_BT_BUF_ACL_TX_COUNT=80
    Attempted Fixes:

    Created child_image/hci_rpmsg.conf with matching DLE/ACL buffer sizes to ensure the Network Core is configured.

    Performed "Pristine Builds" to ensure the Controller child-image is rebuilt.

    Verified with nRF Connect Mobile; the phone supports DLE, but the nRF5340 returns Error -5 locally.

    Question:
    Why would the nRF5340 Controller return -EIO (-5) when the App Core requests BT_LE_DATA_LEN_PARAM_MAX,
    even though the ACL_TX_SIZE and DATA_LENGTH_MAX are set to 251 in both the App and Net core configurations?
    How can I verify if the Network Core is actually accepting these memory allocations?
    How to achieve max throughput of 250kbps?


  • Hi!
    In your application directory, create this file:

    your_app/
    ├── prj.conf
    ├── sysbuild.conf              ← (optional, for sysbuild-level config)
    └── sysbuild/
        └── ipc_radio.conf         ← Network core overlay

    sysbuild/ipc_radio.conf contents:

    CONFIG_BT_BUF_ACL_TX_SIZE=251
    CONFIG_BT_BUF_ACL_RX_SIZE=251
    CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

    Verify Your App Core prj.conf

    # DLE & Buffers
    CONFIG_BT_BUF_ACL_TX_SIZE=251
    CONFIG_BT_BUF_ACL_RX_SIZE=251
    CONFIG_BT_USER_DATA_LEN_UPDATE=y
    
    # PHY
    CONFIG_BT_CTLR_PHY_2M=y
    
    # Throughput tuning
    CONFIG_BT_BUF_ACL_TX_COUNT=20
    CONFIG_BT_L2CAP_TX_MTU=247
    CONFIG_BT_ATT_TX_COUNT=20
    
    # Connection parameters
    CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n

    Do a clean build, after building, check the network core's generated config is correctly set.

    in build/ipc_radio/zephyr/.config

    Search for:

    CONFIG_BT_CTLR_DATA_LENGTH=y          ← must be 'y'
    CONFIG_BT_CTLR_DATA_LENGTH_MAX=251    ← must be 251
    CONFIG_BT_BUF_ACL_TX_SIZE=251

    If CONFIG_BT_CTLR_DATA_LENGTH_MAX=27 or CONFIG_BT_CTLR_DATA_LENGTH is not set, the overlay isn't being picked up.

Related