# How to Update or Customize Configuration Parameters Stored in Flash Memory in rpi_pico_wav_player

> Learn how to update or customize configuration parameters in rpi_pico_wav_player flash memory. Easily manage volume, UI mode, and playback settings using pico_flash_param.

- Repository: [Elehobica/rpi_pico_wav_player](https://github.com/elehobica/rpi_pico_wav_player)
- Tags: how-to-guide
- Published: 2026-03-01

---

**The rpi_pico_wav_player stores all user settings—volume, UI mode, button layout, and playback position—in the Raspberry Pi Pico’s on-board flash using the `pico_flash_param` library, which provides atomic read/write operations through the `ConfigParam` singleton.**

The `elehobica/rpi_pico_wav_player` project persists runtime settings across power cycles by mapping each configuration item to a typed `FlashParamNs::Parameter<T>` member. When the device boots, `ConfigParam::initialize()` loads values from flash; when the UI exits, `ConfigParam::finalize()` commits dirty parameters back to memory in a single atomic write.

## How Flash-Backed Configuration Works

The architecture separates generic flash management from application-specific settings. The **pico_flash_param** submodule (external library) provides the base `FlashParam` class that handles flash-sector management, serialization, and checksums, while the `ConfigParam` class defines the specific parameters for the WAV player.

| Component | Role | Source Location |
|-----------|------|-----------------|
| **pico_flash_param** | Generic `FlashParam` base class, ID handling, and flash-sector operations | External submodule: [`FlashParam.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/FlashParam.h) |
| **ConfigParam** | Application-specific singleton inheriting from `FlashParamNs::FlashParam`; declares parameter IDs (`CFG_ID_…`) and typed `Parameter<T>` members | [`src/ConfigParam.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ConfigParam.h) |
| **UIMode** | Bridges UI lifecycle to flash operations; `loadFromFlash()` triggers `initialize()` and `storeToFlash()` triggers `finalize()` | [`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp) (lines 33-38, 1100-1102) |
| **ConfigMenu** | High-level UI that reads/writes values via `cfgParam.getValue<>` and `cfgParam.setValue<>` | [`src/ConfigMenu.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ConfigMenu.cpp) (lines 44-48, 143-148) |

## Reading and Updating Existing Parameters

All parameters are public members of the `ConfigParam` singleton. To modify a value programmatically, obtain the singleton instance, call `set()` on the specific parameter member, and optionally trigger an immediate save.

### Basic Read/Write Pattern

```cpp
#include "ConfigParam.h"

// Obtain the singleton instance
ConfigParam &cfg = ConfigParam::instance();

// Read current volume (uint8_t)
uint8_t currentVol = cfg.P_CFG_VOLUME.get();

// Update volume to 80%
cfg.P_CFG_VOLUME.set(80);

// Persist immediately (optional—otherwise saves on next power-off)
cfg.finalize();

```

The UI automatically persists changes when the player powers off. In [`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp), the `storeToFlash()` method calls `ConfigParam::finalize()` to write all dirty parameters atomically. If you modify parameters outside the normal UI flow—such as from a serial command interface—you must invoke `finalize()` to ensure the changes survive reboot.

### Runtime Updates via the Configuration Menu

The `ConfigMenu` class provides the UI entry points for parameter adjustment. When a user navigates the configuration menu:

1. `ConfigMenu::enter()` increments the menu level.
2. On selection, `cfgParam.setValue<uint32_t>(curItem->paramID, idx)` writes the new index to the corresponding parameter.
3. If the menu item defines a hook function (e.g., to reconfigure LCD brightness), it executes immediately.
4. The change remains in RAM until `finalize()` persists it to flash.

The underlying implementation in [`src/ConfigMenu.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ConfigMenu.cpp) uses type-safe templates:

```cpp
// Reading current selection
uint32_t current = cfgParam.getValue<uint32_t>(item.paramID);

// Writing new selection
cfgParam.setValue<uint32_t>(curItem->paramID, selectedIndex);

```

## Adding New Configuration Parameters

To extend the firmware with custom settings, you must modify [`src/ConfigParam.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ConfigParam.h) to declare a new ID and parameter member. The `pico_flash_param` library handles serialization automatically once declared.

### Step 1: Reserve a Parameter ID

Append a new entry to the `ParamId_t` enum in [`src/ConfigParam.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ConfigParam.h):

```cpp
enum ParamId_t {
    // ... existing IDs ...
    CFG_ID_ENABLE_WIFI,   // automatically assigned next sequential value
    CFG_ID_MAX
};

```

### Step 2: Declare the Parameter

Add a `FlashParamNs::Parameter<T>` member to the `ConfigParam` class with a default value and size (size is required for strings, optional for primitives):

```cpp
class ConfigParam : public FlashParamNs::FlashParam {
public:
    // ... existing parameters ...
    
    // New boolean flag (stored as uint8_t)
    FlashParamNs::Parameter<uint8_t> P_CFG_ENABLE_WIFI {
        CFG_ID_ENABLE_WIFI,
        "CFG_ENABLE_WIFI",
        0,      // default: disabled
        1       // size in bytes
    };
    
    // New uint16_t setting with default 123
    FlashParamNs::Parameter<uint16_t> P_CFG_TIMEOUT_SEC {
        CFG_ID_TIMEOUT_SEC,
        "CFG_TIMEOUT_SEC",
        123,    // default value
        2       // size in bytes
    };
};

```

### Step 3: Use the New Parameter

Access the new parameter exactly like built-in members:

```cpp
ConfigParam &cfg = ConfigParam::instance();

// Read
bool wifiEnabled = cfg.P_CFG_ENABLE_WIFI.get() != 0;

// Write
cfg.P_CFG_ENABLE_WIFI.set(1);
cfg.finalize();  // Persist to flash

```

### Step 4: Optional UI Integration

To expose the parameter in the configuration menu, add an entry to `ConfigMenu::menuMap` in [`src/ConfigMenu.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ConfigMenu.h). The menu system automatically binds to the parameter ID and handles `getValue<>`/`setValue<>` calls.

## Persisting Changes and Initialization Lifecycle

Understanding when data moves between RAM and flash is critical for reliable operation.

| Phase | Method Call | Location | Effect |
|-------|-------------|----------|--------|
| **Boot** | `cfgParam.initialize()` | `UIMode::loadFromFlash()` ([`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp) lines 33-38) | Reads flash sector into RAM; populates parameters with persisted values or defaults if flash is empty/corrupt. |
| **Runtime** | `cfgParam.<member>.set(value)` | User code | Updates RAM copy only; marks parameter as "dirty". |
| **Immediate Save** | `cfgParam.finalize()` | User code or `UIMode::storeToFlash()` | Atomically writes all dirty parameters to flash sector with checksum. |
| **Power-off** | `cfgParam.finalize()` | `UIMode::storeToFlash()` ([`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp) lines 1100-1102) | Ensures clean shutdown with latest settings stored. |

The `finalize()` operation is atomic: it writes the entire parameter block to a reserved flash sector, ensuring that power loss during write does not corrupt the configuration. Before `UIMode::finalize()` writes settings, it saves the current runtime state—including playback position, volume, and navigation stack—back into the `ConfigParam` members ([`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp) lines 1089-1102).

## Summary

- **Architecture**: The `pico_flash_param` library provides the generic flash storage engine, while `ConfigParam` defines the application-specific settings as typed `Parameter<T>` members.
- **Access Pattern**: Use `ConfigParam::instance()` to get the singleton, then call `get()` and `set()` on specific members like `P_CFG_VOLUME`.
- **Persistence**: Call `finalize()` to commit changes immediately, or rely on `UIMode::storeToFlash()` during normal power-off sequences.
- **Extension**: Add new parameters by extending the `ParamId_t` enum and declaring new `FlashParamNs::Parameter<T>` members in [`src/ConfigParam.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ConfigParam.h).
- **Safety**: All flash writes are atomic and checksum-protected, preventing corruption during unexpected power loss.

## Frequently Asked Questions

### How do I reset configuration parameters to factory defaults?

The `ConfigParam::initialize()` method automatically falls back to default values if the flash sector is erased or the checksum is invalid. To force a reset programmatically, call the inherited `clear()` method from the `FlashParam` base class (provided by `pico_flash_param`) before `initialize()`, or manually overwrite parameters with their default values and call `finalize()`.

### Can I store strings or arrays in flash configuration?

Yes. The `FlashParamNs::Parameter<T>` template supports arbitrary types, but you must specify the size in bytes when declaring the parameter. For example, a 32-character string would use `FlashParamNs::Parameter<char[32]>` or `FlashParamNs::Parameter<uint8_t>` with the size parameter set to 32. Ensure the total size of all parameters does not exceed the allocated flash sector.

### Why are my configuration changes lost after reboot?

Changes are only lost if `finalize()` was not called before power loss. The UI mode only calls `finalize()` during clean shutdown via `storeToFlash()`. If you modify parameters in custom code (e.g., interrupt handlers or serial commands), you must explicitly call `ConfigParam::instance().finalize()` to persist the changes immediately.

### Where is the flash sector physically located?

The `pico_flash_param` library manages the flash address automatically, reserving space at the end of the program flash or in a dedicated region defined by the linker script. You do not specify the raw flash address; the library handles sector erase, wear leveling, and alignment constraints internally.