Searching an SD Card and returning a pointer to the filename

All, using a musicmaker shield and can tie i/o to trigger playing specific files since I know the name of the file and can pass the required parameter (const char *trackname). However, I wanted to be able to create a more dynamic environment. First attempt, I modified the standard examples in the SD library to count the number of song files on the SD card and then used a method that took the index number to return a pointer to that specific filename. As suggested in everything I could read, I used the File.name() to return a char * to the filename and had a custom search method return this. I received a generic warning once only, the first time compiled:

warning: taking address of packed member of 'directoryEntry' may result in an unaligned pointer value

However, all searches stated to ignore and none could suggest a solution. Inside the search method I printed out the value of the return object to the screen. Since I named all my files, track00.mp3, searching for a song with index of 4 resulted in the method selecting track004.mp3, and printing that value out in the method. However, in code that called it, the returned object was garbled. Thus trying to use it to play a song could not work.

I could copy all my code but it boils down to the same thing. If I try to play a file and know the exact name, I can enter it directly and I get music. If I try to dynamically search the card contents and return a pointer to filename at a given location (giving me the ability to "play previous track" or "play the 5th song"), I get garbage back for the pointer.

Any help? Here is a copy of the method to return the song name. As stated, when the method is called the internal print to OLED screen returns "TRACK00.MP3". But in the code that calls it, performing a similar print like oled.println(getSong(SD.open("/), 2) or oled.println(String(getSong(SD.open("/), 2)) returns with weird characters and cannot be used as the argument to play the song.

dir is passed as SD.open("/")
index is the track number

char * getSong(File dir, int index){
  File entry;
  char *song;
  int j = 0;
  //int songTitleLength=0;
  oled.clearDisplay();
  oled.setCursor(0,20);
  oled.println("Selected Track");
  oled.display();
  dir.rewindDirectory();
  
  while(true){
   entry = dir.openNextFile();
   if (entry.isDirectory()){ //Skip directories, song will be at root directory only
     entry.close();
     entry = dir.openNextFile();
   }
   entry.close();
   j++;
     
   if (j == index){
      break;  //exit, pointer is now at track at requested location
   }
  }
  song = entry.name();
  oled.setCursor(0,30);
  oled.println(song);    //prints out correctly on oled display
  oled.display(); 
  return song;
}

Hard to say when you only gave a snippet of code. Appears you did not save the file name when you had an active pointer to the name, but saved a pointer value that was transient and when you wanted to use it was pointing to garbage.
Solution might be to save the actual file name in an array while you have a pointer to it.

Paul,

Wrote a second method that saved all filenames and/or pointers to filenames in an array to be used later. I moved the writing of the values both before the entry.close() call as well as after the entry.close() was issued as shown above. regardless, accessing the data stored after was garbage unless it was an integer like the file size. The name, or pointer to a char array that held the name would print normal inside the method, but when it was passed on to other parts of code, returned "garbage". I will post full code but it has all sorts of alternate methods and such tried as I cannot get past the core point that I cannot search an SD card and store off any information that would let me open a file later by referring to a variable containing the filename or pointer to the filename, rather than a specific filename. If someone could try that, perhaps they would find an elegant solution or what I am doing wrong would pop out. Here is my latest full code that sticks when trying to open the file to play. As a new user, can't upload it as a file. sorry.

#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// These are the pins used for the music maker shield
#define MP3_RESET  -1        // VS1053 reset pin (unused!)
//#define SD_CS       4        // SD Card select pin ;  Use the one on the grand central
#define MP3_CS      7        // VS1053 chip select pin (output)
#define DREQ        3        // VS1053 Data request, ideally an Interrupt pin, common between breakout and shield
#define MP3_DCS     6        // VS1053 Data/command select pin (output)

//The MusicMaker Shield uses dedicated pins for SPI (SCK, MISO, MOSI)
//For a mega/grandcentral m4, please jumper or solder the pads to insure the common SPI 6pin connector is used
//#define CLK 13       // SPI Clock, shared with SD card
//#define MISO 12      // Input data, from VS1053/SD card
//#define MOSI 11      // Output data, to VS1053/SD card
// Connect CLK, MISO and MOSI to hardware SPI pins or you will not get any sound output

//This is the definition needed for the OLED display using i2C protocol
#define SCREEN_WIDTH  128   // OLED display width,  in pixels
#define SCREEN_HEIGHT 64    // OLED display height, in pixels
#define OLED_ADDRESS  0x3C  //OLED i2C address 
#define OLED_RESET     -1   // Reset pin # (or -1 if sharing Arduino reset pin)
// On an arduino UNO:       A4(SDA), A5(SCL)  is defined by the Wire Library

// Button press Arrays
int buttonPushCounter[7] = {0,0,0,0,0,0,0};   // counter for the number of button presses
int buttonState[7] = {0,0,0,0,0,0,0};         // current state of the button
int lastButtonState[7] = {0,0,0,0,0,0,0};     // previous state of the button
//Individual Button Press
int buttonPressCounter = 0;
int buttonCurrentState = 0;
int buttonLastState = 0;

//track & SD management variables
File root;
int numSongs = 0;
int currentSong = 0;
bool waitingForInput = false;
const char * SDSongTitle[99];  // will create an array of const char * elements for each song that hopefully won't get corrupted.  Limiting to 100 songs
unsigned int songSize[99];


//Allows compatibility with MO boards for serial output
#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
  // Required for Serial on Zero based boards
  #define Serial SERIAL_PORT_USBVIRTUAL
#endif

//Create new objects of each item attached to Arduino
Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_CS, MP3_DCS, DREQ, SDCARD_SS_PIN);
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//This is run once only and then program goes to the loop() method
void setup() {
  Serial.begin(9600);
  beginScreen();
  beginMusicMaker();
  processSDCardAlt(root);
}

//Main Code loop that is run continuously
void loop() {  
  checkAllButtons(currentSong);  //check to see what button attached to GPIO 1 thru 7 has been pressed and take action thorugh called processButton method
  updateScreen(); //Update the display
  delay(100);
}

void updateScreen(){
  if (!musicPlayer.playingMusic){// if no more music playing, display screen to let user know
    oled.clearDisplay();
    oled.setCursor(0,20);
    oled.print("MP3 MUSIC MAKER");
    oled.setCursor(0,30);
    oled.print("WAITING FOR INPUT");
    oled.setCursor(0,40);
    oled.print("Songs Available: ");
    oled.print(numSongs);
    oled.display();
  }else{ //Display Number of Songs on SD Card and current track playing
    oled.clearDisplay();
    oled.setCursor(0,20);
    oled.print("MP3 MUSIC MAKER");
    oled.setCursor(0,30);
    oled.print("Playing Track: ");
    oled.print(currentSong);
    oled.setCursor(0,40);
    oled.print("Songs Available: ");
    oled.print(numSongs);
    oled.display();
  }
}

void beginScreen(){

   if (!oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) { //initialize the screen
    Serial.println(F("SSD1306 allocation failed"));
    while (true); // Don't proceed, loop forever
   }
   Serial.println("OLED Screen Found");
   // Show initial display buffer contents on the screen --
   // the library initializes this with an Adafruit splash screen.
   oled.display();
   delay(2000); // Pause for 2 seconds
   oled.clearDisplay();  // Clear the buffer
   oled.setTextSize(1);          // text size
   oled.setTextColor(WHITE);     // text color
   oled.setCursor(0, 0);        // position to display
   oled.println("OLED SCREEN ACTIVE!"); // text to display
   oled.display();               // show on OLED
}

void beginMusicMaker(){

  if (!musicPlayer.begin()) { // initialise the music player
     Serial.println(F("Couldn't find VS1053, Check wiring"));
     while (true); // Don't proceed, loop forever
  }
  Serial.println("MP3 Shield Found");
  oled.setCursor(0, 20);        // position to display
  oled.println("MUSICMAKER ATTACHED!"); // text to display
  oled.display();               // show on OLED
 
  
  if (!SD.begin(SDCARD_SS_PIN)) {
    Serial.println(F("SD Card failed, or not present"));
    for(;;); // Don't proceed, loop forever
  }
  Serial.println("Micro SD Card Found");
  oled.setCursor(0, 30);        // position to display
  oled.println("SD CARD ATTACHED!"); // text to display
  oled.display(); 
  //set the global variable for the root directory now that the serial card has begun
  root = SD.open("/"); 

  // If DREQ is on an interrupt pin (on uno, #2 or #3) we can do background audio playing
  if (! musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT)){
    Serial.println(F("DREQ pin is not an interrupt pin"));
  }
  Serial.println("DREQ Found. Interrupts enabled");
  oled.setCursor(0,40);
  oled.println("INTERRUPTS ENABLED");
  oled.display();

  
  //set the currentSong index to the first song & set WaitingForInput to TRUE
  currentSong = 1;
  waitingForInput = true;

  // Set volume for left, right channels. lower numbers == louder volume!
  musicPlayer.setVolume(1,1);
  
  //set all 7 GPIO pins to inputs to be used as button inputs
  for (uint8_t i=1; i<8; i++) { 
    musicPlayer.GPIO_pinMode(i, INPUT);
  }
  
  //delay for 1 seconds before getting sd card contents
  delay(1000);
}

void checkAllButtons(int index){
  
  //MusicMakeShield has 7 GPIO pins, check status of all to see if pressed
  for (uint8_t i=1; i<8; i++){
   // read the pushbutton input pin:
   buttonState[i-1] = musicPlayer.GPIO_digitalRead(i); //read in the state of GPIO pins and shift to accomodate array starting with element 0
   // compare the buttonState to its previous state
    if (buttonState[i-1] != lastButtonState[i-1]) {
     // if the state has changed, increment the counter
     if (buttonState[i-1] == HIGH) {
       oled.clearDisplay();
       oled.setCursor(0, 30);
       oled.print("Button :");
       oled.print(i);
       oled.print(" pressed ");
       oled.display();
       // if the current state is HIGH then the button went from off to on and lets take action
       processButton(i, index);
       //update the overall counter for appropriate button
       buttonPushCounter[i-1] ++;
       Serial.println("button :");
       Serial.print(i);
       Serial.print("  number of times pressed ");
       Serial.print(buttonPushCounter[i-1]);
      } else {
       // if the current state is LOW then the button went from on to off:
       Serial.println("button :");
       Serial.print(i);
       Serial.println("  off");
      }
      // Delay a little bit to avoid bouncing
      delay(50);
    }
    // save the current state as the last state, for next time through the loop
    lastButtonState[i-1] = buttonState[i-1];
  }
}

void processSDCardAlt(File dir){
  int j = 0;
  oled.clearDisplay();
  oled.setCursor(0,10);
  dir.rewindDirectory();
  
  while(true){
   File entry = dir.openNextFile();
   if (entry.isDirectory()){
     //don't count as a file
     }else{
      j ++;
     }
   if (! entry){
     j --; //Decrement Counter to correct for actual numbers of files found
    // no more files
    break;
    }
   entry.close();
   SDSongTitle[j] = entry.name();
   songSize[j] = entry.size();
   oled.println(String(SDSongTitle[j]) + ": " + String((songSize[j], DEC)));
   oled.display();
  }
  numSongs = j;  //update global variable with the number of Songs on the card
  delay(5000);  //delay 5 seconds before going to main loop
}

void processSDCard(File dir){
  int j = 0;
  oled.clearDisplay();
  oled.setCursor(0,10);
  dir.rewindDirectory();
  
  while(true){
   File entry = dir.openNextFile();
   if (entry.isDirectory()){
     //don't count as a file
     }else{
      SDSongTitle[j] = entry.name();
      oled.println(SDSongTitle[j]);
      oled.display();
      j ++;
     }
   if (! entry){
     j --; //Decrement Counter to correct for actual numbers of files found
    // no more files
    break;
    }
   entry.close();
  }
  numSongs = j;  //update global variable with the number of Songs on the card
  delay(5000);  //delay 5 seconds before going to main loop
}

char * getSong(File dir, int index){
  File entry;
  char *song;
  int j = 0;
  //int songTitleLength=0;
  oled.clearDisplay();
  oled.setCursor(0,20);
  oled.println("Selected Track");
  oled.display();
  dir.rewindDirectory();
  
  while(true){
   entry = dir.openNextFile();
   if (entry.isDirectory()){ //Skip directories, song will be at root directory only
     entry.close();
     entry = dir.openNextFile();
   }
   entry.close();
   j++;
     
   if (j == index){
      break;  //exit, pointer is now at track at requested location
   }
  }
  song = entry.name();
  oled.setCursor(0,30);
  oled.println(song);
  oled.display(); 
  return song;
}

void processButton(int buttonNumber, int index ){
  //char* songToPlay;

  if(buttonNumber == 0){
    //Do nothing.  Buttons are momentary.  
  }

  if (buttonNumber == 1  && musicPlayer.playingMusic){  //assign GPI01 to "STOP"
    musicPlayer.stopPlaying();
  }

  if (buttonNumber == 2 && !musicPlayer.playingMusic){    //Start playing song requested
    //begin playing file with interrupts active so code can continue
   
    /*
    songToPlay = getSong(root, index);
    oled.setCursor(0,40);
    oled.println(songToPlay);
    oled.display();
    
    
    oled.setCursor(0,40);
    oled.println(SDSongTitle[currentSong - 1]); //indexing since array starts at position 0, i.e. song 1 is in slot 0
    oled.display();
    delay(10000);
    */

    oled.clearDisplay();
    oled.setCursor(0,0);
    oled.println(SDSongTitle[currentSong -1]);
    oled.setCursor(0,10);
    oled.println(songSize[currentSong]);
    oled.display();
    
    //if (! musicPlayer.startPlayingFile("/TRACK002.MP3")){
    if (! musicPlayer.startPlayingFile(SDSongTitle[currentSong - 1])){
      Serial.println("Could not open the #");
      Serial.print(String(index) + " song");
      while(1);
    } 
    currentSong++;   //For current single button use.  Will need to change once more buttons implemented
  }

  if (buttonNumber == 3 && musicPlayer.playingMusic){  //Assign GPIO2 to "PAUSE"
    musicPlayer.pausePlaying(true);
  }
  if (buttonNumber == 4 && musicPlayer.paused()){ //Assign GPIO3 to "RESUME"
    musicPlayer.pausePlaying(false);
  }
  if (buttonNumber == 4  && currentSong <= numSongs){  //Assing GPIO4 to "NEXT TRACK"
    //UPDATE THE FILE NAME TO NEXT ONE & Increment the global variable for current track
    currentSong ++;


  }
  if (buttonNumber ==5 && currentSong >= 2){  //Assing GPIO4 to "PREVIOUS TRACK"
   //UPDATE THE FILE NAME TO Previous ONE & Decrement the global variable for current track
    currentSong --;

  }
}

Your pointers are pointing to memory that is NOT global and is being reused by other function calls.
Store the file names immediately into global memory.

Paul,

Here is my ignorance. In theory, I understand but I do not understand the mechanism to do this practically. I could call the musicPlayer.startPlayingFIle() inside the method that locates the file to potentially eliminate reuse by other function calls but how would you in theory create an accessible pointer list to all the files on the SD card that could be used by the program as needed? Thanks

It would be nice to see your structure that is 'packed'

Is there a possibility you can match the destination in the arduino with an 'attribute((packed))'

Or read the entire directory into your machine ...

:smiley_cat:

I modified the routine that I called to return the pointer and instead had it launch the song directly and it works. Seems the memory is indeed the issue. I modified the initial routine that returned the number of songs to also put the names of the songs as Strings and stuff them into an array. Then I can select from the array and use the array index value (minus 1 to account for array first value at 0) as the index to push into the routine to play the song. Thanks all. Not the most elegant but I accomplished my goal.

Actually elegant and the only way for it to work! Glad you got it fixed and learned a bit about pointers.

Great you resolved it...

Remember what 'C' was really about... efficient code for the machine...

An array variable is actually an address pointing to the first object in the array. So when you code array[0], it is the address of the first location... When you do an array[3], three times the 'data' size it added to the address to compute the 4th locations address...

If the 'data' is a character, it's data size is 1, if the array is an array of structures then the data size is the 'sizeof(structure)'.

Most of us learn that for N number of entries we run 0 through N - 1 mostly from knowing how this works..

Good luck...

:smiley_cat:

This creates an array of pointers to strings, but doesn't actually reserve any memory to store the characters of the string. In your function, you get the filename from the SD library, but never store it anywhere. The SD library has its own internal buffer holding the file name, and you get a pointer to that. That buffer gets overwritten with the next filename, so the text of the first file name is now gone. What's worse, is when the getSong function ends, ALL of the memory it used is freed, including the area where the text of your file name was stored.

Since the SD library only uses 8.3 filenames, no file name can be more than 12 characters long, 13 if you include the terminating null character. You might be better off creating a two dimensional array of characters, and passing a pointer to that into the getSong function. getSong can then fill in each row of the array with the text from each filename. Just be aware that this is going to take a lot of memory. An array of 99 filenames will take up something like 2/3 of the total amount of dynamic memory in an Arduino Uno.

As a slower alternative, you could keep track of just how many songs are on the SD card, and when you need to find the title for song number X, you step through the directory to find the xth filename, and print that. It would mean having to read through the entire directory every time you needed to find the title of a song, but the memory requirements would be much lower.

edit: added alternative suggestion.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.