CardInfo example leaves SD in a bad state. Can no longer be read from.

I have tried to combine the SD example code from http://arduino.cc/en/Tutorial/ReadWrite and http://arduino.cc/en/Tutorial/CardInfo to be able to Read, Write, and List in a single sketch. However, in my experimenting I found that I try those 3 operations in a certain order, I get a failure. Though I don’t NEED to do the operations in that order at the moment, knowing that it fails suggests I’m not doing one of the things right. Most likely I’m failing to close something.

The code is below. Take out the define and it works, leave it in and it fails. If you edit the function sd_inspect and remove the volume.init(card) and everything after it, it works again.

#include "Arduino.h"
#include <common.h>
#include <SD.h>

#define MAKE_IT_FAIL

File logfile;
char *logfile_name;
const int cs_ss  = 10; // pin D10    - ATMega default SS pin
const int cs_sd  = 10; // pin D10    - microSD card
const int cs_rtc = 18; // pin A0/D18 - RTC ds3234

void setup()
{
  serial_setup();
  SPI_setup();
  /* ### put sd_inspect() here and sd_dump("20140102.LOG") succeeds ### */
#ifndef MAKE_IT_FAIL
  sd_inspect();
#endif
  sd_setup("20140102.LOG");
  sd_write("this is a test!\n");
  logfile.close();
  
  /* ### put sd_inspect() here and sd_dump("20140102.LOG") fails ### */
#ifdef MAKE_IT_FAIL
  sd_inspect();
#endif
  sd_dump("20140102.LOG");
}

void loop()
{
  
}



void serial_setup()
{
  Serial.begin(9600);  // Start serial for output
  while (!Serial);
  Serial.println();
  Serial.println();
  Serial.println("## BEGIN NEW LOG ##");
}

void SPI_setup()
{
  // The ATMega SS pin must be set to output. Yes, this is redundant.
  // But, if the SD card is moved to a different pin this redundancy will ensure nothing breaks.
  pinMode(cs_ss, OUTPUT);
  pinMode(cs_sd, OUTPUT);
  digitalWrite(cs_sd, HIGH);
  pinMode(cs_sd, OUTPUT);
  digitalWrite(cs_sd, HIGH);

}



void sd_setup(char *filename)
{
  Serial.println("## SETTING UP SD CARD ##");

  logfile_name = filename;

  if (!SD.begin(cs_sd)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("* is a card is inserted?");
    Serial.println("* Is your wiring correct?");
    Serial.println("* did you change the chipSelect pin to match your shield or module?");
    return;
  }
  else {
   Serial.println("Wiring is correct and a card is present."); 
  }

  Serial.print("## OPENING FILE:");
  Serial.print(logfile_name);
  Serial.println(" ##");

  logfile = SD.open(logfile_name, FILE_WRITE);
}

void sd_write(char *chars)
{
  if (logfile) {
    logfile.print(chars); 
  } else {
    // if the file didn't open, print an error:
    Serial.print("error opening ");
    Serial.println(logfile_name);
  }
}

void sd_dump(char *filename)
{
  logfile = SD.open(filename, FILE_READ);

  if (logfile) {
    Serial.print("contents of ");
    Serial.print(filename);
    Serial.println(":");
    
    // read from the file until there's nothing else in it:
    while (logfile.available()) {
      Serial.write(logfile.read());
    }
    // close the file:
    logfile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.print("error opening ");
    Serial.println(logfile_name);
  }
}

void sd_inspect()
{
  Sd2Card card;
  SdVolume volume;
  SdFile root;

  if (!card.init(SPI_HALF_SPEED, cs_sd)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("* is a card is inserted?");
    Serial.println("* Is your wiring correct?");
    Serial.println("* did you change the chipSelect pin to match your shield or module?");
    return;
  } else {
   Serial.println("Wiring is correct and a card is present."); 
  }
  if (!volume.init(card)) {
    Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
    return;
  }
  Serial.println("\nFiles found on the card (name, date and size in bytes): ");
  root.openRoot(volume);
  
  // list all files in the card with date and size
  root.ls(LS_R | LS_DATE | LS_SIZE);
  root.close();
}

Serial output of it working:

## BEGIN NEW LOG ##
Wiring is correct and a card is present.

Files found on the card (name, date and size in bytes): 
TRASHE~1      2014-09-27 20:11:06 0
TEST.TXT      2000-01-01 01:00:00 18
~1.TRA        2014-09-27 20:11:06 0
ONO.TXT       2000-01-01 01:00:00 102
20140101.TXT  2000-01-01 01:00:00 383
SPOTLI~1      2014-09-27 20:11:06 0
20140101.LOG  2000-01-01 01:00:00 16
20140102.LOG  2000-01-01 01:00:00 2928
NET.INI       2014-09-27 20:35:16 1207
20141002.LOG  2000-01-01 01:00:00 64
## SETTING UP SD CARD ##
Wiring is correct and a card is present.
## OPENING FILE:20141002.LOG ##
contents of 20141002.LOG:
this is a test!
this is a test!
this is a test!
this is a test!
this is a test!

Serial output of it failing:

## BEGIN NEW LOG ##
## SETTING UP SD CARD ##
Wiring is correct and a card is present.
## OPENING FILE:20141002.LOG ##
Wiring is correct and a card is present.

Files found on the card (name, date and size in bytes): 
TRASHE~1      2014-09-27 20:11:06 0
TEST.TXT      2000-01-01 01:00:00 18
~1.TRA        2014-09-27 20:11:06 0
ONO.TXT       2000-01-01 01:00:00 102
20140101.TXT  2000-01-01 01:00:00 383
SPOTLI~1      2014-09-27 20:11:06 0
20140101.LOG  2000-01-01 01:00:00 16
20140102.LOG  2000-01-01 01:00:00 2928
NET.INI       2014-09-27 20:35:16 1207
20141002.LOG  2000-01-01 01:00:00 96
contents of 20141002.LOG:

Of course I fix it myself right after I post a question about the issue I’ve been stuck on for 4 days!

I decided to READ THE CODE of some of these functions I’ve just been using from library examples. I found that SD.begin(cs_sd) takes care of card.init(SPI_HALF_SPEED, cs_sd) && volume.init(card) && root.openRoot(volume); I was calling manually in my sd_inspect function. The reason the CardInfo example called it manually was about variable scoping. What I decided to do was to do an SD.begin at the beginning of every SD related function. That required me to open and close my file in the sd_write function rather than leaving it open. I think that is a better practice anyway. Then it occurred to me that all I needed to do was do an SD.begin() at the end of the sd_inspect() function because the resources had been inited to the local scope variables and needed to be returned to the private scope variables. It works and looks much cleaner.

Here are the results of these changes:

#include "Arduino.h"
#include <common.h>
#include <SD.h>

#define MAKE_IT_FAILNOT

File logfile;
char *logfile_name;
const int cs_ss  = 10; // pin D10    - ATMega default SS pin
const int cs_sd  = 10; // pin D10    - microSD card
const int cs_rtc = 18; // pin A0/D18 - RTC ds3234

void setup()
{
  serial_setup();
  SPI_setup();
  sd_setup("20141002.LOG");

  sd_write("This is a test! ");
  sd_write("Now it is unbreakable!\n");
  
  sd_inspect();
  sd_dump("20141002.LOG");
}

void loop(){}

void serial_setup()
{
  Serial.begin(9600); while (!Serial);
  Serial.println("\n\n## BEGIN NEW LOG ##");
}

void SPI_setup()
{
  // The ATMega SS pin must be set to output. Yes, this is redundant.
  // But, if the SD card is moved to a different pin this redundancy will ensure nothing breaks.
  pinMode(cs_ss, OUTPUT);
  pinMode(cs_sd, OUTPUT); digitalWrite(cs_sd, HIGH);
  pinMode(cs_sd, OUTPUT); digitalWrite(cs_sd, HIGH);

}

void sd_setup(char *filename)
{
  Serial.println("## SETTING UP SD CARD ##");

  logfile_name = filename;

  // Only do this check the first time you init the SD
  if (!SD.begin(cs_sd)) {
    Serial.println("initialization failed. Things to check:\n * is a card is inserted?\n * Is your wiring correct?\n * did you change the chipSelect pin to match your shield or module?\n");
    return;
  } else {
   Serial.println("Wiring is correct and a card is present."); 
  }

}

void sd_write(char *chars)
{
  logfile = SD.open(logfile_name, FILE_WRITE);
  if (logfile) {
    logfile.print(chars); 
  } else {
    Serial.println((String) "error opening "+logfile_name);
  }
  logfile.close();
}

void sd_dump(char *filename)
{
  logfile = SD.open(filename, FILE_READ);
  if (logfile) {
    Serial.println((String) "contents of "+filename+":");
    while (logfile.available()) Serial.write(logfile.read());
    logfile.close();
  } else {
    Serial.println((String) "error opening "+logfile_name);
  }
}

void sd_inspect()
{
  // SD.begin() creates these 3 as private variables so you can't root.ls()
  Sd2Card card;
  SdVolume volume;
  SdFile root;

  // init manually using our locally scoped variables
  if(card.init(SPI_HALF_SPEED, cs_sd) && volume.init(card) && root.openRoot(volume)){
    root.ls(LS_R | LS_DATE | LS_SIZE);
    root.close();
  }
  SD.begin(cs_sd);
}

I welcome feedback!

You can't use both of these calls in a single sketch.

 volume.init(card);

and

  SD.begin(cs_sd);

The SD object has an instance of the Volume object internally. There can only be one instance of the Volume object since there is a static 512 byte cache buffer.

Do not use these classes if you access the card with the SD object! There is a instance of each of these in the SD object.

  Sd2Card card;
  SdVolume volume;
  SdFile root;

Do not use these classes if you access the card with the SD object! There is a instance of each of these in the SD object.

I noticed that and was editing my post as you replied. (I got distracted by a phone call from my 7 year old when I was doing this the first time.)

So, if I use the SD object, is there any way to do an ls()? If not I should probably go with global instances of Sd2Card, SdVolume, and SdFile

If not I should probably go with global instances of Sd2Card, SdVolume, and SdFile

If you do this you can't use the SD.h style files. SD.h is wrapper for a version of SdFat that I wrote five year ago.

You might want to consider using a modern version of SdFat, it has an ls() member function https://github.com/greiman/SdFat.

SdFat allows you to access the internal Card, root, and Volume objects. The SD.h wrapper makes these objects private.

Just as an update, this is what the code is looking like now…

#include "Arduino.h"
#include <common.h>
#include <SdFat.h>
#include <Timex.h>
#include <stdio.h>


SdFat sd;
SdFile logfile;
char logfile_name[16];
const int cs_ss  = 10; // pin D10    - ATMega default SS pin
const int cs_sd  = 10; // pin D10    - microSD card
const int cs_rtc = 18; // pin A0/D18 - RTC ds3234

void setup()
{
  Serial.println("\n\n## BEGIN NEW LOG ##");
  serial_setup();
  logname(__DATE__, logfile_name);
  SPI_setup();
  Serial.println("## SETTING UP SD CARD ##");
  sd_setup();

  Serial.println("## WRITING TO THE LOG FILE ##");
  sd_write("This is a test! ");
  sd_write("Now it uses SdFat!\n");

  Serial.println("## INSPECTING THE CARD ##");
  sd_inspect();
  
  sd_dump();
}

void loop(){}

void serial_setup()
{
  Serial.begin(9600); while (!Serial);
  Serial.println(__DATE__);
}

void SPI_setup()
{
  // The ATMega SS pin must be set to output. Yes, this is redundant.
  // But, if the SD card is moved to a different pin this redundancy will ensure nothing breaks.
  pinMode(cs_ss, OUTPUT);
  pinMode(cs_sd, OUTPUT); digitalWrite(cs_sd, HIGH);
  pinMode(cs_sd, OUTPUT); digitalWrite(cs_sd, HIGH);

}

void sd_setup()
{
  if (!sd.begin(cs_sd, SPI_HALF_SPEED)){
    sd.initErrorHalt();
    return;
  }
}

void sd_write(char *chars)
{
  if (!logfile.open(logfile_name, O_RDWR | O_CREAT | O_AT_END)) {
    Serial.println((String) "error opening "+logfile_name);
    sd.errorHalt("opening file for write failed");
  }
  logfile.print(chars); 
  logfile.close();
}

void sd_dump()
{
  int data;
  if (!logfile.open(logfile_name, O_READ)) {
    Serial.println((String) "error opening "+logfile_name);
    sd.errorHalt("opening file for read failed");
  }
  Serial.println((String) "\ncontents of "+logfile_name+":");
  while ((data = logfile.read()) >= 0) Serial.write(data);
  logfile.close();
}

void sd_inspect()
{
  sd.ls(LS_R | LS_DATE | LS_SIZE);
}

/* INTENDED USE
  char filename[16];
  logname(__DATE__, filename);
*/
void logname(char const *date, char *buff) { 
    int month, day, year;
    static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
    sscanf(date, "%s %d %d", buff, &day, &year);
    month = (strstr(month_names, buff)-month_names)/3+1;
    sprintf(buff, "%d%02d%02d.txt", year, month, day);
}

Here is the Serial terminal output…

Oct  9 2014
## SETTING UP SD CARD ##
## WRITING TO THE LOG FILE ##
## INSPECTING THE CARD ##
TEST.TXT       2000-01-01 01:00:00 72
ONO.TXT        2000-01-01 01:00:00 102
20140101.LOG   2000-01-01 01:00:00 383
20140102.LOG   2000-01-01 01:00:00 2928
NET.INI        2014-09-27 20:35:16 1207
20141002.LOG   2000-01-01 01:00:00 1226
20141009.LOG   2000-01-01 01:00:00 105
 
contents of 20141009.txt:
This is a test! Now it uses SdFat!
This is a test! Now it uses SdFat!
This is a test! Now it uses SdFat!
This is a test! Now it uses SdFat!

I can do the tasks in any order and it never fails! Thank you @fat16lib!!!