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

Wake up sd_app_evt_wait() from application

I am trying to keep everything synchronous between the client and peripheral. I am also try to make the peripheral code itself synchronous. So on the nRF51 DK, I use a button press to start advertising and then I enter the main loop which I have modified from the example code. The main loop calls the sd_app_evt_wait() method and when an app event happens it loops through the sd_ble_evt_get() method to get the event, and calls the ble_evt_dispatch() method to handle the event.

The peripheral code then goes through connection, pairing/encryption, and getting two descriptors enabled. It then handles writes from the central. These writes consist of commands such as get config data, send stored data, etc, and then finally send any live data. Since the data being sent is much longer than what can fit in a characteristic, the data string is fragmented and sent in hunks. There are no semaphores here so when the send stored data command is received, the peripheral creates the data to be sent, sets a flag saying 'notify data' and returns. This brings one back to the main loop. After the return from the ble_evt_dispatch() method, a call to a send_data() method that does the sd_ble_gatts_hvx(). Since the flag is set, notifications are sent until the TX buffers are exhausted at which time we need to wait for the TX buffer event. So the main loop goes back to the sd_app_evt_wait() and waits for the TX event, etc.

This successfully does all the spin up and sending of stored data synchronously, but now I want to send live data and my app is waiting on the sd_app_evt_wait(). In the real production device, that 'live' data will arrive over a UART event. On the DK, I cannot use that so I use a periodic timer to 'fake' a sequence of live measurements. Now I realize that I could probably call the sd_ble_gatts_hvx() here which will eventually generate an event. But I dont want to do that since it introduces the possibility of asynchronous behavior. Instead I want to prepare the data, set the notify flag, and wake up the sd_app_evt_wait(). It does not matter if the event is one I do not handle; I just need to wake up the loop so the send_data method is called from that loop. That would keep the entire flow synchronous.

Here is the main for-loop code. Hopefully it will make the above rather cryptic description clearer.

/**@brief Function for the main 'embedded' loop. The Nordic SoftDevice is event driven.
* Any method prefixed by sd_* is a SoftDevice method. SoftDevice methods consist of just
* your basic GATT and GAP level calls. The Nordic SDK provides methods to simplify certain
* processes but they all call these sd_* methods. Here we do not use the SDK but only sd_*.

* The key method in the wait loop is sd_app_evt_wait(). This method waits until a Bluetooth
* or system event is triggered. When that happens, the function returns. The application must
* then use the method sd_ble_evt_get() to get the Bluetooth Event that just occurred. It could
* be a read, write, connection, disconnection, etc. The application then must either handle
* the event or ignore it. In this application the event handler is ble_evt_dispatch(). Note
* that after the event is handled one has to call sd_ble_evt_get() again until the method
* returns that there are no more events. One then calls sd_app_evt_wait() and the process 
* repeats. But we extend that functionality.

* Note that the loop also contains a call to the application method indicate_data(). It is poorly
* named as it does both indications and notifications. What this method does is check if a flag
* has been set that data needs to be indicated/notified. If it has, the flag will be cleared and
* a hunk of data will be indicated/notified. If it is an indication, the app must wait until
* the indication is confirmed so the data is sent and the position in the buffer is updated by
* the amount of data sent. Then the method returns, and we wait for the confirmation event
* BLE_GATTS_EVT_HVC to occur. If all the segments have been sent, the next procedure occurs. If
* not, the indicate flag is set to true and the next segment is sent.

* If the data is notified, hunks can be sent one after the other without waiting for an event
* until the notification buffers are depleted. When that happens, the application must wait for
* the BLE_EVT_TX_COMPLETE. At that point we can continue. This loopy way of handling sending
* data segments would be equivalent to waiting on a semaphore in a real time OS.

* Recall that the GHS works as follows:
*   1. receive a command on the Control Point
*   2. if the command is to send data (current time, system info, config info, get stored records,
*      send live data) the data is notified on the response characteristic until all segments are
*      sent. 
*      Now if the data being sent is measurements, there may be several records, especially when
*      transferring stored data. A record consists of all the measurements with a common time
*      stamp. When a record is completed, the PHD server sends an indication on the CP saying a record
*      has completed. The PHG client shall not send another command yet. The PHD must send a second
*      indication on the CP sayong that all records have been sent. Then the PHG can send another
*      command.
*      The system info, current time info, and config info contain only one long entry. There is
*      no record in that case.
*   3. if the PHD does not support the command or the command is to get the number of stored
*      records or delete stored data, there is only an indication on the CP with the result or DONE.

* Pairing/bonding. This implementation requires that the client ask the PHDe if the PHD wants to
* pair. If the PHD responds 'yes' on the CP, the cleint begins pairing. To do this none of the
* characteristics or descriptors are secured. However, if the application wants to pair and the
* client does not, all other commands will fail. Probably should put a special error message like
* PHD requires pairing and the PHG did not pair. The device could also disconnect. This is up in
* the air. We do this to keep the agony of pairing as user-friendly as possible as well as synchronous.
* The client will never have to worry about getting a security request or insufficient autherntication
* error while doing something else. GREATLY simplifies cleint code not having to check for this crap.
* In addition, the user does not have to know if the PHD is pairable or unpairable; the user only has
* to discover and connect. Pairing has proved to be a user nightmare in the field.

* Clearly the command values and response codes are all up in the air. To just make this work
* a set of minimal values were chosen.
*/

static void main_loop(void)
{
    uint8_t enabled;
    uint16_t len;
    uint32_t result;
    for (;;)
    {
        indicate_data();        // when flag is set, a set of data is indicated or notified depending upon setup. 
                                // Flag is reset in method
        main_wait();            // Contains the sd_app_evt_wait()
        while(true)
        {
            sd_softdevice_is_enabled(&enabled); // Don't do this if disabled, for example when writing flash at the end
            if (enabled != 1)
            {
                NRF_LOG_DEBUG("Restarting application%u\r\n");
                while(NRF_LOG_PROCESS());
                NVIC_SystemReset();
                return;
            }
            result = sd_ble_evt_get(NULL, &len);    // Get size of event
            if (result == NRF_ERROR_NOT_FOUND)      // If there aren't any, go back to wait
            {
                break;
            }
            evt_buf = (uint8_t *)calloc(1, len);                // Make space for event. evt_buf is 4-byte aligned
            result = sd_ble_evt_get((uint8_t *)evt_buf, &len);  // get the event
            if (result == NRF_SUCCESS)
            {
                ble_evt_t *evt = (ble_evt_t *)evt_buf;
                ble_evt_dispatch(evt);                          // dispatch event to handler. This handles only BTLE related events
            }
            else                                                // Hopefully no error but just in case log it.
            {                                                   // Should I do an NVIC_SystemReset() here?
                NRF_LOG_DEBUG("PENDING BLE Event return error: %u\r\n", result);
                free(evt_buf);  // Clean up
                break;          // back to wait
            }
            free(evt_buf);      // clean up and get the next event
        }
    }
}

Parents
  • Hi Brian, 

    When exactly you want to wake up from sd_app_evt_wait() ? 
    The sd_app_evt_wait() only wake up when there is an BLE event or when there is an application event. It doesn't let the application wake up if there is just an BLE event (with no data for example). 
    You can use the timer to periodically wake the sd_app_evt_wait() up. If you simply want to go back to the top of the loop and can go through sd_app_evt_wait() without waiting there, I think you can set a flag and not calling sd_app_evt_wait() if the flag is set.

  • How can I use a timer to wake up sd_app_evt_wait()? I am using the method as a way to emulate a semaphore. So when I get a measurement (say over a UART) I want to encode the measurement, set a flag, and wake up the loop. In the loop the 'indicate_data()" method will be called and an MTU sized hunk of data will be indicated/notified. Then I exit the method and wait for a BTLE event.

    IN that manner, the only place I call the indicate_data() method is in that loop which keeps the flow synchronous.

    At the moment, I don't have a real sensor so I fake measurements in a timer. Since I cannot wake up the sd_app_evt_wait() I simply call the indicate_data() method from the timer and return. That will cause a ble event. THe down side is that this introduces asynchrony as another event may wake up the loop and indicate_data() will be called from two places.

    Lousy non-robust solution.

Reply
  • How can I use a timer to wake up sd_app_evt_wait()? I am using the method as a way to emulate a semaphore. So when I get a measurement (say over a UART) I want to encode the measurement, set a flag, and wake up the loop. In the loop the 'indicate_data()" method will be called and an MTU sized hunk of data will be indicated/notified. Then I exit the method and wait for a BTLE event.

    IN that manner, the only place I call the indicate_data() method is in that loop which keeps the flow synchronous.

    At the moment, I don't have a real sensor so I fake measurements in a timer. Since I cannot wake up the sd_app_evt_wait() I simply call the indicate_data() method from the timer and return. That will cause a ble event. THe down side is that this introduces asynchrony as another event may wake up the loop and indicate_data() will be called from two places.

    Lousy non-robust solution.

Children
  • Hi Brian, 
    I'm quite unsure I understand what you meant by "Since I cannot wake up the sd_app_evt_wait()". When you are in the timer interrupt handler you are already waking up (the CPU running) and you are out of the sd_app_evt_wait(). 

    I have made an example for you. This is modified from ble_app_hrs example. 
    I have this timer in my app: 


    err_code = app_timer_start(m_battery_timer_id, BATTERY_LEVEL_MEAS_INTERVAL, NULL);
     

    With BATTERY_LEVEL_MEAS_INTERVAL = 200ms. 

    Then in my main loop I have this: 

    for (;;)
    {
        nrf_gpio_pin_toggle(28);
        idle_state_handle();
    }


    You can find the logic trace like this: 



    This shows that the nrf_gpio_pin_toggle(28) is called every time the battery timer timeout. 

    So any application interrupt can wake up the chip from sd_app_evt_wait(). 

  • Okay, then if I understand you correctly, it's a little more complicated than just invoking a timer. I already invoke a periodic timer that when signaled calls a method to generate fake measurements. But it does NOT wake up sd_app_evt_wait(). Now what I want to do is wake up sd_app_evt_wait() after I generate the data.

    Its also not clear to me who is doing what your main loop. I assume the 'idle_state_handle()' contains the wait? Maybe not? My main loop is waiting on sd_app_evt_wait(). It is not hiding in any method.  Are you saying that there is some method I can call from my timer handler that can toggle a pin on the chip that will release the wait?

    Below is the method my timer periodically calls. It generates fake data (it is simulating data coming from another MCU say via SPI or a UART). When generated I would like to wakeup the main loop and return and perform the 'indicate_data()' from the main loop. But that I have been unable to do. So I call the method and that WILL wakeup the main loop eventually due to either the BLE_GATTS_EVT_HVC event or the BLE_TX_EVT_COMPLETE event. But doing so introduces asynchronous behavior - which I want to avoid at all costs.

    static void live_data_handler(void * p_context)
    {
        UNUSED_PARAMETER(p_context);
        if (send_live_data)
        {
            live_data_count++;
            int i;
            unsigned long timeStamp32 = getTicks();
            NRF_LOG_DEBUG("timestamp32 %lu\r\n", timeStamp32);
            unsigned long long timeStamp = (unsigned long long)timeStamp32 * 1000;
            if (global_indicate.handle != 0)
            {
                NRF_LOG_DEBUG("Not ready for measurement %lu at time %lu\r\n", live_data_count, timeStamp32);
                return;
            }
            #if (HAS_PULSATILE == 1)
            liveMsmt.msmts.hasPulsatile = true;
            liveMsmt.msmts.pulsatile = 523 + (timeStamp32 & 0xFF);
            #else
            liveMsmt.msmts.hasPulsatile = false;
            #endif
            liveMsmt.msmts.spo2 = 95 + (timeStamp32 & 0x03);
            liveMsmt.msmts.pr = 45 + (timeStamp32 & 0x07);
            #if (HAS_PULSATILE == 1)
            NRF_LOG_DEBUG("Measurement added: SpO2 %u%, PR %u, Pulsatile X 100 %u%, timestamp32 %lu, msmt count %lu\r\n", 
                liveMsmt.msmts.spo2, 
                liveMsmt.msmts.pr, 
                liveMsmt.msmts.pulsatile, timeStamp32, live_data_count);
            #else
            NRF_LOG_DEBUG("Measurement added: SpO2 %u, PR %u, timestamp32 %lu\r\n", 
                liveMsmt.msmts.spo2, liveMsmt.msmts.pr, timeStamp32);
            #endif
            if ((live_data_count & 0x07) == 0x00)
            {
                NRF_LOG_DEBUG("Spot msmt to send\r\n");
                liveMsmt.hasTimeStamp = true;
                for (i = 0; i < 8; i++)
                {
                    liveMsmt.relTime[i] = (unsigned char)(timeStamp & 0xFF);
                    timeStamp = (timeStamp >> 8);
                }
                sendLiveSpotMeasurements(&liveMsmt);
            }
            else
            {
                sendLiveContinuousMeasurements(&liveMsmt);
            }
            indicate_data();  // Here is where I would like to wake up the main loop
        }
    }

  • Hi again, 

    sd_app_evt_wait() is called inside idle_state_handle(). 

    You should have same result if you have this in the main loop: 

    for (;;)
    {
     sd_app_evt_wait();
     nrf_gpio_pin_toggle(28);
    }
    So what I want to show here is that every time you have a timer interrupt (or any application interrupt) and after you are done with the interrupt handler, the function right after sd_app_evt_wait() will be executed. This means the timer interrupt will wake the chip up from sd_app_evt_wait(). 
    The toggling of the pin, is just so that I can show you that the function is executed and the chip is waken up from sd_app_evt_wait(). It can be any function. 

    You don't need to send a dummy notification or anything to wake the chip up again.  

  • Well as it turns out SOMETHING (I don't know what) is waking it up all the time; its happening so fast NRF_LOG overflows. I don't know what is waking it up at such a rate. Even before I start advertising this is happening. So  I can just set up the data and return in my timer callback and the wait will release. I don't feel very good about that since I don't know what is causing it. I am not intentionally causing those wakeups.

  • I would suggest to hook a logic analyzer and do the pin toggling as I did to check the interval of the waking up. 

    You can try to test turn off functionalities of the application until you don't see the waking up and then turn them back on to see what caused the waking up. 

Related