Deep Sleep in ISR broken in 0.5.0 and later - Bug(?)

I’m working on a battery operated device using either a Photon or Electron, and have @peekay123’s SparkIntervalTimer library set to put the device to Deep Sleep if the code has been running too long. This was working fine with 0.4.7 and 0.4.9 using the Photon, but I only recently started using 0.5.0 and then 0.5.1 with the Electron. After the call to System.sleep() in the ISR, the device appears to hang (indicated by a solid cyan light), and the device doesn’t go into a low power state.

Here’s a simplified test case that I tested with the Photon. On 0.4.7 and 0.4.9 the code runs for 15 seconds and sleeps for 5. On 0.5.0 and 0.5.1 the code prints out “Awake Too Long: Sleeping”, then shortly after the LED stops breathing so it’s usually showing solid cyan but occasionally might be dark if it was “between breaths” when it got stuck).

#include "SparkIntervalTimer/SparkIntervalTimer.h"

const int maxWakeTimeSeconds = 15;
const uint32_t sleepTimeSeconds = 5;

void enterDeepSleep(void) {
    System.sleep(SLEEP_MODE_DEEP,sleepTimeSeconds);
}

void resetAfterMaxWakeTime(void) {
    static uint16_t wakeTimeSeconds = 0;
    
    wakeTimeSeconds++;
    
    if(wakeTimeSeconds == maxWakeTimeSeconds) {
        Serial.println("Awake Too Long: Sleeping");
    }
    
    if(wakeTimeSeconds > maxWakeTimeSeconds) {
        enterDeepSleep();
    }
}

void setup() {
    IntervalTimer maxWakeTimer;

    maxWakeTimer.begin(resetAfterMaxWakeTime, 2000, hmSec);

    Serial.println("startup");

    while(1);
}

void loop() {

}

Is this a bug in the Particle system firmware, or just changed behavior with the newer releases?

Do you have an suggestions for a workaround? I’m hoping to avoid putting any code in loop() and treat the sleep-after-max-wake-time behavior like a watchdog of sorts.

Let me know if you need an example that doesn’t use the SparkIntervalTimer library, I assume most people here are familiar with it.

@Pixelmatix, have you looked at the new Application Watchdog function?

Thanks for the tip, I didn’t see that feature in the release notes! This makes for a good workaround, giving me close to the behavior I want.

I’d still like to understand why starting Deep Sleep from an ISR isn’t working though, either to help squash a bug or just learn why not to do this.

@mdma or @BDub - Any idea why Deep Sleep isn’t working from an ISR?

I think part of the problem will be here

void PWR_EnterSTANDBYMode(void)
{
  /* Clear Wakeup flag */
  PWR->CR |= PWR_CR_CWUF;

  /* Select STANDBY mode */
  PWR->CR |= PWR_CR_PDDS;

  /* Set SLEEPDEEP bit of Cortex System Control Register */
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

/* This option is used to ensure that store operations are completed */
#if defined ( __CC_ARM   )
  __force_stores();
#endif
  /* Request Wait For Interrupt */
  __WFI();
}

The last line there will be waiting for an interrup while inside an ISR.

2 Likes

The WFI instruction looks like a problem, but after digging into the details, the WFI instruction is just the signal to the STM32F2 to enter deep sleep, and that code hasn’t changed recently.

It looks like the problem is the addition of a network_disconnect() and network_sleep() call before entering deep sleep. Each function includes a SYSTEM_THREAD_CONTEXT_ASYNC_CALL. I didn’t dig into what that means, but I’m going to assume it’s something that isn’t going to work in an interrupt context.

I made my own ISR-safe deep sleep function, which seems to work on the Photon with 0.5.1 firmware, but I haven’t done a current measurement to see if it is successfully powering down the WiFi module.

#include "rtc_hal.h"

void enterDeepSleep(void) {
    //System.sleep(SLEEP_MODE_DEEP,sleepTimeSeconds);
    HAL_RTC_Set_UnixAlarm(sleepTimeSeconds);
    HAL_Core_Enter_Standby_Mode();
}

This doesn’t power down the Electron cell module properly. I get ~18mA current draw from a battery in Deep Sleep using my ISR-safe code (skipping network_disconnect() and network_sleep() calls), and ~145uA when using the normal Deep Sleep call.

I’d still like to have my ISR code initiate deep sleep. In case a bug is causing all threads to be stuck somewhere, I don’t want to end up draining the battery. Maybe I’ll try resetting the module with a flag set in retained memory, and the sketch can check the flag and go into Deep Sleep first thing after reset.

This strategy seems to be working for the Electron. I’m able to initiate deep sleep from an ISR, by going through a reset cycle first.

One thing that’s not obvious, if you’re using SYSTEM_MODE(MANUAL); you need a call to Cellular.on(); (for the Electron) before calling System.sleep(), otherwise the Particle firmware doesn’t realize the cell module is already on, and won’t try to turn it off.

It takes more than a few seconds for the calls to Cellular.on(); and System.sleep() to communicate with the module, so using System.sleep() directly outside of an ISR is better, but this is a failsafe, so it’s more important for me that it’s working than efficient.