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.

  • 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. 

  • That might be nice but all I have at my home is a volt meter and a soldering iron. For me the best chance of finding out what is going on is, perhaps, to just pull the event (I assume it only wakes up due to events) and print what the event is. All I did so far was to print a line of text after the wait was triggered and it came so fast it overflowed in less than a second. I might save some processing power by simply returning if it is not a ble or event that I actually handle somewhere in my loop.

    I did print it out and used sd_evt_get() to get the id of the event. The id is 25. And they are flying out at an incredible rate. The documentation says that it is an NRF_SOC_EVTS which is an enum containing only 9 events. What is event 25?

  • I don't know what could be soc evt id 25. Please provide a minimum code that can reproduce the issue so we can test here. 

Reply Children
  • I will give you the entire project. You will need the nrf51 DK and putty to view the log. Its a Keil project located here

    nrf_SDK_12.3.0\examples\ble_peripheral\ble_app_ghs_pleth\pca10028\s130\arm4

    nrfSDK_12.3.0.zip

    Look at the method 

    static void power_manage(void)

    in main.c on line 1954.

  • Hi Brian, 
    I tried here and it show Soc event 0 continuously. I don't know how you detect id 25. 

    But I would suggest to only print out "Soc event" only when the err_code from sd_evt_get() return NRF_SUCCESS. 
    If the chip is waking up from application's interrupt it won't have anything in sd_evt_get. 

  • Hung,

    I am less concerned about the value of the event (next time I ran it the event id was 26) but why is it waking up at such a high frequency? The only timer I am running before a DK button press is once a second. I have to have that timer in order to use the method app_timer_cnt_get(). I had no idea that this method was being woken up at this high rate. It's pretty important I understand why it is doing that as it will have a big impact upon my implementation. WIll it still behave like this when I put in on a chip and not the DK?

  • Please do what I suggested, turn off the functionality of the program until you see no waking up, and then turn them on one by one. You can start with disable any BLE functionality, no advertising. 

  • I don't have much to play with. I have not started advertising yet so there are (or should be) no Bluetooth events. The only thing that has happened is the spin up to get the chip operational. I have to press a button to start Bluetooth.

    This is all I have and a lot of this is Nordic SDK stuff to initialize the chip.

    int main(void)
    {
        uint32_t err_code;
        unsigned char cccds[2];
    
        written = false;
        ghs_abort = false;
        send_live_data = false;
        first_cont_sent = false;
        numberOfStoredMsmtGroups = 0;
        memset(&storedSpot, 0, 30 * sizeof(s_MsmtData));
    
        // Initialize.
        err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        spotGroupLength = 67;
    
        // Allocate memory for the security keys
        allocateMemoryForSecurityKeys(&keys);
        sec_params_init();
        timers_init();
    
        memset(&m_sccd_ghs_cp_handle, 0, sizeof(m_sccd_ghs_cp_handle));
        memset(&m_sccd_ghs_response_handle, 0, sizeof(m_sccd_ghs_response_handle));
    
        memset(cccds, 0, noOfCccds);
        loadKeysFromFlash(&keys, &saveDataBuffer, &saveDataLength, cccds, &noOfCccds);
        memcpy(cccdSet, cccds, noOfCccds);
    
    
        buttons_leds_init();
    
        gpiote_init_new();
    
        // This also provides the callback method to receive events.
        ble_stack_init();
    
        // Set device address
        ble_gap_addr_t addrStruct;
        addrStruct.addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;
        memcpy(addrStruct.addr, bluetoothAddress, 6);
        err_code = sd_ble_gap_address_set(BLE_GAP_ADDR_CYCLE_MODE_NONE, &addrStruct);
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_DEBUG("Could not set the Bluetooth Address\r\n");
            APP_ERROR_CHECK(err_code);
        }
        // Sets max and min connection intervals, slave latency, and the GAP characteristic entries
        // for the friendly name and appearance.
        gap_params_init();
        // Creates the advertisement and scan response data arrays
        err_code = advertisement_ghs_set();
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_DEBUG("Could not configure the advertisements\r\n");
            APP_ERROR_CHECK(err_code);
        }
        // ===================================== Create the GHS service
        err_code = createPrimaryService(&m_sccd_ghs_service_handle, BTLE_SCCD_GHS_SERVICE);
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_DEBUG("Could not create GHS service\r\n");
            APP_ERROR_CHECK(err_code);
        }
        // Create the GHS control point characteristic
        err_code = createStandardCharacteristic(m_sccd_ghs_service_handle,
            &m_sccd_ghs_cp_handle,
            BTLE_SCCD_GHS_CP_CHAR,
            true,   // Has CCCD
            true,   // This will cause indicate instead of notify if CCCD is set to true
            0,
            NULL,
            false,
            (ble_gap_conn_sec_mode_t) {1, 1},   // [CCCD write is open]
            (ble_gap_conn_sec_mode_t) {0, 0},   // Reading characteristic value is forbidden
            (ble_gap_conn_sec_mode_t) {1, 1});  // [Writing characteristic is open]
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_DEBUG("Could not create GHS control point characteristic\r\n");
            APP_ERROR_CHECK(err_code);
        }
        // ===================================== Create the GHS response characteristic
        err_code = createStandardCharacteristic(m_sccd_ghs_service_handle,
            &m_sccd_ghs_response_handle,
            BTLE_SCCD_GHS_RESPONSE_CHAR,
            true,   // Has CCCD
            false,  // This will cause indicate instead of notify if CCCD is set to true
            0,
            NULL,
            false,
            (ble_gap_conn_sec_mode_t)
            {
                1, 1
            },
            // CCCD write is open
            (ble_gap_conn_sec_mode_t)
            {
                0, 0
            },
                // Reading characteristic value is forbidden
            (ble_gap_conn_sec_mode_t)
            {
                1, 1
            });  // Writing characteristic is open
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_DEBUG("Could not create GHS response characteristic\r\n");
            APP_ERROR_CHECK(err_code);
        }
        // Not sure about this yet. I think it allows one to put up a fight to argue 
        // over connection parameters and sets up responses to connection parameter
        // change requests.
        conn_params_init();
    
        // Start execution.
        err_code = app_timer_start(m_app_dummy_timer_id, CMD_SENSOR_TIME, NULL);
        APP_ERROR_CHECK(err_code);
        elapsedTimeStart = app_timer_cnt_get();
        NRF_LOG_INFO("GHS Pulse Ox Start at time %u!\r\r\n", getTicks());
            
        //=============================================================================== Start GHS Pulse OX
        // Enter main loop.
        main_loop();
    }
    

    Unless by initializing SoftDevice and the Buttons so I can press buttons and a single timer is causing these continuous wakeups. Would starting a timer of any type regardless of the frequency of repeats cause events at the rate of the RTC updates? I am not sure how all this hardware works.

    If it's not Bluetooth related, I am pretty ignorant as to the possible causes. But I guess an easy first start is to comment out the dummy timer. If that does not change any behavior, I don't really know where to go next.

Related