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?

  • Application event comes from the interrupts sources listed in  the nrf51.h header. I've highlighted those that are reserved to the Softdevice in the image below. Interrupts reserved by the Softdevice are proccessed in the background without invoking the application.

    brianreinhold said:
    what events would cause 'my_command_handler' to be called? Would a BLE_GATTS_EVT_WRITE cause the sd_app_evt_wait() to return? Is BLE_GATTS_EVT_WRITE an Application event?

     The app will wake up on the SD_EVT_IRQn, and then start to poll the Softdevice event queue (BLE_GATTS_EVT_WRITE, etc). It's the SD_EVT_IRQn interrupt that makes the app tor return from sd_app_evt_wait().

    brianreinhold said:
    what events would cause 'my_command_handler' to be called?

     After any of the interrupt shown above, so you will still need to use a bolean flag if you want "my_command_handler()" to only be executed after a certain event.

  • It appears something you have told me is wrong. When sd_app_evt_wait() returns the only function I have at my disposal is sd_evt_get(). This method reports these events according to the documentation:

    enum NRF_SOC_EVTS
    {
      NRF_EVT_HFCLKSTARTED,                         /**< Event indicating that the HFCLK has started. */
      NRF_EVT_POWER_FAILURE_WARNING,                /**< Event indicating that a power failure warning has occurred. */
      NRF_EVT_FLASH_OPERATION_SUCCESS,              /**< Event indicating that the ongoing flash operation has completed successfully. */
      NRF_EVT_FLASH_OPERATION_ERROR,                /**< Event indicating that the ongoing flash operation has timed out with an error. */
      NRF_EVT_RADIO_BLOCKED,                        /**< Event indicating that a radio timeslot was blocked. */
      NRF_EVT_RADIO_CANCELED,                       /**< Event indicating that a radio timeslot was canceled by SoftDevice. */
      NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN, /**< Event indicating that a radio timeslot signal callback handler return was invalid. */
      NRF_EVT_RADIO_SESSION_IDLE,                   /**< Event indicating that a radio timeslot session is idle. */
      NRF_EVT_RADIO_SESSION_CLOSED,                 /**< Event indicating that a radio timeslot session is closed. */
      NRF_EVT_NUMBER_OF_EVTS
    };

    These are useless to me and have nothing to do with Bluetooth. How do I get the BLE events?

    Do I have to deal with this function: sd_nvic_GetPendingIRQ()? If so, what is the correct IRQn_Type to use? Is it SWI2_IRQn? How would I use this function to get the BLE events?

    When you say app will wake up you mean my app, correct?

    How do I poll the softdevice event queue? I have not found a method to do that.

  • So I guess you are not using the SoftDevice handler library to retreive and forward Softdevice events as you are trying to implement this mechanism yourself? To get BLE events, you will need to call sd_ble_evt_get(). sd_evt_get() is for SoC events like you said.

    Do I have to deal with this function: sd_nvic_GetPendingIRQ()? If so, what is the correct IRQn_Type to use? Is it SWI2_IRQn? How would I use this function to get the BLE events?

    I recommend you review the existing softdevice_handler.c implementation to see how it's done there. You may also want to read up on the ARM Cortex M exception model to get a better understandig of interrupts work on this chip.

    sd_nvic_GetPendingIRQ(SWI2_IRQn) is always going return false because the interrupt pending bit is cleared as soon as the interrupt is serviced by the  SWI2_IRQHandler (Note: SWI2_IRQHandler  is redefined to SOFTDEVICE_EVT_IRQHandler in sdk code). It only makes sense to use sd_nvic_GetPendingIRQ() if the interrupt is masked (ie temporarly disabled)

    When you say app will wake up you mean my app, correct?

    Yes, correct. The program will return from the sd_app_evt_wait() call when an application interrupt has been triggered.

    How do I poll the softdevice event queue? I have not found a method to do that.

    With sd_evt_get() and sd_ble_evt_get()

  • Yes, I found the sd_ble_evt_get() method. The documentation itself is pretty good, but the search engine stinks. It doesn't even find text on the same page that you put into the search bar. I stumbled on this method after methodically looking for every 'function' list I could find in the SoftDevice. 

    I also did a simple experiment by putting a handler in the that final for-loop and trying each of the sd_*_get() methods that had to do with getting events and printing the results. I didn't try the sd_ble_evt-get() method yet as it was the last one I found.

    In my case sd_nvic_GetPendingIRQ(SWI2_IRQn)  always returned true and quickly overflowed the print buffer.

    I am not using the SDK for a couple of reasons - I want to minimize SDK calls making portability from one chip to another as well as portability to newer versions much simpler. There were HUGE changes in the SDK from s110 v 8 to s130 v 12.3 demanding major code re-writes. However, after converting the old s110 v 8 code to using JUST SoftDevice, moving to s130 was very simple and took but a few minutes. Changes to move from nRF51822 to nRF52840 using just SoftDevice are also minimal. I personally find it easier sticking with BLE fundamentals which SoftDevice gives; you know that every BLE API must be able to perform certain basic tasks. Those I know. Learning how an SDK wrapper works is always a big commitment.

    BUT the biggest reason for sticking with SoftDevice only is that I also have code for the pc-ble-driver which ONLY has SoftDevice. It's a great test platform as you can use the PC. Having all code bases as similar and as portable as possible is clearly advantageous.

    Now I try sd_ble_evt_get() in my test loop and see what it gives me. First and foremost, who gets the event first. My test loop or my ble event dispatch function that appears in all the Nordic health device examples?

    Result of try

    Well it looks like the event handler I registered with SoftDevice gets it first and when I get it in the main 'for' loop the event is already drained. So that means (I guess) that I need to call my event handler from my 'test' method where I call sd_ble_evt_get().  AND do NOT register my event handler with SoftDevice. (Does this slow things down?)

    The whole purpose is, again, to allow me to call the indication method repeatedly in a 'BUSY' loop without hanging. Since I would be calling my event handler from the main for-loop, the event handler and the timer should all be in the main process.   I hope.

  • Events are removed from the Softdevice's internal event buffer when you run the event get functions. So,  these functions should not be called from multiple places in your code.

    You may use the "Thread mode event" retrival approach shown by the sequence chart linked to below if you preffer to poll the SD events from the main loop. Just make sure the Softdevice interrupt is not enabled in that case.

    Relevant Message Sequence Charts

    Interrupt-driven Event Retrieval

    Thread Mode Event Retrieval

    Also, a bit back to the original topic, you can take a look at the ancs_tx_buffer.c implementation in the ble_app_ancs_c example to see how we use a ring buffer to queue up multiple indication packets in the application.

Reply
  • Events are removed from the Softdevice's internal event buffer when you run the event get functions. So,  these functions should not be called from multiple places in your code.

    You may use the "Thread mode event" retrival approach shown by the sequence chart linked to below if you preffer to poll the SD events from the main loop. Just make sure the Softdevice interrupt is not enabled in that case.

    Relevant Message Sequence Charts

    Interrupt-driven Event Retrieval

    Thread Mode Event Retrieval

    Also, a bit back to the original topic, you can take a look at the ancs_tx_buffer.c implementation in the ble_app_ancs_c example to see how we use a ring buffer to queue up multiple indication packets in the application.

Children
  • Okay, I looked at the thread mode which I am doing or trying to do. However, it contradicts what I have read in the documentation. The docs say that I need to call the sd_ble_evt_get() until I drain all the queued events. That's not what I see in the message flow. The message flow says to call the sd_app_evt_wait() after the call with response NRF_SUCCESS.

    I am not doing that now (though things are still not working). Which is correct? cycle through the sd_ble_evt_get() and dispatch event until I get no more events OR call once, dispatch event, call sd_app_evt_wait and repeat?

  • Turns out all the issues I was having had nothing to do with indication sequences and handling of the sd_ble_evt_get().

    It was NRF_LOG_DEBUG. I guess I was trying to write an array of bytes converted to a HEX string that was too long. I do not know what the limit is, but commenting out that line of code solved the problem. Cost me a week and a half of hair pulling frustration.

    Was looking for answers in all the wrong places....

    So the approach I took where in the for(;;) loop I handle the sd_app_evt_wait()

    followed by the sd_ble_evt_get() with all its own loops but eventual dispatch on

    ble_evt_dispatch()

    I put the command handler in a one-short timer and set a flag if data needed to be indicated

    The indicate_data() method was in that main for(;;) loop. It would only get invoked if the flag was set.

    Though the final solution is VERY different than any of the health device examples it works. It might be good if you had an example which illustrated this kind of process. THough it is no semaphore; it accomplishes what a semaphore would have done. But it is much more like a basic embedded loop where you loop through, perform/handle one task, loop again and handle the next and so on.

Related