Sleep mode (stop) needs "delay" to wake up again

I’m struggling a bit with the sleep mode (stop). After a lot of debugging I found that when calling my stateManager function waking up doesn’t work fine.

I made this example code to demonstrate it. I use a timer to keep track of an on/off button (the type that keep’s in the state, so either stays HIGH or it stays LOW). I do this because in my original code lot of things happen. Using a timer guaranties that button is checked every millisecond (I don’t use an interrupt because that doesn’t work with debounce).

I’ve added a delay in the loop of 100ms. Removing this, break the program. I have no idea why.

#include "Particle.h"

SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);

void senseOnOffButton();
void stateManager();

const int onOffButtonPin = A6;

Timer senseOnOffButtonTimer(1, senseOnOffButton);

bool WkpValue;
bool lastWkpValue;

bool wkpButtonState;

long _lastBounceTime;

enum States { WAKE_FROM_OFF, WAKE_FROM_SLEEP, WAKING_UP, AWAKE, GO_TO_SLEEP, ASLEEP };
enum States state;

void setup()
{
  pinMode(onOffButtonPin, INPUT);
  pinMode(D7, OUTPUT);

  //Serial.begin(57600);
  // delay(2000);
  // Serial.println("Hi! Setup!");

  state = WAKE_FROM_OFF;
}

void loop()
{
  stateManager();

  // This delay is needed otherwise we don't wake up from sleep anymore.
  // Why can't we call stateManager as fast as possible?
  // What kind of hidden things happen in the background?
  delay(100);
}

void stateManager() {

  switch(state) {

    case WAKE_FROM_OFF:

      // Serial.println("WAKE_FROM_OFF");
      state = WAKING_UP;

    break;

    case WAKE_FROM_SLEEP:

      // Serial.println("WAKE_FROM_SLEEP");

      // delay(500);

      // The device will not reset before going into stop mode so all the application
      // variables are preserved after waking up from this mode.
      // we might force a reset to be sure things won't happen twice.
      //System.reset();

      state = WAKING_UP;

    break;

    case WAKING_UP:

      //WkpValue = digitalRead(onOffButtonPin);
      //lastWkpValue = WkpValue;
      //wkpButtonState = WkpValue;

      // Serial.println("WAKING_UP");

      senseOnOffButtonTimer.start();

      state = AWAKE;

    break;

    case AWAKE:

      digitalWrite(D7, HIGH);

      if(Particle.connected()) {
        // we can parse the Cloud
      }


    break;

    case GO_TO_SLEEP:

      // Serial.println("GO_TO_SLEEP");
      // We can't stop this one, because then we don't detect the button on
      // waking up.
      // senseOnOffButtonTimer.stop();

      state = ASLEEP;

    break;

    case ASLEEP:

      digitalWrite(D7, LOW);

      // Serial.println("ASLEEP");

      // stop mode
      System.sleep(onOffButtonPin, CHANGE);

    break;

  }

}

void senseOnOffButton() {

  // we sense every ms. First idea was to connect it to and interrupt and let the interrupt start
  // at timer. That doesn't work and seems to break the interupt as well...
  // This works so no need to improve it now.

  WkpValue = digitalRead(onOffButtonPin);

  if(WkpValue != lastWkpValue) {

    _lastBounceTime = (long) millis();
    lastWkpValue = WkpValue;
  }

  if(WkpValue != wkpButtonState)
  {
    if ((long) millis() - _lastBounceTime > 20)
    {
      if(state != ASLEEP) {
        // go to sleep
        state = GO_TO_SLEEP;
      }
      else {
        state = WAKE_FROM_SLEEP;
      }

      wkpButtonState = WkpValue;

      stateManager();

    }

  }

}

It looks to me that if you are in state == ASLEEP, the system goes to sleep. However, since you don’t set the state to something else before going to stop mode sleep, when it wakes up, execution continues from the line after System.sleep with all variables still assigned, so the loop calls stateMachine again and goes right back to sleep.

When you have the delay, it’s enough time for the timer to fire and reset the state to something other than ASLEEP.

2 Likes

@rickkas7 Thanks, this helped my finding the error. I’ve added the state as you said and changed the if-statement in my button.

For completeness of this topic (in case others need code like this), the working code (including a counter that show that variables are kept in stop mode.

#include "Particle.h"

SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);

void senseOnOffButton();
void stateManager();

const int onOffButtonPin = A6;

Timer senseOnOffButtonTimer(1, senseOnOffButton);

bool WkpValue;
bool lastWkpValue;

bool wkpButtonState;

long _lastBounceTime;

enum States { WAKE_FROM_OFF, WAKE_FROM_SLEEP, WAKING_UP, AWAKE, GO_TO_SLEEP, ASLEEP };
enum States state;

int counter;

long prevMillis;

void setup()
{
  pinMode(onOffButtonPin, INPUT);
  pinMode(D7, OUTPUT);

  Serial.begin(57600);
  //delay(2000);
  Serial.println("Hi! Setup!");

  state = WAKE_FROM_OFF;

  counter = 0;
}

void loop()
{
  stateManager();

}

void stateManager() {

  switch(state) {

    case WAKE_FROM_OFF:

      Serial.println("WAKE_FROM_OFF");
      state = WAKING_UP;

    break;

    case WAKE_FROM_SLEEP:

      Serial.println("WAKE_FROM_SLEEP");

      // The device will not reset before going into stop mode so all the application
      // variables are preserved after waking up from this mode.
      // We can forge a reset, so next state will be WAKE_FROM_OFF
      //System.reset();

      state = WAKING_UP;

    break;

    case WAKING_UP:

      // prevent a false trigger on the start
      WkpValue        = digitalRead(onOffButtonPin);
      lastWkpValue    = WkpValue;
      wkpButtonState  = WkpValue;

      Serial.println("WAKING_UP");

      senseOnOffButtonTimer.start();

      state = AWAKE;

    break;

    case AWAKE:

      digitalWrite(D7, HIGH);

      if(millis() - prevMillis > 1000) {
        counter++;
        Serial.println(counter);
        prevMillis = millis();
      }


      if(Particle.connected()) {
        // we can parse the Cloud
      }


    break;

    case GO_TO_SLEEP:

      Serial.println("GO_TO_SLEEP");
      // We can't stop this one, because then we don't detect the button on
      // waking up.
      senseOnOffButtonTimer.stop();

      state = ASLEEP;

    break;

    case ASLEEP:

      digitalWrite(D7, LOW);

      Serial.println("ASLEEP");
      state = WAKE_FROM_SLEEP;

      // stop mode
      System.sleep(onOffButtonPin, CHANGE);

    break;

  }

}

void senseOnOffButton() {

  // we sense every ms. First idea was to connect it to and interrupt and let the interrupt start
  // at timer. That doesn't work and seems to break the interupt as well...
  // This works so no need to improve it now.

  WkpValue = digitalRead(onOffButtonPin);

  if(WkpValue != lastWkpValue) {

    _lastBounceTime = (long) millis();
    lastWkpValue = WkpValue;
  }

  if(WkpValue != wkpButtonState)
  {
    if ((long) millis() - _lastBounceTime > 20)
    {
      //if(state != ASLEEP) {
      if(state == AWAKE) {
        // go to sleep
        state = GO_TO_SLEEP;
      }
      //else {
      //  state = WAKE_FROM_SLEEP;
      //}

      wkpButtonState = WkpValue;

      stateManager();

    }

  }

}
1 Like