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

  • Your replacement for the function sd_app_event_wait() didn't work. It woke up again immediately. Do you have another idea?

  • its filter for interrupt forwarding mechanism, you seems to have decoded it almost. one flag is incremented everytime there is an interrupt forwarded from SD vector table to App vector table. the other flag is just checks that there are enough events happened for it to not wait. When they are not equal, the second will assign itself the value of the first one and exits, if they are equal then it waits. This is too simple to have a bug in it.

    @Remo: from your observation of RK's suggestion it is 100% clear that there is an application interrupt happening (definetely not softdevice events). Check all your interrupt handlers. I also see that you did not enable clear timer short

     NRF_TIMER2->SHORTS  = (TIMER_SHORTS_COMPARE1_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos);
    

    Check if there are any other peripheral interrupts like GPIOTE etc.

  • well you do have the benefit of having the source code :)

    If the flag is incremented any time there's an interrupt forwarded to the app vector table after the softdevice is active, and if the only thing which resets it again is running sd_app_evt_wait(), then it seems if any user interrupt has occurred at any time after the SD was enabled then sd_app_evt_wait() will instantly exit the first time it's run, and there's not much Remo can do about that. A rather hacky fix would be to ensure a user interrupt has occurred, then run sd_app_evt_wait() twice, once to clear the flags, then to wait.

  • I didn't enable the shortcut for clearing the timer because the compare event triggers when the counter value is 0xFFFF, which is followed by the overflow of the counter value. Do I need to enable this shortcut anyway?

    I checked the NVIC registers for enabled and pending interrupts. The enabled interrupts are (NVIC->ISER = 0x03002801):

    • POWER_CLOCK_IRQn
    • RTC0_IRQn
    • RNG_IRQn
    • SWI4_IRQn
    • SWI5_IRQn

    And the pending interrupts are (NVIC->ISPR = 0x00002000):

    • RNG_IRQn

    Are there any other registers to check whether an event/interrupt is pending? Assuming that a user interrupt like GPIOTE is enabled and pending, why would it only occur when the LFCLK crystal is used?

  • Don't think it's what's currently enabled or pending, it's which user space interrupts have fired since the softdevice was enabled and after any other calls to sd_app_event_wait(). If I've understood correctly what Aryan said, the user space interrupt sets the flag, the next call to sd_app_event_wait() clears it and exits instantly, else it waits.

    I don't see where the LFCLK vs XTAL comes into that equation at all. I got the impression in your code that by the time you turned it off, TIMER1 had already fired at least once and generated an interrupt.

Related