How the File Menu System Is Implemented and Navigated in RPi Pico WAV Player
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.
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, 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:
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:
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():
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():
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():
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:
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:
#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_playerabstracts FatFs into an order-based navigation API defined inlib/file_menu/file_menu_FatFs.h. - Directory entries are indexed in
entry_listand sorted using a custom quick-sort inidx_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, whilefile_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 viafile_menu_get_fname(). - The implementation isolates hardware specifics in
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. 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.
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 →