The main goal is to reproduce similar low-power timer and wake-up functionalities in Micropython. It provides direct access to hardware registers through memory-mapped I/O and is intended for bare-metal embedded development.
This project is inspired by and based on the powman library implementation available in the Raspberry Pi Pico SDK
The library directly accesses the RP2350 power management and timer registers via memory-mapped I/O. Official documentation can be found in the RP2350 datasheet, especially in Section 6.0 (Power Management Overview and Section 6.4 (Register Configuration)
It is designed to control low-power modes, timers, and wake-up alarms on Raspberry Pi Pico 2.
Here for tinygo library
- Micropython
- Raspberry Pi Pico 2
Important:
powmanGetWakeupReason()must be called beforepowmanInit(), because init writes to POWMAN registers that may affect wake-up state.
import deepsleep
reason = deepsleep.powmanGetWakeupReason()
if reason == 0:
print("fresh boot")
elif reason & deepsleep.WAKEUP_ALARM:
print("woke from timer")
elif reason & deepsleep.WAKEUP_GPIO0:
print("woke from GPIO")Set the absolute system time in milliseconds (must be > 0):
deepsleep.powmanInit(1704067200)Sleep for a fixed duration (milliseconds):
deepsleep.powmanOffForMs(10000) # sleep 10 secondsOr sleep until a GPIO goes HIGH:
deepsleep.powmanOffUntilGPIO(15) # wake on GP15 HIGHBoth functions never return — the chip reboots on wake-up.
import deepsleep
import time
def main():
reason = deepsleep.powmanGetWakeupReason() # before init!
if reason == 0:
print("fresh boot")
elif reason & deepsleep.WAKEUP_ALARM:
print("woke from timer alarm")
elif reason & (deepsleep.WAKEUP_GPIO0 | deepsleep.WAKEUP_GPIO1 |
deepsleep.WAKEUP_GPIO2 | deepsleep.WAKEUP_GPIO3):
print("woke from GPIO")
deepsleep.powmanInit(1704067200)
deepsleep.powmanOffForMs(5000)
main()In main.py there is a minimal example`.
Power consumption over one minute.
The consumption during low power mode
Powman directly accesses memory-mapped registers to control:
- System timer
- Alarm registers
- Power regulator
- Boot configuration
- Interrupt enable flags
The library uses mem32 internally to read and write hardware registers.
This is required for bare-metal development and is safe in this context.
Note: every
powmanOff*()function below automatically forces GP0-22 to LOW before sleeping — actively driven (machine.Pin(gpio, Pin.OUT, value=0)), not just a weak pull — except whichever GPIO(s) you passed in as wake-up pins, or listed in the optionalexcludeGpios=(...)argument everypowmanOff*()accepts. GPIO pads live in the always-on domain, so they stay live throughout dormant sleep regardless of what else is powered down; this doubles as a way to cut power to an accessory wired to a GPIO instead of3V3(which can't be switched off from software — see the capacitive-touch-button example inmain.py). GP23-29 are left untouched (reserved for the wireless chip / ADC diode caveat on Pico 2 W — see thelowPowerWifiChipflag onpowmanInitbelow).Careful: this actively drives every unexcluded GP0-22 — if any of them is wired to something else that's also actively driving it (not just a passive load), the two will fight. Pass every such GPIO in
excludeGpiosto leave it alone.
Returns the reason for the last wake-up as a bitmask. Must be called before powmanInit().
| Constant | Value | Meaning |
|---|---|---|
0 |
0x00 |
Fresh boot / software reset |
WAKEUP_CHIP_RESET |
0x01 |
Chip-level reset |
WAKEUP_GPIO0 |
0x02 |
Wake from PWRUP0 (used by powmanOffUntilGPIO) |
WAKEUP_GPIO1 |
0x04 |
Wake from PWRUP1 |
WAKEUP_GPIO2 |
0x08 |
Wake from PWRUP2 |
WAKEUP_GPIO3 |
0x10 |
Wake from PWRUP3 |
WAKEUP_ALARM |
0x40 |
Wake from timer alarm |
The register is a bitmask: multiple bits can be set simultaneously. Use & to test individual sources.
Internally reads CHIP_RESET.HAD_SWCORE_PD (bit 25) to confirm a POWMAN sleep occurred, then reads LAST_SWCORE_PWRUP (offset 0xA0) for the source.
powmanInit(absTimeMs: int, lowPowerXosc=False, lowPowerRosc=False, lowPowerPlls=False, lowPowerUsbPhy=False, lowPowerWifiChip=False, excludeGpios=())
Initializes the POWMAN timer with an absolute timestamp in milliseconds (must be > 0).
The lowPower* flags enable extra, optional current-saving steps on top of the normal SWCORE/XIP/SRAM power-down (stopXosc/stopRosc/powerDownPlls/isolateUsbPhy/powerDownWifiChip, all implemented in deepsleep.py). Enabling them here means every subsequent powmanOff*() call applies them automatically — no need to build a beforeSleep callback yourself. lowPowerXosc takes effect immediately; the other four are deferred and applied last, right before the chip halts.
| Flag | Effect |
|---|---|
lowPowerXosc |
Stops the external crystal oscillator. |
lowPowerRosc |
Stops the ring oscillator too (implies clocks drop to POWMAN's 32.768 kHz LPOSC for whatever code runs after this point — arm everything before enabling this). |
lowPowerPlls |
Powers down PLL_SYS/PLL_USB. Requires lowPowerXosc (clk_sys must already be off the PLL path). |
lowPowerUsbPhy |
Re-isolates the USB PHY (matches the RP2350 datasheet's own low-power test methodology). |
lowPowerWifiChip |
Pico 2 W only. Powers down the CYW43439 wireless companion chip. No-op on plain Pico 2, or if the chip was never powered up. |
excludeGpios |
GPIO(s) — a single int or a list/tuple — that the automatic GP0-22 force-low pass should always leave alone, on every subsequent powmanOff*() call. Combined with whatever excludeGpios each individual call also specifies. |
Risk: lowPowerRosc and lowPowerPlls carry real risk — if clocks aren't moved off an oscillator/PLL before it's stopped, the chip hangs and needs a physical reset/reflash (BOOTSEL) to recover. Only tested on Pico 2 (RP2350) with stock boot clock configuration. lowPowerUsbPhy/lowPowerWifiChip are low risk (no clock changes).
For maximum power savings:
import deepsleep
deepsleep.powmanInit(1704067200, lowPowerXosc=True, lowPowerRosc=True,
lowPowerPlls=True, lowPowerUsbPhy=True, lowPowerWifiChip=True)
deepsleep.powmanOffForMsOrGPIO(10000, [(8, True)])In testing (Pico 2 W, lowPowerXosc/lowPowerRosc/lowPowerUsbPhy/lowPowerWifiChip enabled), sleep current went from ~600µA to ~230-250µA measured on VSYS. lowPowerPlls has not been measured yet.
Enters deep sleep and reboots after sleepingMs milliseconds. Never returns. Since there's no wake-up GPIO here, every GP0-22 not listed in excludeGpios gets forced low.
powmanOffUntilGPIO(gpio: int, high: bool = True, slot: int = PWRUP0, beforeSleep=None, excludeGpios=())
Enters deep sleep and reboots when the specified GPIO pin reaches the target level. gpio must be 0–49. Never returns.
| Parameter | Description |
|---|---|
gpio |
GPIO pin number (0–49) |
high |
True = wake on HIGH, False = wake on LOW |
slot |
Which of the 4 PWRUP registers to use (PWRUP0..PWRUP3). Defaults to PWRUP0. |
beforeSleep |
Optional callable, invoked last, right before the chip actually halts. |
excludeGpios |
Extra GPIOs (besides gpio itself) to leave untouched by the automatic force-low pass — see the note at the top of the API Reference. |
Important: The GPIO must already be at the opposite level before calling this function. POWMAN requires a level transition to fire — if the GPIO is already at the wake level when sleep is entered, the chip will never wake.
highGPIO must be before sleep Wake trigger TrueLOW GPIO goes HIGH FalseHIGH GPIO goes LOW
Enters deep sleep and reboots when any of up to 4 GPIO pins reaches its target level. pins is a list/tuple of up to 4 (gpio, high) pairs, each mapped to one of the 4 independent PWRUP wake-up slots. Never returns.
# wake when GP15 goes HIGH or GP16 goes LOW
deepsleep.powmanOffUntilAnyGPIO([(15, True), (16, False)])The same transition requirement as powmanOffUntilGPIO applies to every pin. After reboot, use powmanGetWakeupReason() to tell which one fired: pins[0] → WAKEUP_GPIO0, pins[1] → WAKEUP_GPIO1, and so on.
powmanOffForMsOrGPIO(sleepingMs: int, pins: list[tuple[int, bool]], beforeSleep=None, excludeGpios=())
Enters deep sleep and reboots when either the timer alarm expires or any of up to 4 GPIO pins reaches its target level — whichever happens first. Never returns.
# wake after 10s, or immediately if GP15 goes HIGH before that
deepsleep.powmanOffForMsOrGPIO(10000, [(15, True)])Same transition requirement as powmanOffUntilGPIO/powmanOffUntilAnyGPIO applies to every pin. After reboot, powmanGetWakeupReason() tells you which source actually fired: WAKEUP_ALARM for the timer, WAKEUP_GPIO0..WAKEUP_GPIO3 for pins[0]..pins[3].
Waking up (from timer or GPIO) triggers a full chip reboot, which also resets the USB stack. Your serial terminal has to reconnect to the newly re-enumerated USB device, and usually doesn't do so fast enough to catch the first print() calls in main().
To verify wake-up behavior without relying on the serial terminal, blink the onboard LED a different number of times depending on powmanGetWakeupReason() — it's visible immediately and doesn't depend on USB reconnecting.
from machine import Pin
import time, deepsleep
def blink(times):
led = Pin("LED", Pin.OUT)
for _ in range(times):
led.on()
time.sleep_ms(200)
led.off()
time.sleep_ms(200)
def main():
reason = deepsleep.powmanGetWakeupReason() # before init!
if reason & deepsleep.WAKEUP_GPIO0:
blink(1) # woke from GP8
elif reason & deepsleep.WAKEUP_GPIO1:
blink(2) # woke from GP9
elif reason & deepsleep.WAKEUP_GPIO2:
blink(3) # woke from GP10
elif reason & deepsleep.WAKEUP_GPIO3:
blink(4) # woke from GP11
elif reason & deepsleep.WAKEUP_ALARM:
blink(5) # woke from timer alarm
else:
blink(6) # fresh boot / other
deepsleep.powmanInit(1704067200)
deepsleep.powmanOffUntilAnyGPIO([(8, True), (9, True), (10, True), (11, True)])
main()- Timer-based deep sleep (
powmanOffForMs) - GPIO wake-up (
powmanOffUntilGPIO) - Multi-GPIO wake-up (
powmanOffUntilAnyGPIO) - Combined timer + multi-GPIO wake-up (
powmanOffForMsOrGPIO) - Wake-up reason detection (
powmanGetWakeupReason) - Optimize power management by disabling unnecessary components (
lowPowerXosc/lowPowerRosc/lowPowerPlls/lowPowerUsbPhy/lowPowerWifiChiponpowmanInit) — ~600µA → ~230-250µA measured on Pico 2 W (lowPowerPllsnot yet measured) - Automatic force-low of unexcluded GPIOs (GP0-22) before sleep via
excludeGpios, to avoid leakage from floating inputs and to let accessories be powered from a GPIO instead of3V3(which can't be switched off from software)
- Implement a sleep mode that does not reboot the system and preserves the values of variables like
machine.lightsleep() - Edge-triggered GPIO wake-up as an alternative to the current level-triggered mode (
PWRUPx.MODEbit, never set so far)
- Only use in embedded/bare-metal environments.
- Incorrect register values may brick your device.
Tested primarily on:
- Raspberry Pi Pico 2

