A while ago I wanted to buy a musicplayer for my daugther. The first idea was to buy one of these “pinky” plastic things made in east asia. While thinking about the lifetime of these plastic players in a 2 years old child hand, I decided to look for alternatives. Then I found the famous hoerbert which is hand made in Germany, with responsible ingredients and really suitable for a child. The price is not cheap but really fair, as I know after building my version of a child capable music player. Inspired by the hoerbert I decided to build my own version called ABox because I want to have some changes:
- run with an rechargeable pack instead of batteries
- easy upload of songs with any format without the need of conversion
- “stereo” Sound
For using standard audiofiles I needed to build a stereo musicplayer anyway. To have a good sound with a mono device, the music must be compressed to one channel. this means each file must be converted before uploading it to the players SD card.
Like most of my small diy projects i decided to use an arduino as base for the player. I combined it with adafruits mp3 shield and a self welded button panel. The 12 button panel works with 12 resistors which are bridged if a button is pressed. With this technique its possible to measure the voltage on one of the arduinos analog ports and to decide which button was pressed. A circuit plan can be found here or by searching for “arduino button keyboard resistor”. As you can see in the Images, I added two input resistors which helped to get better measurement values.
The concept of operation is easy: 10 Buttons represent a folder/playlist on the SD card, Button 11 jumps back, Button 12 jumps forward. If the AdaBox is switched off, it stores the actual played file on the sd card. If it’s switched on, it starts playing with the last file.
Here’s the code:
/*
* Software for the ABox - a Hoerbert inspired music player for children.
* Uses the Adafruit SD MP3 Player. The buttonboard is a 12 button board
* which uses one of the analog ports and 1/4W 3,3K do divide the voltage
* if a button is pressed. For more infos search for "arduino button analog resitor"
*
* Usage of the player: The SD Card must contain 10 Directories with music files.
* Button 1-10 starts playing the music in the correspondent file.
* Button 11 rewinds.
* Button 12 plays the next musicfile
* of the directory.
*
* Feel free to use the software on your own risk. If you extend the software
* i would be happy to get your code. Inform me at koelnerwasser.de
*
*/
#include
#include
#include
//## user preferences
const char lastPlayedStorageFile[] = "LASTFILE.TXT";
//## programm variables
// buttonpad
//last pressed button
int actualButtonId = 99;
bool firstRun = true;
const int pinPadPin = A3;
// music dir/file
char actualMusicDir[5];
char* actualMusicFileName;
char actualMusicFileFullPath[20];
char lastSessionMusicFilePath[20];
const int maxFilesInDir = 50;
// list of played files for the rewind function
char listOfLastPlayedFilesInDir [maxFilesInDir][20];
int countOfEntryInLastPlayedArr = 0;
// volume
const int volumePotiPin = A2;
// Mp3 Board
#define BREAKOUT_RESET 9 // VS1053 reset pin (output)
#define BREAKOUT_CS 10 // VS1053 chip select pin (output)
#define BREAKOUT_DCS 8 // VS1053 Data/command select pin (output)
// These are the pins used for the music maker shield
#define SHIELD_RESET -1 // VS1053 reset pin (unused!)
#define SHIELD_CS 7 // VS1053 chip select pin (output)
#define SHIELD_DCS 6 // VS1053 Data/command select pin (output)
// These are common pins between breakout and shield
#define CARDCS 4 // Card chip select pin
#define DREQ 3 // VS1053 Data request, ideally an Interrupt pin
// create MusicPlayer object
Adafruit_VS1053_FilePlayer musicPlayer =
Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
void setup() {
//Serial.begin(9600);
Serial.println("Adafruit VS1053 Library Test");
// initialise the music player
if (! musicPlayer.begin()) {
Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
while (1);
}
Serial.println(F("VS1053 found"));
// generate Testtone
// musicPlayer.sineTest(0x44, 500);
// we need bass, bass
// bass values
byte bassAmp = 9; // value should be 0-15
byte bassFrq = 9; // value should be 0-150
// treble values
byte trebAmp = 1; // value should be 0-15
byte trebFrq = 1;
// convert values into ushort for cpu command
unsigned short bassCalced = ((trebAmp & 0x0f) << 12)
| ((trebFrq & 0x0f) << 8)
| ((bassAmp & 0x0f) << 4)
| ((bassFrq & 0x0f) << 0); // process bass cpu command musicPlayer.sciWrite(0x02, bassCalced); if (!SD.begin(CARDCS)) { Serial.println(F("SD failed, or not present")); while (1); // don't do anything more } Serial.println("SD OK!"); // list files // printDirectory(SD.open("/"), 0); // define interrupt - Pininterrupt if (! musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT)) Serial.println(F("DREQ pin is not an interrupt pin")); } void loop() { // Set volume // lower numbers == louder volume! int volume = getVolumeValue(); musicPlayer.setVolume(volume, volume); File actualDirPointer; File actualFilePointer; // ## inital start: nothing pressed, read stored last used file and play it ## if (firstRun) { // read last played file readLastSessionFilenameFromSd(); if (sizeof(lastSessionMusicFilePath) > 0) {
//Serial.print(dumpCharArray(lastSessionMusicFilePath,sizeof(lastSessionMusicFilePath)));
if (SD.exists(lastSessionMusicFilePath)) {
Serial.println(F("Found LastPlayed Musicfile of Last Session on SD, try to initalize variables for playing it"));
// extract dir
char *ptrOfSlashOccurence = strchr(lastSessionMusicFilePath, '/');
// set dir and file pointer to the correct dir
if (ptrOfSlashOccurence) {
// extract dir and file string
int indexOfSlash = ptrOfSlashOccurence - lastSessionMusicFilePath;
strncpy(actualMusicDir, lastSessionMusicFilePath, indexOfSlash + 1);
char lastSessionMusicFileName[15];
strncpy(lastSessionMusicFileName, lastSessionMusicFilePath + indexOfSlash + 1, sizeof(lastSessionMusicFilePath));
// set filepointer
actualFilePointer = SD.open(lastSessionMusicFilePath);
// set dirpointer
actualDirPointer = SD.open(actualMusicDir);
// forward dir pointer to lastSessionMusicFilePath
int breaker = 0;
File toCompare;
while (breaker < maxFilesInDir) {
breaker++;
toCompare = actualDirPointer.openNextFile();
if (strcmp(toCompare.name(), lastSessionMusicFileName) == 0 ) {
toCompare.close();
break;
}
toCompare.close();
}
if (breaker < 50) { Serial.println(F("Initalize of variables for playing lastSession Music File successful")); firstRun = false; goto lastSessionStart; } } } } Serial.println(F("Did not found the file on sd or unable to init the variables")); firstRun = false; } // jumpPoint: playlistelected goto playlistSelected: // ## main entry ## if (checkButton()) { // check if a playlist was selected if (actualButtonId > 0 && actualButtonId < 11) { // open the dir sprintf (actualMusicDir, "%i/", actualButtonId); actualDirPointer = SD.open(actualMusicDir); Serial.print(F("Actual Dir: ")); Serial.println(actualMusicDir); // play selected playlist until no more files to play while (true) { // jumpPoint: forward goto forward: // reset actual buttonId for detect a new buttonPress // - the selected value is not longer needed actualButtonId = 99; // get file from directory handle actualFilePointer = actualDirPointer.openNextFile(); if (!actualFilePointer) { Serial.println(F("Last File of dir reached.. Starting at the beginning of the actual dir")); actualDirPointer.rewindDirectory(); actualFilePointer = actualDirPointer.openNextFile(); } Serial.print(F("Actual File: ")); Serial.println(actualFilePointer.name()); //Ignore underscore prefix "._ " Files of OSX which are delivered by openNextFile() if (strchr(actualFilePointer.name(), '_')) { int indexOfUnderScore = strchr(actualFilePointer.name(), '_') - actualFilePointer.name(); if (indexOfUnderScore == 0) { Serial.println(F("Filename starts with underscore, ignoring it because its a OSX Filesystem file")); actualFilePointer = actualDirPointer.openNextFile(); if (!actualFilePointer) { Serial.println(F("Last File of dir reached.. Starting at the beginning of the actual dir")); actualDirPointer.rewindDirectory(); actualFilePointer = actualDirPointer.openNextFile(); } } } // jumpPoint: start with last session file lastSessionStart: // no file found if (!actualFilePointer) { Serial.println(F("No Files/Dir found goto playlistSelected")); goto playlistSelected; } // build filename to play actualMusicFileName = actualFilePointer.name(); sprintf (actualMusicFileFullPath, "%s%s", actualMusicDir, actualMusicFileName); Serial.print(F("Created full Filepath: ")); Serial.println(actualMusicFileFullPath); // store last played filename, for use as initial file after restarting the system // store before playing because SD Lib cant not read and write at one time.. saveFilenameOnSd(actualMusicFileFullPath); // store it for the rewind function sprintf (listOfLastPlayedFilesInDir[countOfEntryInLastPlayedArr], "%s", actualMusicFileName); countOfEntryInLastPlayedArr++; // play //dumpCharArray(actualMusicFileFullPath,sizeof(actualMusicFileFullPath)); if (! musicPlayer.startPlayingFile(actualMusicFileFullPath)) { Serial.println(F("Could not open file")); musicPlayer.reset(); goto forward; } else { Serial.print(F("Started playing:")); Serial.println(actualMusicFileFullPath); } // ## check button pressed button while playing ## while (musicPlayer.playingMusic) { // set volume while playing int volume = getVolumeValue(); musicPlayer.setVolume(volume, volume); // check for changed playlist, forward or stop if (checkButton()) { actualFilePointer.close(); //Serial.print(F("Actual Button ID:")); //Serial.println(actualButtonId); // backward if(actualButtonId == 12){ Serial.println(F("Backward pressed")); musicPlayer.stopPlaying(); musicPlayer.softReset(); actualDirPointer.rewindDirectory(); delay(300); goto forward; } // forward else if (actualButtonId == 11) { Serial.println(F("Forward pressed")); musicPlayer.stopPlaying(); musicPlayer.softReset(); actualFilePointer.close(); delay(300); goto forward; } // playlist else if (actualButtonId > 0 && actualButtonId < 11 ) { musicPlayer.stopPlaying(); musicPlayer.softReset(); actualFilePointer.close(); Serial.println(F("Playlist selected")); delay(300); countOfEntryInLastPlayedArr = 0; goto playlistSelected; } } //delay(300); } musicPlayer.reset(); actualFilePointer.close(); } } } } // FUNKTION get value of the volume poti int getVolumeValue() { // read PotiData int vol = analogRead(volumePotiPin) ; // 0-1023 // Serial.println("poti Value:"); // Serial.println(v); // value correction vol = vol / 15; // limit max volume 0 => max vol
if (vol < 30) {
vol = 30;
}
// Serial.println("Volume Value:");
// Serial.println(v);
return vol;
}
// FUNKTION File listing helper
void printDirectory(File dir, int numTabs) {
while (true) {
File entry = dir.openNextFile();
if (! entry) {
// no more files
//Serial.println("**nomorefiles**");
break;
}
for (uint8_t i = 0; i < numTabs; i++) { Serial.print('\t'); } Serial.print(entry.name()); if (entry.isDirectory()) { Serial.println("/"); printDirectory(entry, numTabs + 1); } else { // files have sizes, directories do not Serial.print("\t\t"); Serial.println(entry.size(), DEC); } entry.close(); } } // FUNKTION check button boolean checkButton() { int buttonTmp = readPressedButtonId(); if (buttonTmp > 0 && buttonTmp < 13) { if (actualButtonId != buttonTmp) { actualButtonId = buttonTmp; return true; } else { return false; } } } // FUNKTION read Keypad int readPressedButtonId() { long button = 0; long voltage = 501; voltage = analogRead(pinPadPin); /* String dbg = "Volt aktuell: "; dbg += voltage; Serial.println(dbg); */ if (voltage > 850) {
button = 12;
}
else if (voltage > 790) {
button = 1;
}
else if (voltage > 710) {
button = 2;
}
else if (voltage > 640) {
button = 3;
}
else if (voltage > 560) {
button = 4;
}
else if (voltage > 540) {
button = 5;
}
else if (voltage > 490) {
button = 99;
}
else if (voltage > 460) {
button = 10;
}
else if (voltage > 420) {
button = 9;
}
else if (voltage > 360) {
button = 8;
}
else if (voltage > 300) {
button = 7;
}
else if (voltage > 220) {
button = 6;
}
else if (voltage > 120) {
button = 11;
}
/*
dbg = "Knopf: ";
dbg += button;
dbg += " gedrueckt";
Serial.println(dbg);
*/
return button;
}
// FUNKTION safe given filename into a file on the sd card
void saveFilenameOnSd(char* fileNameToStore) {
File fileToWrite = SD.open(lastPlayedStorageFile, FILE_WRITE);
if (fileToWrite) {
fileToWrite.seek(0);
fileToWrite.println(fileNameToStore);
// close the file:
fileToWrite.close();
Serial.print(F("Saved given Filename to SD Card:"));
Serial.println(fileNameToStore);
} else {
// if the file didn't open, print an error:
Serial.print(F("SaveFilenameOnSd: error opening File:"));
Serial.println(lastPlayedStorageFile);
}
}
// FUNKTION read last played filename stored in file from sd card
void readLastSessionFilenameFromSd() {
File fileToRead = SD.open(lastPlayedStorageFile);
if (fileToRead) {
int i = 0;
int maxRead = sizeof(lastSessionMusicFilePath);
while (fileToRead.available() && i < maxRead - 1) {
lastSessionMusicFilePath[i] = fileToRead.read();
// change ascii 13 from fileend to string end
if (lastSessionMusicFilePath[i] == '\r') {
lastSessionMusicFilePath[i] = '\0';
break;
}
i++;
}
// close the file:
fileToRead.close();
Serial.print(F("Read saved Filename from SD Card:"));
Serial.println(lastSessionMusicFilePath);
} else {
// if the file didn't open, print an error:
Serial.print(F("getSavedFilenameFromSd: error opening File:"));
Serial.println(lastPlayedStorageFile);
}
}
void dumpCharArray(char* charArrayToDump, int lengt) {
for (int i = 0; i < lengt; i++)
{
Serial.print(F("["));
Serial.print(i);
Serial.print(F("] is {"));
Serial.print(charArrayToDump[i]);
Serial.print(F("} which has an ascii value of "));
Serial.println(charArrayToDump[i], DEC);
}
Serial.println();
}
Download the code and the library here: ABox.zip
I ordered the parts at reichelt.de, here is the partlist (without the Arduino..):
OrderNumber |
Description |
Price |
1/4W 3,3K
|
Kohleschichtwiderstand 1/4W, 5%, 3,3 K-Ohm
|
14
|
0,46 Euro |
PO6M-LIN 22K
|
Drehpoti. linear, 6mm, mono 22 k-Ohm
|
2
|
2,90 Euro
|
KAPPE 1D08
|
Kappe, rund, rot für Taster 3F…
|
3
|
0,75 Euro |
KAPPE 1D06
|
Kappe, rund, weiß für Taster 3F…
|
4
|
1,00 Euro |
KAPPE 1D04
|
Kappe, rund, gelb für Taster 3F…
|
3
|
0,75 Euro |
TASTER 3FTL6
|
Multimec Taster ohne Beleucht., Printanschluss
|
12
|
11,52 Euro |
KAPPE 1D00
|
Kappe, rund, blau für Taster 3F…
|
3
|
0,75 Euro |
H25PS160
|
Punkt-Streifenrasterplati. Hartpapier, 160x100mm
|
3
|
5,85 Euro |
VIS FR 10HM-8
|
VISATON Breitbandlautsprecher, 10cm
|
2
|
11,20 Euro |
AKKU-PACK PP1
|
Akku-Pack, NiMh, 7,2 Volt, 1,1 Ah, 6 Zellen
|
1
|
10,75 Euro |