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

Wakeup immediately from sd_app_evt_wait() with external LFCLK

Hi all

We use the nRF51822 with the S130 V1.0.0. Our application initializes the TIMER2 (f = 16MHz) with a EVENTS_COMPARE interrupt on the CC channel 1 to monitor some timings.

Afterwards the SoftDevice is enabled with the LFCLK source "NRF_CLOCK_LFCLKSRC_XTAL_20_PPM". The TIMER2 stays running.

Later on, we want to reduce the power consumption. Therefore we disable the TIMER2 with the following code:

   NRF_TIMER2->TASKS_STOP = 1;
   NRF_TIMER2->TASKS_SHUTDOWN = 1;

   NVIC_DisableIRQ(TIMER2_IRQn);
   NVIC_ClearPendingIRQ(TIMER2_IRQn);

Afterwards we call the function sd_app_evt_wait() but the nRF51822 wakes up immediately. The strange thing is, that when I change the LFCLK source at the SoftDevice initialization to "NRF_CLOCK_LFCLKSRC_RC_250_PPM_TEMP_4000MS_CALIBRATION", the nRF51822 stays in the low power mode. Therefore there should be no event/interrupt in our application that awakes the chip.

Further observations are:

  • When we disable the TIMER2 before initializing the SoftDevice (LFCLK source is the XTAL) everything works fine
  • When we initialize the TIMER2 after initializing the SoftDevice (LFCLK source is the XTAL) everything works fine too

Is there a problem when the TIMER2 is running during the SD init in combination with the LF crystal? Does anyone know this behavior?

EDIT: I've added a stripped-down version of our application to reproduce this behavior:

Function to enable the query-timer:

static void query_timer_init(void)
{
   // :NOTE:
   //    Currently the following capture/compare channels are used:
   //       - Channel 0: Used to capture the timer value
   //       - Channel 1: Used to handle the overflow of the 16bit timer

   // Stop the timer before the configuration
   NRF_TIMER2->TASKS_STOP = 1;

   // Init module variables
   gTIMER2_high_word = 0;

   // Configure TIMER2 in timer mode with 16 bits
   NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;
   NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_16Bit;

   // Use 16 MHz (disable the prescaler)
   // -> f = HFCLK / (2 ^ prescaler_value)
   NRF_TIMER2->PRESCALER = 0;

   // Clear the timer before using it
   NRF_TIMER2->TASKS_CLEAR = 1;

   // Setup capture/compare channel 1 for timer overflow
   NRF_TIMER2->CC[1] = 0xFFFF;
   NRF_TIMER2->EVENTS_COMPARE[1] = 0;
   NRF_TIMER2->INTENSET = (TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos);

   // Enable interrupt for timer 2
   NVIC_SetPriority(TIMER2_IRQn, APP_IRQ_PRIORITY_HIGH);
   NVIC_ClearPendingIRQ(TIMER2_IRQn);
   NVIC_EnableIRQ(TIMER2_IRQn);

   // Start the timer
   NRF_TIMER2->TASKS_START = 1;
}

Function to disable the query-timer:

static void query_timer_disable(void)
{
   // Stop the timer
   NRF_TIMER2->TASKS_STOP = 1;

   // Shutdown the timer module
   NRF_TIMER2->TASKS_SHUTDOWN = 1;

   // Disable and clear the timer interrupt
   NVIC_DisableIRQ(TIMER2_IRQn);
   NVIC_ClearPendingIRQ(TIMER2_IRQn);
}

The main function:

int32_t main(void)
{
   uint32_t sd_stat = NRF_SUCCESS;
   ble_enable_params_t ble_enable_params;

   // Initialize the query timer
   query_timer_init();

   // Init the SoftDevice
   sd_stat = sd_softdevice_enable(
      //NRF_CLOCK_LFCLKSRC_RC_250_PPM_TEMP_4000MS_CALIBRATION, NULL); // -> OK
      NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, NULL);                          // -> NOK
   if (sd_stat != NRF_SUCCESS) {goto exception;}

   // Init the BLE stack
   ble_enable_params.gatts_enable_params.service_changed = 0;
   ble_enable_params.gatts_enable_params.attr_tab_size = BLE_GATTS_ATTR_TAB_SIZE_DEFAULT;
   sd_stat = sd_ble_enable(&ble_enable_params);
   if (sd_stat != NRF_SUCCESS) {goto exception;}

   // Disable the query timer
   query_timer_disable();

   // Activate the system ON low power mode
   sd_stat = sd_app_evt_wait();
   if (sd_stat != NRF_SUCCESS) {goto exception;}

   // -> This point should not be reached since there is no enabled wakeup source!


   // Re-enable the query timer
   query_timer_init();

   // the main loop
   while (1) {}

   // -> Should never reach this point!


exception:
   while (sd_stat != NRF_SUCCESS) {}
   return 0;
}

Kind regards

  • I tried what you've suggested and you are right. The content of 0x2000006b is 0x01 and 0x2000006c is 0x00. After the function call sd_app_event_wait() they are both 0x01. So this means that there is any pending system event, right? But when I call the function sd_evt_get() right before sd_app_event_wait() it says that there is no pending event.

  • That's what I expected. However I don't know what events increment the counter at 0x2000006b. You would think it would only happen when there's an event ready to be retrieved with sd_evt_get() but clearly there's at least one other happening which sets the flag. I'm afraid I can't even really hazard a guess as to what that might be. Does calling sd_evt_get(), even though it returns no pending event, reset the flags or does the event wait still wake up immediately? Did you also try getting a ble event, it could be one of those too.

    I suspect most people use sd_app_event_wait() to sleep the CPU in a processing loop and not to try and sleep the CPU exactly once and have it wake exactly once, so they don't notice the issue.

    That exhausts my knowledge on this

  • It looks like the function sd_evt_get() doesn't reset these flags and it still wakes up immediately. I checked also if there is a pending BLE event, but it isn't. I guess you are right, but unfortunately, we need to put the CPU into sleep mode only at a specific point. Thank you for your help!

  • @RK, where did you get those addresses? They are in RAM space used by softdevice. What kind of magic are you doing :)

    @Remo Can you try replacing sd_app_event_wait() with

    /* Wait for event */
    __WFE();
    
    /* Clear Event Register */
    __SEV();
    __WFE();
    

    If this works for you, there is something that looks to have a bug and it will make me dig more into softdevice code.

  • I hit break inside sd_app_event_wait(), it's only about 10 lines of assembler and it's not too hard to see what it does. If the two bytes are equal, it loops back to the __wfe(), if they aren't, it exits the wait (having set them equal again).

    I don't think your suggestion will help. The softdevice is constantly generating interrupts and events so that code will pretty much instantly exit in all cases. Those internal flags seem to act as a filter to keep it looping on internal events and interrupts and only exit sd_app_event_wait() when the softdevice has generated an event the user might want to wake up for. What I don't know is what increments the flag which says 'this is a real user event' and causes the loop to exit.

Related