ConfigParam.h in rpi_pico_wav_player: Managing Persistent Player Settings

ConfigParam.h serves as the central configuration manager for the Raspberry Pi Pico WAV player, providing a type-safe singleton interface that abstracts flash storage operations for all user-adjustable settings.

In the elehobica/rpi_pico_wav_player firmware, src/ConfigParam.h defines the backbone of persistent storage. It transforms raw flash memory operations into intuitive C++ object interactions, allowing the player to remember volume levels, UI modes, and playback positions across power cycles without manual byte-level flash management.

Core Architecture of ConfigParam.h

The header implements a singleton pattern that wraps the generic FlashParam.h infrastructure. This design ensures that all firmware modules access a single, consistent configuration state while the underlying flash read/write logic remains completely hidden.

Parameter Enumeration with ParamId_t

At the heart of the system lies the ParamId_t enum, which assigns every configurable setting a unique identifier. These IDs start from FlashParamNs::CFG_ID_BASE and cover all user-facing options:

typedef enum {
    CFG_ID_VERSION = FlashParamNs::CFG_ID_BASE,
    CFG_ID_VOLUME,
    CFG_ID_UIMODE,
    CFG_ID_MENU_IDX_GENERAL_PLAY_POS,
    // ... additional parameters
    CFG_ID_TAIL
} ParamId_t;

This enumeration in src/ConfigParam.h (lines 13-46) ensures that each parameter has a stable address in the flash storage layout, preventing corruption when firmware updates add new settings.

Typed Parameter Wrappers

Rather than exposing raw memory addresses, ConfigParam.h encapsulates each setting in a FlashParamNs::Parameter<T> template object. These objects store the parameter ID, a human-readable name, and a default value:

FlashParamNs::Parameter<std::string> P_CFG_VERSION {CFG_ID_VERSION, "CFG_VERSION", "0.0.0"};
FlashParamNs::Parameter<uint8_t>     P_CFG_VOLUME  {CFG_ID_VOLUME, "CFG_VOLUME", 65};
FlashParamNs::Parameter<uint32_t>    P_CFG_UIMODE   {CFG_ID_UIMODE, "CFG_UIMODE", 0};

This pattern, defined in lines 57-90 of src/ConfigParam.h, provides compile-time type safety. Attempting to assign a string to a numeric parameter triggers a compiler error rather than a runtime flash corruption.

Flash Persistence Management

The class inherits from FlashParamNs::FlashParam, which implements the actual flash storage algorithms. ConfigParam.h exposes two critical lifecycle methods that the firmware calls during state transitions:

  • initialize(): Loads all persisted values from RP2040 flash memory into the parameter objects
  • finalize(): Writes modified values back to flash before power-down

These methods (lines 91-95) ensure atomic updates—either all parameters persist successfully, or none do, preventing partial configuration corruption.

Singleton Global Access

To prevent multiple instances from desynchronizing the flash state, ConfigParam.h implements a singleton pattern:

static ConfigParam& instance() {
    static ConfigParam instance;
    return instance;
}

Accessed via ConfigParam::instance(), this global entry point (lines 51-55) allows any UI mode or audio handler to read or modify settings without passing configuration pointers through the entire call stack.

Practical Usage Examples

The firmware demonstrates three primary interaction patterns with ConfigParam.h: initialization, modification, and runtime querying.

Loading Configuration at Startup

When the player boots, UIInitialMode::loadFromFlash() retrieves the singleton instance and initializes the flash system:

ConfigParam& cfg = ConfigParam::instance();
cfg.initialize();  // Reads all persisted values from flash

printf("Raspberry Pi Pico Player: %s\r\n", 
       cfg.P_CFG_VERSION.get().c_str());

This pattern, found in src/UIMode.cpp (lines 33-38), ensures that volume, UI mode, and playback position reflect the user's last session rather than factory defaults.

Saving Settings Before Power Off

When the user initiates shutdown, UIPowerOffMode::storeToFlash() persists the current state:

ConfigParam& cfg = ConfigParam::instance();
cfg.P_CFG_VOLUME.set(75);  // Update volume to current level
cfg.P_CFG_UIMODE.set(static_cast<uint32_t>(uiMode));  // Remember UI state
cfg.finalize();  // Atomic write to flash

This implementation in src/UIPowerOffMode.cpp (lines 71-99) guarantees that user adjustments survive power cycles without requiring real-time flash writes during normal operation.

Reading Menu Preferences at Runtime

During button handling, UIFileViewMode::update() queries configuration to determine navigation behavior:

auto& layout = (btn_unit == button_unit_t::PushButtons)
    ? cfgParam.P_CFG_MENU_IDX_GENERAL_PUSH_BUTTON_LAYOUT
    : cfgParam.P_CFG_MENU_IDX_GENERAL_HP_BUTTON_LAYOUT;

if (layout.get() == static_cast<uint32_t>(button_layout_t::Horizontal)) {
    idxInc();  // Horizontal navigation
} else {
    idxDec();  // Vertical navigation
}

This logic in src/UIFileViewMode.cpp (lines 99-104) demonstrates how ConfigParam.h enables dynamic UI adaptation based on user-configured hardware layouts.

Integration with the Firmware Ecosystem

ConfigParam.h operates as the central hub in a three-layer configuration system:

  • Storage Layer: FlashParam.h handles low-level flash sector management and wear leveling
  • Model Layer: ConfigParam.h defines the specific parameters and their defaults
  • Interface Layer: ConfigMenu.h maps these parameters to the on-screen menu system that users interact with

This architecture ensures that adding a new setting requires only three steps: adding an entry to the ParamId_t enum in ConfigParam.h, declaring the Parameter<T> object with default value, and registering it in ConfigMenu.h for UI exposure.

Summary

  • ConfigParam.h defines the singleton ConfigParam class, the authoritative source for all user-adjustable settings in the RP2040 WAV player.
  • The ParamId_t enum assigns stable flash storage IDs to each parameter, preventing data corruption during firmware updates.
  • Typed Parameter<T> wrappers provide compile-time safety and encapsulate default values, eliminating raw memory manipulation.
  • initialize() and finalize() methods abstract the flash persistence logic, ensuring atomic load/save operations across power cycles.
  • Global singleton access via ConfigParam::instance() allows any firmware module to read or modify settings without complex dependency injection.

Frequently Asked Questions

How does ConfigParam.h differ from FlashParam.h?

FlashParam.h provides the generic infrastructure for reading and writing raw bytes to the RP2040's flash memory, including sector management and wear-leveling algorithms. ConfigParam.h builds upon this foundation by defining the specific application-level parameters (volume, UI mode, etc.) and wrapping them in type-safe C++ objects. While FlashParam.h knows about memory addresses, ConfigParam.h knows about user settings.

What happens if flash storage becomes corrupted?

During initialize(), the FlashParam base class validates the flash sector checksums. If corruption is detected, the system automatically falls back to the default values defined in the Parameter<T> declarations within ConfigParam.h. This ensures the player remains functional even after flash wear or unexpected power loss during a write operation, though user settings revert to factory defaults.

Can I add new configuration parameters without modifying existing flash addresses?

Yes. The ParamId_t enum in ConfigParam.h uses CFG_ID_TAIL as a sentinel value, and new parameters should be inserted before this marker. Because each parameter stores its ID within its own flash slot and the FlashParam system scans by ID rather than fixed offset, adding new entries does not shift the storage location of existing parameters. This prevents corruption when upgrading firmware while preserving user settings.

Where are the default values for settings defined?

Default values are hardcoded in the Parameter<T> object declarations within src/ConfigParam.h. For example, P_CFG_VOLUME initializes with a default of 65, and P_CFG_VERSION defaults to "0.0.0". These values are applied when the flash sector is empty or corrupted, and they serve as the baseline until the user modifies the setting through the menu system.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →