# How the File Menu System Is Implemented and Navigated in RPi Pico WAV Player

> Discover how the RPi Pico WAV Player uses FatFs to implement a lazy-sorted file menu system and navigate directories with its C API. Learn the internals of this SD card file indexing.

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

---

**The RPi Pico WAV Player implements a lazy-sorted, order-based file menu system using FatFs that indexes SD card directories into memory and provides navigation through a clean C API in [`lib/file_menu/file_menu_FatFs.c`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/lib/file_menu/file_menu_FatFs.c).**

The file menu system in the `elehobica/rpi_pico_wav_player` repository provides a lightweight abstraction over the ChaN FatFs library (v0.90), enabling efficient browsing of WAV files on SD cards from the Raspberry Pi Pico’s limited RAM. It trades memory for speed by maintaining a sorted index of directory entries and performing incremental background sorting to keep UI latency low.

## Architecture of the File Menu System

### FatFs Integration Layer

The physical SD card access is handled by [`lib/tf_card.c`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/lib/tf_card.c), which configures the SPI peripheral and mounts the FAT volume using `f_mount()`. This layer provides the block-level I/O that FatFs requires, isolating hardware specifics from the menu logic.

### Index and State Management

The core state resides in static globals defined in [`lib/file_menu/file_menu_FatFs.c`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/lib/file_menu/file_menu_FatFs.c):

- `entry_list` – Array of directory entry indices.
- `sorted_flg` – Bitmask tracking which ranges of the list are already sorted.
- `is_file_flg` – Bitmask distinguishing files from directories.
- `fast_fname_list` – 4-character cache for quick string comparisons during sorting.
- `last_order` – Tracks the most recently accessed entry to optimize idle sorting.

### Sorting Engine

Sorting is performed by `idx_qsort_entry_list_by_range()`, a custom quick-sort implementation that operates on the `entry_list`. It uses the `fast_fname_list` cache to compare filenames without repeated `f_stat()` calls, and it implements a "The " prefix-stripping rule for natural album sorting. The function updates `sorted_flg` to mark ranges as sorted, preventing redundant work during subsequent calls.

## Core Navigation Workflow

### Initialization and Mounting

The system initializes via `file_menu_init()`, which attempts to mount the SD card and returns the detected filesystem type:

```c
FRESULT file_menu_init(uint8_t *fs_type);

```

This function retries `f_mount()` until the card is ready, ensuring robust startup behavior.

### Opening Directories

To browse a directory, the UI calls `file_menu_open_dir()`:

```c
FRESULT file_menu_open_dir(const TCHAR *path);

```

This function changes the working directory with `f_chdir()`, opens the directory handle with `f_opendir()`, and invokes `idx_sort_new()` to allocate and populate the entry index. At this stage, the directory contents are indexed but not fully sorted.

### Retrieving File Entries

The player requests specific entries by **order** (0-based index) using `file_menu_get_fname()`:

```c
FRESULT file_menu_get_fname(uint16_t order, char *buf, uint16_t size);

```

Before returning the filename, the function ensures the requested window is sorted by calling `file_menu_sort_entry(order, order+5)`. It then uses `idx_f_stat()` to retrieve the `FILINFO` structure and copies the filename into the provided buffer.

### Background Sorting

To prevent UI freezes on large directories, sorting happens incrementally via `file_menu_idle()`:

```c
void file_menu_idle(void);

```

This function should be called from the main loop. It expands the sorted range in small chunks (≤ 5 entries), prioritizing areas near `last_order` to keep the user’s current view responsive. The bitmask `sorted_flg` tracks progress, ensuring the quick-sort only runs on unsorted segments.

### Directory Traversal

When the user selects a folder, `file_menu_ch_dir()` handles navigation:

```c
FRESULT file_menu_ch_dir(uint16_t order);

```

This function retrieves the folder name via `idx_f_stat()`, closes the current directory with `f_closedir()`, changes the working directory, and rebuilds the index for the new location. Cleanup is performed by `file_menu_close_dir()`, which frees all allocated buffers.

## Practical Implementation Example

The following snippet demonstrates how the UI layer integrates the file menu API to browse and select files:

```c
#include "file_menu_FatFs.h"
#include <stdio.h>

static uint8_t fs_type;

void wav_player_init(void)
{
    if (file_menu_init(&fs_type) != FR_OK) {
        printf("SD card mount failed!\n");
        return;
    }

    if (file_menu_open_dir("/") != FR_OK) {
        printf("Failed to open root directory\n");
        return;
    }
}

void list_current_page(uint16_t start_order, uint16_t count)
{
    char name[64];
    for (uint16_t i = start_order; i < start_order + count; ++i) {
        if (file_menu_get_fname(i, name, sizeof(name)) == FR_OK) {
            int is_dir = file_menu_is_dir(i);
            printf("%2u: %s%s\n", i,
                   is_dir ? "[DIR] " : "      ",
                   name);
        }
    }
}

void user_select(uint16_t order)
{
    if (file_menu_is_dir(order) > 0) {
        if (file_menu_ch_dir(order) == FR_OK) {
            printf("Entered directory %u\n", order);
        }
    } else {
        printf("Play file %u\n", order);
    }
}

int main(void)
{
    wav_player_init();
    list_current_page(0, 10);
    user_select(2);

    while (1) {
        file_menu_idle();
    }

    return 0;
}

```

This example illustrates the **order-based API** design: the UI requests entries by numeric index, the module handles sorting lazily, and background idle processing keeps the directory index optimized without blocking playback.

## Summary

- The file menu system in `elehobica/rpi_pico_wav_player` abstracts FatFs into an order-based navigation API defined in [`lib/file_menu/file_menu_FatFs.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/lib/file_menu/file_menu_FatFs.h).
- Directory entries are indexed in `entry_list` and sorted using a custom quick-sort in `idx_qsort_entry_list_by_range()` that leverages a 4-character fast-name cache.
- **Lazy sorting** ensures UI responsiveness: `file_menu_get_fname()` sorts only the requested window, while `file_menu_idle()` incrementally sorts the remaining entries in the background.
- Navigation uses numeric **order** indices rather than pointers, allowing the UI to browse directories with `file_menu_ch_dir()` and retrieve filenames via `file_menu_get_fname()`.
- The implementation isolates hardware specifics in [`lib/tf_card.c`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/lib/tf_card.c), keeping the menu logic portable and maintainable.

## Frequently Asked Questions

### How does the file menu system handle large directories without blocking the UI?

The system uses **incremental background sorting**. When a directory is opened, `idx_sort_new()` builds an unsorted index. As the user navigates, `file_menu_get_fname()` triggers `file_menu_sort_entry()` to sort only the visible window (typically 5 entries). Meanwhile, the main loop calls `file_menu_idle()`, which sorts small chunks (≤ 5 entries) of the remaining index on each iteration. The `sorted_flg` bitmask tracks progress, ensuring the quick-sort engine in `idx_qsort_entry_list_by_range()` never repeats work.

### What is the purpose of the fast four-character cache in the sorting algorithm?

The `fast_fname_list` array stores the first four characters of every filename in the directory index. During the quick-sort operation in `idx_qsort_entry_list_by_range()`, the algorithm compares these cached characters instead of repeatedly calling `f_stat()` or `strlen()` on the SD card. This **reduces SD card I/O** and CPU cycles, which is critical on the RP2040 microcontroller. The cache also supports the "The " prefix-stripping rule for natural sorting of album names.

### How does navigation into subdirectories work in the RPi Pico WAV Player?

When the user selects a directory entry, the UI calls `file_menu_ch_dir(order)` with the entry’s order index. This function retrieves the actual folder name via `idx_f_stat()`, closes the current directory handle with `f_closedir()`, changes the FatFs working directory with `f_chdir()`, and finally invokes `idx_sort_new()` to build a fresh index for the new location. The previous directory’s index is freed automatically, ensuring memory remains available for deep directory trees.

### Where is the public API for the file menu system defined?

The public interface is declared in [`lib/file_menu/file_menu_FatFs.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/lib/file_menu/file_menu_FatFs.h). Key functions include `file_menu_init()` for SD card mounting, `file_menu_open_dir()` for directory indexing, `file_menu_get_fname()` for retrieving filenames by order, `file_menu_is_dir()` for type checking, `file_menu_ch_dir()` for navigation, and `file_menu_idle()` for background processing. The header also defines return types and constants used throughout the implementation.