mp3 player basteln

hallo Leute,

momentan arbeite ich an einem etwas grösserem Projekt, einem MP3 player für Kinder.
Als hardware habe ich eine Mega mit einem musicmaker shild von Adafruit. Die mp3’s sind auf eine SD-Karte gespeicher, je Hörspiel oder Album ein eigener Ordner Mit den Namen “00” bis max. “99”. In den Ordnern ist jeweils eine CSV-Datei namens “mp3.txt”, welche den Namen und den Titel des Albums beinhaltet (z.B.“TKKG;Folge 5”). Einen Drehenkoder will ich zur Navigation durch die Verzeichnise aus der SD-Karte nutzen. Der Encoder liefert eine Zahl die dem Verzeichnis entsprich in dem die mp3-Dateien und die mp3.txt liegen. Beim betätigen des Encodeers soll auf dem Display (2x16) der inhalt der mp3.txt angezeigt werden.

Diese beiden Projektteile habe ich bereits programmiert und beide für sich funktionieren ganz gut. Wenn ich beide aber zusammen in ein Skatch vereine funktioniert es nicht wie gewünscht. Es wird zwar kompiliert aber die Funktion ist nich mehr da.

Kann sich meine beiden sketches mal jemand anschauen und mir einen Tip gebenwie das funktionieren könnte?

Gruß Michael

//encoder_mit_dosplay.ino

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <Rotary.h>

LiquidCrystal_I2C lcd(0x3f,16,2);     //LCD Display mit 16 Zeichen und 2 Zeilen

Rotary r = Rotary(52, 53);            //Encoder auf digitalen Eingängen


int counter=0;
const int cMax=5;                     //Anzahl der Ordner
const int cMin=0;
char fName[]="/mp3.txt";              //Dateiname mit CSV-Datei
char vNum[16];
char* path;                           //Dateipfad für mp3.txt


void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
}

void displayChange(int num){
  lcd.clear();
  lcd.setCursor(0,0);
  sprintf(vNum,"%02i",num);         //Zeichenkette wird formatiert
  path = strcat(vNum,fName);        //Pfad mit Dateiname zusammenfügen z.B. "04/mp3.txt"
  lcd.print(path);                  //Anzeige im Display
  path = "";  
 
}


void loop() {
  unsigned char result = r.process();   //Encoderwert bestimmen
  if (result) {
    if (result==16){
      if (counter > cMin) counter--;
      else counter = cMax;              //Begrenzung des Wertebereichs
    }
    if (result==32){
      if (counter < cMax) counter++;
      else counter = cMin;              //Begrenzung des Wertebereichs
    }
    displayChange(counter);             //mitzählen
    Serial.println(counter+1);
  }
}
//csv-datei_lesen_von_sd.ino

#include <SdFat.h>

const uint8_t chipSelect = 4;

SdFat sd;

#define N 2                   //2 Elemente werden gelsen
char text[N][17];             // mit je 16 Zeichen

void readFileToVars(char* fn){
  byte i,feld;
  char c; 
  ifstream file(fn);
  if (!file.is_open()) sd.errorHalt("open failed"); 
  i=0;feld=0;
  while ((c = file.get()) >= 0)
  {
    switch(c){              //zeichen werden zusammengesetzt
      case '\r': break;
      case ';' : {          //Trennzeichen
        feld++;
        i=0;
        break;
      }   
    default:
      if (c>=32){
        text[feld][i]=c;
        i++;
      } 
    }
  }    
the_end: 
  file.close();
}


void setup() {
  Serial.begin(9600);
  while (!Serial) {}  
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();
  readFileToVars("02/mp3.txt");     //hier wird der Dateiname aufgerzufen
  for (int i=0;i<N;i++)             //Text wird in N Zeilen geschrieben
  {
    Serial.println(text[i]);
  }


}

void loop() {

}

Kannst Du uns "Die Funktion ist nicht mehr da" mal inhaltlich beschreiben?

Da nur ein Sketch ein gefülltes Loop hat, brauchst Du doch eigentlich nur alle globalen Festlegungen zu vereinen und die Setup-Teile. Dabei Doppelungen der Bezeichner ausbügeln. Die Funktionen kannst Du so übernehmen.

Gruß Tommy

das Problem liegt in der Funktion readFileToVars(char* fn). Vermutlich kann der Dateiname nicht gefunden werden, obwohl die generierte Zeichenkette für mich so aussieht wie die, die ich manuel eingebe.
Hier der verknüfte code:

//encoder_mit _display_mit_sd.ino

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <Rotary.h>
#include <SdFat.h>

const uint8_t chipSelect = 4;

SdFat sd;

LiquidCrystal_I2C lcd(0x3f,16,2);     //LCD Display mit 16 Zeichen und 2 Zeilen

Rotary r = Rotary(52, 53);            //Encoder auf digitalen Eingängen

int counter=0;
const int cMax=5;                     //Anzahl der Ordner
const int cMin=0;
char fName[]="/mp3.txt";              //Dateiname mit CSV-Datei
char vNum[16];
char* path;                           //Dateipfad für mp3.txt

#define N 2                   //2 Elemente werden gelsen
char text[N][17];             // mit je 16 Zeichen

void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
}

void readFileToVars(char* fn){
  byte i,feld;
  char c; 
  ifstream file(fn);
  if (!file.is_open()) sd.errorHalt("open failed"); 
  i=0;feld=0;
  while ((c = file.get()) >= 0)
  {
    switch(c){              //zeichen werden zusammengesetzt
      case '\r': break;
      case ';' : {          //Trennzeichen
        feld++;
        i=0;
        break;
      }   
    default:
      if (c>=32){
        text[feld][i]=c;
        i++;
      } 
    }
  }    
the_end: 
  file.close();
}

void displayChange(int num){
  lcd.clear();
  sprintf(vNum,"%02i",num);         //Zeichenkette wird formatiert
  path = strcat(vNum,fName);        //Pfad mit Dateiname zusammenfügen z.B. "04/mp3.txt"

//  readFileToVars("02/mp3.txt");     //hier wird der Dateiname aufgerzufen

  readFileToVars(path);   // DIES FUNKTIONIERT NICHT !!!!!
  
  for (int i=0;i<N;i++)             //Text wird in N Zeilen geschrieben
  {
    lcd.setCursor(0,i);             //Zeile wählen
    lcd.print(text[i]);            //Anzeige im Display
    Serial.println(text[i]);
  } 
}


void loop() {
  unsigned char result = r.process();   //Encoderwert bestimmen
  if (result) {
    if (result==16){
      if (counter > cMin) counter--;
      else counter = cMax;              //Begrenzung des Wertebereichs
    }
    if (result==32){
      if (counter < cMax) counter++;
      else counter = cMin;              //Begrenzung des Wertebereichs
    }
    displayChange(counter);             //mitzählen
    Serial.println(counter+1);
  }
}

Ich sehe const uint8_t chipSelect = 4;, aber wo wird es verwendet?

Mit der richtigen Initialisierung funktioniert es dann auch:

TKKG.png

Den MP3-Player habe ich auch schon mal verwendet.

wie sollte denn die richtige initialisierung aussehen?

Das Display hat das auch schon angezeigt bevor ich die beiden Teile zusammengebaut habe.

im serial monitor bekomm ich die Meldung daß das öffnen der SD karte fehlgeschlagen ist

Diese Zeile hast Du vergessen zu übernehmen:

if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();

ok, das hatte ich vergessen. Aber es zeigt nur 1 Ordner an. Wenn ich nach dem Einschalten nach rechts dreh dann bekomm ich die mp3.txt vom Ordner 01 und wenn ich nach links dreh dei vom Ordner 05 und dann ändert sich auch nichtsmehr beim weiter drehen.

sieht so aus als würde er bei der Funktion readFileToVars(char* fn) hängen bleiben.

Ich habe keinen Drehencoder, habe es mit einem Schalter simuliert, funktioniert wie gewünscht (Anzeige komprimiert):

TKKG    Folge 1 1
TKKG    Folge 2 2
TKKG    Folge 3 3
TKKG    Folge 4 4
TKKG    Folge 5 5
TKKG    Folge 0 0
TKKG    Folge 1 1
TKKG    Folge 2 2
TKKG    Folge 3 3
TKKG    Folge 4 4
TKKG    Folge 5 5
TKKG    Folge 0 0
TKKG    Folge 5 5
TKKG    Folge 4 4

wie hast du das gemacht?

Mein Test-Simalations-Programm, das Du nicht 1:1 übernehmen kannst:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SdFat.h>

const uint8_t chipSelect = 10;

SdFat sd;

LiquidCrystal_I2C lcd(0x27, 16, 2);   //LCD Display mit 16 Zeichen und 2 Zeilen

int counter = 0;
const int cMax = 5;                   //Anzahl der Ordner
const int cMin = 0;
char fName[] = "/mp3.txt";            //Dateiname mit CSV-Datei
char vNum[16];
char* path;                           //Dateipfad für mp3.txt

#define N 2                   //2 Elemente werden gelsen
char text[N][17];             // mit je 16 Zeichen

void setup() {
  Serial.begin(9600);
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();
  lcd.begin();
  lcd.backlight();
  pinMode(2, INPUT_PULLUP);
}

void readFileToVars(char* fn) {
  byte i, feld;
  char c;
  ifstream file(fn);
  if (!file.is_open()) sd.errorHalt("open failed");
  i = 0; feld = 0;
  while ((c = file.get()) >= 0)
  {
    switch (c) {            //zeichen werden zusammengesetzt
      case '\r': break;
      case ';' : {          //Trennzeichen
          feld++;
          i = 0;
          break;
        }
      default:
        if (c >= 32) {
          text[feld][i] = c;
          i++;
        }
    }
  }
  file.close();
}

void displayChange(int num) {
  lcd.clear();
  sprintf(vNum, "%02i", num);       //Zeichenkette wird formatiert
  path = strcat(vNum, fName);       //Pfad mit Dateiname zusammenfügen z.B. "04/mp3.txt"

  //  readFileToVars("02/mp3.txt");     //hier wird der Dateiname aufgerzufen

  readFileToVars(path);   // DIES FUNKTIONIERT NICHT !!!!!

  for (int i = 0; i < N; i++)       //Text wird in N Zeilen geschrieben
  {
    lcd.setCursor(0, i);            //Zeile wählen
    lcd.print(text[i]);            //Anzeige im Display
    Serial.println(text[i]);
  }
}


void loop() {
  unsigned char result;
  if (digitalRead(2)) {
    result = 16;
  } else {
    result = 32;
  }
  if (result) {
    if (result == 16) {
      if (counter > cMin) counter--;
      else counter = cMax;              //Begrenzung des Wertebereichs
    }
    if (result == 32) {
      if (counter < cMax) counter++;
      else counter = cMin;              //Begrenzung des Wertebereichs
    }
    displayChange(counter);             //mitzählen
    Serial.println(counter);
  }
  delay(2000);
}

wenn ich ein delay ans Ende setze, dann funktioniert garnichts mehr. Und wenn ichs weg lass dann zeigt es immer nur eine mp3.txt an. Die loop funktion wird unterbrochen. aber warum?

Gibt es bei Dir auch die Verzeichnisse "00" bis "05"? Wenn ich "00" lösche, bekomme ich diese (komprimierte) Anzeige:

TKKG    Folge 5 5
TKKG    Folge 4 4
TKKG    Folge 3 3
TKKG    Folge 2 2
TKKG    Folge 1 1
error: open failed

ja die gibt es alle. Wenn ich die manuell eingebe funktionierts auch.

ich glaub ich konnte den Fehler lokalisieren. In der loop Funktion der Zähler mit dem Encoder funktioniert nicht sobald noch irgendetwas dazu kommt, z.B. die displayChange(int num) Funktion oder auch nur ein delay. Werde weiter testen...

Wo du es jetzt schreibst: Der Drehencoder ist zeitkritisch. Das könnte auch beim Abspielen von Musik zum Problem werden. Bei mir funktionierte die IR-Fernbedienung nicht zusammen mit dem Abspielen, weshalb ich einen ATtiny85 für die IR-Fernbedienung genutzt habe, verbunden mit den UNO mittels I2C-Bus.

Die Bibliothek bietet aber auch eine Variante mit Interrupt.

Danke für den Hinweis mit dem Interrupt.

//encoder_mit _display_mit_sd.ino

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Rotary.h>
#include <SdFat.h>

const uint8_t chipSelect = 4;

SdFat sd;

LiquidCrystal_I2C lcd(0x3f,16,2);     //LCD Display mit 16 Zeichen und 2 Zeilen

Rotary r = Rotary(18, 19);            //Encoder auf digitalen Eingängen

int counter=0;
int lastCounter=1;
const int cMax=5;                     //Anzahl der Ordner
const int cMin=0;
char fName[]="/mp3.txt";              //Dateiname mit CSV-Datei
char vNum[16];
char* path;                           //Dateipfad für mp3.txt

#define N 2                   //2 Elemente werden gelsen
char text[N][17];             // mit je 16 Zeichen

void setup() {
  Serial.begin(9600);
  attachInterrupt(4, encoder, CHANGE);
  attachInterrupt(5, encoder, CHANGE);
  lcd.init();
  lcd.backlight();
}

void readFileToVars(char* fn){
  byte i,feld;
  char c;
  ifstream file(fn);
  if (!file.is_open()) sd.errorHalt("open failed");
  i=0;feld=0;
  while ((c = file.get()) >= 0)
  {
    switch(c){              //zeichen werden zusammengesetzt
      case '\r': break;
      case ';' : {          //Trennzeichen
        feld++;
        i=0;
        break;
      }   
    default:
      if (c>=32){
        text[feld][i]=c;
        i++;
      }
    }
  }   
  file.close();
}

void displayChange(int num){
  lcd.clear();
  sprintf(vNum,"%02i",num);         //Zeichenkette wird formatiert
  path = strcat(vNum,fName);        //Pfad mit Dateiname zusammenfügen z.B. "04/mp3.txt"

  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();
  readFileToVars(path);   // DIES FUNKTIONIERT NICHT !!!!!
 
  for (int i=0;i<N;i++)             //Text wird in N Zeilen geschrieben
  {
    lcd.setCursor(0,i);             //Zeile wählen
    lcd.print(text[i]);            //Anzeige im Display
    Serial.println(text[i]);
    for (int j=0; j<17; ++j) {text[i][j] = 0; }   //lösche alten Inhalt
  }
  
}


void encoder() {                        //Interrupt Funktion für Encoder
  unsigned char result = r.process();   //Encoderwert bestimmen
  if (result) {
    if (result==16){
      if (counter > cMin) counter--;
      else counter = cMax;              //Begrenzung des Wertebereichs
    }
    if (result==32){
      if (counter < cMax) counter++;
      else counter = cMin;              //Begrenzung des Wertebereichs
    }
    Serial.println(counter);
  }
}
void loop(){
  if (counter != lastCounter){        //Aktualisiere Anzeige wenn sich Zähler ändert
    displayChange(counter);  
    lastCounter=counter;
  }

}

so funktioniert der erste große Teil meines Progemmes.
Jetzt gehts weiter mit dem musikmaker shield…

mire: so funktioniert der erste große Teil meines Progemmes.

Gratulation :)

In der Referenz zu attachInterrupt() lese ich: "Typically global variables are used to pass data between an ISR and the main program. To make sure variables shared between an ISR and the main program are updated correctly, declare them as volatile." Das solltest Du noch berücksichtigen.