Arduino Musicplayer for my daughter

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


//## 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 =

void setup() {

  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("/"), 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) {

      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 =;
          // set dirpointer
          actualDirPointer =;

          // forward dir pointer to lastSessionMusicFilePath
          int breaker = 0;
          File toCompare;
          while (breaker < maxFilesInDir) {
            toCompare = actualDirPointer.openNextFile();
            if (strcmp(, lastSessionMusicFileName)  == 0 ) {
          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 =; 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(; //Ignore underscore prefix "._ " Files of OSX which are delivered by openNextFile() if (strchr(, '_')) { int indexOfUnderScore = strchr(, '_') -; 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 =; 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
    for (uint8_t i = 0; i < numTabs; i++) { Serial.print('\t'); } Serial.print(; 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";

  return button;

// FUNKTION safe given filename into a file  on the sd card
void saveFilenameOnSd(char* fileNameToStore) {

  File fileToWrite =, FILE_WRITE);

  if (fileToWrite) {;

    // close the file:
    Serial.print(F("Saved given Filename to SD Card:"));
  } else {
    // if the file didn't open, print an error:
    Serial.print(F("SaveFilenameOnSd: error opening File:"));

// FUNKTION read last played filename stored in file from sd card
void readLastSessionFilenameFromSd() {

  File fileToRead =;

  if (fileToRead) {

    int i = 0;
    int maxRead = sizeof(lastSessionMusicFilePath);
    while (fileToRead.available() && i < maxRead - 1) {
      lastSessionMusicFilePath[i] =;

      // change ascii 13 from fileend to string end
      if (lastSessionMusicFilePath[i] == '\r') {
        lastSessionMusicFilePath[i] = '\0';

    // close the file:
    Serial.print(F("Read saved Filename from SD Card:"));

  } else {
    // if the file didn't open, print an error:
    Serial.print(F("getSavedFilenameFromSd: error opening File:"));

void dumpCharArray(char* charArrayToDump, int lengt) {

  for (int i = 0; i < lengt; i++)
    Serial.print(F("] is {"));
    Serial.print(F("} which has an ascii value of "));
    Serial.println(charArrayToDump[i], DEC);


Download the code and the library here:

I ordered the parts at, here is the partlist (without the Arduino..):

OrderNumber Description Price
1/4W 3,3K
Kohleschichtwiderstand 1/4W, 5%, 3,3 K-Ohm
0,46 Euro
Drehpoti. linear, 6mm, mono 22 k-Ohm
2,90 Euro
Kappe, rund, rot für Taster 3F…
0,75 Euro
Kappe, rund, weiß für Taster 3F…
1,00 Euro
Kappe, rund, gelb für Taster 3F…
0,75 Euro
Multimec Taster ohne Beleucht., Printanschluss
11,52 Euro
Kappe, rund, blau für Taster 3F…
0,75 Euro
Punkt-Streifenrasterplati. Hartpapier, 160x100mm
5,85 Euro
VISATON Breitbandlautsprecher, 10cm
11,20 Euro
Akku-Pack, NiMh, 7,2 Volt, 1,1 Ah, 6 Zellen
10,75 Euro

Demoka M203 Arduino Kaffeemühlen Timer die Zweite

Nachdem ich die Mühle nach ihrem Umbau ein paar Tage im Einsatz hatte, bin ich zu dem Entschluss gekommen, dass der Timer eine Feineinstellung ohne Rechner benötigt. Zudem brauche ich die verschiedenen Betriebsmodi (doppelte Füllung, Abbruch des Mahlvorganges) nicht, die über verschiedene Knopfdrücke ausgelöst wurden. Sie sind dem WAF-Faktor nicht unbedingt zuträglich und irgendwie finde ich die Funktionen überflüssig. Falls ich sie wieder brauchen sollte, kann ich sie ja über den USP-Port schnell
wieder einfügen.
Für die Feineinstellung habe ich in der Bastelkiste noch ein 5K Poti aus Metall gefunden, das ich oberhalb des An/Aus Schalters anbringen möchte. Dazu muss natürlich erstmal die Mühle geöffnet werden, um das Poti zu montieren und die Kabel an den Arduino zu löten:
DSC01233 DSC01235

Was hier noch etwas wild aussieht, bekommt mit ein paar Kabelbindern und etwas Schrumpfschlauch schon gleich ein professionelleres Aussehen:DSC01242

Den Schaltplan aus dem letzten Post habe ich etwas angepasst und neu gezeichnet. Zudem habe ich noch den Pull-Down Widerstand am Taster eingezeichnet, den ich beim letzten Schaltplan vergessen hatte einzuzeichnen.

Den Code habe ich angepasst und vor allen Dingen die Button API rausgenommen, da sie doch einen gewissen Overhead hat und nicht wirklich gebraucht wird:

const int grinderPin = 4;
// debug with arduino led
// const int grinderPin = 13;
const int buttonPin = A1;
const int potiPin = A2;

int buttonState = LOW;
int isGrinding = LOW;

unsigned long stopTime = 0;

// Grind Time in Millis
int grindTimeShort= 6000;
int grindTimeLong= 13000;

void grindOnce(int timeOffset) {
Serial.println("grindOnce() called with grindingTime:");
Serial.println(grindTimeShort + timeOffset);
stopTime = millis() + grindTimeShort + timeOffset;
isGrinding = HIGH;
digitalWrite(grinderPin, HIGH);

// read PotiData
int l;
int getPotiValue() {
int v = analogRead(potiPin) ; // 0-1023

// double the value to archive 2000ms difference in grinding time
Serial.println("poti Value:");
return v*2;

void setup()

// init grinderPin
pinMode(grinderPin, OUTPUT);
digitalWrite(grinderPin, LOW);

void loop()
// keep watching the grinderStart button:
int buttonReading = digitalRead(buttonPin);

// only grind if the new button state is HIGH
if (buttonReading == HIGH && isGrinding == LOW) {
Serial.println("Start grinding");

// stop the grinder if nessesary
if(millis() >= stopTime && isGrinding == HIGH){
Serial.println("Stop grinding");
isGrinding = LOW;
digitalWrite(grinderPin, LOW);

Noch ein Knopf aus Messing gedreht (gibt es natürlich auch zu kaufen..), auf das Poti montiert und fertig..




Demoka Kaffeemühle M203 Lärmschutz und Arduino Timer Umbau

Seit einiger Zeit wohnt bei mir eine Siebträgerkaffeemaschine, zu der auch eine Demoka M203 Mühle gehört.
Die Mühle mahlt sehr gut, hat aber ein paar Nachteile. Unter anderem ist sie recht laut, es landet beim Mahlen immer etwas Kaffee neben dem Siebträger und sie hat keinen Timer. In einem ersten Schritt habe ich mich deshalb um eine Dämmung und das Problem des danebenfallenden Kaffees gekümmert. Zum Dämmen habe ich die Maschine aufgeschraubt und die Maschine innen mit 8mm starken Moosgummi gedämmt:
Das Moosgummi habe ich einfach zurechtgeschnitten und in die Gehäusebleche eingelegt. Beim hinteren Blech muss man darauf achten, das man unten in der Mitte ein Stück ausschneidet, damit sich das Lüfterrad des Motors noch frei bewegen kann. Die Lärmentwicklung hat sich dadurch auf ein sehr erträgliches Mass reduziert, vor allem ist der unangenehme “Kreischton” weg.

Das Danebenfallen des Kaffees habe ich durch Kürzen des Einschaltknopfes und Entfernen der Siebträgerauflage behoben. Der Siebträger lässt durch das Blech nicht genau genug unter dem Kaffeeauslass positionieren, so dass der Kaffee gerade bei halbgefülltem Siebträger immer danebenfiel. Im Gegensatz zu den Lösungen mit Auffangbehältern wird das Problem behoben und der optische Eindruck der Mühle bleibt erhalten.
Das Blech habe ich einfach gelöst, indem ich die beiden Nieten mit einem 3,5mm Bohrer aufgebohrt habe. Die Löcher habe ich dann wieder mit zwei 4mm Blindnieten geschlossen. Der Knopf des Schalters steht etwas zu weit vor, was zum einen etwas “wackelig” aussieht und zum anderen ebenfalls verhindert, das man den Siebträger mittig unter den Kaffeeauslass halten kann. Den Knopf habe ich einfach abgezogen und den Teil, der in den Schalter gesteckt wird, einfach mit einem scharfen Messer um ca. 2mm gekürzt. Beim Wiederaufstecken habe ich den Schalterstift mit dem Messer festgehalten, damit der Schalter blockiert ist und ich den Knopf richtig festdrücken kann










Mit diesen einfachen Massnahmen kann man den Siebträger so halten, dass so gut wie nichts mehr daneben fällt. Das macht die Demoka meines Erachtens zu einer sehr brauchbaren, preiswerte Mühle. Leider muss man jedoch die Kaffeemenge immer noch per Augenmass bestimmen, da sie ja keinen Timer hat. Also musste ich auch hier etwas tun.. Ausserdem ist eine rein analoge Mühle ja nichts für einen Informatiker. In meinem Bastelfundus befand sich noch ein kleiner Arduino nano (ca. 10€ bei ebah) der für solche einfachen Timeraufgaben fast schon etwas zu mächtig ist, aber dafür natürlich alle Möglichkeiten offen lässt. Und er war halt  gerade in der Bastelkiste 😉 Zunächst mal ist in der Mühle wenig  Platz zum Unterbringen der Bauteile, wie man auf dem nachfolgenden Bild vlt. erkennen kann:


Ich wollte gerne alles in der Mühle haben, um den optischen Gesamteindruck zu erhalten und nicht irgendetwas Unnötiges in der Küche rumstehen zu haben. Als Netzteil habe ich deshalb ein altes elektronisches Ladegerät (z.b.: Handy, Kamera etc.) benutzt. Diese sind meist klein, kosten nichts und passen gut zum Arduino. Zudem habe ich noch einen Netzschalter in das Gehäuse eingebaut und USB Anschluss nach aussen geführt. um den Arduino programmieren zu können. Auf einen Poti zum Einstellen der Laufzeit und eine Anzeige habe ich verzichtet. Der Arduino ist per Notebook schnell programmiert und ich wollte den Aufwand gering halten. (Inzwischen geändert, siehe hier) Den Netzschalter habe ich hinten rechts in das Gehäuse eingesetzt, die USB Dose (Weibchen) ebenfalls. Als USB Dose habe ich einfach eine alte USB Verlängerung benutzt, die ich mit 2 Komponentenkleber in das Gehäuseblech eingeklebt habe:

Wie man auf dem Bild erkennen kann, schalte ich den Motor mit einem mechanischen Relais. Ein elektronisches geht auch und braucht keine Zusatzschaltung, um vom Arduino angesteuert zu werden, ist aber viel teurer und war leider in meiner Bastelkiste nicht vorhanden.. Ich habe also ein Omron G2R-2 mit 5A Schaltstrom verwendet, das ich mit einem Transistor 2N2222 und einer Diode 1N4007 als Prellschutz für den Arduino ansteuere.  Hier der Schaltplan:

Das Ganze habe ich auf einem Steckbrett vertestet und dann “inline” zusammengelötet.
DSC01171 DSC01187
und mit reichlich Schrumpfschlauch möglichst platzsparend zusammengepackt:


Die ganze Elektrik habe ich mit Kabelbindern zusammen an der Innenseite der Platte befestigt, die auch den Schalter trägt (im Bild ist es noch an der Platte).

Anschliessend die Gehäuseteile wieder angebaut und den Arduino programmiert:

Hier das Programm für den Arduino:

#include OneButton.h

OneButton button(A1, true);
const int grinderPin = 4;
unsigned long stopTime = 0;

// Grind Time in Millis
int grindTimeShort= 7000;
int grindTimeLong= 13000;

void oneClick() {
stopTime = millis() + grindTimeShort;
digitalWrite(grinderPin, HIGH);

void doubleClick() {
stopTime = millis() + grindTimeLong;
digitalWrite(grinderPin, HIGH);

void buttonPress() {
digitalWrite(grinderPin, LOW);

void setup()
// init grinderPin
pinMode(grinderPin, OUTPUT);
digitalWrite(grinderPin, LOW);

// link the doubleclick function to be called on a doubleclick event.

void loop()
// keep watching the grinderStart button:

// stop the grinder if nessesary
if(millis() >= stopTime){
digitalWrite(grinderPin, LOW);

Ich habe der Einfachheit halber die Onebutton Bibliothek von Matthias Hertel verwendet. Wird der Knopf einmal gedrückt, wird der Kaffee für einen 1fach Siebträger, bei einem doppelten Drücken für einen 2fach Siebträger gemahlen. Dadurch können unterschiedliche Zeiten und Kaffeemengen eingestellt werden. Längeres Drücken beendet den Mahlvorgang vorzeitig. Ich brauche nun mit dem Siebträger den Knopf nur kurz anzutippen und die Mühle mahlt 7 Gramm, während ich das herabfallende Mehl durch Bewegen optimal im Siebträger verteilen kann.
Das Ganze funktioniert bisher einwandfrei, jedoch habe ich den Doppelklick und den Buttonhold auskommentiert. Eventuell rüste ich noch einen Poti nach, bisher musste ich die Mahldauer jedoch noch nicht so oft umstellen, als dass mich das Anschliessen des Notebooks gestört hätte. Alles in allem ein sehr lohnenswerter Umbau, der die Mühle sehr flexibel und millisekundengenau macht. Ausserdem finde ich den USB Anschluss an der Mühle cool 🙂

Update: Inzwischen habe das ganze nochmal geändert, hier gehts weiter.