This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Extremely Slow BLE Throughput on the nRF52840, using the BMD-340

Hi all,

We are using the BMD-340 from uBlox, which runs the nRF52840 SoC.

On this BLE Peripheral, we are running the ble peripheral demo project ble_app_uart (pca10056), which has only been lightly modified for our uses. We are using the nRF device to receive file transfer packets over BLE, and to send them to our processor over UART (115200 baud). We are using SDK version 16.0.0, and SoftDevice S140 v7.0.1. Despite our best efforts, we are seeing very slow throughput data rates. 

We are trying to perform a file transfer (a binary update via a bootloader) over Bluetooth, implementing the xModem protocol (a quick google search will explain it - not sure if I'm allowed to post links). In essence, this a simple file transfer protocol, in which the host sends 128 byte packets, and the client sends back a 1 byte response (ACK, NAK, etc) for every received packet. The host will not send the next packet until it received an ACK. Note: after accounting for encoding overhead, our payload packets (coming from the host, i.e. the tablet) are 155 bytes, and the responses from the client (the bootloader/BLE peripheral) are 27 bytes. 

For reference, when completing this process over a straight serial UART cable (115200 baud), we see a data rate of ~7.50-8.50KB/s.

On an iPad Air 2 running iOS 13.5.1, we saw an effective data rate of 2.08kB/s

On a Samsung Galaxy Tab A running Android 9, we saw an effective data rate of 0.71KB/s.

This is completely baffling to our team - but admittedly, our team is fairly inexperienced with programming with the BLE spec. Please see below for the changes we've made to the demo ble_app_uart project.

We have made the following changes to the main.c:

#define UART_TX_BUF_SIZE 1024 /**< UART TX buffer size. */

#define UART_RX_BUF_SIZE 1024 /**< UART RX buffer size. */

^ increased to 1024 from 256

-> We also added a simple function that wraps all of our received messages over BLE with COBS encoding (a very simple framing scheme) before transmitting those messages over the UART line.

-> We also have a very simple function that checks if the incoming message is of a specific message type

We don't have any reason to believe that these changes are what's causing the extremely low throughput.

And we have made the following changes to the sdk_config.h:

#define NRF_SDH_BLE_GATT_MAX_MTU_SIZE 247

#define NRF_SDH_BLE_GAP_DATA_LENGTH 251

^ both of these values were 160 originally

Other than the above, nothing else is currently different from the demo project. 

We've also tried changing many other settings (Min/Max Connection Interval, MTU size, GAP Event Length, etc) but none of them yielded very positive results, nor were they very consistent across devices. And yes we know we can try increasing the size of our payload messages, however this will be integrated into a consumer product, and must therefor work with a variety of mobile phones and tablets (some of which won't support MTU's greater than 185 - the iPad Air 2 for example).

Any and all help would be greatly appreciated!! We're even curious why the iPad Air 2 is almost 3x faster than the Galaxy TabA 10.1, since the iPad is about 5 years older.

Thank you in advance! Feel free to ask for additional information, I will be happy to oblige where possible.

Cheers,

Ty

  • Hello,

    Thanks for your detailed problem description.  I actually think the "ACKing" of every 128-byte is what's causing the main bottleneck - the central  will effectively be limited to sending 128 bytes of payload on every other connection event if it has to wait for the target to ACK the packet. First connection event to send the data, then the next to receive the ack, and so on.

    I haven't worked with this particular protocol, but is there a way you can increase the required ACK interval? In our DFU transport we usually compute and report the checksum once for every 4096-byte data object. And we are using the GATT write without response procedure for the data transfer (GATTC Characteristic Value Write Without Response).

    For reference, when completing this process over a straight serial UART cable (115200 baud), we see a data rate of ~7.50-8.50KB/s.

     This is close to the same throughput we get with our BLE DFU implementation. It varies a bit depending on which phone is used.

    Also, I have modified the ble_app_uart example to measure notification throughput which is similiar to write without response except that it's sent from the GATT server. It can be helpful if you want to play around with different connection paramaters and see what effect it has. All you have to do on the client side is to enable notifications (in nrf connect app or similiar). The nRF5 will log the measured transfer rate over RTT.

    Throughput test example:

    nrf5_sdk_17.0.2 - ble_app_uart_throughput.zip

    Cheers,

    Vidar

  • Hi Vidar,

    Thank you for your prompt and detailed response! It had been one of our main concerns that the ACK'ing of every packet was causing the low throughput - we just didn't expect it to have such a dramatic impact.

    And yes, we have tried modifying the protocol to be "batch-based" - we tried ACKing only after receiving 4KB worth of payload data (32 packets). This meant our central device would blast out 32 155 byte packets, before the peripheral would respond with its 27 byte ACK. The issue with this was that we saw the BLE Peripheral (the BMD-340 with the nRF52840)  simply disconnect and crash... the initial fatal error we were seeing was a disconnect, then a reconnect attempt immediately followed by the BLE_ERROR_GATTS_SYS_ATTR_MISSING. We resolved that by adding in:

    err_code = sd_ble_gatts_sys_attr_set(m_conn_handle, NULL, 0, 0);
    APP_ERROR_CHECK(err_code);

    After the BLE_GAP_EVT_CONNECTED case within ble_evt_handler(). Now, this resolved the crashing, however our BLE Peripheral was still sometimes disconnecting and reconnecting, and after the reconnect our central and peripheral couldn't communicate properly, despite the negotiation seemingly going smoothly. We lowered our batch size to only 10 packets instead of 32, which seemed to mostly resolve the issue - but we were still skeptical, since we lack an understanding of why this change worked, or why it even failed to begin with.

    Since the purpose of the BLE Peripheral in this context is to facilitate the file transfer for a bootloader, reliability was considered of utmost importance, and the strange (and very unreliable) results we were seeing was considered not worth the risk, despite the improved throughput (we were seeing up to ~1.4KB on the Android tablet - didn't test on the iPad Air 2). Do you have any suggestions or ideas that immediately come to your mind? I can recreate the problem again and give you a more detailed explanation, if that helps. I should also mention that we arbitrarily added a 5-10ms (we experimented) delay between each packet, since we were worried about "flooding" the GATT client with write notifications. But that didn't seem to have any impact on the behavior.

    Thanks again Vidar! I'll take a look at the throughput project you sent as well. I hope you have a great rest of your day.

    Cheers,

    Ty

  • Hi,

    The BLE_ERROR_GATTS_SYS_ATTR_MISSING error indicates that the FW app tried to send a notification right after connection before it had been re-enabled by the client. I see now that this is something that can happen with the ble_app_uart example because it doesn't clear its internal "is_notification_enabled" flag on re-connect like it should. CCCD settings are only supposed to stay valid for the duration of a connection session when not bonded. I.e. notification(s) must always be re-enabled on re-connect.

    That said, it's perfectly ok to call sd_ble_gatts_sys_attr_set() on connect, this will make it so that you get  NRF_ERROR_INVALID_STATE error which is ignored by default, instead of the BLE_ERROR_GATTS_SYS_ATTR_MISSING error you get when sys attributes are not initialized.

    I'm more surprised about the disconnect. The softdevice stack has no problem to keep up with the incoming data so I'm wondering if there is something else that's crashing in the app. Are you catching all code assertions with with the Error module? As you can see from the link, the default error handling is to try recovering with a system reset.

    Cheers,

    Vidar

Related