Power Management - amir1387aht/MicroCS GitHub Wiki
Sleep, wake pins and shutdown
Power is registered with the HAL (MCS_ENABLE_HAL). It separates a cooperative VM
pause from a hardware-wide power transition. Use Hal.Has(...) on the connected
firmware, not the chip name, to decide what is available. Unsupported hardware modes
throw NotSupportedException; they never silently become an ordinary delay.
API
| Member | Meaning |
|---|---|
Power.Sleep(milliseconds) |
Cooperative pause, like Thread.Sleep: timers, VM idle hooks and cancellation can run. Does not promise a hardware low-power state. Nonnegative signed 32-bit milliseconds. |
Power.SetWakePin(pin, high) |
Select one wake input and its active level (true high, false low); number or port pin name. Validates without changing hardware. Replaces the previous selection only on success. |
Power.ClearWakePin() |
Clear the VM's selection; does not reconfigure GPIO or native SDK wake sources immediately. |
Power.WakePin, Power.WakeHigh |
Selected numeric input (-1 if none) and active level. Per VM; reset when the HAL is reopened / firmware restarts. |
Power.LightSleep(milliseconds = 0) |
Enter the port's light/CPU-idle mode; resumes after a timeout or active selected pin. 0 means pin-only, not an immediate return. |
Power.DeepSleep(milliseconds = 0) |
Enter reset-on-wake deep sleep / standby. RAM, VM state, scheduler and normal variables are lost. Physical hardware does not resume the next line. |
Power.Shutdown() |
Enter the port's actual shutdown/poweroff state, not a deep-sleep alias. Recovery requires the board's supported wake/reset/power cycle. No timer argument. |
LightSleep and DeepSleep require a positive timeout or a selected wake pin. Negative
or greater-than-2147483647 millisecond values are rejected. When both sources are
supported and selected, either may wake the board. Shutdown can be reset-only when
the backend supports that; STM32's adapter instead requires its supported wake pin.
These calls return void and do not report a wake reason.
Capability names
Power,Power.Sleep: cooperative API present.Power.LightSleep,Power.DeepSleep,Power.Shutdown: implemented hardware modes.- Append
.Timerto light/deep sleep, or.Pinto any mode, for supported wake sources, for examplePower.DeepSleep.TimerandPower.LightSleep.Pin.
A .Pin capability means some inputs work, not every GPIO. SetWakePin checks
whether a pin is usable for at least one mode; the mode call validates it again. An
ESP32 digital-only pin can be valid for light sleep but rejected for deep sleep.
Port support
| Port | Light sleep | Deep sleep | Shutdown |
|---|---|---|---|
| ESP-IDF, ESP32 family | SDK light sleep, timer and digital GPIO level wake, subject to SOC_LIGHT_SLEEP_SUPPORTED |
SDK deep sleep, timer; GPIO only where ext0/ext1 or deep-GPIO wake exists | Not exposed: deep sleep is not true shutdown |
| Arduino-ESP32 core 3.x (IDF 5) | Same shared SDK adapter and SoC guards | Same | Unsupported |
| pico-sdk, RP2040 / RP2350 (including W / Zero boards) | SDK sleep_ms / WFE CPU idle; timer and GPIO sampled every ~1 ms |
Not exposed | Not exposed |
STM32 with Cube HAL_PWR_MODULE_ENABLED and SLEEP support |
CPU SLEEP/WFI, live HAL tick; GPIO sampled on interrupts/ticks | F4/L4/G4 only: standby, PA0 / WKUP1, high only, no timer wrapper | L4 only: hardware shutdown, same PA0/high requirement |
| Zephyr | Not exposed; the kernel manages idle power policy | Not exposed | sys_poweroff() only with CONFIG_POWEROFF; wake/reset is board-specific, no portable pin/timer wrapper |
| Other Arduino, template, custom HAL, Cortex-M demo | Only if the application fills the v4 callbacks/masks | Same | Same |
Host simulator (--sim) |
Records request and advances virtual board time | Records request and returns, no reset | Records request and returns, no poweroff |
All ports with the HAL get cooperative Power.Sleep, even if every hardware mode is
unsupported. Older Arduino-ESP32 cores without the required SoC capability macros
may expose only cooperative sleep; the SDK 5 / core 3.x adapter is the supported target. RP2 light sleep is not dormant mode, does not stop clocks, and does not
promise that the other core or radio sleeps. STM32 light sleep is not STOP mode:
clocks and the tick remain available. Short GPIO pulses can be missed on these sampled
backends; hold the input at the wake level. Deep sleep and shutdown are deliberately
unavailable on ports that lack a safe, tested clock/USB/wake restoration wrapper, even
if the silicon itself has additional low-power states.
STM32 applications must provide a working interrupt-driven HAL tick; do not suspend
SysTick / your HAL timebase before calling LightSleep. Standby/shutdown require a
bonded PA0/WKUP1 and the board's electrical wake conditions. Other native RTC, reset
or wake sources configured by the application may also wake the device.
Before entering hardware sleep
- Save/close files, finish transfers, stop periodic jobs and quiesce attached devices. GPIO outputs, LEDs, regulators and USB circuitry can dominate current consumption.
- Stop radios explicitly. ESP32 rejects entry while its configured Wi-Fi mode or BT
controller is active: call
Wifi.Off()(also stops ESP-NOW) andBle.Reset()when those modules are in use. Arduino users must also stop native radio stacks (WiFi.mode(WIFI_OFF)). Hardware sleep does not reconnect radios afterwards. - Configure the selected GPIO as a dedicated input with a safe pull. The active
level is the physical/raw input level, not
GpioConfig.Invert's software inversion. CallGPIO.Modefirst to reset any pin-mux overrides. Do not share the wake pin with an IRQ handler: MicroCS rejects its ownGPIO.OnChangeregistrations there; native handlers must be removed by the application (ESP light wake changes the pad's interrupt type). Use external resistors for deep sleep where the chip's powered-down domains cannot retain internal pulls. Never assume the LED, BOOT button, flash, USB or radio pin is a safe wake input. - Use a timeout for initial light-sleep tests. Hardware entry is synchronous: no C#
callbacks, Studio Stop, shell commands or uploads are serviced until it wakes.
Pin-only sleep can therefore make Studio appear stuck. This differs from
Power.Sleep. - Expect ESP light sleep to disrupt USB/serial and deep sleep/shutdown to disconnect it; reconnect Studio after wake. A watchdog or native firmware task may still reset the board. These APIs do not disable watchdogs or suspend arbitrary application tasks.
Hardware-wide modes reject worker VMs and in-flight MicroCS DMA. Builds with real
threads conservatively reject entry once MicroCS worker threads have been started
(the thread subsystem's shared-resource flag remains set); reboot before a sleep-only
session. Cooperative Power.Sleep remains available to workers. Native application
threads/peripherals are the embedding application's responsibility.
ESP entry owns/replaces the SDK wake configuration: it disables existing wake sources, arms the requested ones, and clears its sources after light wake or an error. Do not mix it with independently managed ULP/touch/UART wake configurations. An already active wake level can cause immediate wake or an SDK sleep-rejected error. SDK errors propagate as hardware exceptions; a rejected pin/mode does not start sleeping.
Do not put an unconditional deep-sleep/shutdown call in /boot.cs, /main.cs or a
repeating startup job: wake runs the firmware boot sequence again and can create a
sleep loop. Test from a manually run script first; keep the board's recovery procedure
(BOOTSEL / bootloader / reset and filesystem recovery) available.
Examples
A bounded, portable pause:
if (!Hal.Has("Power.Sleep")) return;
Console.WriteLine("Pause without blocking Studio cancellation");
Power.Sleep(250);
Timer-only hardware light sleep (stop radios first if they were started):
if (!Hal.Has("Power.LightSleep.Timer")) return;
Power.ClearWakePin();
Power.LightSleep(1000);
Console.WriteLine("Resumed");
Select a board-specific pin, with a timeout safety net. Replace -1 only after
checking the board's pinout; on sampled backends hold the input at the active level.
const int WAKE_PIN = -1; // choose a safe, supported input
if (WAKE_PIN < 0 || !Hal.Has("Power.LightSleep.Pin") || !Hal.Has("Power.LightSleep.Timer")) return;
GPIO.Mode(WAKE_PIN, GPIO.InputPullUp);
Power.SetWakePin(WAKE_PIN, false);
Power.LightSleep(5000);
Power.ClearWakePin();
Studio has six guarded templates in Power management, including timer and pin deep sleep and shutdown. Destructive/resetting templates require explicit opt-in before entry; mode and wake-source requirements hide them on incompatible firmware.
Implementing a port (HAL API v4)
The append-only mcs_hal_t fields are power_modes, power_timer_modes,
power_pin_modes, power_validate, power_enter. Each mask uses
MCS_POWER_LIGHT, MCS_POWER_DEEP, MCS_POWER_SHUTDOWN. Zero masks/NULL callbacks
preserve old ports. Recompile applications with the new header; this is source-level,
not binary struct-layout compatibility.
power_validate(ctx, mode, ms, pin, high) must have no hardware side effects. Mode 0
checks a prospective pin against at least one supported mode; pin == -1 means none.
For real entry validate the specific mode, duration, pin and polarity. Return
MCS_HAL_ENOTSUP for an unimplemented mode/source, MCS_HAL_EINVAL for invalid
arguments and MCS_HAL_EBUSY for unsafe entry. power_enter must recheck native
application/radio state and implement the requested mode, not a delay fallback.
Return nonnegative after light sleep, negative on failure; physical deep/shutdown
normally never return. The simulator is explicitly the exception. Do not advertise
wake-source bits your backend cannot honor. Timer wake bits describe source support,
not a promise about elapsed wall-clock accuracy or retained peripherals.
Vendor references: ESP-IDF sleep modes, pico-sdk time, STM32Cube HAL, Zephyr poweroff.