If you'd like to maximize the battery life on a CircuitPython project, you need to be able to put your program to sleep when it's not doing something. For instance, you may want to read a temperature or fetch some data only every few minutes or hours. In between, your board can go to sleep and draw only a tiny amount of power from the battery.

If you're using a display that is visible even when powered off, such as the e-ink display on the Adafruit MagTag, then you can sleep between updates to the display.

This guide will talk about using the sleep and wake-up alarm capabilities that are available in CircuitPython.

Terminology

We'll distinguish between deep sleep and light sleep:

  • If a program does a deep sleep, it first exits, and then the microcontroller goes to sleep, turning off as much as possible while still being able to wake up later. When the microcontroller wakes up, it will start your program (code.py) from the beginning.
  • If a program does a light sleep, it still goes to sleep but continues running the program, resuming after the statement that did the light sleep. Power consumption will be minimized. However, on some boards, such as the ESP32-S2, light sleep does not save power compared with just using time.sleep().

CircuitPython uses alarms to wake up from sleeping. An alarm can be triggered based on a specified time being reached, or based on an external event, such as a pin changing state. The pin might be attached to a button, so you would be able to wake up on a button press.

The alarm module

Alarms and sleep are available in the alarm module in CircuitPython (latest documentation). You create one or more alarms, and then go into a light sleep or deep sleep while waiting for them.

TimeAlarm Light Sleep

Here's a simple program that just blinks the status NeoPixel every 10 seconds, and does a light sleep in between, using a TimeAlarm. The video above demonstrates this program, eliding the 10-second sleeps.

import alarm
import board
import digitalio
import neopixel
import time

# On MagTag, enable power to NeoPixels.
# Remove these two lines on boards without board.NEOPIXEL_POWER.
np_power = digitalio.DigitalInOut(board.NEOPIXEL_POWER)
np_power.switch_to_output(value=False)

np = neopixel.NeoPixel(board.NEOPIXEL, 1)

while True:
    np[0] = (50, 50, 50)
    time.sleep(1)
    np[0] = (0, 0, 0)

    # Create a an alarm that will trigger 10 seconds from now.
    time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 10)

    # Do a light sleep until the alarm wakes us.
    alarm.light_sleep_until_alarms(time_alarm)
    # Finished sleeping. Continue from here.

TimeAlarm Deep Sleep

Here's a similar program, which does a deep sleep. The video above is still what you'd see. Remember that for deep sleep, the program exits, and restarts when woken up. So in this program there's no while True: loop.

import alarm
import board
import digitalio
import neopixel
import time

# On MagTag, enable power to NeoPixels.
# Remove these two lines on boards without board.NEOPIXEL_POWER.
np_power = digitalio.DigitalInOut(board.NEOPIXEL_POWER)
np_power.switch_to_output(value=False)

np = neopixel.NeoPixel(board.NEOPIXEL, 1)

np[0] = (50, 50, 50)
time.sleep(1)
np[0] = (0, 0, 0)

# Create a an alarm that will trigger 20 seconds from now.
time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 20)
# Exit the program, and then deep sleep until the alarm wakes us.
alarm.exit_and_deep_sleep_until_alarms(time_alarm)
# Does not return, so we never get here.

PinAlarm Deep Sleep

This example uses PinAlarm instead of TimeAlarm. It will deep sleep until the D11 button on the lower right of the MagTag is pressed. On the MagTag, pressing a button connects a pin to ground, so we wait for a False value. We also enable a pull-up to hold the pin high (True) when the button is not pressed.

import alarm
import board
import digitalio
import neopixel
import time

# On MagTag, enable power to NeoPixels.
# Remove these two lines on boards without board.NEOPIXEL_POWER.
np_power = digitalio.DigitalInOut(board.NEOPIXEL_POWER)
np_power.switch_to_output(value=False)

np = neopixel.NeoPixel(board.NEOPIXEL, 1)

np[0] = (50, 50, 50)
time.sleep(1)
np[0] = (0, 0, 0)

pin_alarm = alarm.pin.PinAlarm(pin=board.D11, value=False, pull=True)

# Exit the program, and then deep sleep until the alarm wakes us.
alarm.exit_and_deep_sleep_until_alarms(pin_alarm)

# Does not return, so we never get here.

TouchAlarm Deep Sleep

This example is for the Metro ESP32-S2. The MagTag has no pins that can be used for touch. (D10 could theoretically be used, but protection components are connected to it that prevent it being used for touch.)

It will sleep until pin IO5 is touched, or 10 seconds has elapsed, whichever comes first. The on-board LED blinks for one second at the beginning of the program.

import alarm
import board
import digitalio
import time

# Print out which alarm woke us up, if any.
print(alarm.wake_alarm)

led = digitalio.DigitalInOut(board.LED)
led.switch_to_output(value=True)
time.sleep(1)
led.value = False

# Create an alarm that will trigger 10 seconds from now.
time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 10)
# Create an alarm that will trigger if pin IO5 is touched.
touch_alarm = alarm.touch.TouchAlarm(pin=board.IO5)

# Exit the program, and then deep sleep until one of the alarms wakes us.
alarm.exit_and_deep_sleep_until_alarms(time_alarm, touch_alarm)
# Does not return, so we never get here.

Pretending to Sleep When Connected

When your board is connected to a host computer via USB, you don't want it to really do a light sleep or deep sleep, because that would break the USB connection and make it difficult to debug or edit your program. So when the board is connected, we simulate light and deep sleep. If CircuitPython can reduce power consumption while pretending to sleep and still remain connected, it will do so, but you will not be saving nearly as much power as when you're not connected.

So if you're trying to measure how much power you're saving while sleeping, you really need to do your measurements from battery power (or a power supply) while unconnected.

What Woke Me Up?

When a program awakens from light sleep or deep sleep, it's because of an alarm. You can find out what kind of alarm woke up the program by looking at alarm.wake_alarm.

If the program did not wake up from sleep, then alarm.wake_alarm will be None

If the program woke up from a light sleep, then alarm.wake_alarm will be one of the alarm objects passed to alarm.light_sleep_until_alarms(...). The triggered alarm is also returned by that function, so you can get the alarm value directly. Here are two ways to get the triggered alarm:

triggered_alarm = alarm.light_sleep_until_alarms(alarm1, alarm2)

# is the same as

alarm.light_sleep_until_alarms(alarm1, alarm2)
triggered_alarm = alarm.wake_alarm

If the program restarted after a deep sleep, then alarm.wake_alarm will be an alarm object of the same type as the original alarm, but it will not be exactly the same object. It's attributes may be different, or they may be incomplete in some way. For instance, for TimeAlarm, the .monotonic_time attribute may not contain the same value. But you can still do an isinstance(alarm.wake_alarm, TimeAlarm) to find out it was a TimeAlarm that woke up the program.

When a program goes into deep sleep, it exits and then sleeps. So all the information in its variables is lost. But the program might want to remember something for use when it restarts. It could write into a file onto the board CIRCUITPY drive, or it could write into internal flash using microcontroller.nvm. But flash has a limited lifetime, so it's better not to write it over and over. Instead, the program can write into a special part of memory (RAM) that is powered during deep sleep. In most microcontrollers, this kind of memory is called "backup RAM"; in CircuitPython, we call it alarm.sleep_memory. This memory requires very little power to maintain. If power is removed completely, then the memory is lost, but as long as USB power or a battery is connected, it will remember what is stored in it.

alarm.sleep_memory is just a byte array of a few thousand bytes. You can use it to store whatever you want, but you'll need to encode the data as bytes. You could use struct.pack and struct.unpack, or use JSON, or some other format that's convenient for you.

MagTag Example

Here's a simple example of using sleep memory, without any encoding. Each time the program wakes up, it increments a count kept in one byte in alarm.sleep_memory. This program displays the current battery voltage and the count on the MagTag display.

The very first time the program runs, we want to initialize the count. We can tell if this is the first time the program has run by checking alarm.wake_alarm. If it is None, then we know we have not done a deep sleep yet.

The video below shows the program running, with the long 60 second-sleeps elided.

import alarm
import microcontroller
import time
from adafruit_magtag.magtag import MagTag


magtag = MagTag()

magtag.add_text(
    text_scale=2,
    text_wrap=25,
    text_maxlen=300,
    text_position=(10, 10),
    text_anchor_point=(0, 0),
)

# Reset the count if we haven't slept yet.
if not alarm.wake_alarm:
    # Use byte 5 in sleep memory. This is just an example.
    alarm.sleep_memory[5] = 0

alarm.sleep_memory[5] = (alarm.sleep_memory[5] + 1) % 256

# Display the current battery voltage and the count.
magtag.set_text(
    "battery: {}V    count: {}".format(
        magtag.peripherals.battery, alarm.sleep_memory[5]
    )
)

magtag.refresh()

# Sleep for 60 seconds.
al = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 60)
alarm.exit_and_deep_sleep_until_alarms(al)
# Does not return. Exits, and restarts after 60 seconds.

How much power would you actually save by using light sleep or deep sleep? It depends on which board you're using, what your program is doing when it's not sleeping, what is left turned on during light sleep, and how long you spend sleeping vs running (your program's duty cycle).

Note that if there are other devices on your board that can be left turned on, such as sensors or NeoPixels, a deep sleep will not automatically turn them off. Your program should shut down what it can if you want to minimize power usage. Sometimes these on-board devices are controlled by the microcontroller pins, and may be turned off as a consequence of the pins being turned off, but on some boards, the on-board devices are powered whenever the board is powered.

Given all those variables, let's look at power consumption using the sample programs in the Alarms and Sleep section of this guide. If you recall, those programs just blink a NeoPixel for one second and then sleep for ten seconds.

We'll show a bunch of power-consumption graphs below. These screenshots were taken using the Nordic Semiconductor Power Profiler Kit II (PPK2) hardware and software. The PPK2 is quite inexpensive (< $100) compared to the usual power meter, is easy to use, and works well.

ESP32-S2 TimeAlarm Deep Sleep Power Consumption

Here's a graph showing the TimeAlarm deep sleep demo program, running on a MagTag ESP32-S2, sleeping every ten seconds. There's a big short spike when the program starts up after sleeping. The program blinks the NeoPixel, and then sleeps. We are supplying 3.7V to the board, which is a typical LiPo battery voltage.

Let's zoom in on the power consumption while the program is actively running during one cycle. You can see it's using about 50mA. Note that the vertical axis scale has changed, because we're not including the big spike at the beginning of a run.

If this program were using WiFi, you'd see much higher power consumption, up to a few hundred mA during active WiFi use.

Now let's look at the power consumption during deep sleep. In the graphs above, that's where the power consumption line looks very close to 0. Again, the vertical axis has expanded, so we can see microamps accurately. You can see the board is using a little under 230uA when it's sleeping. About 25-30uA of this is the actual ESP32-S2 module on the MagTag; the rest is board overhead, like the voltage regulator, and the (dim) power LED.

ESP32-S2 TimeAlarm Light Sleep Sample Power Consumption

For comparison, here's a graph of the TimeAlarm light sleep demo program, which also cycles every ten seconds. On the ESP32-S2, as mentioned, light sleep is no better than time.sleep in terms of power consumption. For one second before the NeoPixel is turned on (marker 1), power consumption is about 33mA. Turning on the NeoPixel raises the current drawn to about 75mA. Then the program sleeps (marker 2), but the consumption is still about 33mA.

So there's not much reason to use light sleep on the ESP32-S2 if you're trying to save power.

ESP32-S2 PinAlarm Deep Sleep Power Consumption

Now let's look at deep sleep power consumption when using a PinAlarm on the ESP32-S2 MagTag. Here are several cycles of sleeping. Each red arrow points to when the D11 button was pressed. The intervals are different lengths because the time between button pushes was not the same.

Here is the start of a single PinAlarm sleep cycle. Unfortunately, the circuitry that needs to stay on to detect pin changes takes a significant amount of current. Even when deep sleeping, the board is using about 1.65mA, much more than it would if we were just using TimeAlarm.

ESP32-S2 TouchAlarm Deep Sleep Power Consumption

ESP32-S2 TouchAlarm power consumption is similarly higher than you might like  It's about 2.6mA, even  higher than PinAlarm, about 2.6mA. So again, if you really want to save your battery, use TimeAlarm.

Sleep Power Summary

Here's a chart of deep sleep power consumption on ESP32-S2 when different kinds of alarms are used. For other chip families, see the other pages in this guide.

ESP32-S2 (MagTag) Deep Sleep

  • TimeAlarm: 230uA
  • PinAlarm: 1.65mA
  • TouchAlarm: 2.6mA

ESP32-S2 Light Sleep

  • Sleep current is the same as time.sleep(), so there's no advantage to using light sleep.
Do not measure true power consumption on USB power as CircuitPython only simulates sleep while connected via USB.

Measure to be Sure

As we mentioned, for any particular program and board, there are many things that can affect its power consumption. If you want to be know how long your battery will last, and whether you're consuming power unnecessarily, it's really helpful to have a power meter.

Don't Forget About Simulated Sleep

Don't forget that when your board is connected to a host computer via USB, it does not actually sleep when sleep is requested, because we don't want to break the USB serial and mass storage (CIRCUITPY) connections. So you need to test power consumption when not connected. The board can be powered from the battery connector or via the USB port (via a power pack or a wall adapter), but it can't be actively connected to a host computer.

The Alarm module has also been implemented on the NRF, STM32, and RP2040 ports. Sleep modes always work a little differently between MCU families - some chips have better power savings, some have worse, some chips have big differences between light and deep sleep and others don’t, some may only be able to wake on specific kinds of pin transitions, etc etc. In addition, development boards themselves will have components that passively consume power - many dev boards that aren’t specifically designed for maintaining a long battery life may consume several mA of current even when the MCU is completely turned off! 

We’ve done our best to make the CircuitPython API represent the light sleep and deep sleep states in as similar a way as possible across ports. This page describes the differences in setup and power performance you may see across some common dev boards, and includes some extra sketches that may be helpful for using sleep mode with family-specific strengths, like BLE on NRF52 boards, or SDIO on the STM32 family.

Generic Examples

These sketches are similar to those used on the MagTag earlier in this guide. However, they have some additional flexibility for boards that may only have an LED instead of a Neopixel, and they show some of the differences between chip families in terms of setup. As a recap on the terms used by the API:

  • edge: this defines whether CircuitPython will wake up only when it sees the pin value change, versus anytime it sees the pin at the target value. This doesn’t always impact behavior that much, but it’s important for certain specific cases: for instance, a level interrupt for a “high” value will wake up immediately if the PinAlarm is already high when it goes to sleep, but an edge interrupt will wait until the pin drops and then goes high again. Most chip families only support one of these options.
  • value: this is the logic level value of the pin that will cause CircuitPython to wake, either on a pin transition (when edge is True) or when it is polled at that level (edge is False)
  • pull: sets whether the internal pullup/pulldown resistors are enabled or not. It does not set the direction - the pulls are automatically set to the opposite of value!

Light Sleep

On many ports, light sleep is equivalent to the "wait for interrupt" used in time.sleep(), so it won't always save that much power.

import alarm
import board
import time
import digitalio
import neopixel

## WAKING PINS - uncomment appropriate pin per microcontroller
wake_pin = board.X1                     # STM32F4 Pyboard
# wake_pin = board.GP0                    # RP2040 Pico
# wake_pin = board.A4                     # NRF52840 Feather
# wake_pin = board.IO5                    # ESP32-S2 Saola

## LED - use on RP2040 Pico, STM32F4 Pyboard
## PinAlarms blink 1x, TimeAlarms 2x, Startup 3x
led_pin = board.LED
led = digitalio.DigitalInOut(led_pin)
led.direction = digitalio.Direction.OUTPUT
def blink(num_blinks):
    for i in range(num_blinks):
        led.value = True
        time.sleep(0.2)
        led.value = False
        time.sleep(0.2)
def show_timealarm():
    blink(2)
def show_pinalarm():
    blink(1)
def show_noalarm():
    blink(3)
## Comment out above if using Neopixel

## NEOPIXEL - use on Circuitplayground Bluefruit, ESP32-S2 Saola
## TimeAlarms are red, PinAlarms are blue, Default is white
# np = neopixel.NeoPixel(board.NEOPIXEL, 1)
# def show_timealarm():
#     np[0] = (50, 0, 0)
#     time.sleep(1)
#     np[0] = (0, 0, 0)
# def show_pinalarm():
#     np[0] = (0, 0, 50)
#     time.sleep(1)
#     np[0] = (0, 0, 0)
# def show_noalarm:
#     np[0] = (50, 50, 50)
#     time.sleep(1)
#     np[0] = (0, 0, 0)
## Comment out above if using LED

## PinAlarm only needs to be set once
pin_alarm = alarm.pin.PinAlarm(pin=wake_pin, value=False, edge=True, pull=True)     # STM32, RP2040
# pin_alarm = alarm.pin.PinAlarm(pin=wake_pin, value=False, edge=False, pull=True)  # NRF, ESP32-S2, RP2040

while True:
    ## TimeAlarms must be reset each time you sleep, since they use monotonic time
    time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 10)
    ret_alarm = alarm.light_sleep_until_alarms(pin_alarm, time_alarm)

    print("Returned alarm vs global alarm:") # These should be the same
    print(ret_alarm)
    print(alarm.wake_alarm)

    if isinstance(ret_alarm, alarm.time.TimeAlarm):
        show_timealarm()
    elif isinstance(ret_alarm, alarm.pin.PinAlarm):
        show_pinalarm()
    else:
        show_noalarm()

Deep Sleep

Deep sleep is designed to put the board in the lowest possible power consuming state. If you need to do datalogging for a really long time, this is the best tool.

import alarm
import board
import time
import digitalio
import neopixel

## WAKING PINS - uncomment appropriate pin per microcontroller
wake_pin = board.X1                     # STM32F4 Pyboard
# wake_pin = board.GP0                    # RP2040 Pico
# wake_pin = board.A4                     # NRF52840 Feather
# wake_pin = board.IO5                    # ESP32-S2 Saola

## LED - use on RP2040 Pico, STM32F4 Pyboard
## PinAlarms blink 1x, TimeAlarms 2x, Startup 3x
led_pin = board.LED
led = digitalio.DigitalInOut(led_pin)
led.direction = digitalio.Direction.OUTPUT
def blink(num_blinks):
    for i in range(num_blinks):
        led.value = True
        time.sleep(0.2)
        led.value = False
        time.sleep(0.2)
def show_timealarm():
    blink(2)
def show_pinalarm():
    blink(1)
def show_noalarm():
    blink(3)
## Comment out above if using Neopixel

## NEOPIXEL - use on Circuitplayground Bluefruit, ESP32-S2 Saola
## TimeAlarms are red, PinAlarms are blue, Default is white
# np = neopixel.NeoPixel(board.NEOPIXEL, 1)
# def show_timealarm():
#     np[0] = (50, 0, 0)
#     time.sleep(1)
#     np[0] = (0, 0, 0)
# def show_pinalarm():
#     np[0] = (0, 0, 50)
#     time.sleep(1)
#     np[0] = (0, 0, 0)
# def show_noalarm:
#     np[0] = (50, 50, 50)
#     time.sleep(1)
#     np[0] = (0, 0, 0)
## Comment out above if using LED

## Show which alarm woke the chip
print("Wake alarm:")
print(alarm.wake_alarm)
if isinstance(alarm.wake_alarm, alarm.time.TimeAlarm):
    show_timealarm()
elif isinstance(alarm.wake_alarm, alarm.pin.PinAlarm):
    show_pinalarm()
else:
    show_noalarm()

## USB enumeration may take 4-5s per restart
time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 10)

## Deep sleep pin alarms may only accept a single configuration.
pin_alarm = alarm.pin.PinAlarm(pin=wake_pin, value=True, edge=True, pull=True)      # STM32 must be this exact config
# pin_alarm = alarm.pin.PinAlarm(pin=wake_pin, value=False, edge=False, pull=True)  # NRF and ESP32S2 must use level, not edge
# pin_alarm = alarm.pin.PinAlarm(pin=wake_pin, value=False, edge=True, pull=True)   # RP2040 supports any config

alarm.exit_and_deep_sleep_until_alarms(time_alarm, pin_alarm)
# alarm.exit_and_deep_sleep_until_alarms(pin_alarm) # Using TimeAlarm will reduce performance on the RP2040
# alarm.exit_and_deep_sleep_until_alarms(time_alarm) # Using PinAlarm will reduce performance on the ESP32-S2
STM32 only allows a single pin to wake from deep sleep, PA00, and it isn’t exposed on the STM32F405 Feather Express. Thus, the Feather can only wake from deep sleep using the clock (unless you do some very tricky soldering with magnet wire). If you want to use PinAlarms, you'll need something like the Micropython Pyboard, which exposes PA00 on the X1 pin.

To observe power consumption on the STM32F405 Feather Express, connect your power probe to the VBAT and GND pins.

Observing the Feather with our generic deepsleep test shows how much the power drops when sleeping, even with both alarms enabled. Note that the period of relatively high power consumption right before sleeping is the chip waiting to see if there's a viable USB connection, before deciding to enter "fake" or "real" deep sleep.

In contrast, STM32 uses a simple WFI (wit for interrupt) operation for light sleep, so it doesn't save very much power compared to regular operation:

STM32 Example

One of the benefits of the STM32F405 Feather Express is the built in SD card reader! This could be beneficial to a datalogging project, where you want to collect and store data from a sensor onto easily removable external storage. As an example, the following sketch will wake up every ten seconds to store the internal chip temperature to the SD card:

import alarm
import board
import time
import microcontroller
import sdioio
import storage
import neopixel

np = neopixel.NeoPixel(board.NEOPIXEL, 1)
np[0] = (50, 0, 0)

# SD Card
sd = sdioio.SDCard(
    clock=board.SDIO_CLOCK,
    command=board.SDIO_COMMAND,
    data=board.SDIO_DATA,
    frequency=25000000)
vfs = storage.VfsFat(sd)
storage.mount(vfs, '/sd')

with open("/sd/log.txt", "a") as sdc:
    temperature = microcontroller.cpu.temperature
    print("Temperature:", temperature)
    sdc.write("{}\n".format(temperature))

## USB enumeration may take 4-5s per restart
time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 10)

np[0] = (0, 0, 0)

alarm.exit_and_deep_sleep_until_alarms(time_alarm)

This sketch uses deep sleep, so it saves quite a bit of power when not logging data. However, just having the SD card plugged in costs a little bit of power, taking consumption up from under a milliamp to around 3.7mA:

The nRf examples here use the Circuit Playground Bluefruit board. If you don't have a JST connector handy, the Circuit Playground Bluefruit can also be equivalently monitored with the VCC and GND clip pads.

The Circuit Playground Bluefruit is a good example of a board that suffers a bit in sleep performance because of parasitic components - since the board has many sensor ICs, the power does not drop very dramatically during deep sleep:

Light sleep is similar, if a bit noisier:

However, other boards may get better power performance depending on their PCB design.

nRF Example

A core NRF feature that makes sense to pair with low power is BLE Advertising. By spacing out advertising with deep sleep in between, a device can periodically signal without draining its battery too quickly. The following sketch shows how this could be implemented in practice:

import time
import adafruit_ble
from adafruit_ble_eddystone import uid, url
import alarm

np = neopixel.NeoPixel(board.NEOPIXEL, 1)

radio = adafruit_ble.BLERadio()
# Reuse the BLE address as our Eddystone instance id.
eddystone_uid = uid.EddystoneUID(radio.address_bytes)
eddystone_url = url.EddystoneURL("https://adafru.it/discord")

while True:
    np[0] = (50, 0, 0)
    # Alternate between advertising our ID and our URL.
    radio.start_advertising(eddystone_uid)
    time.sleep(0.5)
    radio.stop_advertising()

    radio.start_advertising(eddystone_url)
    time.sleep(0.5)
    radio.stop_advertising()

    ## USB enumeration may take 4-5s per restart
    time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 10)

    np[0] = (0, 0, 0)

    alarm.exit_and_deep_sleep_until_alarms(time_alarm)

You can learn more about BLE Advertising in the official Learn Guide.

Boot.py:

The Raspberry Pi Pico development board power consumption can be monitored by connecting to VBUS and GND.

The RP2040, conveniently, is the only currently supported chip that supports every kind of wakeup configuration across all sleep types. However, note that unlike most other chip families, it will actually lose a bit of deep sleep performance when using a TimeAlarm. This is because the RP2040 dormant mode actually doesn’t support the RTC at all without an external clock source, so our deep sleep is actually a very extreme version of light sleep, rather than a separate mode.

When avoiding TimeAlarms, the Raspberry Pi Pico devboard can achieve under 2mA while in deep sleep:

Note that detecting the wakeup alarm in true deep sleep mode is not yet supported on RP2040 as of version 7.0

When a Timealarm is added, the draw raises up to 7mA.

RP2040's Light sleep mode, while not near deep sleep levels of efficiency, is still a useful power reduction, dropping power consumption by over a third:

RP2040 Example

While it doesn’t have removable storage, the RP2040 can also be used for datalogging using the internal storage on the chip. The following sketch will gather the internal chip temperature and append it to a file in the internal storage, which can be accessed whenever the board is plugged in. 

Since this sketch uses the storage module to access the internal filesystem, you'll need to also include a boot.py file, and will not be able to access the CPy drive while the datalogger is running. You can read more about the storage module in the Guide.

code.py:

import alarm
import board
import time
import microcontroller
import storage

temperature = microcontroller.cpu.temperature
print("Temperature:", temperature)

try:
    with open("/log.txt", "a") as sdc:
        sdc.write("{}\n".format(temperature))
except OSError as e:
    print("Cannot write to fs, is GP4 grounded?")

## USB enumeration may take 4-5s per restart
time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 10)

alarm.exit_and_deep_sleep_until_alarms(time_alarm)

boot.py:

import board
import digitalio
import storage

switch = digitalio.DigitalInOut(board.GP4)

switch.direction = digitalio.Direction.INPUT
switch.pull = digitalio.Pull.UP

# If the switch pin is connected to ground CircuitPython can write to the drive
storage.remount("/", switch.value)

Other Resources for RP2040

User bablokb on GitHub has done extensive measurements of RP2040 power consumption. 

This guide was first published on Dec 17, 2020. It was last updated on Mar 08, 2024.