# ESP32 system power management — light sleep between events. # # AUTO-ENABLED for repeater builds on boards whose zephcore.yml declares # `light_sleep: true`, and for companion builds on boards that declare # `light_sleep_companion: true` (cmake/zephcore_board.cmake, CMakeLists.txt). # Passing it by hand is supported for testing another board: # # west build -b heltec_wifi_lora32_v4_r8/esp32s3/procpu zephcore --pristine --sysbuild -- -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/pm_esp32.conf" # # What it buys: Espressif parts have no equivalent of the nRF52's "idle at # microamps in WFI" — WAITI gates the core clock but leaves PLL, peripherals # and RAM powered, so an idle ESP32 sits in the tens of mA. Light sleep is # sub-mA. On a repeater that is the difference between the MCU dominating the # power budget and the radio dominating it. # # THE PIECES # - pm_esp32.overlay: &rtc_timer as the timed wake source (without it power.c # logs "Sleep skipped" and CONFIG_PM silently does nothing) and &gpio0 # wakeup-source, which turns the ESP32 GPIO wake source on. # - helpers/pm_esp32_wake.c: the LoRa IRQ line and the user button (sw0) # become level wakes on any pad for the length of each sleep, and the # interrupt a sleep swallowed is re-raised on wake. Also the `powersaving` # switch and the `get pm` counters. GPIO_INT_WAKEUP (EXT1) is NOT used: # RTC pads only, one polarity for all pins on the S3, pads left on the RTC # mux after a sleep (devdocs/lld/13-power-management.md, F-1/F-2). # - helpers/pm_esp32_console.c: flushes the console UART before every sleep, # and holds a console window (ZEPHCORE_PM_BOOT_AWAKE_MS, 10 min) after boot # and after every button press. # - Pin holds, in each board's DTS/overlay: light sleep isolates every pad # that is not held (input and output off, no pulls), RTC pad or not, while # the radio keeps receiving. NSS, RESET and any MCU-driven RF switch, FEM # enable or FEM supply carry ESP32_GPIO_SLEEP_HOLD_EN (inert without PM). # # CONSOLE: outside the console window, characters typed at a sleeping node are # dropped. On boards that console over the S3's USB Serial/JTAG (V4, V4.3, # Wireless Tracker V2, XIAO, Station G2) the HAL disables the USJ pad for every # sleep, so the USB device detaches and reattaches continuously. Press the user # button to reopen the window, or send `powersaving off` (persisted) over LoRa; # `powersaving on` allows sleep again. Light-sleep builds default to on. # # WiFi: the ESP32 WiFi driver starts WiFi at boot (NULL mode), and the WiFi # library holds its modem lock for as long as the WiFi radio is on, so NO S3 # repeater (they all carry WiFi OTA) ever light-slept before 2026-09-26. # helpers/pm_esp32_wake.c stops WiFi at boot; `start ota` (AP) and a station # connect start it again. An OTA session holds a pm_policy lock for its # duration (adapters/ota/wifi_ota.c). A station (the MQTT uplink) runs in WiFi # power save: the library gives its lock up between beacons and the WiFi MAC # wakes the SoC for each one. The station holds a lock of its own until its # MQTT session is up, and again whenever the link or the session is lost # (adapters/wifi/ZephyrWiFiStation.c); a node whose access point is away stays # awake. devdocs/lld/13-power-management.md, section 12. # # COMPANIONS sleep only after `powersaving on` (their default is off), and then # not while a USB host is on the bus, the display is on, or a BLE link is not # yet encrypted. The BLE controller gives its lock up between events and its # wake timer bounds each sleep (CONFIG_PM_CUSTOM_TICKS_HOOK below). The build # costs about 4 KB of DRAM, which is why the companion has its own capability # line. devdocs/lld/13-power-management.md, section 13. # # CHECKING A BOARD: `get pm` shows the share of time asleep, the sleep count and # wake causes (t = RTC timer, g = GPIO, o = other) and per-pin wake counts. # A deaf node is the failure to rule out first: radio wakes should track the # packets it hears, and its receive counters should track a non-sleeping # neighbour's. # # Kernel time is safe across sleep: the Xtensa/ESP32 timer drivers implement # the LPM hooks (esp32_lptim_hook_on_lpm_entry/exit) that read the RTC counter # and announce elapsed ticks, so k_uptime_get() and every maintenance deadline # stay correct. CONFIG_PM=y # The RTC timer is a COUNTER driver. Enabling the DT node alone is not enough: # without the counter subsystem drivers/counter is never built, no device # object is emitted, and power.c's DEVICE_DT_GET_OR_NULL(rtc_timer) fails to # link ("undefined reference to __device_dts_ord_NN"). COUNTER_RTC_ESP32 is # then default y off the enabled DT node. CONFIG_COUNTER=y # System-managed device PM stays off. prj.conf pins it off globally so that # turning on CONFIG_PM cannot silently start suspending devices from the idle # thread — that is the trap that historically killed GPS. Device suspends stay # explicit (pm_device_action_run from the main thread). CONFIG_PM_DEVICE_SYSTEM_MANAGED=n # Bound each light sleep by the next esp_timer alarm as well as the kernel's # next timeout (helpers/pm_esp32_wake.c, pm_policy_next_custom_ticks()). CONFIG_PM_CUSTOM_TICKS_HOOK=y