BLE UART data loss at speed

Hi,

We have an application based on BLE_UART TXing sensor data.

Most of our sensors are read through PPI with minimal code interaction (except for packet counters etc.)

It's running on our own hardware using the 52833 and S140 SD.

It's sending PDU's of 232 bytes to nRF toolbox running on an iPad (BLE 5.0)

The data rate is currently 25 PDU/s and it works quite reliably at this rate.

 

We want to increase this rate to 62.5 PDU/s.

However at this rate we lose a large proportion of the packets and also receive duplicates.

We also get an BLE_GAP_EVT_DISCONNECTED event after 90 seconds or so.

We have found the highest reliable data rate is 35 PDU/s

Even at 62.5 PDU/s that is only 116kbits/s well inside BLE 5.0 capability.

 

What can be done to fix this?

 

Thanks

 

 

 

Here is our setup parameters:

 

#define APP_BLE_CONN_CFG_TAG            1

#define APP_FEATURE_NOT_SUPPORTED       BLE_GATT_STATUS_ATTERR_APP_BEGIN + 2

#define DEVICE_NAME                     "Device8701"

#define NUS_SERVICE_UUID_TYPE           BLE_UUID_TYPE_VENDOR_BEGIN

#define APP_BLE_OBSERVER_PRIO           3

#define APP_ADV_INTERVAL                64

#define APP_ADV_TIMEOUT_IN_SECONDS      18000

#define MIN_CONN_INTERVAL               MSEC_TO_UNITS(20, UNIT_1_25_MS)

#define MAX_CONN_INTERVAL               MSEC_TO_UNITS(75, UNIT_1_25_MS)

#define SLAVE_LATENCY                   0

#define CONN_SUP_TIMEOUT                MSEC_TO_UNITS(4000, UNIT_10_MS)

#define FIRST_CONN_PARAMS_UPDATE_DELAY  APP_TIMER_TICKS(5000)

#define NEXT_CONN_PARAMS_UPDATE_DELAY   APP_TIMER_TICKS(30000)

#define MAX_CONN_PARAMS_UPDATE_COUNT    3

  • Hi Kenneth,

    Below is our updated code.

    If the API returns NRF_ERROR_RESOURCES it simply waits for BLE_GATTS_EVT_HVN_TX_COMPLETE in the form of a flag b_ready_to_tx to set and just re-sends the message. The packet data is created in another thread so could be modified by the time it is re-sent, but we will implement a buffer to ensure no data is lost, if needed.

    We did tests at 16, 20 and 24ms.

    The packet loss seen in each test are as follows:

    • 56 Packets Lost when IMU_READ_RATE = 24ms
    • 1264 Packets Lost when IMU_READ_RATE = 20ms
    • 5204 Packets Lost when IMU_READ_RATE = 16ms

    Attached are the wire shark sniffs for each, can you please have a look and let me know what we can do to improve?

    Some questions:

    1. Can we increase the SD buffer?

    2. How can we change the connection interval?

    Thanks

    BTW for this data here are our connection inteval settings:

    #define MIN_CONN_INTERVAL MSEC_TO_UNITS(20, UNIT_1_25_MS) /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */

    #define MAX_CONN_INTERVAL MSEC_TO_UNITS(75, UNIT_1_25_MS) /**< Maximum acceptable connection interval (75 ms), Connection interval uses 1.25 ms units. */

    Are this limited by the iPad?

    void nus_data_send(uint8_t * message, uint16_t length)
    {
    static uint32_t err_code;
    // If the message is too long set it to max
    if ( length > BLE_NUS_MAX_DATA_LEN )
    {
    length = BLE_NUS_MAX_DATA_LEN;
    }

    b_ready_to_tx = false;
    err_code = ble_nus_data_send(&m_nus, (uint8_t *)message, &length, m_conn_handle);

    if ( err_code == NRF_ERROR_INVALID_STATE )
    {
    err_invalid_state_count++;
    }
    else if ( err_code == NRF_ERROR_RESOURCES )
    {
    err_resources_count++;

    while( ! b_ready_to_tx )
    {
    }
    err_code = ble_nus_data_send(&m_nus, (uint8_t *)message, &length, m_conn_handle);
    }
    else if ( err_code == NRF_ERROR_NOT_FOUND )
    {
    err_not_found_count++;
    }
    else
    {
    APP_ERROR_CHECK(err_code);
    }
    }

     

     

  • I can see from the log that the More Data bit is not set as frequently as I would expect it to be set, the More Data bit should be set if the peripheral have more data to be sent. If it's not set this indicate to the central that there is no more data to be fetched.

    The problem based on this I believe is that you are not calling ble_nus_data_send() as frequently as you should be able to, you can call ble_nus_data_send() several times until NRF_ERROR_RESOURCES, in which case you can wait for BLE_GATTS_EVT_HVN_TX_COMPLETE event (as you do) and then you can call ble_nus_data_send() several times again. Alternatively instead (and possibly the easiest) may just be to call ble_nus_data_send() several times from a 10ms app_timer timeout handler, and call it until NRF_ERROR_RESOURCES, in which case you try again in 10ms. As long as the app_timer timeout handle is shorter than the connection interval that should work well and keep the softdevice buffer full.

    If changing the implementation as suggested above doesn't help, can you share your sdk_config.h file?

    Edit, you can find these two links useful in general (though I think the biggest problem at the moment is that the More Data bit is not set as expected):
    https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf 
    https://www.novelbits.io/bluetooth-5-speed-maximum-throughput/ 

    Best regards,
    Kenneth

  • Hi Kenneth,

    I guess the more data bit is not being set because we only have 120 Kbit/s even at 4ms, thats 6% of the max 2 Mbit/s capacity. 

    We added a (32 for now) deep buffer and it seems to work at 4ms without error.

    Our strategy is to sent the data every 4ms, if we get NRF_ERROR_RESOURCES we wait till we get BLE_GATTS_EVT_HVN_TX_COMPLETE.

    Depending on the length of the error/s our buffer will fill up in the background.

    So once BLE_GATTS_EVT_HVN_TX_COMPLETE clears we will start sending packets in quick  succession (as you suggested) till our buffer is empty.

    Only if our buffer is blown will we lose data.

    Below is our code (with some debug) our sdk_config.h is attached. Could you sanity check it?

    Two questions....

    1. How do we set BLE to longer distance/lower speed?

    2. Is there a better way to call ble_nus_data_send() rather than a look in main()?

    Thanks

     

    void nus_data_send(uint8_t * message, uint16_t length)
    {
    static uint32_t err_code;
    // If the message is too long set it to max
    if ( length > BLE_NUS_MAX_DATA_LEN )
    {
    length = BLE_NUS_MAX_DATA_LEN;
    }
    b_ready_to_tx = false;
    err_code = ble_nus_data_send(&m_nus, (uint8_t *)message, &length, m_conn_handle);

    if ( err_code == NRF_ERROR_INVALID_STATE )
    {
    err_invalid_state_count++;
    }
    else if ( err_code == NRF_ERROR_RESOURCES )
    {
    err_resources_count++;
    debug_tx [debug_tx_ctr][0] = packet_ctr;
    debug_tx [debug_tx_ctr][1] = buffer_read_idx;
    debug_tx [debug_tx_ctr][2] = buffer_write_idx;

    if( debug_tx_ctr < 1000 )
    {
    debug_tx_ctr++;
    }

    while( ! b_ready_to_tx )
    {
    }
    err_code = ble_nus_data_send(&m_nus, (uint8_t *)message, &length, m_conn_handle);
    }
    else if ( err_code == NRF_ERROR_NOT_FOUND )
    {
    err_not_found_count++;
    }
    else
    {
    APP_ERROR_CHECK(err_code);

    }

    }

     

  • There is a nice tutorial here on how to improve throughput:
    https://www.novelbits.io/bluetooth-5-speed-maximum-throughput/

    Longer range typically means using coded phy instead of 2Mbps, since coded phy have better sensitivity, but drawback is use 8times longer on-air (=higher risk for interference, higher power consumpion and lower throughput). Using higher output power can also increase range.

    You can call ble_nus_data_send() from anywhere below interrupt priorty 4:
    https://infocenter.nordicsemi.com/topic/sds_s132/SDS/s1xx/processor_avail_interrupt_latency/exception_mgmt_sd.html

    The important is that you call it sufficiently often that it's called between BLE connection intervals. How you implement this is entirely up to you.

    Kenneth

Related