Problem with char data from SD file

I'm not new to Arduino coding, but it's the first time i'm working with SD card, files. I'm always tripped up by string handling and this is probably my issue, but it may be file reading also. For info, I'm using a Teensy 3.2 with the Audio card reader. I seem to be able to read the data on the file, but it's not storing it correctly.

#include <SPI.h>
#include <SD.h>

#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

// SD - Soundcard
char fileDescription[14];
char line[14];
char* descriptionLine[10];
int descriptionCount = 0;

void setup() {

  // --------------------------------- SD Card Setup
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    // Display "no SD Card" but allow processing to continue
    Serial.println("No SD CARD!");
  }
  if (SD.exists("INDEX.TXT")) {
    Serial.println("Reading SD");
    File fileDescription = SD.open("INDEX.TXT");
    if (fileDescription) {

      // Read from the file until there's nothing else in it
      while (fileDescription.available()) {

        // clear the linebuffer
        memset(line, '\0', sizeof(line));

        // read a line fromfile
        fileDescription.readBytesUntil('\n', line, sizeof(line) - 1);

        // Print the line as read
        //        Serial.print(line);
        Serial.print(descriptionCount);
        Serial.print(" - ");
        Serial.println(line);

        // Put filename in Description array
        descriptionLine[descriptionCount] = line;

        //      also tried: strcpy(descriptionLine[descriptionCount],line);

        descriptionCount++;

      } // More to read
    }   // Done reading

    // close the file:
    fileDescription.close();

    // Verify the results
    Serial.println();
    Serial.println("Description array");
    for (int i = 0; i < descriptionCount; i++) {
      Serial.print(i);
      Serial.print(" = ");
      Serial.println(descriptionLine[i]);
    }
  } else { // if the file didn't open, print an error:
    Serial.println("error");
  } // Error, but continue processing
  
}   // End Setup

void loop() {
  // put your main code here, to run repeatedly:

} // End Loop

The output reads:
Reading SD
0 - SCI-FI
1 - Bitcrush
2 - Drone
3 - Crackle1
4 - Crackle2
5 - underground
6 - Fan Noise
7 - Num7
8 - Num8
9 - Num9

Description array
0 = Num9
1 = Num9
2 = Num9
3 = Num9
4 = Num9
5 = Num9
6 = Num9
7 = Num9
8 = Num9
9 = Num9

I feel like i'm trapped in a Beatles White Album playing it backwards... any suggestions?

Well done using autoformat and code tags when posting the code!
Can expand the description of the file handling issue? You manage to read files from the SD. Where do Yo intend to store? What goes wrong, how....?

So while it LOOKS like i'm reading the data from the file correctly, what's actually stored in every iteration is only the last item read, "Num9."

You print the Description part of the file/line read, not the file content, the line, it looks like to me.

The problem is twofold. You are setting every element of descriptionLine to point to line. When you're done reading, line contains num9 and so your print loop prints it ten times.

What you need is strcpy, but that won't work either because descriptionLine is an array of pointers that have not got any memory to point to, so strcpy will likely crash your program.

Try making descriptionLine a 2d array of char so you have some space available to copy the lines from the file into.

Yeah, i think you're right that it's a character array problem i have, but i still have a hard time wrapping my head around it. You mean a 2D array like:

char* descriptionLine[14][10];

but then how do i denote the first part of the array to put it into the char array?

descriptionLine[14][descriptionCount] = line; // ???

That gives me the same result...

I guess i'm not good at describing the problem, but my intent is to put the line items of the file into the array...

more like char descriptionLine[14][10]

It's probably safer to do char descriptionLine[14][16]

To copy data then to the array, instead of this

descriptionLine[descriptionCount] = line

Use this

memcpy(&descriptionLine[descriptionCount][0], line, 14);

I think that should work...

When i use:

memcpy(&descriptionLine[descriptionCount][0], line, 14);

The program seems to go off into LaLa Land:

Reading SD
0 - SCI-FI
1 - Bitcrush
2 - Drone
3 - Crackle1
4 - Crackle2
5 - underground
6 - Fan Noise
7 - Num7
8 - Num8
9 - Num9

Description array
0 =
1 =

I'm wondering why it's getting complicated with 2D arrays... when i directly assign, as in

#include <SPI.h>
#include <SD.h>

#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

// SD - Soundcard
char fileDescription[14];
char line[14];
char* descriptionLine[10];
int descriptionCount = 0;

void setup() {

  // --------------------------------- SD Card Setup
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    // Display "no SD Card" but allow processing to continue
    Serial.println("No SD CARD!");
  }
  if (SD.exists("INDEX.TXT")) {
    Serial.println("Reading SD");
    File fileDescription = SD.open("INDEX.TXT");
    if (fileDescription) {

      // Read from the file until there's nothing else in it
      while (fileDescription.available()) {

        // clear the linebuffer
        memset(line, '\0', sizeof(line));

        // read a line fromfile
        fileDescription.readBytesUntil('\n', line, sizeof(line) - 1);

        // Print the line as read
        //        Serial.print(line);
        Serial.print(descriptionCount);
        Serial.print(" - ");
        Serial.println(line);

        descriptionLine[descriptionCount] = line;
        descriptionCount++;

      } // More to read
    }   // Done reading

    // close the file:
    fileDescription.close();

    // Verify the results

    descriptionLine[8] = "XXX"; // 
    Serial.println();
    Serial.println("Description array");
    for (int i = 0; i < descriptionCount; i++) {
      Serial.print(i);
      Serial.print(" = ");
      Serial.println(descriptionLine[i]);
    }
  } else { // if the file didn't open, print an error:
    Serial.println("error");
  } // Error, but continue processing

}   // End Setup

void loop() {
  // put your main code here, to run repeatedly:

} // End Loop

I can directly assign

descriptionLine[8] = "XXX";

and the output shows that it contains the text of the array...

Description array
0 = Num9
1 = Num9
2 = Num9
3 = Num9
4 = Num9
5 = Num9
6 = Num9
7 = Num9
8 = XXX
9 = Num9

My guess is i just can't fathom how C or Arduino handles text though i've read thru the descriptions and explanations probably a hundred times.

//char* descriptionLine[10];
char descriptionLine[10][14];

You set up 10 pointers, but not to any specific memory block, and you are getting into issues of dynamic memory allocation. I'm certain there are appropriate and safe ways to handle this on the Teensy, but I am only familiar with the practices of using allocated memory as you do with the smaller processors like the AT328.

To fill the descriptionLine arrays you can use strcpy as the line array is null terminated and will always be smaller or the same size as descriptionLine.

strcpy(descriptionLine[descriptionCount],line);//you only need the first index

#include <SPI.h>
#include <SD.h>

#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

// SD - Soundcard
char fileDescription[14];
char line[14];
//char* descriptionLine[10];
char descriptionLine[10][14];//descriptionLine[10][sizeof(line)];
int descriptionCount = 0;

void setup() {

  // --------------------------------- SD Card Setup
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    // Display "no SD Card" but allow processing to continue
    Serial.println("No SD CARD!");
  }
  if (SD.exists("INDEX.TXT")) {
    Serial.println("Reading SD");
    File fileDescription = SD.open("INDEX.TXT");
    if (fileDescription) {

      // Read from the file until there's nothing else in it
      while (fileDescription.available()) {

        // clear the linebuffer
        memset(line, '\0', sizeof(line));

        // read a line fromfile
        fileDescription.readBytesUntil('\n', line, sizeof(line) - 1);

        // Print the line as read
        //        Serial.print(line);
        Serial.print(descriptionCount);
        Serial.print(" - ");
        Serial.println(line);

        // Put filename in Description array
        //descriptionLine[descriptionCount] = line;
        strcpy(descriptionLine[descriptionCount],line);

        //      also tried: strcpy(descriptionLine[descriptionCount],line);

        descriptionCount++;

      } // More to read
    }   // Done reading

    // close the file:
    fileDescription.close();

    // Verify the results
    Serial.println();
    Serial.println("Description array");
    for (int i = 0; i < descriptionCount; i++) {
      Serial.print(i);
      Serial.print(" = ");
      Serial.println(descriptionLine[i]);
    }
  } else { // if the file didn't open, print an error:
    Serial.println("error");
  } // Error, but continue processing
  
}   // End Setup

void loop() {
  // put your main code here, to run repeatedly:

} // End Loop

It's probably because you didn't change the declaration of the descriptionLine variable to something like

char descriptionLine[x][y]

instead of your previous declaration

char * descriptionLine[x]

Well that worked beautifully! Thanks, so much, hzrnbgy!

I guess i'm not used to assigning an array to be 2D, and accessing as a 1D.

char descriptionLine[14][10];

...

Serial.println(descriptionLine[i]);

I guess because the 'char' type is a form of array itself? I fear i'll never truly get character and string handling, but i really do appreciate you guiding me thru it and providing a workable answer!

A two dimensional char array looks like this in memory

char data[14][11];

data[0] is all the characters in the first row
data[0][1] is the character h

memcpy(&data[0][0], someString, 11) means

copy 11 characters of someString starting from the address of data[0][0]

1 Like