Electron SLEEP_MODE_DEEP tips and examples

It’s a little confusing getting an Electron to go into deep sleep, using the 130 µA that it should. These six annotated code examples should hopefully clear things up.

All tests were run on 0.6.0 on a Electron G350.

AUTOMATIC, no system thread

#include "Particle.h"

void setup() {
}

void loop() {
	// Results in 133 uA power consumption
	// Modem is turned off, full cellular resync after restart (blinking green, blinking cyan, fast blinking cyan), 5-6K of data usage
	System.sleep(SLEEP_MODE_DEEP, 30);

	// This part not reached; sleep mode deep starts over again after setup()
}

SLEEP_MODE_SOFTPOWEROFF

#include "Particle.h"

void setup() {
}

void loop() {
	// Resulted in 91.5 uA power consumption
	// SLEEP_MODE_SOFTPOWEROFF turns off the fuel gauge in addition to normal SLEEP_MODE_DEEP

	// Modem is turned off, full cellular resync after restart (blinking green, blinking cyan, fast blinking cyan), 5-6K of data usage
	System.sleep(SLEEP_MODE_SOFTPOWEROFF, 30);

	// This part not reached; sleep mode deep starts over again after setup()
}

MANUAL, no system thread (failure example)

#include "Particle.h"

// This behaves the same for SEMI_AUTOAMTIC and MANUAL
SYSTEM_MODE(MANUAL);

void setup() {
}

void loop() {
	// Note the use of SYSTEM_MODE(MANUAL) or SYSTEM_MODE(SEMI_AUTOMATIC).

	// Resulted in 0.70 mA (700 uA) power consumption, with occasional spikes higher
	// It should be 130 uA, but it's not because the modem wasn't really powered down
	// properly because it was never turned on.

	// See the following example for a workaround. Don't do this.

	System.sleep(SLEEP_MODE_DEEP, 30);

	// This part not reached; sleep mode deep starts over again after setup()
}

MANUAL, no system thread (good example)

#include "Particle.h"

// This behaves the same for SEMI_AUTOMATIC and MANUAL
SYSTEM_MODE(MANUAL);

void setup() {
}

void loop() {
	// It's necessary to turn on the cellular modem in MANUAL or SEMI_AUTOMATIC mode
	// before going to sleep. This happens because the modem is put to sleep using AT
	// commands, and they don't work when the modem is not on.
	Cellular.on();

	// Resulted in 132 uA power consumption, as it should

	System.sleep(SLEEP_MODE_DEEP, 30);

	// This part not reached; sleep mode deep starts over again after setup()
}

MANUAL, system thread (failure example)

#include "Particle.h"

// This behaves the same for SEMI_AUTOMATIC and MANUAL
SYSTEM_MODE(MANUAL);

// Unlike the previous example, this enables system thread
SYSTEM_THREAD(ENABLED);

void setup() {
}

void loop() {
	// It's necessary to turn on the cellular modem in MANUAL or SEMI_AUTOMATIC mode
	// before going to sleep. This happens because the modem is put to sleep using AT
	// commands, and they don't work when the modem is not on.
	Cellular.on();

	// Resulted in numbers all over the place, 60 mA, sometimes more, sometimes less.

	// The problem is that in system threaded mode, Cellular.on() is asynchronous so it doesn't
	// finish starting up before sleeping, so SLEEP_MODE_DEEP fails to really put the modem
	// into sleep mode.

	// See the following example for a workaround. Don't do this.

	System.sleep(SLEEP_MODE_DEEP, 30);

	// This part not reached; sleep mode deep starts over again after setup()
}

MANUAL, system thread (good example)

#include "Particle.h"

// This behaves the same for SEMI_AUTOMATIC and MANUAL
SYSTEM_MODE(MANUAL);

SYSTEM_THREAD(ENABLED);

void setup() {
}

void loop() {
	// It's necessary to turn on the cellular modem in MANUAL or SEMI_AUTOMATIC mode
	// before going to sleep. This happens because the modem is put to sleep using AT
	// commands, and they don't work when the modem is not on.
	Cellular.on();

	// This workaround appears to be necessary when using SYSTEM_THREAD(ENABLED).
	// The reason is that .on() and .off() are asynchronous in system threaded mode
	// and if the calls don't complete, sleep mode is not properly entered.
	delay(2000);

	// The Cellular.off() requirement is probably a bug. I think it's because when
	// going to sleep in system threaded mode, it's not waiting for the modem to actually
	// go to sleep. As of 0.6.0 it's necessary, otherwise you drop in a weird mode that
	// ends up using 2.8 to 60 mA instead of 132 uA.
	Cellular.off();
	delay(1000);

	// Resulted in 132 uA power consumption, as it should

	System.sleep(SLEEP_MODE_DEEP, 30);

	// This part not reached; sleep mode deep starts over again after setup()
}

11 Likes

This is another very Helpful thread!

Thank You!

The documentation needs to be updated with these specific examples, since right now they don’t seem to address sleep modes for the Electron at all.

3 Likes

Thanks for the input. What of these recommendations apply for sleep_network_standby to save on battery? I am using an electron and put it to sleep with System.sleep(PIN, RISING, SLEEP_NETWORK_STANDBY) as I need to save on data usage. However, the battery seems to be draining a lot faster than expected.

In my lab tests, the battery lasted for more than 10 days if the electron was sleep all the time. However, when deployed in the field, it only lasts a couple of days.
Thanks for the advice

There’s one more useful case: If, for example, you wake up, read a sensor, and go back to sleep, you don’t need to turn the cellular modem on then off again unless it was a cold boot. If the modem was already in sleep it will stay asleep and you’ll still have low power consumption.

In this example, you get two fast sleeps (awake for 2 seconds), and the third time it stays awake long enough to publish.

With this code running on system firmware 0.7.0 I got 160 uA after sleep after cold boot, after sleep after warm boot, and after sleep after publishing.

#include "Particle.h"

#include "cellular_hal.h"

SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);

void startup();
STARTUP(startup());

SerialLogHandler logHandler(LOG_LEVEL_TRACE);

const unsigned long SLEEP_PERIOD_SEC = 30;

retained int bootCount = 0;
bool forceModemOff = false;
bool publishEvent = false;

void startup() {
	System.enableFeature(FEATURE_RESET_INFO);
	System.enableFeature(FEATURE_RETAINED_MEMORY);
}

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

	switch(System.resetReason()) {
	case RESET_REASON_POWER_MANAGEMENT:
	case RESET_REASON_POWER_DOWN:
	case RESET_REASON_POWER_BROWNOUT:
		// Probably a cold boot
		forceModemOff = true;
		break;
	}

	bootCount++;

	// Every third boot publish an event
	publishEvent = ((bootCount % 3) == 0);
}

bool notConnected() {
    return !Particle.connected();
}

void loop() {
	if (publishEvent) {
		// Publishing an event
		Log.info("going to publish an event");

		Particle.connect();

		if (waitFor(Particle.connected, 90000)) {
			// Wait up to 90 seconds to connect to the cloud
			char buf[256];
			snprintf(buf, sizeof(buf), "bootCount=%d", bootCount);

			// Wait for the publish to complete before proceeding
		    Particle.publish("testBoot", buf, PRIVATE, WITH_ACK);
			Log.info("published %s", buf);
		}

		// Disconnect from the cloud
		Particle.disconnect();
		waitFor(notConnected, 15000);

		// Turn off the modem as well.
		Cellular.off();

		// Wait a few seconds to makes sure modem is actually off
		delay(2000);
	}
	else {
		// Not publishing an event
		if (forceModemOff) {
			// From a cold boot we need to wake the modem up to put it into deep sleep.
			Log.info("forceModemOff - powering up and down modem");

			// In order to put the modem in deep sleep mode from a cold boot, you need to power it
			// in first. cellular_on from cellular_hal.h is blocking in system threaded mode,
			// so it eliminates the delay in the other examples.
			cellular_on(NULL);

			// cellular_off (in cellular_hal.h) IS blocking in system thread mode, eliminating the need
			// for a delay as in the other examples.
			cellular_off(NULL);
		}
		else {
			Log.info("only staying awake for 2 seconds");

			// Otherwise only stay awake for 2 seconds and go back to sleep.
			// This simulates waking up, taking a sensor reading, then going back to sleep.
			delay(2000);
		}

	}

	System.sleep(SLEEP_MODE_DEEP, SLEEP_PERIOD_SEC);

	// This line not reached; after waking up from SLEEP_MODE_DEEP you start over with
	// startup, setup, and loop again as if reset.

}
5 Likes

Thank you @rickkas7 for helping the Particle community, and providing the above example code.

For my current project, I need to measure A0 analog channel and publish data every minute. How would I do this using the above code? My initial thoughts are to change bootCount from 3 to 1, and set SLEEP_PERIOD_SEC = 60. Do you agree?

Also, I’m a little unsure where I place my analog A0 measurement calls. Can you please provide the location(s) or example code?

Cheers, Sam. Thanks again!

Is the particle.disconnect call needed prior to cellular off?

Also is there any advantage of using the vellular_off(null) vs cellular.off()?

No. Cellular.off() will handle that and it's more reliable if you allow it to.

I agree.

I was going to try adapting your example for my own project, and I’m using Particle Workbench for the first time. I’m getting an error on the retain int bootCount declaration: this declaration has no storage class or type specifier. Do you have any ideas on how to fix that?

If it comes down to it, I can yank that out, because I don’t think I’ll need to track the number of boots. But my first step was going to be trying to use your code mostly as-is.

This should not be retain but retained

Oh, my bad for re-typing instead of cut-and-pasting. In my code is correctly retained.

Is this an actual compile error or rather just a false flag from intelli sense?
Workbench has still issues with collecting and understanding all the keywords :wink:
But that wouldn’t impact the actual build.

3 Likes

I think it was just Intellisense. But this morning, Workbench said there was an update available, and after I did that, the notices went away. So, problem solved, I guess. :smiley:
EDIT: Nevermind, it’s back. I guess it just took a minute to rescan the code and flag the statement again.
EDIT2: And now compiling is failing, I think because of the git repo renaming with the DeviceOS 1.0 release. Should be an easy fix, but I’m at work now, so I’ll have to look at it later.

2 Likes

Just for the record, after the latest alpha7 update for Particle Workbench, I was able to locally compile and flash my Electron test code just fine. I just need to re-think how to accomplish my goals and refactor my code.

What I want to do is use the Google Maps integration for location tracking. To make things extra fun, I want to dynamically adjust sleep times by determining whether the tracker is in motion or staying still. So if the current location and previous location are the same (modulus some error factor), the Electron will sleep longer (say, 10-20 minutes). But if the tracker has moved since the last update, it will shorten the sleep time (maybe 1-5 minutes), so that it can keep more fine-grained path info.

I just need to figure out the best way to integrate the mapping library’s need for the cellular modem with the controls to bring it up/down for sleep…