Sleep mode explained using code samples


#1

There was a recently a question about sleep mode on the Photon, and it wasn’t anything I ever played with, so I made a little circuit and 4 test sketches to play around with it and see how things work, since I was still a little confused after reading the documentation. Each example has the code with a lot of comments followed by an annotated example output, so you can probably figure it out just by reading the examples even if you don’t run the code yourself.

Here’s the circuit:

The main thing is the button, which connects D0 to ground, with an external 10K pull-up resistor. Also, I connected a battery to VBAT to preserve the retained variables in SRAM. Actually, you don’t need the battery and the first three tests don’t use it.

###Test 1 - Wi-Fi Sleep

#include "Particle.h"

// Simple test of using sleep(), which really just puts the Wi-Fi adapter to sleep
// but your code continues to run. I think I'd prefer to use SYSTEM_MODE(MANUAL)
// instead for more explicit control, but this does work.

unsigned long firstAvailable = 0;
int counter;

void setup() {
	Serial.begin(9600);
}

void loop() {
	bool wifiReady = WiFi.ready();
	bool cloudReady = Particle.connected();

	Serial.printlnf("wifi=%s cloud=%s counter=%d", (wifiReady ? "on" : "off"), (cloudReady ? "on" : "off"), counter++);

	if (wifiReady) {
		if (firstAvailable == 0) {
			firstAvailable = millis();
		}
		if (millis() - firstAvailable > 30000) {
			// After we've been up for 30 seconds, go to sleep
			Serial.println("calling System.sleep(30)");
			System.sleep(30);

			// This happens immediately - System.sleep(30) doesn't block and your code keeps running, just with
			// Wi-Fi and cloud turned off. The LED should breathe white instead of cyan in this state. Then,
			// after the time expires, reconnection to the cloud occurs
			Serial.println("returned from sleep");
		}
	}
	else {
		firstAvailable = 0;
	}

	delay(1000);
}

/*
[led breathing cyan]
[power consumption: ~ 0.07 A]
wifi=on cloud=on counter=28
wifi=on cloud=on counter=29
wifi=on cloud=on counter=30
calling System.sleep(30)
[power consumption: ~ 0.03 A]
[led breathing white now]
returned from sleep
wifi=off cloud=off counter=31
wifi=off cloud=off counter=32
wifi=off cloud=off counter=33
...
wifi=off cloud=off counter=57
wifi=off cloud=off counter=58
wifi=off cloud=off counter=59
[power consumption: ~ 0.07 A]
[led breathing cyan again]
wifi=on cloud=on counter=60
wifi=on cloud=on counter=61
wifi=on cloud=on counter=62
...

 */

###Test 2 - Deep Sleep

#include "Particle.h"

// Simple test of using sleep(SLEEP_MODE_DEEP, 30). This halts execution of your code, and when it
// wakes up again, it goes through setup() again with all variables cleared.

// Running this test without a battery connected to VBAT is interesting, however, because retained
// variables ARE preserved in deep sleep, even without a battery.

// IMPORTANT NOTE: If using retained variables to preserve values across deep sleep when NOT using
// a battery, be sure to tie VBAT to ground. This somewhat counter-intuitive step is necessary
// because otherwise when you first power up the device, the retained memory will not be initialized,
// so it will contain random values, which will probably confuse your code!

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));


unsigned long firstAvailable = 0;
int counter;
retained int retainedCounter = 0;

void setup() {
	Serial.begin(9600);
}

void loop() {
	bool wifiReady = WiFi.ready();
	bool cloudReady = Particle.connected();

	Serial.printlnf("wifi=%s cloud=%s counter=%d retainedCounter=%d", (wifiReady ? "on" : "off"), (cloudReady ? "on" : "off"),
			counter++, retainedCounter++);

	if (wifiReady) {
		if (firstAvailable == 0) {
			firstAvailable = millis();
		}
		if (millis() - firstAvailable > 30000) {
			// After we've been up for 30 seconds, go to sleep. The delay is so the serial output gets written out before
			// sleeping.
			Serial.println("calling System.sleep(SLEEP_MODE_DEEP, 30)");
			delay(2);

			System.sleep(SLEEP_MODE_DEEP, 30);

			// The rest of the code here is not reached. SLEEP_MODE_DEEP causes the code execution to stop,
			// and when wake up occurs, it's like a reset where you start again with setup(), all variables are
			// cleared, etc.
			Serial.println("returned from sleep, should not occur");
		}
	}
	else {
		firstAvailable = 0;
	}

	delay(1000);
}

/*
[power consumption: ~ 0.07 A, battery NOT connected to VBAT]
wifi=on cloud=on counter=1 retainedCounter=1
wifi=on cloud=on counter=2 retainedCounter=2
..
wifi=on cloud=on counter=29 retainedCounter=29
wifi=on cloud=on counter=30 retainedCounter=30
calling System.sleep(SLEEP_MODE_DEEP, 30)
[serial disconnected, LED off]
[power consumption: 0.00 A. Not actually 0, but too small for my USB power meter to measure.]
[30 seconds later, device goes through normal boot sequence, then]
[power consumption: ~ 0.07 A]
wifi=on cloud=on counter=0 retainedCounter=31
wifi=on cloud=on counter=1 retainedCounter=32
wifi=on cloud=on counter=2 retainedCounter=33
wifi=on cloud=on counter=3 retainedCounter=34

[press reset button - retained values still preserved]
wifi=on cloud=on counter=0 retainedCounter=77
wifi=on cloud=on counter=1 retainedCounter=78
wifi=on cloud=on counter=2 retainedCounter=79
wifi=on cloud=on counter=3 retainedCounter=80

 */

###Test 3 - Pin change sleep

#include "Particle.h"

// Simple test of using System.sleep(D0, RISING, 30). This uses processor stop mode on the Photon
// to stop the processor and Wi-Fi until either the pin condition or the timeout occurs. When
// the wake occurs, execution continues where it left off with all variables preserved.

// Connect D0 to GND through a button with a 10K pull-up resistor.


unsigned long firstAvailable = 0;
int counter;

void setup() {
	Serial.begin(9600);
	Serial.println("setup called");
}

void loop() {
	bool wifiReady = WiFi.ready();
	bool cloudReady = Particle.connected();

	Serial.printlnf("wifi=%s cloud=%s counter=%d", (wifiReady ? "on" : "off"), (cloudReady ? "on" : "off"), counter++);

	if (wifiReady) {
		if (firstAvailable == 0) {
			firstAvailable = millis();
		}
		if (millis() - firstAvailable > 30000) {
			// After we've been up for 30 seconds, go to sleep

			Serial.println("calling System.sleep(D0, RISING, 30)");

			// This delay is here so the serial print above goes out before going to sleep
			delay(1);


			System.sleep(D0, RISING, 30);

			// In this mode, setup() is not called again. Sometimes, but not always, the Serial port does
			// not restore properly. This solves the problem.
			Serial.begin(9600);

			// In this mode,after the sleep period is done, execution continues where we left off,
			// with variables intact.
			Serial.println("returned from sleep");

			// Important! If you don't reset this, you'll immediately go back to sleep, since variables
			// are preserved.
			firstAvailable = 0;
		}
	}
	else {
		firstAvailable = 0;
	}

	delay(1000);
}

/*
[power consumption: ~ 0.07 A]
wifi=on cloud=on counter=29
wifi=on cloud=on counter=30
calling System.sleep(D0, RISING, 30)
[serial disconnected LED is off]
[power consumption: 0.00 A. Not actually 0, but too small for my USB power meter to measure.]
...
[30 seconds later]
[power consumption: ~ 0.07 A]
returned from sleep
wifi=on cloud=on counter=31
wifi=on cloud=on counter=32
wifi=on cloud=on counter=33
 */

###Test 4 - Battery backup and WKP test

#include "Particle.h"

// Simple test of using System.sleep(SLEEP_MODE_DEEP, 30) along with a backup battery and the WKP pin.


// Connect WKP to 3V3 through a button with a 10K pull-down resistor.
// Connect a battery (I used a CR2032 coin cell) to VBAT

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));


unsigned long firstAvailable = 0;
int counter;
retained int retainedCounter = 0;

void setup() {
	Serial.begin(9600);
	Serial.println("setup called");
}

void loop() {
	bool wifiReady = WiFi.ready();
	bool cloudReady = Particle.connected();

	Serial.printlnf("wifi=%s cloud=%s counter=%d retainedCounter=%d",
			(wifiReady ? "on" : "off"), (cloudReady ? "on" : "off"), counter++, retainedCounter++);

	if (wifiReady) {
		if (firstAvailable == 0) {
			firstAvailable = millis();
		}
		if (millis() - firstAvailable > 30000) {
			// After we've been up for 30 seconds, go to sleep

			Serial.println("calling System.sleep(SLEEP_MODE_DEEP, 30)");

			// This delay is here so the serial print above goes out before going to sleep
			delay(1);

			System.sleep(SLEEP_MODE_DEEP, 30);

			Serial.println("should never be reached");
		}
	}
	else {
		firstAvailable = 0;
	}

	delay(1000);
}

/*
setup called
wifi=on cloud=on counter=0 retainedCounter=27
wifi=on cloud=on counter=1 retainedCounter=28
...
wifi=on cloud=on counter=27 retainedCounter=54
wifi=on cloud=on counter=28 retainedCounter=55
wifi=on cloud=on counter=29 retainedCounter=56
wifi=on cloud=on counter=30 retainedCounter=57
calling System.sleep(SLEEP_MODE_DEEP, 30)
[30 seconds later]
setup called
wifi=on cloud=on counter=0 retainedCounter=58
wifi=on cloud=on counter=1 retainedCounter=59
wifi=on cloud=on counter=2 retainedCounter=60

[Using the wakeup button works like timeout does]
setup called
wifi=on cloud=on counter=0 retainedCounter=89
wifi=on cloud=on counter=1 retainedCounter=90
wifi=on cloud=on counter=2 retainedCounter=91

[press reset button]
setup called
wifi=on cloud=on counter=0 retainedCounter=223
wifi=on cloud=on counter=1 retainedCounter=224
wifi=on cloud=on counter=2 retainedCounter=225
wifi=on cloud=on counter=3 retainedCounter=226

[unplug USB power and plug back in, battery connected]
setup called
wifi=on cloud=on counter=0 retainedCounter=230
wifi=on cloud=on counter=1 retainedCounter=231
wifi=on cloud=on counter=2 retainedCounter=232

 */


Difference Between Power Saving Sleep Modes
Comparison table of the sleep modes
System.sleep(seconds) broken in 0.6.0?
#2

This was super helpfull!! thanks a lot!

for the Electron you just switch out
bool wifiReady = WiFi.ready(); with bool cellReady = Cellular.ready();


#3

Thanks for posting this Rick, it clearly is helping people!


#4

Thanks for the example! I am having issues with the WiFi reconnecting after System.sleep(); however. The LED never turns back to breathing cyan after the first System.sleep() call.

I loaded the first example up to test my theory, and sure enough, wifiReady and cloudReady both return false after the photon was scheduled to wake up!

/*
[led breathing cyan]
wifi=on cloud=on counter=28
wifi=on cloud=on counter=29
wifi=on cloud=on counter=30
calling System.sleep(30)
[led breathing white now]
returned from sleep
wifi=off cloud=off counter=31
wifi=off cloud=off counter=32
wifi=off cloud=off counter=33
...
wifi=off cloud=off counter=57
wifi=off cloud=off counter=58
wifi=off cloud=off counter=59

This all works the same on my photon, however…

[led still breathing white]
wifi=off cloud=off counter=60
wifi=off cloud=off counter=61
wifi=off cloud=off counter=62
...
wifi=off cloud=off counter=110
wifi=off cloud=off counter=111
wifi=off cloud=off counter=112
...
 */

It never seems to reconnect.

I am on build 0.5.3 and running the exact code in the first example. The second example’s System.sleep(SLEEP_MODE_DEEP, 30); does work as expected, shutting down and coming back on after 30s, however I can’t use this sleep mode because I have a couple software interrupts that I need to run while the photon is sleeping.

Any suggestions?


#5

The not actually sleep mode System.sleep(time) that stops the WiFi modem for a period of time appears to not wake back up at time expiration in 0.5.3 and 0.6.0rc2. It works fine in 0.5.2 and earlier in my quick and not exhaustive test.

While that would be a bug and ought to be fixed; since that mode isn’t really sleep anyway, I’d just use MANUAL system mode and use WiFi.on() and WiFi.off(). You may need to call Particle.connect() and Particle.disconnect() as well; I’m not positive about that.


#6

Appreciate it.

I was curious if there was anything more to the basic System.sleep(long seconds) mode than just WiFi management. Guess not! I ended up coding a manual delay and reconnection script upon “wake”. Strange that it was working in an earlier firmware. Perhaps it will get fixed in future updates

// Delay loop until "sleep" is finished
Serial.println("Sleeping");
for (int i = 0; i < secondsToSleep; i++){
  ledToggle = (ledToggle == HIGH) ? LOW : HIGH;
  digitalWrite(STATUS_LED, ledToggle);
  Serial.print(".");
  delay(1000); // Wait 1 second
}

//Reconnect to cloud if not already
if(!WiFi.ready()){
  WiFi.on();                               // Turn wifi on
  WiFi.connect(WIFI_CONNECT_SKIP_LISTEN);  // Connect to particle cloud
  Particle.process();                      // Run WLAN background loop
}

#7

Hey Rick. Picking up on an old thread. You say “The not actually sleep mode System.sleep(time) that stops the WiFi modem for a period of time appears to not wake back up at time expiration in 0.5.3”. Do you know if this also applies to cellular connections? I would assume so. I’m looking at an issue with devices on 0.5.3 not waking up after going into a System.sleep. Do you have any more details on this issue? It seems to happen pretty sporadically but when it does, it basically bricks remote devices.


#8

Which sleep version are you using? Just the System.sleep(time)? I know it didn’t work in a number of versions, 0.5.3 could have been one of them but I’m not sure. It’s not really all that useful, it’s better to use SYSTEM_MODE(SEMI_AUTOMATIC) and directly control the modem to turn on or off as needed.


#9

We’re using the following:
System.sleep(D6, CHANGE, SLEEP_NETWORK_STANDBY, sleepIntervalSeconds);

This was implemented a while ago, definitely before we heard about issues with System.sleep. From what you are saying, as well as comments by others including ScruffR, it sounds like the move is to change the system mode & directly control the modem + handle interrupts.


#10

No, that call should be fine. You may still want to manually control the modem, however. And also make sure your sleep interval isn’t less than, say 10 seconds. And upgrade to a newer system firmware version. 0.5.3 is pretty old and there are a lot of changes since that at 0.6.4.