RPi Pico WAV Player Power Management Features: Deep Sleep, Battery Monitoring, and Peripheral Control
The RPi Pico WAV Player firmware implements comprehensive power management including dynamic backlight control, dual-mode battery monitoring with low-voltage protection, USB power detection, audio DAC muting, and ultra-low-power dormant sleep with configurable wake-up sources.
The rpi_pico_wav_player repository provides a dedicated power management subsystem centered in src/power_manage.cpp that handles everything from display dimming to deep sleep recovery. These power management features ensure efficient battery operation for portable audio playback on the Raspberry Pi Pico and Waveshare RP2040-LCD-0.96 boards.
Hardware Initialization and Configuration
The power management subsystem initializes through pm_init() in src/power_manage.cpp, which configures GPIO pins, detects hardware variants, and establishes the battery monitoring timer.
GPIO Configuration and Power Control Pins
The initialization routine sets up critical power control pins including GPIO 24 for USB power detection, GPIO 19 for the power-keep MOSFET, GPIO 27 for audio DAC mute control, and GPIO 8 for the active battery check enable circuit. The code also configures GPIO 21 for button pull-up control and sets the DCDC-PSM mode based on the board variant.
Active vs. Static Battery Circuit Detection
The firmware automatically detects whether the board includes the optional active battery-check circuit by probing voltage levels on GPIO 28 (ADC2) with the enable pin (GPIO 8) toggled high and low. If the measured voltage behaves as expected, the global _use_active_batt_check flag is set to true, directing subsequent voltage readings to use the active circuit rather than the static divider on GPIO 29 (ADC3).
DCDC Mode Selection and Timer Initialization
For the Raspberry Pi Pico, the RT6150B-33GQW regulator is configured to PWM mode to minimize audio ripple, while the Waveshare board's TPS63000 is set to power-save off mode. The initialization concludes by starting a repeating timer that calls pm_monitor_battery_voltage() at 20 Hz to maintain real-time battery status.
Dynamic Backlight Brightness Management
The firmware implements intelligent display power management through pm_backlight_update(), which adjusts OLED backlight PWM levels based on user activity timeouts.
Idle-Time-Based Dimming
The function checks ui_get_idle_count() against the configured DISPLAY_TIME_TO_BACKLIGHT_LOW threshold (converted to loop cycles based on the 50 ms UI update rate). When idle time exceeds the threshold, the backlight drops to DISPLAY_BACKLIGHT_LOW_LEVEL; otherwise, it maintains DISPLAY_BACKLIGHT_HIGH_LEVEL. The hardware PWM driver OLED_BLK_Set_PWM() applies the calculated value.
UI Integration
Every UI redraw cycle, particularly in UIMode::draw() and UIFileViewMode::draw(), invokes pm_backlight_update() to ensure the display brightness remains synchronized with user interaction patterns, significantly reducing power consumption during passive listening sessions.
Battery Voltage Monitoring and Protection
The power management subsystem provides comprehensive battery state monitoring through dual-mode ADC measurement and configurable low-voltage protection.
Dual-Mode Voltage Measurement
The _get_battery_voltage() function in src/power_manage.cpp selects between active and static measurement modes based on the _use_active_batt_check flag. For static mode, it reads ADC3 (GPIO 29); for active mode, it enables the external circuit via GPIO 8 and reads ADC2 (GPIO 28). Raw ADC values are converted to real-world voltages using calibrated coefficients (COEF_A_* and COEF_B_*) that account for voltage divider ratios and diode drops.
Periodic Monitoring and Low-Battery Detection
The pm_monitor_battery_voltage() timer callback executes at 20 Hz, periodically sampling the battery voltage and storing the result in the global _battery_voltage variable. The pm_get_low_battery() function compares this value against the LOW_BATT_LVL threshold (2.9 V), returning true when the battery requires immediate attention. This compile-time flag can be disabled via NO_BATTERY_VOLTAGE_CHECK for hardware configurations without battery monitoring.
Peripheral Power Control
The firmware manages power states for external peripherals including the audio DAC, USB detection, and button interfaces through dedicated GPIO control functions.
USB Power Detection
The pm_usb_power_detected() function reads GPIO 24 to determine if the board is connected to USB host power or a wall charger. This status drives UI decisions in UIInitialMode::update(), determining whether the device enters charging mode or normal playback mode upon startup.
Audio DAC Mute Control
To prevent power-on pops and save energy, pm_set_audio_dac_enable() controls GPIO 27 to enable or mute the external I²S audio DAC. The UI registers this function as a callback via audio_codec_set_dac_enable_func() during UIOpeningMode::entry(), allowing playback code to toggle DAC power without hardware-specific knowledge.
Power-Keep and Button Control
The pm_set_power_keep() function drives GPIO 19 to control a MOSFET that maintains board power during USB charging, even after the user releases the physical power button. Complementing this, pm_enable_button_control() toggles pull-ups on GPIO 21 to enable or disable button detection for wake-up purposes, conserving power when the device is in sleep states.
Deep Sleep and Wake Management
The firmware implements ultra-low-power dormant mode for battery conservation during charging idle periods, with full system restoration upon wake.
Entering Dormant Mode
The pm_enter_dormant_and_wake() function orchestrates the sleep sequence: it turns off the backlight, sets the DCDC converter to PFM mode for maximum efficiency, de-initializes USB CDC, and configures the wake-up source (typically the center button via ui_set_center_switch_for_wakeup()). The function waits for button release (>500 ms) to prevent immediate re-triggering, then calls pm_enter_dormant_and_wake_core() which disables interrupts, switches the clock source to XOSC, and enters dormant mode until the specified GPIO goes low.
Clock Restoration and PLL Reconfiguration
Upon wake, the system must restore the 96 MHz PLL required for I²S audio. The pw_set_pll_usb_96MHz() function (lines 309-353 in power_manage.cpp) reinitializes the PLL, updates clk_usb, clk_sys, and clk_peri, then restores UART and USB CDC drivers. This ensures audio peripherals receive stable clocks after exiting dormant mode.
System Reset and Reboot Handling
The firmware provides controlled reboot capabilities with cause detection to optimize startup behavior.
Watchdog-Based Soft Reboot
The pm_reboot() function utilizes watchdog_reboot() to restart the Pico while preserving USB-CDC connection parameters via PICO_STDIO_USB_RESET_RESET_TO_FLASH_DELAY_MS. This allows the system to restart cleanly without physical power cycling.
Reboot Cause Detection
The pm_is_caused_reboot() function queries the watchdog status flag to determine if the current boot resulted from a watchdog-triggered reset. The UI uses this in UIMode::initialize() and UIInitialMode::update() to skip the charging screen when recovering from a software-initiated reboot, streamlining the user experience.
Summary
The RPi Pico WAV Player firmware delivers a complete power management solution optimized for portable battery operation:
- Dynamic backlight control reduces display power based on user idle time.
- Dual-mode battery monitoring supports both static and active voltage sensing with 2.9 V low-battery protection.
- Peripheral power control manages USB detection, audio DAC muting, power-keep MOSFETs, and button pull-ups.
- Ultra-low-power dormant mode enables sub-milliamp sleep currents with full system restoration including 96 MHz PLL reconfiguration.
- Intelligent reboot handling distinguishes between cold boots and watchdog resets to optimize startup flow.
Frequently Asked Questions
How does the firmware detect low battery conditions?
The firmware samples battery voltage at 20 Hz through pm_monitor_battery_voltage() and stores the result in a global variable. The function pm_get_low_battery() compares this value against the LOW_BATT_LVL threshold of 2.9 V, returning true when the battery requires charging. This check can be disabled at compile time using the NO_BATTERY_VOLTAGE_CHECK flag for hardware configurations without battery monitoring circuitry.
What is the difference between active and static battery checking?
Static checking uses the Raspberry Pi Pico's built-in voltage divider on GPIO 29 (ADC3) to measure battery voltage passively. Active checking utilizes an optional external circuit on GPIO 28 (ADC2) controlled by GPIO 8, which enables the voltage divider only during measurement to reduce quiescent current. During pm_init(), the firmware automatically probes both configurations to detect which hardware variant is present and sets the _use_active_batt_check flag accordingly.
How does deep sleep (dormant mode) work in this firmware?
When the player remains idle on the charging screen for two seconds, pm_enter_dormant_and_wake() initiates a low-power sequence: it disables the backlight, sets the DCDC converter to PFM mode, de-initializes USB CDC, and configures the center button as a wake source. The RP2040 enters dormant mode, consuming minimal current until the button pulls the wake pin low. Upon wake, pw_set_pll_usb_96MHz() restores the 96 MHz PLL and peripheral clocks required for I²S audio playback.
How does the firmware handle USB power detection?
The pm_usb_power_detected() function reads GPIO 24 to determine if the board is connected to USB host or charger power. This status drives critical UI decisions in UIInitialMode::update(), determining whether the device enters charging mode or proceeds directly to playback mode. When USB power is detected, the firmware also engages the power-keep circuit (GPIO 19) to maintain board operation even if the physical power button is released, ensuring stable charging behavior.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →