> Nick Parlante 8/2025
The Raspberry Pi Pico 2 (RP2350) programmed with micropython is a great and inexpensive way to put together a microcontroller project. I was looking at some Pico 2 projects where I wanted the power use to be low enough to run on a battery. Here I'll show example code with the simple "lightsleep" approach for low power usage. The technique is not difficult, but I thought it would be good to get the code spelled out and with measurement numbers out on the internet. There are other techniques which are more complicated and use even less power which I'll mention at the end of the doc.
To show the technique, the example project linked below will wait for a button to pressed and then blink a couple LEDs.
The code is simple, just enough to show how to do the "wait for button click" with low power use.
Here is pseudo code of the main loop (full code)
while True:
if button is clicked:
blink LEDs or whatever
lightsleep(50) # 50ms low-power sleep
At the top of the loop, the code checks if the button has been clicked, and if so blinks the LEDs. Then the code calls the lightsleep(ms) function which pauses the run of the code for the given number of milliseconds, like a regular sleep(). However, lightsleep() also shuts down parts of the chip to save power for the duration of the sleep. When lightsleep() exits, it turns everything back on, restoring the chip to its normal functionality, so when the code loops back to check the button again, that code runs with the chip in its normal state. After checking the button, the code runs lightsleep() again, giving another 50 ms of low-power operation, and so on. As a practical matter, the chip will spend almost all the time in the low-power state, waking up periodically for little slices of time to check the button, and then going back to the low power state.
I worked out the 50 ms figure experimentally. Sleeping for 200 ms, it was definitely possible to click the button fast enough that the chip would not detect it, while at 50 ms the clicks were always detected on my test setup. The results may depend on quirks of the button and wiring, so you might want to test your setup to make sure it detects reliably. You can always use a smaller delay-ms number for more sensitive button detection at the cost of slightly more power use.
I tested the power use of a Pico 2 and an Pico v1, powering the chip with 3.3 volts on its VSYS input pin, using micropython version 1.25.
Using the above lightsleep() loop, the board used 1.92 mA (6.3 mW). The same setup but with regular sleep used 21.90 mA, so lightsleep() provided a 10x improvement, which is great considering how simple the code is. Credit to the Micropython team for putting the needed smarts into the lightsleep() function for such great power saving.
An AA battery has something like 2000 mA Hours of capacity, so 1.92 mA corresponds to about 1000 hours of operation, which is about 40 days. That's pretty good considering how simple the code is, although maybe not good enough for a remote sensor, where you'd want more like a year between battery changes (see last section on other techniques).
The VSYS pin adds a little power usage with its voltage conversion. If we put the input power directly on the 3.3v pin, the power use drops to 1.56 mA, which gets us up to 53 days on an AA battery. Doing this is a little non-standard, bypassing the voltage protection that the VSYS pin provides, and I'm not sure how low-voltage brownout detection works on the Pico.
On the Pico v1 chip the results were similar, using 2.05 mA with lightsleep() vs. 27.65 mA with regular sleep. It's encouraging that the Pico 2 has made its power usage more efficient.
I tried running on the "wireless" Pico 2 W, but I could not get its power usage down nearly as much as the Pico 2.
I determined the 50ms figure based on what was fast enough to catch button clicks. But how much extra power does it taketo go faster? According to my tests, it takes very little power to go faster than 50ms, down to around 5ms. The technique falls apart at 1ms, taking a little more power than just regular sleep().
ms delay mA draw
50 1.93
20 2.03
10 2.13
5 2.31
2 2.85
1 21.6
lightsleep()One problem with lightsleep() is that it interferes with the regular usb communication with the Pico. Therefore if you have a Pico running code where it keeps using lighsleep(), plugging the board in to load new code will not work, since lightsleep() is messing with the usb communication. My solution is to have a "dev mode" pin integrated with the code so when that pin is jumpered to ground, sleeping in the code uses regular utime.sleep(), and otherwise uses lightsleep(). In this way, you can build and test the software normally, but when you need usb communication to work, put in the dev jumper so usb starts working. All the other logic works the same, dev mode or not. In the picture of the test board above, you can see the dev pin jumpered to ground with a white wire. Having wasted enough time trying to communicate with chips that are down in some power-save mode, I think this is a good technique: have a dev mode you can activate on the fly, but which leaves the rest of the logic intact.
The code is at lightsleep-blink.py and it's released to the public domain. The button is on pin 17 and dev-mode is on pin 18, each of these connect to ground to activate. The LED is on pin 16. I like using the mpremote command line that comes with micropython. Running the code on an attached Pico 2 is just:
mpremote run lightsleep-blink.py
There's a print option in the code, just to verify that serial I/O is surviving whatever lightsleep() is turning off and on.
When micropython version 1.27 came out, I re-ran the code with that version to check things. And it wouldn't be Computer Science without some problem down in the details! With version 1.27, the power usage increased to 2.68 mA. That's not terrible, but not as good as the 1.92 mA seen with version 1.25. Now in fairness to version 1.27, the power use of the non-lightsleep code decreased a little from 21 mA to around 19 mA, although that's no help for our low-power strategy where the code is spending all its time down in lightsleep(). If anyone is interested, I experimented a bit, and the power use increase seems to be correlated with the length of the python code. I gradually removed lines, keeping most of the algorithm, and eventually the power use snapped back down to 1.92 mA (here's the stripped-down-version). Maybe there's some detail about the memory footprint introduced after 1.25. For the most part, using the latest version is fine, or use version 1.25 for the lowest power use with this lightsleep() strategy.
In micropython lightsleep() is the simple power-save approach. There is also a deepsleep() function (micropython sleep docs) which lowers the power usage much more aggressively, and the code can register particular pins which will end the sleep. When the deepsleep() ends, the program does not resume running on the next line. Instead, the program runs again from its beginning, as if it had just booted up. It sounds strange, but it's a structure you can work with. In fact, if you take the above code, and simply change lightsleep() to deepsleep() it would almost work. Unfortunately, deepsleep() is not implemented in micropython for many boards, and in particular it is not currently implemented for the Pico series. I do hope it will be implemented someday, as the Pico is such an inexpensive and widely used chip.
Just for comparison, I ran the deepsleep() solution on an Adafruit ESP32 Feather, a chip with good low power performance and where micropython implements it. The Feather also costs like four times as much as the Pico. Using deepsleep() the Feather consumed 28 uA (microamps) at 3.5 volts input. That's a pretty good low power consumption figure, corresponding to about 8 years of running on AA batteries.
If you want to program in C, then the Pico 2 SDK has support for more advanced low-power techniques, but with more programming effort. See this github issue which links to example code, claiming to get down to 150 uA. I'm spoiled with the ease of using micropython, so I'm feeling too lazy to solve this in C.
Someday maybe deepsleep() will be added to micropython for the Pico, and then I'll can add a section about using that technique for even lower power use.
Below is a copy of the code, or here: lightsleep-blink.py
"""
Pico micropython example, blinking leds
when a button is clicked.
Demonstrates using lightsleep() for
low power usage.
Should work on pico and pico2.
- Nick Parlante
This code is placed in the public domain,
free for any use.
Button is on 17
LED is on 16
xVsysG3.3 1918G1716
-------------------
u|
-------------------
1213G1415
"""
import machine
from machine import Pin
import utime
# Pins held high, ground to activate
BUTTON = 17
DEV = 18
LED1 = 25
LED2 = 16
# True = output some text per button click
# showing that serial output works
PRINT = False
# Use pull-up .. avoid pull_down errata, ground on click,
# so .value() == 0 -> clicked
button = Pin(BUTTON, Pin.IN, Pin.PULL_UP)
dev_mode = Pin(DEV, Pin.IN, Pin.PULL_UP)
led1 = Pin(LED1, Pin.OUT)
led2 = Pin(LED2, Pin.OUT)
led1.low()
led2.low()
# Count number of clicks to relay in printing
click_count = 0
def blink_led(led, delay_ms):
"Blink led twice with given delay."
for i in range(2):
led.high()
utime.sleep_ms(delay_ms)
led.low()
utime.sleep_ms(delay_ms)
# lightsleep for the given ms, unless dev mode,
# in which use regular utime sleep
def dev_sleep(ms):
"Sleep, either regular or lightsleep() depending on dev mode."
if dev_mode.value() == 0:
utime.sleep_ms(ms)
else:
machine.lightsleep(ms)
# Fast blink at boot
blink_led(led1, 50)
if PRINT:
print('start low power blink ticks: {}'.format(utime.ticks_ms()))
while True:
# If button is clicked -> do something,
if button.value() == 0:
blink_led(led1, 100)
blink_led(led2, 100)
click_count += 1
if PRINT:
print('button click: {} ticks: {}'.format(click_count, utime.ticks_ms()))
# Sleep 50 ms, then loop around to check again.
# 50 ms is fast enough to detect clicks while saving significant power.
dev_sleep(50)
>: Nick's Home
Nick Parlante nick.parlante -at- cs.stanford.edu