CartridgeMP3
as featured on Hackaday / May 2022
The CartridgeMP3 plays MP3s off an SD card, but you don't pick tracks from a menu. You take a small printed cartridge, slide it into the slot on the front, and it plays. One object per thing you want to hear, the way a CD or cassette player works.
The body is 3D printed. Inside there's an ATmega32U4 and a VS1053 MP3 decoder, with the audio itself on a microSD card. Each cartridge carries an 8-bit code that the microcontroller reads over a 12-pin interface. Volume runs through a potentiometer with a built-in power switch, and a USB-C port handles both power and programming.
I built this in 2022. I use mine as a bedside player.
Technical Overview
The Feather 32U4 Basic Proto does everything except decode audio. It reads the cartridge, picks a filename, and hands playback off to the Music Maker FeatherWing, which carries the VS1053 and the microSD slot. Splitting it up that way keeps the firmware short.
The cartridge interface is the part worth explaining. There are twelve pins.
The two on the outside are ground, and the eight in the middle get read as a
byte. Each of those eight lines has a 10K pull-up resistor on a
quarter-sized perma-proto board, so a pin with nothing attached reads as a 1.
A cartridge sets its code by tying some of those pins to the ground pins
using cut-off resistor legs. Every pin you tie down reads 0. Tie the fourth
one and the cartridge shows up as 11011111.
The rest of the front panel is simple. A dual-log potentiometer with an on-off switch handles volume on both channels and cuts power. A mechanical key switch is wired to reset. A 3 mm LED on pin 13 lights up during playback. The headphone jack takes the potentiometer output. For USB-C I cut up a micro-B cable, isolated the conductors, and soldered each one to its matching pad, running the 5 V line through the switch.
The enclosure is three printed parts: a shell, a faceplate, and a pin holder. I designed them in Fusion 360 and printed them on an Ender 3 at 0.2 mm layers and 20% infill. Everything mounts to the faceplate, which then goes into the shell as one assembly. The pin holder is there to keep the cartridge header square and centered in its slot, which matters more than you'd think for whether a cartridge seats cleanly.
Firmware
The sketch uses Adafruit's VS1053 library. Setup configures the twelve cartridge lines as inputs, brings up the decoder and the SD card, and sets the volume. One quirk of the VS1053 is that a lower number means louder. The loop is then a flat list of cartridge codes, each with a filename attached:
// CartridgeMP3 V1 - code
// Library and base code from Adafruit, everything else from Justinas Petkauskas
// Please modify this and share it on instructables. My code sucks
// Enjoy your CartridgeMP3!
#include <SPI.h>
#include <SD.h>
#include <Adafruit_VS1053.h>
#define VS1053_RESET -1 // VS1053 reset pin (not used!)
#define VS1053_CS 6 // VS1053 chip select pin (output)
#define VS1053_DCS 10 // VS1053 Data/command select pin (output)
#define CARDCS 5 // Card chip select pin
#define VS1053_DREQ 9 // VS1053 Data request, ideally an Interrupt pin
Adafruit_VS1053_FilePlayer musicPlayer =
Adafruit_VS1053_FilePlayer(VS1053_RESET, VS1053_CS, VS1053_DCS, VS1053_DREQ, CARDCS);
void setup() {
pinMode(0, INPUT); // setting up all the cartridge interface inputs
pinMode(1, INPUT);
pinMode(2, INPUT);
pinMode(3, INPUT);
pinMode(A0, INPUT);
pinMode(A1, INPUT);
pinMode(A2, INPUT);
pinMode(A3, INPUT);
pinMode(A4, INPUT);
pinMode(A5, INPUT);
pinMode(11, INPUT);
pinMode(12, INPUT);
pinMode(13, OUTPUT);
if (! musicPlayer.begin()) { // initializing the music player
while (1);
}
if (!SD.begin(CARDCS)) { // initializing the SD card
while (1);
}
musicPlayer.setVolume(0,0); // sets volume...LOWER IS HIGHER
musicPlayer.useInterrupt(VS1053_FILEPLAYER_TIMER0_INT); // Has something to do with file playback...not sure exactly what
}
void loop() { // First Track. Choose the cartridge pins that are pulled to ground and define them here, and then assign this to the track that is a few lines down. Tracknames can only be 8 letters long with a 3 letter extension!!!!!
while (digitalRead(0) == LOW && digitalRead(1) == LOW && digitalRead(2) == HIGH)
{
analogWrite(13, 1); // Notification LED, PWM at minimum brightness
delay(871); // random delay for dramatic effect
musicPlayer.startPlayingFile("/track001.mp3"); // plays music!
}
while (digitalRead(A2) == HIGH && digitalRead(A3) == LOW && digitalRead(12) == LOW) // read the first one
{
analogWrite(13, 1);
delay(692);
musicPlayer.startPlayingFile("/track002.mp3");
}
while (digitalRead(0) == LOW && digitalRead(1) == LOW && digitalRead(2) == LOW)
{
analogWrite(13, 1);
delay(748);
musicPlayer.startPlayingFile("/track003.mp3");
}
while (digitalRead(A2) == LOW && digitalRead(A3) == LOW && digitalRead(12) == LOW)
{
analogWrite(13, 1);
delay(419);
musicPlayer.startPlayingFile("/track004.mp3");
}
}
Two things to know before you load any music. Filenames have to be exactly
eight characters plus a .mp3 extension or the library won't open
them, so track001.mp3 and so on is the easy path. The card also
wants to be formatted empty with the files dropped straight onto it, no
folders.
Build
[every part below is from Adafruit, so it's one order.]
You'll need:
- Feather 32U4 Basic Proto and Music Maker FeatherWing
- Quarter-sized perma-proto board and 8× 10K resistors
- Panel-mount 10K dual-log potentiometer with on-off switch
- Mechanical key switch, 3 mm LED, headphone jack
- 12-pin female header per cartridge, plus male header and hookup wire
- USB-C breakout, or a sacrificial micro-B cable
- Printed parts: enclosure, faceplate, pin holder, cartridges
- Start the prints first so they finish while you're doing the electronics. Print the pin holder, then the faceplate, then the enclosure, so you're never waiting on the part you need next.
- Wire the decoder to the microcontroller with jumpers and check that both come up before doing anything else. Then build the pull-up network for the cartridge interface on the perfboard.
- Solder the cartridge pins and coat the joints in hot glue. They take all the insertion force, so this is the connection most worth overbuilding. Glue the header dead center in the pin holder, then glue the pin holder into the faceplate slot.
- Mount the rest of the components to the faceplate and solder their connections: the pin 13 LED, the potentiometer, the reset switch to ground, and the headphone jack off the potentiometer output. Wire the USB-C connector, with 5 V going through the switch.
- Flash the sketch and check that a cartridge plays. Then thread the faceplate assembly in through the front of the enclosure, keeping the USB-C port and microSD slot reachable from the back. Glue down the loose parts inside, run hot glue around the edge of the faceplate, and press it flush.
Cartridges
A cartridge is two copies of the same printed part, since it's symmetrical, superglued around a 12-pin female header. Tie some of the inner eight pins to the outer ground pins with cut-off resistor legs to set the code, then add the matching conditional to the sketch.
Labels are what make it feel like an actual format. A 36×44 mm label fits the face. I made mine in Inkscape, though the hand-drawn ones cut from printer paper came out better than I expected.
Conclusion
It still works. The part that gave me the most trouble wasn't the code or the audio, it was the connector. Eight contacts that have to land every time you push a printed part into a slot is a mechanical problem, and I spent more time on the pin holder and the glue-up than on anything else in the build.
If I did it again I'd use a proper card-edge connector instead of a female header, and I'd read the cartridge once when it's inserted rather than polling it in a while loop.
Gallery




