This project is made by Jiří Praus.
Small and handy radio for your little ones. Let them play their favorite songs or fairytales. And that all in the wooden box!
Things used in this project
Hardware components
Story
When I saw the Horbert radio I knew in instant I want to build something similar for my 2yo daughter. But yet I wanted to make it smaller and handier. Maybe put some lights there. So here we are. Radio for your little ones. It features 7 color buttons, small yet powerful speaker and MP3 player capable of playing songs or fairytales from SD card. Last but not least, it features rainbow LEDs around the case to create nice ambient light which can be used as a night lamp while the bedtime fairytale is being played.
The wooden case
I wanted to make the case wooden. It looks nice and I love working with wood. Luckily by the same time, my friend got himself a laser cutter to cut plywood. And it is awesome! Various shapes cut within a few seconds and with awesome accuracy! The graphics file is attached at the bottom of this article.
Dimensions: 90x140x35mm
The case is made out of 4mm plywood cut into 8 parts. Front and back plate. Four side plates. Battery and speaker cover. First I glued the four side plates together to form a frame. I've also used some extra wood to reinforce the corners because I want them to be round at the end so I need to have some extra material there to be able to sand the corners down a little bit.
Before I glued in the front piece I inserted a thin layer of plexiglass between the front plate and the frame. This will allow me to later attach the LEDs to create a nice shiny ring around the radio. Some extra design touch. I've used 4mm plexiglass which I cut into rough shape to fit on the frame. Plexiglass is glued to the frame and front plate with the use of super glue. Don't worry about the overlapping plexiglass, it will be sanded out with final shaping.
Ambient rainbow lights
To add an extra coolness and effect in the dark I've added 4 RGB LEDs around the case. Plexiglass inserted earlier will allow the emitted light to spread around the case. I've used standard addressable WS2818 RGB LEDs daisy-chained together. One in each corner of the case.
First, glue these tiny LEDs into the corners on top of the plexiglass with super glue. Don't forget to mark the pin 1 otherwise you won't be able to properly interconnect them - see my tiny black mark made by permanent marker. Second, interconnect them together. I've used 0.3mm copper wire with transparent insulation.
- OUT to IN of the next LED
Finalizing the case
Prior to gluing the back plate in place cut small holes on one side of the frame to fit Arduino Nano USB port, MP3 player SD card slot and power switch. I've decided to put them on the left side of the radio. Using a small knife and a square file I've cut precise holes. As small as possible to hide as much as possible.
Now it is time to glue the back plate in place and form the final shape of the radio. After the glue set, I've used a bench sander to shape the round corners and clean up all the surfaces. To protect the edges I've also sanded them a little bit. Protect the wires coming out of the RGB LEDs to prevent any damage to the insulation while sanding.
The last step is to glue a speaker cover ring over the speaker hole. It will hold the speaker in place. As a final protection, I've applied clear lacquer in 4 layers.
Speaker cover mesh
To protect the speaker from being damaged by the curious fingers I wanted to put a mesh of some kind over the speaker hole. But since I don't own a 3D printer and I didn't have anything else suitable, I've created it by soldering 1mm brass rods into a mesh.
It's perfect. It's incredibly solid. Nothing can penetrate it and also looks pretty nice. To hide the solder joints I've sprayed it with white color. Speaker cover is glued into the radio with use of super glue.
Inside the radio
The heart of the radio is my favorite Arduino Nano board. It reads the states of 7 buttons, controls DFPlayer mini MP3 player and controls the addressable RGB LEDs. DFPlayer is a tiny MP3 player with built-in SD card slot and sound amplifier which can handle up to 3W speaker. I've used a 0.5W 8ohm speaker.
The buttons
First of all, create the buttons platform. Radio contains 7 programmable buttons in total soldered to small piece of a perf board. Originally I wanted each button to have a different color to play 7 different songs. But it didn't look nice so I decided to have one black button to change the volume level and yellow, blue and red pairs to select previous or next song out of 3 groups of songs. Each button is connected to one digital pin of Arduino and they are all grounded via 1Kohm resistor.
The buttons don't have standard perf board friendly spacing so I had to place them into the holes in the case and solder them together with a piece of a brass rod using free form technique. Now when the layout of buttons was fixed it was time to solder it to the perf board upside down and add 7 wires to each button. Finally, I soldered the 8th wire for the common ground.
Assembling it together
Now when you have all the parts needed, let's finish the job. I first soldered all the wires between the Arduino Nano, DFPlayer, buttons, speaker, RGB LEDs, battery and on/off switch outside the radio because there is not enough room inside the case once they will be inserted. It also allowed me to test the electronics outside the case before all the components are irreversibly glued inside.
First I inserted and glued Arduino Nano, DFPlayer and on/off switch into the case with use of Mamut Glue. The Mamut Glue gave me more time to precisely and nicely place the boards inside and will hold them better in the future because it is flexible. Next was a speaker mounted on the bottom of the speaker cover mesh again with Mamut Glue. And the last parts were the buttons and the battery pack. The battery pack sits in the hole on the back to allow easily replace the batteries. Later I replaced the 4xAA batteries with a single 9V battery. It's much lighter.
It is finished! Let's program it!
Playing the songs!
I've simply used examples from DFPlayer site and added some code to read the buttons and control the LEDs from my Ever Blooming Mechanical Tulip project. The code is quite simple.Definitely not the cleanest I ever made but it works.
It requires the SD card to contains 3 folders named 01, 02, 03 for each color pair. Within each folder can be up to 255 songs named 001.mp3 to 255.mp3. I'd rather name the folders red, blue and yellow but DFPlayer does not support that.
DFPlayer is pretty easy to use. It communicates via serial line and has a pretty nice library with various functions to read and control the state of the player. I would only add support for human readable folder and file names.
Code
#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <Adafruit_NeoPixel.h>
#define BUTTON_1 A2 // yellow left
#define BUTTON_2 A1 // yellow right
#define BUTTON_3 A3 // red left
#define BUTTON_4 A0 // red right
#define BUTTON_5 A4 // blue left
#define BUTTON_6 2 // blue right
#define BUTTON_7 3 // black
#define BUTTONS_COUNT 7
#define BUTTON_RELEASED 0
#define BUTTON_PRESSED 1
#define BUTTON_HANDLED 2
byte buttonPins[] = {BUTTON_1, BUTTON_2, BUTTON_3, BUTTON_4, BUTTON_5, BUTTON_6, BUTTON_7};
byte buttonState[] = {0, 0, 0, 0, 0, 0, 0};
#define NEOPIXEL_PIN 9
#define RED 0
#define GREEN 1
#define BLUE 2
float currentRGB[] = {0, 0, 0};
float changeRGB[] = {0, 0, 0};
byte newRGB[] = {0, 0, 0};
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(4, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ400);
SoftwareSerial playerSerial(7, 6); // RX, TX
DFRobotDFPlayerMini player;
#define FOLDER_RED 0
#define FOLDER_BLUE 1
#define FOLDER_YELLOW 2
byte currentSong[] = {0, 0, 0}; // 1 to size of folder
byte folderSizes[] = {0, 0, 0};
byte volume = 5;
byte lightIntensity = 0;
void setup() {
randomSeed(analogRead(A7));
playerSerial.begin(9600);
Serial.begin(115200);
pixels.begin();
pixelsUnifiedColor(0);
for (int i = 0; i < BUTTONS_COUNT; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
buttonState[i] = BUTTON_RELEASED;
}
Serial.println();
Serial.println(F("DFRobot DFPlayer Mini Demo"));
Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
if (!player.begin(playerSerial)) { //Use softwareSerial to communicate with mp3.
Serial.println(F("Unable to begin:"));
Serial.println(F("1.Please recheck the connection!"));
Serial.println(F("2.Please insert the SD card!"));
while(true);
}
Serial.println(F("DFPlayer Mini online."));
//player.EQ(DFPLAYER_EQ_ROCK);
player.outputDevice(DFPLAYER_DEVICE_SD);
player.volume(volume);
//player.play(1);
folderSizes[FOLDER_RED] = player.readFileCountsInFolder(FOLDER_RED + 1);
folderSizes[FOLDER_BLUE] = player.readFileCountsInFolder(FOLDER_BLUE + 1);
folderSizes[FOLDER_YELLOW] = player.readFileCountsInFolder(FOLDER_YELLOW + 1);
Serial.println(F("Radio ready."));
}
void loop() {
if (player.available()) {
printDetail(player.readType(), player.read()); //Print the detail message from DFPlayer to handle different errors and states.
}
readButtons();
crossFade();
delay(15);
}
void readButtons() {
for (int i = 0; i < BUTTONS_COUNT; i++) {
if (digitalRead(buttonPins[i]) == HIGH) {
buttonState[i] = BUTTON_RELEASED;
}
else if (buttonState[i] == BUTTON_RELEASED) {
buttonState[i] = BUTTON_PRESSED;
}
}
if (buttonState[0] == BUTTON_PRESSED) { // yellow left
buttonState[0] = BUTTON_HANDLED;
playNextInFolder(FOLDER_YELLOW);
}
else if (buttonState[1] == BUTTON_PRESSED) { // yellow right
buttonState[1] = BUTTON_HANDLED;
playPreviousInFolder(FOLDER_YELLOW);
}
else if (buttonState[2] == BUTTON_PRESSED) { // red left
buttonState[2] = BUTTON_HANDLED;
playNextInFolder(FOLDER_RED);
}
else if (buttonState[3] == BUTTON_PRESSED) { // red right
buttonState[3] = BUTTON_HANDLED;
playPreviousInFolder(FOLDER_RED);
}
else if (buttonState[4] == BUTTON_PRESSED) { // blue left
buttonState[4] = BUTTON_HANDLED;
playNextInFolder(FOLDER_BLUE);
}
else if (buttonState[5] == BUTTON_PRESSED) { // blue right
buttonState[5] = BUTTON_HANDLED;
playPreviousInFolder(FOLDER_BLUE);
}
else if (buttonState[6] == BUTTON_PRESSED) { // black
volume = volume >= 15 ? 5 : volume + 5;
player.volume(volume);
buttonState[6] = BUTTON_HANDLED;
}
}
void playNextInFolder(byte folder) {
currentSong[folder]++;
if (currentSong[folder] > folderSizes[folder]) {
currentSong[folder] = 1;
}
player.playFolder(folder + 1, currentSong[folder]);
}
void playPreviousInFolder(byte folder) {
currentSong[folder]--;
if (currentSong[folder] <= 0) {
currentSong[folder] = folderSizes[folder];
}
player.playFolder(folder + 1, currentSong[folder]);
}
void printDetail(uint8_t type, int value){
switch (type) {
case TimeOut:
Serial.println(F("Time Out!"));
break;
case WrongStack:
Serial.println(F("Stack Wrong!"));
break;
case DFPlayerCardInserted:
Serial.println(F("Card Inserted!"));
break;
case DFPlayerCardRemoved:
Serial.println(F("Card Removed!"));
break;
case DFPlayerCardOnline:
Serial.println(F("Card Online!"));
break;
case DFPlayerPlayFinished:
Serial.print(F("Number:"));
Serial.print(value);
Serial.println(F(" Play Finished!"));
break;
case DFPlayerError:
Serial.print(F("DFPlayerError:"));
switch (value) {
case Busy:
Serial.println(F("Card not found"));
break;
case Sleeping:
Serial.println(F("Sleeping"));
break;
case SerialWrongStack:
Serial.println(F("Get Wrong Stack"));
break;
case CheckSumNotMatch:
Serial.println(F("Check Sum Not Match"));
break;
case FileIndexOut:
Serial.println(F("File Index Out of Bound"));
break;
case FileMismatch:
Serial.println(F("Cannot Find File"));
break;
case Advertise:
Serial.println(F("In Advertise"));
break;
default:
break;
}
break;
default:
break;
}
}
void prepareCrossFade(byte red, byte green, byte blue, unsigned int duration) {
float rchange = red - currentRGB[RED];
float gchange = green - currentRGB[GREEN];
float bchange = blue - currentRGB[BLUE];
changeRGB[RED] = rchange / (float) duration;
changeRGB[GREEN] = gchange / (float) duration;
changeRGB[BLUE] = bchange / (float) duration;
newRGB[RED] = red;
newRGB[GREEN] = green;
newRGB[BLUE] = blue;
Serial.print(newRGB[RED]);
Serial.print(" ");
Serial.print(newRGB[GREEN]);
Serial.print(" ");
Serial.print(newRGB[BLUE]);
Serial.print(" (");
Serial.print(changeRGB[RED]);
Serial.print(" ");
Serial.print(changeRGB[GREEN]);
Serial.print(" ");
Serial.print(changeRGB[BLUE]);
Serial.println(")");
}
boolean crossFade() {
if (currentRGB[RED] == newRGB[RED] && currentRGB[GREEN] == newRGB[GREEN] && currentRGB[BLUE] == newRGB[BLUE]) {
return true;
}
for (byte i = 0; i < 3; i++) {
if (changeRGB[i] > 0 && currentRGB[i] < newRGB[i]) {
currentRGB[i] = currentRGB[i] + changeRGB[i];
}
else if (changeRGB[i] < 0 && currentRGB[i] > newRGB[i]) {
currentRGB[i] = currentRGB[i] + changeRGB[i];
}
else {
currentRGB[i] = newRGB[i];
}
}
pixelsUnifiedColor(pixels.Color(currentRGB[RED], currentRGB[GREEN], currentRGB[BLUE]));
return false;
}
void pixelsUnifiedColor(uint32_t color) {
for (unsigned int i = 0; i < pixels.numPixels(); i++) {
pixels.setPixelColor(i, color);
}
pixels.show();
}
void rainbow(int j) {
uint16_t i;
byte num = pixels.numPixels() - 1;
pixels.setPixelColor(pixels.numPixels() - 1, 100, 100, 100);
for (i = 0; i < num; i++) {
pixels.setPixelColor(i, colorWheel(((i * 256 / num) + j) & 255));
}
pixels.show();
}
uint32_t colorWheel(byte wheelPos) {
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
wheelPos = 255 - wheelPos;
if (wheelPos < 85) {
return pixels.Color(255 - wheelPos * 3, 0, wheelPos * 3);
}
if (wheelPos < 170) {
wheelPos -= 85;
return pixels.Color(0, wheelPos * 3, 255 - wheelPos * 3);
}
wheelPos -= 170;
return pixels.Color(wheelPos * 3, 255 - wheelPos * 3, 0);
}