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

BLE NUS concurrency, collisions

Hello,

I'm new to BLE and have a general question about concurrency and collisions. Sorry if a repeat question. I'm developing for nRF52832 using SDK 15.3 and S112 v6.1.1. I'm using three nRF52 Dev Kits for testing.

My application is based on ble_app_uart example, using NUS, with UART removed.

Generally, once connected to a central (iOS device):

- PPI and GPIOTE are used to sense toggle of a pin and call ble_nus_data_send() to send a few bytes of data to the central. These events occur randomly, separated by at least a few seconds.

- From time to time, the connected BLE central will send a message to the peripheral (by writing value for the appropriate characteristic) which results in a call to nus_data_handler(). For testing, this happens every 5 seconds. This, in turn, causes a message to be sent back to the central by calling ble_nus_data_send().

What happens if the application calls ble_nus_data_send() while the Softdevice is receiving a message from the central? Will the call to sd_ble_gatts_hvx() fail with NRF_ERROR_BUSY or NRF_ERROR_INVALID_STATE? Or will the Softdevice manage the collision?

What happens if the application issues call to ble_nus_data_send() while the Softdevice is processing a previously issued call to ble_nus_data_send()? Same as above?

Does the peripheral application have to manage this? Can it be managed safely by simply enclosing calls to ble_nus_data_send() within a while loop like this:

uint32_t err_code;
do {
     err_code = ble_nus_data_send(&m_nus, message, &length, m_conn_handle);
} while (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_BUSY || err_code == NRF_ERROR_NOT_FOUND)
APP_ERROR_CHECK(err_code);

Many thanks,

Tim

  • Hello Tim,

    I think many of your questions are answered by the following:
    The SoftDevice's timing-critical tasks takes priority over any application layer task. For example, when in a connection and the connection interval is coming up, the SoftDevice will take control of the CPU for the duration of the event. This means that all application layer code happening at that time will be interrupted.
    This means that if your GPIOTE event triggers while the SoftDevice has the CPU, then it will not be processed until the CPU is released by the SoftDevice.
    So, to summarize: You do not need to account for the correct timing in your application code ( in regards to SoftDevice API function calls ), but you will have to account for the possibility that you application will be interrupted at any time, when the SoftDevice is active.

    Can it be managed safely by simply enclosing calls to ble_nus_data_send() within a while loop like this:

    I suppose this is one way of doing it, but it is not what I would recommend. Instead, you should populate the existing error handler with what you would like it to do when such and such error's occur.
    For example, in the case of "NRF_ERROR_BUSY" you might want the application to try the call again at another time, etc.
    The default behavior of the default error handler is to reset the device - which might not be the best solution in all cases.

    The downsides of your proposed do-while loop is that it might make the call to ble_nus_data_send happen multiple times in rapid succession ( depending on your configuration ), and it is also a very hard-coded way to use the error codes. Furthermore, if this code snippet is somehow called when you are not in a connection ( invalid connection handler ), you will be stuck in this loop since the return value forever will be NRF_ERROR_INVALID_STATE. This can for example happen if your device is disconnected from the peer right before it is time to send a notification.
    Please also bear in mind that the ble_nus_data_send might return the error codes from sd_ble_gatts_hvx, so this approach does not scale very well either.

    Please do not hesitate to ask if anything should be unclear, or if you should encounter any more issues or questions!

    Best regards,
    Karl

  • Thank you Karl.

    Yes, I understand that the SoftDevice will block/delay application interrupts. My application is designed to account for this by using PPI, TIMERs and GPIOTE for critical timing.

    I'm more interested in understanding concurrency relating to ble_nus_data_send() and nus_data_handler() (for receiving data from the central). I understand that ble_nus_data_send() calls sd_ble_gatts_hvx() to notify the NUS attribute value. I assume sd_ble_gatts_hvx() is non-blocking, so it initiates the operation and returns immediately. When the operation completes, a BLE_GATTS_EVT_HVN_TX_COMPLETE event will be generated and that causes the NUS data handler to be called with event BLE_NUS_EVT_TX_RDY.

    Am I correct to assume that if my application calls ble_nus_data_send() before a previous call to ble_nus_data_send() has completed, that sd_ble_gatts_hvx() will return NRF_ERROR_BUSY, which will in turn be returned by ble_nus_data_send() to the application?

    In looking at the documentation for sd_ble_gatts_hvx(), it says this for NRF_ERROR_BUSY:

    For BLE_GATT_HVX_INDICATION Procedure already in progress. Wait for a BLE_GATTS_EVT_HVC event and retry.

    NUS uses BLE_GATT_HVX_NOTIFICATION. Will sd_ble_gatts_hvx() return NRF_ERROR_BUSY if BLE_GATT_HVX_NOTIFICATION is in progress? Or will it return NRF_ERROR_RESOURCES instead? From sd_ble_gatts_hvx() documentation:

    Too many notifications queued. Wait for a BLE_GATTS_EVT_HVN_TX_COMPLETE event and retry.

    I understand the drawbacks you point out about using a do-while loop for handling these collisions. I could also set up a queue for calls to ble_nus_data_send() and whenever ble_nus_data_send() returns NRF_ERROR_BUSY or NRF_ERROR_RESOURCES, add the relevant data to the queue. Then I could extend nus_data_handler() to watch for BLE_NUS_EVT_TX_RDY events and check the queue and if non-empty, call ble_nus_data_send() with data for the next item in the queue and remove from the queue. A reasonable approach?

    Also related to this, if the SoftDevice is processing a notification initiated by the central (which will result in a call to nus_data_handler() with event BLE_NUS_EVT_RX_DATA when complete), will a call to ble_nus_data_send() also be blocked and return NRF_ERROR_BUSY or NRF_ERROR_RESOURCES?

    Many thanks Karl.

    Tim

  • Hello Tim,

    Tim said:
    Yes, I understand that the SoftDevice will block/delay application interrupts. My application is designed to account for this by using PPI, TIMERs and GPIOTE for critical timing.

    Great, I am glad to hear that!  

    Tim said:
    I assume sd_ble_gatts_hvx() is non-blocking, so it initiates the operation and returns immediately.

    Yes, the function will queue the notification for sending - it needs to wait for the next connection event - and return with NRF_SUCCESS, or in the case of other errors it will return with an error code, this is correct.

    Tim said:
    When the operation completes, a BLE_GATTS_EVT_HVN_TX_COMPLETE event will be generated and that causes the NUS data handler to be called with event BLE_NUS_EVT_TX_RDY.

    This is also correct, and the BLE_GATTS_EVT_HVN_TX_COMPLETE will contain the number of notifications sent. Since you are working out of the ble_app_uart example this is already set up for you, so that you event is passed to the correct event handler.

    Tim said:
    Am I correct to assume that if my application calls ble_nus_data_send() before a previous call to ble_nus_data_send() has completed, that sd_ble_gatts_hvx() will return NRF_ERROR_BUSY, which will in turn be returned by ble_nus_data_send() to the application?

    No, this is not always the case. Depending on what your queue size is, the function might just queue another notification to be send during the upcoming connection interval. Therefore, you may for example instead receive an NRF_ERROR_RESOURCES instead, if you are trying to queue to many notifications.
    You can see all the possible return values of the ble_nus_data_send in the sd_ble_gatts_hvx documentation.
    Please make sure that you are checking the return values of Nordic driver function calls, and that you have DEBUG defined in your preprocessor defines, as shown in the included image:

    This will let you see any error generated outputted to the logger, before the device resets.

    Tim said:
    I understand the drawbacks you point out about using a do-while loop for handling these collisions. I could also set up a queue for calls to ble_nus_data_send() and whenever ble_nus_data_send() returns NRF_ERROR_BUSY or NRF_ERROR_RESOURCES, add the relevant data to the queue.

    Tim said:

    A reasonable approach?

    Yes, you could absolutely implement it like this. However, please note this exempt from the sd_ble_gatts_hvx documentation:

    The number of Handle Value Notifications that can be queued is configured by ble_gatts_conn_cfg_t::hvn_tx_queue_size When the queue is full, the function call will return NRF_ERROR_RESOURCES. A BLE_GATTS_EVT_HVN_TX_COMPLETE event will be issued as soon as the transmission of the notification is complete.

    What is your current hvn_tx_queue_size set to? This way, you might not need to implement this queuing yourself. 
    Additionally, you might also increase your maximum MTU size, if you intend to send a lot of data during each connection interval.
    From a power-optimization perspective, it is better to have a large payload and a longer connection interval, if this is acceptable for your application. This may drastically reduce active radio time, and as such drastically reduce necessary active radio time.

    Tim said:
    Also related to this, if the SoftDevice is processing a notification initiated by the central (which will result in a call to nus_data_handler() with event BLE_NUS_EVT_RX_DATA when complete), will a call to ble_nus_data_send() also be blocked and return NRF_ERROR_BUSY or NRF_ERROR_RESOURCES?

    There will be no application call to ble_nus_data_send() while the SoftDevice is receiving data from the central - the SoftDevice has complete control over the CPU, and is not yielding any time to the application as long as it needs to keep receiving. So, if you have an interrupt happening at the exact time of a connection event, no new notification will be queued until after the SoftDevice is finished and has yielded the CPU back to the application.
    Just in case, I will mention that while notifications and indications both happens 'as soon as possible' they both still wait for the next connection event - because it is only in this connection event that the peer device will be listening or sending anything.

    Please do not hesitate to ask if anything should be unclear!

    Best regards,
    Karl

  • Thank you again Karl. I appreciate your explanation. I think hvn_tx_queue_size is 1. For S112 of SDK 15.3, BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT is 1. MTU is set low at 23 because we need to support older iOS devices and, also, my application only sends small amounts of data in either direction (<20 bytes).

    In testing I've not yet encountered an NRF_ERROR_RESOURCES error, so I'm uncertain if I should be implementing the queue myself. Will test further.

    Thank you again for helping me understand. Much appreciated.

    Tim

Related