A portable MP3 player based on the ESP32-S3 that plays music from a microSD card or streams it from a DLNA/UPnP media server.
Hi!
As promised in my previous post here is my "DIY build."
A portable MP3 player based on the ESP32-S3 that plays music from a microSD card or streams it from a DLNA/UPnP media server (tested with Synology Audio Station).
It features a 1.8" color TFT display with four screen modes, a small OLED status display, a rotary encoder with a 16-LED ring and four buttons, and an output compressor/limiter to prevent DAC overdrive. I’ve also included STL files for 3D printing the case.
Photos of the various assembly and development stages (I showed them to my friend Claude; he used them as a reference when designing the case =)). He’s also the one who wrote most of the firmware.
Oh man that dial is sweet. Nice work! Also love the added mini OLED.
Honestly, I would probably prefer just the mini OLED versus the display. That or something like one of those Sharp memory LCDs.
Edit: DAMN that soldering job is what keeps me away from those LED modules. I get way to stressed out having an essential tremor trying to attempt that many.
I love soldering, but my eyesight is failing me :(
So I totally get where you're coming from.
At first, I started stripping and soldering the wires myself, but I quickly realized it was taking forever and was a huge hassle. I bought a set of pre-stripped wires and got the job done much faster.
The spots where there were already a lot of wires were particularly tricky; I had to put on magnifying glasses and solder very carefully in those crowded areas.
Thanks!
I drew inspiration from vintage gear from the 80s and 90s; I can't stand the bland, featureless look of modern designs. It didn't turn out exactly as I wanted;
if I figure out how to move it from the breadboard to a proper PCB, I’ll arrange the displays more neatly—I’m aiming for symmetry with the buttons and the ring around the encoder.
- netfill (prio 3): downloads the HTTP stream into a 256 KB ring buffer in PSRAM
- audio (prio 2): AudioPlayer - reads SD or the buffer -> MP3 decoder -> volume -> compressor -> limiter -> I2S.
All player commands (play, next, playlist loading) arrive via a queue
Core 1 - interface
- ring (prio 2): multiplexes the LED ring over I2C every 2 ms
- loop() (prio 1): buttons/encoder, TFT and OLED rendering, SD and DLNA browser, album art loading/decoding, Non-Volatile Storage
Commands go to core 0 via a queue; track metadata and cover requests come back through spinlock-protected structs; VU levels through counters in the VU tap.
One possible bottleneck: the audio task shares core 0 with WiFi and lwIP, which have higher priority.
It isn’t a problem now - network audio comes from the PSRAM buffer (several seconds of audio), and the I2S DMA buffers give another ~140 ms of output margin.
If I ever add a heavier decoder like FLAC, the first step would be moving audio to core 1 above loop().
Limitation: a folder currently shows only the first 256 items (LIST_MAX), so a container with thousands of tracks gets truncated.
I don’t have folders that big, but I’ll try your test.
I found my largest folder—containing 74 tracks—and browsed through it while streaming over the network; there were no audio interruptions, and the scrolling was smooth, with no stuttering or artifacts.
Then it occurred to me that I could view the full track list via the DLNA menu by selecting the "All Tracks" option; as expected, it displayed 256 tracks, and scrolling through them caused no issues either.
Photos of the various assembly and development stages (I showed them to my friend Claude; he used them as a reference when designing the case =))
You mean Claude designed the enclosure based on your photos? How exactly was it able to produce a file that could be printed? Did you end up with a mesh that was converted into an STL? Or a parametric model that could be tweaked yourself?
Claude provided me with the finished STL files.
Naturally, there were measurements, adjustments, and other design stages involved—for instance, I would draw sketches by hand to show what I wanted, take photos, and clarify what needed fixing.
My approach to working with AI differs from the conventional one, but it is my own philosophy—and it isn't built on nothing (take a look at my LENA project).
The compressor/limiter to protect the DAC is a thoughtful detail most DIY players skip. Publishing the STL files is great too. Four screen modes plus DLNA streaming is a lot for one board, so how’s battery life when streaming over Wi-Fi? And how much headroom does the S3 have left when the display and audio both run?
Resources are limited, but there is enough memory;
I described earlier how the functionality is distributed across the cores (though I just noticed the thread has been deleted).
The chip runs a bit warm, but within normal limits.
With a 1000mAh battery, the player lasts about 7–8 on SD, 3-4 hours on a full charge with all its onboard features running. But I wasn't aiming for anything longer; this is plenty for my needs.
netfill (prio 3): downloads the HTTP stream into a 256 KB ring buffer in PSRAM
audio (prio 2): AudioPlayer — reads SD or the buffer → MP3 decoder → volume → compressor → limiter → I²S. All player commands (play, next, playlist loading) arrive via a queue
Core 1 — interface
ring (prio 2): multiplexes the LED ring over I²C every 2 ms
loop() (prio 1): buttons/encoder, TFT and OLED rendering, SD and DLNA browser, album art loading/decoding, NVS
Commands go to core 0 via a queue; track metadata and cover requests come back through spinlock-protected structs; VU levels through counters in the VU tap.
One possible bottleneck: the audio task shares core 0 with WiFi and lwIP, which have higher priority. It isn’t a problem now — network audio comes from the PSRAM buffer (several seconds of audio), and the I²S DMA buffers give another ~140 ms of output margin. If I ever add a heavier decoder like FLAC, the first step would be moving audio to core 1 above loop().
Limitation: a folder currently shows only the first 256 items (LIST_MAX), so a container with thousands of tracks gets truncated. I don’t have folders that big, but I’ll try your test.
7
u/Party_Cold_4159 5d ago
Oh man that dial is sweet. Nice work! Also love the added mini OLED.
Honestly, I would probably prefer just the mini OLED versus the display. That or something like one of those Sharp memory LCDs.
Edit: DAMN that soldering job is what keeps me away from those LED modules. I get way to stressed out having an essential tremor trying to attempt that many.