This project creates a music box that automatically plays different songs when you place various NFC tags on the reader. Each tag contains a small piece of data telling the ESP32 which track to play.
As my boy started to grow and his passion for music grew even stronger each day, we eventually traded the click-clack of the keys for the sound of our mobile devices. Soon, his demands shifted: he had his own favorite song list, and each one absolutely had to be played five times before we could move to the next. The order was random, of course, and he fiercely insisted on control of the very next song. At the start, it was cute, but after some time, my wife and I found ourselves with no ability to do anything else besides being his own private, utterly exhausted D.J.
I wanted to give Michael the ability to choose his own songs and feel in control of his listening experience. The solution was the Avatar Music Box, a system designed to play a specific song whenever a unique, physical avatar representing that track was placed on the device. To make this magic happen, the project uses several core electronic modules. The heart of the system is the ESP32-C3 Super Mini, which acts as the brain, processing input from the NFC reader. I paired this with the PN532 NFC Reader and embedded NFC Ntags inside small avatars that I have 3D printed, giving each physical object a unique digital ID. When Michael places an avatar on the box, the PN532 reads the tag's ID and transmits this data to the ESP32. The ESP32 then tells the DFPlayer Mini a compact MP3 module with its own amplifier to play the corresponding song file stored on an SD card. This allows for rich, dedicated sound output through a small speaker. For power, a Dual 18650 lithium-ion battery ensures the box is completely portable and can play many songs throughout the day without needing an external supply, controlled by a simple rocker switch. Finally, two push buttons were added for volume control, and an internal LED provides visual confirmation to the boy that the avatar has been correctly detected, making the entire experience intuitive and independent.
![]()
![]()
I have connected the MVP elements, ESP32, DFPlayer, speaker, NFC reader to see that all modules are working fine and the connection are stable, I have create short code that can handle the tag detection, I used I2C protocol as I found it best for this project, low assumably steps (4 wire), and the most documentation on the web, other protocol have other benefits see the table below.
To set the module protocol there is config guide on the board.

| Mode | Pros | Cons | Best For | 4# of wires |
|---|---|---|---|---|
| UART | Easy, stable, universal | Slower, uses Serial pins | Beginners & simple NFC reading | 4 |
| I²C | Allows multiple devices on the same bus | Unstable on long wires, needs pull-ups (depends on the module) | Sensor-heavy projects, pin-limited boards | 4 |
| SPI | Fastest, reliable, best performance | More pins, sensitive wiring | Advanced NFC, card emulation, fast operations | 6 |
I had a vision in mind but didn’t see it clear so I asked GPT to create an image of this box, and it worked quite well. it helped me think on some small details that I didn't have in mind like small stroke lines and the location of cut between the two box parts.

I tried to same some time with the creation of the 3D module of the box by using MalerLab by MakerWorld, it worked pretty well and it was looking like I can just print it.
after downloading the object I same issue with the texture and when there is functionality like buttons and part maybe this tool is not there yet, but for simple modeling its doing wonderful job.

Try image to 3D MakerWorld
So I have to go and module it, for the speakers holes I still wanted a cat so I asked GPT to create image of a cat that is made by dots, I think this image convert is to SVG and used it to cut holes in the model
![]()
After printing the box parts, I had to connect all the items together, some patient and good music help trough the process.
![]()
This section provides a focused overview of the core functions that power the project, giving you a clear understanding of how the main logic works without overwhelming you with every implementation detail. If you’d like to dive deeper, explore the full source code, or better understand how all the components connect together, you can visit the Full Project Guide
When a tag is placed:
When the tag is removed:
A 2-second grace period The grace period mechanism allows songs to keep playing for 2 seconds after tag removal, preventing abrupt stops if the tag shifts slightly.. After that → playback stops.
the device have 3 different mode to support from single application song play and tag maintenance
PLAY_MODE (default) - Automatically plays songs when tags are detected WRITE_MODE - Programs new tags via serial commands READ_MODE - Displays what song number is stored on a tag
void playSong(int trackNumber) { dfPlayer.play(trackNumber); setLED(true); state.isSongPlaying = true; }
Plays the song number stored on the NFC tag, turns on an indicator LED, and updates the system state.
void stopSong() { dfPlayer.stop(); setLED(false); state.isSongPlaying = false; }
Stops the currently playing song, turns off the indicator LED, and updates the system state.
void checkVolumeButtons() { bool upPressed = (digitalRead(VOLUME_UP_PIN) == LOW); if (upPressed && !buttons.lastUpState) { adjustVolume(1); } }
Monitors two physical buttons to increase/decrease volume, with debouncing to prevent accidental multiple presses.
void writeSongNumber(uint8_t songNum) { uint8_t data[4] = {'S', 'O', 'N', songNum}; nfc.ntag2xx_WritePage(4, data); }
'S', 'O', 'N', and the song number (1-99)write 15 programs tag for song #15int readSongNumberFromTag() { uint8_t data[4]; nfc.ntag2xx_ReadPage(4, data); if (data[0] == 'S' && data[1] == 'O' && data[2] == 'N') { return data[3]; // Returns song number } return -1; // Tag not programmed }
NFC Memory Map Graphic (ASCII) +------- NFC TAG MEMORY PAGE 4 ------+ | BYTE0 | BYTE1 | BYTE2 | BYTE3 | +-------+-------+-------+------------+ | S | O | N | Song # | +------------------------------------+ Example: [53] [4F] [4E] [05] Meaning: S O N Track 5
void checkNFCTag() { bool tagDetected = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 100); if (tagDetected) { int songNumber = readSongNumberFromTag(); playSong(songNumber); } }
Polls for NFC tags every 200ms, reads the song number, and starts playback automatically.
During development, I ran into a strange behavior: NFC tags with even numbers would not play any songs, while odd-numbered tags worked perfectly.
My first guess was an SD card formatting issue, so I reformatted the card using FAT32 with 32KB clusters:
# 1. List disks diskutil list # 2. Unmount the SD card diskutil unmountDisk /dev/disk2 # 3. Format as FAT32 with 32KB clusters sudo newfs_msdos -F 32 -c 64 -v MUSIC /dev/disk2 # (-c 64 → 64 sectors × 512 bytes = 32KB) # 4. Eject diskutil eject /dev/disk2
But this didn’t fix the issue. I added debug logs, tested different MP3 files, tried other sketches that play audio directly from the DFPlayer… still nothing. The pattern stayed the same:
In short: only odd-numbered tag values trigger audio, and the DFPlayer shifts the track number.
At some point, the number of investigation hours I had allocated for this task simply ran out. So for now, I’m leaving this as an open issue in the project.
If you’re building your own Avatar Music Box, follow the pattern above — or even better, solve the mystery and share your solution with me so we can close this chapter together.
Thanks for reading!