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

How to send a sequence of indications?

I need to indicate a characteristic value that is longer than the MTU. Using the pc-ble-driver, no problem. Windows supports semaphores and I can call sd_ble_gatts_hvx(), wait on a semaphore, and release the semaphore in the BLE_GATTS_EVT_HVC event and then indicate the next hunk.

That doesn't work when you write code for the chip as the SDK and SoftDevice do not support semaphores or an equivalent to semaphore functionality in any form. So how does one indicate a characteristic value longer than the MTU? It may require a sequence of N indications to get all the data across.

I have tried the following:

call err_code = sd_ble_gatts_hvx() and if the err_code = NRF_ERROR_BUSY then call sd_ble_gatts_hvx() again until one gets success. If I replace the semaphore with this while loop in the pc-ble-driver, it works. However, when I do the same thing on the chip, I get the NRF_ERROR_BUSY and then it hangs. It seems one cannot repeatedly call this method when busy even though the documentation says you can. How can I solve this issue on the nRF51822?

I do not know if the problem persists on the nRF52840. My pc-ble-driver code is for the nRF52840 dongle where both semaphores and the busy loop work. I have not used the pc-ble-driver on a nrf51822 chip.

I put the method that sends the sequence of indications in a single-shot timer, called from the BLE_GATTS_EVT_WRITE event handler (the central peer writes commands to the peripheral to send data - no I do NOT want to have the peer do reads here!!!!). It helped a little bit in the sense I got three indications in the sequence sent but then it stopped.

Thanks for any solution to this problem.

Parents
  • Hi,

    sd_ble_gatts_hvx() may get stuck with the NRF_ERROR_BUSY error if you're calling it from an interrupt context as it may block the BLE_GATTS_EVT_HVC Softdevice event from ever coming through. A solution to this can be to send the data from your main loop instead, and either use a flag or a semaphore to know when you have received the BLE_GATTS_EVT_HVC event.

    /* Flag to wait for pending 
     * Handle Value Confirmation 
     */
    static volatile bool wait_for_hvc; 
    
    /* This function is invoked in the Softdevice interrupt context
       unless you are using a scheduler */
    static void on_ble_evt(ble_evt_t * p_ble_evt)
    {
        uint32_t err_code;
    
        switch (p_ble_evt->header.evt_id)
        {
            case BLE_GATTS_EVT_HVC:
                wait_for_hvc = false;
        ..
    ..
    
    int main(void)
    {
        ...
        for (;;)
        {
            if (<send data>)
            {
                send_data();
                wait_for_hvc = true;

  • How do you use a semaphore in s130 SDK 12.3.0 - as I stated in the initial post I can't do that. There is no such thing in SDK 12.3.0! My ideal solution would be to create a semaphore, indicate the data, wait on that semaphore, and release the semaphore in the BLE_GATTS_EVT_HVC. But that is not possible.

    That for-loop looks deadly - will completely floor the processor; running an infinite loop as fast as the processor can go.

    I do not think I am calling it from an interrupt context. In the BLE_GATTS_EVT_WRITE event where I 'call' the send data method, all I do is invoke a timer. When the timer expires, it calls the send data. It is there where I get three indications out and then it stops. 

    So given you are using a full-throttle for-loop that tags the CPU I suppose I could do the same thing in my sender. Just do a while loop on the wait_for_hvc. That's what a semaphore does after all without flooring the processor because the semaphore stops the thread from eating up CPU.

    Now if I do that, what happens if a get an NRF_ERROR_BUSY? Do I have to call again or is it so that the first call will eventually complete if I wait long enough? Otherwise it seems like a catch-22. I can't retry a BUSY because it might become stuck, but if I get a busy and then do a full throttle while loop wait, the call will never complete because of the BUSY.

    None of this makes too much sense. If I tag the processor in that for or while loop, how is it going to get anything done?

  • Ok, maybe it doesn't make sense to use an actual semaphore as this is an interrupt driven system without a task scheduler. But you can still use a boolean flag like I suggested.

    brianreinhold said:
    That for-loop looks deadly - will completely floor the processor; running an infinite loop as fast as the processor can go.

     That's true only if you don't call the sleep function after processing the if statements.

    brianreinhold said:
    Now if I do that, what happens if a get an NRF_ERROR_BUSY? Do I have to call again or is it so that the first call will eventually complete if I wait long enough?

     Yes, you can implement retries. It should not get stuck if you are doing it from the main context.

  • Since I cannot send data until the client asks for it via a BLE_GATTS_EVT_WRITE event, I invoke a timer and when the timer completes, I invoke the call. I do not know if that is in the main context or not. If the only way to call it from the main context is in that infinite for-loop that means basically I have to put my whole command handler in there and wait on it to be triggered. In the pc-ble-driver case I do, but I use a semaphore so it works like a charm. Now I guess the only way to do this with some efficiency is to wait on a volatile boolean triggered in the BLE_GATTS_EVT_WRITE event and after the first trigger reset the sleep to a much shorter time.

    This sounds messy and inefficient. But you still have not answered my question about NRF_ERROR_BUSY. If I get that response is it the case I MUST recall the method? If I do not, the data will not be indicated. OR can I just wait?

    By the way, does an nrf_delay_ms(50); count as a sleep? My understanding is that this is also a full throttle tag of the CPU with a bunch of NOPs. It does not solve the problem but I only have 50 ms so far.

Reply
  • Since I cannot send data until the client asks for it via a BLE_GATTS_EVT_WRITE event, I invoke a timer and when the timer completes, I invoke the call. I do not know if that is in the main context or not. If the only way to call it from the main context is in that infinite for-loop that means basically I have to put my whole command handler in there and wait on it to be triggered. In the pc-ble-driver case I do, but I use a semaphore so it works like a charm. Now I guess the only way to do this with some efficiency is to wait on a volatile boolean triggered in the BLE_GATTS_EVT_WRITE event and after the first trigger reset the sleep to a much shorter time.

    This sounds messy and inefficient. But you still have not answered my question about NRF_ERROR_BUSY. If I get that response is it the case I MUST recall the method? If I do not, the data will not be indicated. OR can I just wait?

    By the way, does an nrf_delay_ms(50); count as a sleep? My understanding is that this is also a full throttle tag of the CPU with a bunch of NOPs. It does not solve the problem but I only have 50 ms so far.

Children
  • Since I cannot send data until the client asks for it via a BLE_GATTS_EVT_WRITE event, I invoke a timer and when the timer completes, I invoke the call. I do not know if that is in the main context or not.

    Is it the SDK app timer? In that case, you will have to use it with the app scheduler as Einar explains in the nRF5 SDK Scheduler Tutorial. The timer handler will be invoked in an interrupt context if not.

    This sounds messy and inefficient. But you still have not answered my question about NRF_ERROR_BUSY. If I get that response is it the case I MUST recall the method? If I do not, the data will not be indicated. OR can I just wait?

    The data will only be sent if the call returned NRF_SUCCESS

    By the way, does an nrf_delay_ms(50); count as a sleep?

    No, it's a busy wait. The CPU will execute a bunch of NOPs as you said.

  • Simple question that's not so simple - How does one invoke a sleep as you reference in this system?

  • Now that just adds to the confusion. I have long looked at this function as a hopeful solution to many problems but I have never been able to understand what an 'application event'  was. In most contexts that I have worked in, 'application' is what I write on top of an SDK. But if that is true, I would expect I could define an event but I cannot. So how do I wake this thing up? Once called does it wake on every event such as a BLE_GATTS_EVT_WRITE?

    Also, enumerations like BLE_GATTS_EVT_WRITE are events, not interrupts as I understand it. Well they are are called events. Maybe not? When I receive a GATTS or GAP event, am I back on the main context? Or am I only in the main context if I am in the main function? So am I forced to be that for loop which is now occupied by the 'power_manage()' method?

    If I were sending notifications instead of indications would it still be so complicated? Many vendors send sequences of data but I don't know if they use Nordic chips. But they also use notifications instead of indications.

  • As stated in the API documentation for sd_app_evt_wait(), an application event is either an application interrupt or a pended interrupt when the interrupt is disabled. An example of this is when the RTC1 interrupt gets triggered inside the app_timer module. This interrupt would then make your program return from the sd_app_evt_wait() function.

    brianreinhold said:
    Also, enumerations like BLE_GATTS_EVT_WRITE are events, not interrupts as I understand it.

     Yes, you've understood it correctly. These are internal Softdevice events.

    The Softdevice triggers the SOFTDEVICE_EVT_IRQ interrupt to let the application know when new Softdevice events are available, and the application will then retrieve the the events by calling  sd_evt_get() and sd_ble_evt_get() while still inside the Softdevice interrupt handler.

    brianreinhold said:
    If I were sending notifications instead of indications would it still be so complicated? Many vendors send sequences of data but I don't know if they use Nordic chips. But they also use notifications instead of indications.

    Notifications are a bit easier to deal with as you can queue up multiple notifications at a time. Though, instead of handling busy errors, you will have to handle the NRF_ERROR_NO_TX_PACKETS error if the notification output queue becomes full.

    Also, I took a screenshot from Keil to try illustrate how Softdevice events are being propagated to the on_ble_evt() handler from the Softdevice interrupt. Let me know if you have questions to this.

    on_ble_evt() invoked in interrupt context:

Related