Demystifying Sleep on the Particle Photon
Having worked with microcontrollers in one form or another over the last decade I've often used some form of sleep or deep sleep to achieve lower power consumption. Most of my experience in this realm came with PIC microcontrollers and even the Arduino where sleep implied some form of stopping instruction execution on the chip. For this reason I was thrown off a bit when trying to achieve longer battery life on the Stat Tracking Hockey Puck project by using System.sleep(). My goal was to grab the latest stats from the Losant backend and then go to sleep for 5 minutes and then check again. The result was that it checked in once at boot time and then never checked in again. WTF?
If you want the video version of this same content check out the DailyIoT episode I did on the topic. Otherwise skip over the video and keep reading.
Under the Covers
Before we go any further in discussing sleep on the Photon it's important to understand what is going on under the covers. The Photon is not just a microcontroller as illustrated in the following block diagram.
The two main modules that make up the Photon are the STM32 chip (the main microcontroller) and the BCM (WiFi chip). The System.sleep() calls encapsulate BOTH of these modules which is where my confusion came from. So let's look at a couple of the calls from the documentation.
This is the call I tried in my hockey puck code. Grab the stats, make this call with 300 seconds as the argument and wait for it to return. The problem is, it returns immediately and continues executing code. All this call does is shut down the BCM WiFi module for the time specified. If your RGB LED is active it will begin to breath white indicating it is no longer connected to the network. In my opinion the function is a bit of a misnomer. While the WiFi module is sleeping, nothing else is. I would have preferred something like System.sleepWiFi() to make it more explicit.
System.sleep(SLEEP_MODE_DEEP, long seconds)
This call is closer to what we are looking for. With the SLEEP_MODE_DEEP option passed as the first argument the Photon will put the STM32 into standby mode (more on this below). This call essentially causes a reboot. It stops execution of code and waits for the specified number of seconds to pass and then starts running your code from the beginning of the setup() function. Don't expect it to pick up where it left off. This is the function that will give you the most power saving bang for your buck. If your application does a lot of one time setup or has a lot of stuff stored in memory keep reading as this call might not be the best for you because you'll have to do it all again when the Photon wakes up.
System.sleep(uint16_t wakeUpPin, uint16_t edgeTriggerMode)
This call will put the STM32 into stop mode and wait for an external interrupt to wake it up. This is great for applications where the press of a button or flip of a switch is meant to wake up the device. Unlike the deep sleep call your code will continue to run when the Photon wakes up having preserved all variables in SRAM. Notice, however, that there is no option to pass a timeframe. For that we need the last variant of sleep call.
System.sleep(uint16_t wakeUpPin, uint16_t edgeTriggerMode, long seconds)
This call is exactly the same as the previous one with the addition of a seconds argument. It puts the STM32 into stop mode. Ultimately this is the call I was looking for. I would have used the deep sleep version but due to the way the puck starts up it causes a flicker in the e-paper display. I didn't want that to happen on every wakeup from sleep. In other words, I needed to retain the state of variables in SRAM which this call allows. The problem with this call, in my opinion, is it forces you to specific a pin which will now be used as an input and an edge mode which I don't require for my application. I just want to go into stop mode for a set period of time but this call forces me to use an I/O pin too which could be a problem in an application that can't spare an extra pin.
Understanding STM32 Sleep Modes
When the Particle documentation talks about putting the microcontroller into "stop" and "standby" modes it would be nice to have a clear understanding of what they mean. For that we have to go directly to the STM32 datasheet which gives us the following clarification of terms on page 32.
In Sleep mode, only the CPU is stopped. All peripherals continue to operate and can wake up the CPU when an interrupt/event occurs.
The Stop mode achieves the lowest power consumption while retaining the contents of SRAM and registers. All clocks in the 1.2 V domain are stopped, the PLL, the HSI RC and the HSE crystal oscillators are disabled. The voltage regulator can also be put either in normal or in low-power mode. The device can be woken up from the Stop mode by any of the EXTI line. The EXTI line source can be one of the 16 external lines, the PVD output, the RTC alarm / wakeup / tamper / time stamp events, the USB OTG FS/HS wakeup or the Ethernet wakeup.
The Standby mode is used to achieve the lowest power consumption. The internal voltage regulator is switched off so that the entire 1.2 V domain is powered off. The PLL, the HSI RC and the HSE crystal oscillators are also switched off. After entering Standby mode, the SRAM and register contents are lost except for registers in the backup domain and the backup SRAM when selected. The device exits the Standby mode when an external reset (NRST pin), an IWDG reset, a rising edge on the WKUP pin, or an RTC alarm / wakeup / tamper /time stamp event occurs.
The Photon gives you several options for achieving low power modes. These options can be a little confusing at first but once you understand what's going on under the hood and how different subsystems are affected by the calls it's pretty easy to achieve minimal power consumption in your application.