Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lyra - Handheld MP3 Player

A handheld music player I built on the Adafruit Feather RP2040. Touchscreen, physical buttons, album art, and proper I2S audio output. Started as a CircuitPython prototype, ended up as a full C++ rewrite (PlatformIO + arduino-pico) when I hit the limits of what CircuitPython could do for audio.


What it does

  • MP3 playback via I2S DAC (PCM5102A)
  • Touchscreen Now Playing view with album art, track info, and volume control
  • Queue browser: reorder, insert, remove, and save tracks
  • Playlist support via .m3u files on the SD card
  • Screen timeout and wake source settings, persisted across reboots
  • Physical PREV / PAUSE / NEXT buttons
  • Battery percentage indicator (color-coded)
  • Restores saved queue on reboot; shuffles all songs if no saved queue exists

Bill of Materials

Component Details Qty Cost
MCU Adafruit Feather RP2040 (dual-core, 133 MHz) 1 $18.0
Display ILI9341 SPI TFT, 240x320 portrait 1 $15.3
Touchscreen XPT2046 (often integrated with display) 1 incl^
Audio DAC PCM5102A I2S DAC module 1 $4.91
Storage MicroSD card (FAT32, any size) 1 $13.5
Battery LiPo cell (3.7V) 1 $9.00
Buttons Momentary tactile push buttons 3 $0.50
Misc Wires, headers, resistors (voltage divider for battery ADC) - $0.50
Case 3D printed, ~7 revisions to get right - $1.50

Total: ~$63


Pin Mapping

All pins are defined in lyra/src/Constants.h.

Role Feather Pin GPIO
Display SPI1 SCK D10 10
Display SPI1 MOSI D11 11
Display SPI1 MISO D12 12
TFT CS D9 9
TFT DC D6 8
TFT RST D5 7
Backlight PWM A2 28
Touch CS (XPT2046) SDA 2
Touch IRQ (XPT2046) SCL 3
SD SPI0 SCK SCK 18
SD SPI0 MOSI MOSI 19
SD SPI0 MISO MISO 20
SD CS RX 1
I2S BCLK D24 24
I2S LRCLK D25 25
I2S DIN A3 29
PREV button TX 0
PAUSE button D4 6
NEXT button D13 13
Battery ADC A1 27

D5 -> GPIO7 and D6 -> GPIO8. If the display is blank after first flash, check these two pins first.


SD Card Layout

Format as FAT32 with MBR partition table.

/music/          # MP3 files (subdirectories OK, e.g. /music/Artist/Song.mp3)
/images/         # album art JPEGs, pre-scaled to 110x110 (generated by sync_music.py)
/playlists/      # .m3u playlist files
/state/          # internal state (queue.m3u, auto-created)
/music_list.txt  # file index cache (auto-built on first boot)
/music_meta.txt  # metadata cache: path, artist, title, album, duration (written by sync_music.py)

Only MP3 files are played. Re-encode any .wav files before copying.


Building and Flashing

First build (downloads platform + libraries, ~300 MB):

cd lyra
pio run

Flash via PlatformIO:

pio run --target upload

Flash manually (UF2 drag-drop):

  1. Hold BOOTSEL while plugging in USB, or double-tap RESET
  2. Board appears as RPI-RP2 drive
  3. Drag .pio/build/adafruit_feather_rp2040/firmware.uf2 to the drive
  4. Board reboots

Python Tools

Run on your computer, not on the device.

Script What it does
tools/sync_music.py Copies MP3s from a local source to the SD card, generates album art in /images/, writes /music_list.txt and /music_meta.txt
tools/playlist_manager.py Creates and edits .m3u playlists on the SD card

music_meta.txt format: path \t artist \t title \t album \t duration_ms


UI

Now Playing (tab 0)

  • Artist, title, album, prev/next track context
  • 110x110 album art (loaded asynchronously from /images/, cached in RAM)
  • Vertical volume bar + V-/V+ buttons
  • << / Play/Pause / >> buttons
  • Current song progress
  • Battery % (green >50%, yellow >20%, red <=20%)
  • Settings button

Queue (tab 1)

Toggle between Queue and All Songs.

  • Queue: reorder (Move Up/Down), Remove, Save to SD
  • All Songs: insert any track after the current position

Playlists (tab 2)

Lists .m3u files from /playlists/. Tap to load, replaces current queue.

Settings (gear icon)

  • Screen timeout: Off / 5s / 10s / 30s / 1m
  • Wake source: Touch+Buttons / Buttons Only
  • Persists to flash on pause

Buttons

Button Short press Long press
PREV Previous track Volume up (>0.5 s)
NEXT Next track Volume down (>0.5 s)
PAUSE Toggle play/pause (no screen wake) Toggle screen (>2 s)

How it's structured

The RP2040 has two cores, split cleanly:

  • Core 0: UI: display, touch, buttons, battery, EEPROM
  • Core 1: Audio: MP3 decode (Helix) + I2S output

Cross-core state uses volatile globals. The SD bus (SPI0) is owned by Core 1 during playback, so Core 0 requests a yield via g_sdYieldRequest before any SD access (album art load, queue save). This took a while to get right.

State (volume, track, settings) lives in EEPROM flash. EEPROM.commit() freezes both cores for ~100ms, which empties the I2S buffer and causes a screech, so commits only happen when audio is already paused.

About

Lyra - a handheld MP3 player built on the Adafruit Feather RP2040. Touchscreen, album art, queue management, and I2S DAC audio. Written in C++.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages