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

BLE UART - ble_nus_string_send() connection interval & data transfer

Hey guys,

From what I understand, the minimum connection interval on say, an iPhone, is 20ms. Also, you can send up to 6 packets of 20 bytes within a connection interval. So that's 120 bytes every 20ms.

I've been playing around with the UART peripheral example in SDK 14. I notice that the function ble_nus_string_send() sends a max length of 20 bytes.

What I want to know is how exactly are the data packets and connection interval handled? For example, if I call ble_nus_string_send() once with 20 bytes, will it just go ahead and send the 20 bytes and take up the entire 20ms connection interval?

Consequently, if I call ble_nus_string_send() 6 times with 20 bytes, will all that data transfer within the one connection interval?

Just trying to wrap my head around how this all works. I'll search for some explanations on the SDK and tutorials, but thought it might be more time effective to ask on here.

Thanks!

  • This calls for wider discussion about "how BLE stack looks and work" but in short:

    • Connection interval is controlled by Master (on Link Layer) aka Central (on GAP layer).
    • Slave (aka Peripheral) can try to negotiate different parameters but it's always Master which decides.
    • As far as I know iOS devices default to 30ms connection interval, only HID applications can get lower.
    • Now because BLE stack is fairly recent and modern so the upper layers (on which you operate = you speak about 20B per packet which corresponds to APP layer bandwidth on top of GATT when (G)ATT uses default MTU length of 23B) act independently on Link Layer. Nordic BLE stack is following this design so you cannot be sure when exactly your GATT packets departure (not only because of LL timing but also because of packet losses and re-transmission) but you simply stack as many of them as possible (= if you have data ready and if your stack support it) and you just get notifications when packets are acknowledged by LL as received by the opposite side.
    • Now the rest is optimization game which you can play if you control at least one part of the link (in this case nRF5x side):
    • If you need maximized throughput then you should use PDU length extension (on Link Layer) and MTU extension (on (G)ATT layer) and you should fine-tune these parameters to the connection interval (surprisingly having long interval with large PDUs might give you better effective throughput then super-short intervals, read some blog posts by Nordic on throughput topic).
    • Finally if you use NUS from Nordic nRF5 SDK then simply make sure that you use the largest (G)ATT MTU size available o the link at given moment and that you push all data you have ready to the SoftDevice (stack) until you get "no TX buffers" error response. Then simply wait for "TX complete" event and continue.

    The biggest problem is that there is no magic set of parameters unless you perfectly control both sides of the link. If you target single mobile device (like iOS devices of certain version) then still you can come up with optimal sequence of steps (like connection parameters change, PDU and MTU extension) but once you want to support more (typically all mobile devices which have BLE capabilities) then you typically need to default to certain "average" configuration (like let the mobile keep its connection interval - 30ms for iOS and 48.75 for Android, try to extend MTU and PDU and just push packets of (MTU-3) size over GATT layer of SoftDevice API (Notifications or Write commands depending on Server/Client role).

    Finally back to Nordi BLE UART Service (NUS) example code in SDK: as you can see from its implementation in \components\ble\ble_services\ble_nus\components\ble\ble_services\ble_nus\ble_nus.c it simply calls sd_ble_gatts_hvx function and returns error/result code from the stack so it's up to you to manage all fragmentation and queuing in your application FW.

    • I understand the connection interval is controlled by the central. I have it currently set up so that the peripheral demands a min/max of 20ms connection interval. When using the nRF Sniffer and the UART app on Android I can see that it goes through several connection updates to eventually agree upon the 20ms connection interval (from what I've read online, iOS minimum is 20ms)

    • I understand what you're saying about unexpected timing of packets going out because of all the stuff the stack handles in the background. I'll try just queueing as many packet transfers as possible. I'm sampling data at 100Hz and want to make sure the BLE buffer can be sent in time before the double buffer gets full

    • I'll look into the GAAT MTU stuff...I don't really know what you're referring to at the moment

    • What I am a little confused about though is that when using the nRF sniffer and sending 6 successive 20 byte packets using ble_nus_string_send(), it looks like the time stamps between each packets works out to around 20ms between each packet. I thought that you are able to send up to 6 packets within one connection interval, but it seems to be only sending one packet per interval. Any suggestions?

    --------------- UPDATE ------------------

    See attached images. When the GAP_EVENT_LENGTH is set to 3 (default UART example), each packet is sent out every 20ms+. When I set it to 16, it seems to send out all 6 packets within the 20ms connection interval which is within spec...

    --------------- UPDATE 2 ------------------

    I tried again with GAP EVENT set to 3 and a 20ms connection interval. All other times I do see that each packet transmits at 20ms and not 40-45 like the last image...so I don't really know what happened there. See attached images. I've also included images for the last LL_CONNECTION_UPDATE message before it gets accepted to show that the connection interval is set to 20ms. So it does seem like this parameter in the config file is solving the issue. Will have to do further reading to fully understand why though.

    Also, for reference, here are the Wireshark data dump files.

  • Probably just last bullet point opens new question, here is my humble answer: are you sure that you called ble_nus_string_send function as far as it was returning NRF_SUCCESS? When you receive BLE_GATTS_EVT_HVN_TX_COMPLETE event and what you do when stack call-back gives you this event value? Are you sure that you'r connection bandwidth setting on Nordic side is HIGH so you could see multiple PDUs per interval?

  • To be honest, I just copied and pasted ble_nus_string_send() 6 times to see what it would do. I'm just working off the ble_app_uart example. Nothing is handling the TX_COMPLETE event as far as I can see.

    Basically in my nus handler, I call ble_nus_string_send() six times trying to send over some constant data packets of 20 bytes.

    Not sure about the connection bandwidth...I will look into it further...

  • In case of such test simply just push some consecutive sequence of bytes (like 0x00 01 02 03...) split into 20-byte blocks through that NUS function and paste sniffer trace here for analysis. It should really give multiple packets per interval and if not then at least we can see More Data (to send) bit in PDU header so it will indicate if it's phone's stack which blocks more packets or if simply Nordic stack doesn't have more things to send (which would indicate either problem in bandwidth settings or in queuing of NUS function calls in your FW).

Related