SdFat library with I2C and SoftwareSerial

I have a project on a Arduino Uno. It uses a gps device (GP-635T which uses SoftwareSerial.h and TinyGPS++.h), and LCD display (which uses I2C via the LiquidCrystal_I2C.h library. I also have a Sparkfun MicroSD shield stacked onto the Uno.

Everything worked fine until i try to add the code to save data to the SD card. Now i cannot initialize the card. Here is my Setup routine:

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <stdio.h>

//Add the SdFat Libraries
#include <SdFat.h>
#include <SdFatUtil.h> 
#include <ctype.h>

// ==================================
#define I2C_Address 0x27
#define I2C_NumChar 20
#define I2C_NumLines 4
#define LED 9
#define BUTTON 7
#define SDCARD 10
#define PRINT_DATA 0
#define DONT_PRINT_DATA 1
#define error(s) sd.errorHalt_P(PSTR(s))

static const int RXPin = 2, TXPin = 3;
static const uint32_t GPSBaud = 9600;

// 4 line, 20 character/line LCD Display
LiquidCrystal_I2C lcd(I2C_Address,I2C_NumChar,I2C_NumLines); 
// The TinyGPS++ object
TinyGPSPlus gps;
// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

// MicroSD Card
//Create the variables to be used by SdFat Library
char fileName[] = "GPS_Teat_01.txt";//Create an array that contains the name of our file.   
SdFat sd;
SdFile myFile;

const uint8_t chipSelect = 8; // CS = 8 on Sparkfun Card

// Global variables
bool haveData=true;
bool firstPass = true;
float homeLat, homeLong, currentLat, currentLong;
unsigned long last = 0UL;
char Latitude[20],Longitude[20],CurrentTime[20],Status[22],CurrentShortTime[20],LatShort[20],LongShort[20];
int lastDistance=0;
unsigned long dist;  
static int lastChecksum=0,currentChecksum=0;

void setup()
{
  bool result;
  
  Serial.begin(9600);
  ss.begin(GPSBaud);

  Serial.println(F("SD Test.ino"));
  Serial.println(F("Testing saving data to SD Card"));
  Serial.println(F("by Evan Westermann"));
  Serial.println();
  
  pinMode(LED,OUTPUT);
  pinMode(BUTTON,INPUT);
  pinMode(SDCARD, OUTPUT);
  pinMode(chipSelect, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(12, INPUT);

  lcd.init(); 
  lcd.backlight();
  
  Serial.println("Attempting to initialize SD Card");
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) 
     sd.initErrorHalt();
  Serial.println("SD Card Initialized successfully");
  digitalWrite(chipSelect,HIGH);
}

And here is where i try to initialize and write to the SD card

void SaveData(int option)
{
  char outData[100];
  bool result;
  char msg[100];
  
  lastChecksum = currentChecksum;
  currentChecksum = gps.failedChecksum();
  
  sprintf(outData,"%s,%s,%s,%05d,%05d,%05d,%05d",
     CurrentShortTime,LatShort,LongShort,dist,abs(gps.GetSatellites()),currentChecksum-lastChecksum);
  
  // save data to SD Card  
  // open the file for write at end like the Native SD library
  if (!myFile.open(fileName, O_RDWR | O_CREAT | O_AT_END)) {
    sprintf(msg,"Opening file for write failed. Error = %d. Data = %d",sd.card()->errorCode(),
       sd.card()->errorData());
    sd.errorHalt(msg);
  }
  // if the file opened okay, write to it:
  Serial.print("Writing to test.txt...");
  myFile.println(outData);

  // close the file:
  myFile.close();
  Serial.println("done.");
  
  // write out data to Serial Monitor
  if(option == PRINT_DATA)
     Serial.println(outData);
}

When it starts i get the following error code from myFile.open command:
error: Opening file for write failed. Error = 0. Data = 0

I don’t even see an error code 0. If i comment out the section in SaveData() that does the write, the program runs fine. Can someone help me with this, thanks.

SdFat only supports 8.3 file names. This is not a valid 8.3 name.

char fileName[] = "GPS_Teat_01.txt"

You get zero error code from card since this is not a hardware SD card error. sd.card() is the raw block device and doesn't know about files.

   sprintf(msg,"Opening file for write failed. Error = %d. Data = %d",sd.card()->errorCode(),
       sd.card()->errorData());

I changed the file name to "GPS01.txt", and it still fails. How do I get the reason for failure when trying to open the file, I had thought that the sd.card->errorCode() would get me that info.

I substituted your file name for “test.txt” in the SdFatReadWrite example and it runs so there must be a more subtle problem. Maybe someone will see something if you post all of the code.

Here is the program:

// Ported to SdFat from the native Arduino SD library example by Bill Greiman
// On the Ethernet Shield, CS is pin 4. SdFat handles setting SS
const int chipSelect = 10;
/*
 SD card read/write
  
 This example shows how to read and write data to and from an SD card file 	
 The circuit:
 * SD card attached to SPI bus as follows:
 ** MOSI - pin 11
 ** MISO - pin 12
 ** CLK - pin 13
 ** CS - pin 4
 
 created   Nov 2010
 by David A. Mellis
 updated 2 Dec 2010
 by Tom Igoe
 modified by Bill Greiman 11 Apr 2011
 This example code is in the public domain.
 	 
 */
#include <SdFat.h>
SdFat sd;
SdFile myFile;

void setup() {
  Serial.begin(9600);
  while (!Serial) {}  // wait for Leonardo
  Serial.println("Type any character to start");
  while (Serial.read() <= 0) {}
  delay(400);  // catch Due reset problem
  
  // Initialize SdFat or print a detailed error message and halt
  // Use half speed like the native library.
  // change to SPI_FULL_SPEED for more performance.
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) sd.initErrorHalt();

  // open the file for write at end like the Native SD library
  if (!myFile.open("GPS01.txt", O_RDWR | O_CREAT | O_AT_END)) {
    sd.errorHalt("opening test.txt for write failed");
  }
  // if the file opened okay, write to it:
  Serial.print("Writing to GPS01.txt...");
  myFile.println("testing 1, 2, 3.");

  // close the file:
  myFile.close();
  Serial.println("done.");

  // re-open the file for reading:
  if (!myFile.open("GPS01.txt", O_READ)) {
    sd.errorHalt("opening test.txt for read failed");
  }
  Serial.println("GPS01.txt:");

  // read from the file until there's nothing else in it:
  int data;
  while ((data = myFile.read()) >= 0) Serial.write(data);
  // close the file:
  myFile.close();
}

void loop() {
  // nothing happens after setup
}

Here is the output.

Type any character to start
Writing to GPS01.txt…done.
GPS01.txt:
testing 1, 2, 3.

I created a smaller sketch so that i could isolate the cause of the problem. I believe that the problem is related to multiple modes of communication interfering which each other. I don’t know enough about these modes to figure out what is wrong. Here is the small test sketch:

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <stdio.h>

//Add the SdFat Libraries
#include <SdFat.h>
#include <SdFatUtil.h> 
#include <ctype.h>

// ==================================
#define I2C_Address 0x27
#define I2C_NumChar 20
#define I2C_NumLines 4
#define LED 9
#define BUTTON 7
#define SDCARD 10
#define PRINT_DATA 0
#define DONT_PRINT_DATA 1
#define error(s) sd.errorHalt_P(PSTR(s))

static const int RXPin = 2, TXPin = 3;
static const uint32_t GPSBaud = 9600;

// 4 line, 20 character/line LCD Display
LiquidCrystal_I2C lcd(I2C_Address,I2C_NumChar,I2C_NumLines); 
// The TinyGPS++ object
TinyGPSPlus gps;
// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

// MicroSD Card
//Create the variables to be used by SdFat Library
char fileName[] = "GPS01.txt";//Create an array that contains the name of our file.   
SdFat sd;
SdFile myFile;

const uint8_t chipSelect = 8; // CS = 8 on Sparkfun Card

// Global variables
bool haveData=true;
bool firstPass = true;
float homeLat, homeLong, currentLat, currentLong;
unsigned long last = 0UL;
char Latitude[20],Longitude[20],CurrentTime[20],Status[22],CurrentShortTime[20],LatShort[20],LongShort[20];
int lastDistance=0;
unsigned long dist;  
static int lastChecksum=0,currentChecksum=0;

void setup()
{
  bool result;
  int err;
  
  Serial.begin(9600);
  ss.begin(GPSBaud);
  Serial.println("Software Serial has been initialized");

  Serial.println(F("SD Test.ino"));
  Serial.println(F("Testing saving data to SD Card"));
  Serial.println(F("by Evan Westermann"));
  Serial.println();
  
  pinMode(LED,OUTPUT);
  pinMode(BUTTON,INPUT);
  pinMode(SDCARD, OUTPUT);
  pinMode(chipSelect, OUTPUT);
  Serial.println("Pin Modes have been set");
  
  Serial.println("Attempting to initialize SD Card");
  err = sd.begin(chipSelect, SPI_HALF_SPEED);
  if (!err) 
    sd.errorHalt("Unable to initialize SD Card.");
  else
    Serial.println("SD Card Initialized successfully");
  
  digitalWrite(chipSelect,HIGH);
  
  Serial.println("Attempting to Open file on SD Card");
  err = myFile.open(fileName, O_RDWR | O_CREAT | O_APPEND);
  if (!err) 
    sd.errorHalt("Opening file for write failed");
  else
    {
    Serial.println("Open successful");
    myFile.close();
    }

  Serial.println("Attempting to initialize LCD Display");
  lcd.init(); 
  lcd.backlight();
  Serial.println("Initialization of LCD Display successful");
}

// This custom version of delay() ensures that the gps object
// is being "fed".
static void smartDelay(unsigned long ms)
{
  bool homeSet=false;
  
  unsigned long start = millis();
  do 
  {
    while (ss.available())
      gps.encode(ss.read());
//   if(digitalRead(BUTTON) == HIGH && !homeSet)
//      {
//      digitalWrite(LED,HIGH);
//      homeLat = currentLat;
//      homeLong = currentLong;
//      Serial.println("Home Position set");
//      homeSet = true;
//      }
  } while (millis() - start < ms);
//  digitalWrite(LED,LOW);
}

void SPrintLatitude()
{
  currentLat = gps.location.lat();
  // Location parameters have 5 decimal places
  int dec = (float)(currentLat - (int)currentLat)*100000;
  int num = currentLat;

  sprintf(LatShort,"%4d.%5d",num,dec);
  sprintf(Latitude,"Lat: %s St%02d",LatShort,gps.GetSatellites());
//  smartDelay(0);
}

void SPrintLongitude()
{
  currentLong = gps.location.lng();
  // Location parameters have 5 decimal places
  int dec = abs((float)(currentLong - (int)currentLong)*100000);
  int num = currentLong;

  sprintf(LongShort,"%4d.%05d",num,dec);
  sprintf(Longitude,"Long: %s",LongShort);
//  smartDelay(0);
}

void SPrintTime(TinyGPSTime &t)
{
  int hours;
  
  if (!t.isValid())
    sprintf(CurrentTime,"Time: %02d:%02d:%02d  ",0,0,0);
  else
   {
    // -4 hours to get to EST
    hours = t.hour() - 4;
    if(hours < 0)
       hours += 24;
    sprintf(CurrentTime, "Time: %02d:%02d:%02d  ", hours, t.minute(), t.second());
    sprintf(CurrentShortTime, "%02d:%02d:%02d  ", hours, t.minute(), t.second());
   }
//  smartDelay(0);
}

void loop()
{
  char message[] = "This is a test string";
  int err;
  Serial.println("In Loop");
  
//  SPrintLatitude();
//  SPrintLongitude();
//  SPrintTime(gps.time);
   
   // attempt to write data to file
  Serial.println("Attempting to Open file on SD Card");
  err = myFile.open(fileName, O_RDWR | O_CREAT | O_APPEND);
  if (!err) 
    sd.errorHalt("Opening file for write failed");
  else
    Serial.println("Open successful");
    
  Serial.println("Writing to file...");
  myFile.println(message);

  // close the file:
  myFile.close();
  Serial.println("done.");
  
  smartDelay(1000);
  Serial.println("In loop, after SmartDelay");
}

Without the call to SmartDelay(1000) at the end, this works fine and the Serial Monitor shows:

Software Serial has been initialized
SD Test.ino
Testing saving data to SD Card
by Evan Westermann

Pin Modes have been set
Attempting to initialize SD Card
SD Card Initialized successfully
Attempting to Open file on SD Card
Open successful
Attempting to initialize LCD Display
Initialization of LCD Display successful
In Loop
Attempting to Open file on SD Card
Open successful
Writing to file…
done.
In Loop
Attempting to Open file on SD Card
Open successful
Writing to file…
done.
In Loop
Attempting to Open file on SD Card
Open successful
Writing to file…
done.
In Loop
Attempting to Open file on SD Card
Open successful
Writing to file…
done.
In Loop
Attempting to Open file on SD Card
Open successful
Writing to file…
done.
In Loop
Attempting to Open file on SD Card
Open successful
Writing to file…
done.

With the call, the program hangs trying to open the file:

Software Serial has been initialized
SD Test.ino
Testing saving data to SD Card
by Evan Westermann

Pin Modes have been set
Attempting to initialize SD Card
SD Card Initialized successfully
Attempting to Open file on SD Card

The routine SmartDelay is used to obtain GPS data from the device, and use SoftwareSerial for communications and is attached to Arduino pins 2 and 3. Without the SDCard, the GPS device works fine, so i assume the communications are getting garbled, but i don’t know how to fix it.

Without the SDCard, the GPS device works fine, so i assume the communications are getting garbled, but i don't know how to fix it.

One of the frustrating issues when dealing with uC is that as you build out devices that utilize more and more of the capabilities of the uC, things can go amok.

Q: does the SD-Card work without the GPS? Looking for mutually exclusive behavior to test your theory of 'interaction." I suspect the issue is more subtle.

Have you had success using the SD card write with the same SD card you are now using. Was the card formatted with a PC (not good) or was it formatted with the recommended format utility? So many SD card issues can be traced back to the SD card proper.

You may also want to simply eliminate all of the SD hassles in total by off-loading your logging to a dedicated 8MHz SD Card writer. With some perfboard, you can build your own shield and run the whole thing at 3.3Volts to eliminate buffering issues.
Mine: off load serial data recording to dedicated 328P and SD card - Storage - Arduino Forum
Bill's (Sdfatlib): Google Code Archive - Long-term storage for Google Code Project Hosting.

The entire issue around using multiple uC (Arduinos in this case) is an interesting one and not all members agree; however, the penalty is only $2 - $3 for a naked Atmega328P-PU and some scrap perf-board. Your case may be a bit more difficult as you have the SD Shield. SD shields modules are very inexpensive and around the $2 mark from China merchants on eBay.

I do many hobby builds and I love it when I can create an 'encapsulated' multi-purpose item that can be reused. It's like reusable code, but with the hardware thrown if for good measure! Besides SD, IR receivers are a perfect off-load scenario and can be easily built with an Atmega85 as shown here: MIM 5383H4 Infrared Module with ATtiny85 (UNO set time/date menu sample) - Exhibition / Gallery - Arduino Forum

The difficulty in answering your question as it currently stands is that it is very difficult to simulate the error unless one has the piggyback shield.

Ray

Thanks for the info, i will attemept to build one of these. I did resolve the problem with the sketch by changing over to hardware serial, i replaced

  unsigned long start = millis();
  do 
  {
    while (ss.available())
      gps.encode(ss.read());

with

  unsigned long start = millis();
  do 
  {
    while (Serial.available())
      gps.encode(Serial.read());

and everything is working.

Thanks again for your help and info.

I spoke too soon. This does not work. As long as i don't call

    while (Serial.available())
      gps.encode(Serial.read());

The program works fine, when i include these lines, the program continuously reboots itself. I guess i have to look into a standalone SD Card writer, unless someone has an alternative.

Are you compiling with 1.5.x so you know your free-memory? If not, you may want to use the freeMem() utility:

// Private function: from http://arduino.cc/playground/Code/AvailableMemory  
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

I went back on my notebook and looked at the tinyGPS library and the example:

    while (ss.available())
    {
      char c = ss.read();
      // Serial.write(c); // uncomment this line if you want to see the GPS data flowing
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }

So, perhaps you want to pass the char to the gps.encode() rather than the function.

Ray

I simply took out all calls to Serial.print() since i am using an LCD to display all the data. Now it works fine. Thanks for your help. The constant rebooting was caused by sending too many characters at a time. Here is the final version:

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <stdio.h>

//Add the SdFat Libraries
#include <SdFat.h>
#include <SdFatUtil.h> 
#include <ctype.h>

// ==================================
#define I2C_Address 0x27
#define I2C_NumChar 20
#define I2C_NumLines 4
#define LED 9
#define BUTTON 7
#define SDCARD 10
#define PRINT_DATA 0
#define DONT_PRINT_DATA 1
#define error(s) sd.errorHalt_P(PSTR(s))

//static const int RXPin = 2, TXPin = 3;
//static const uint32_t GPSBaud = 9600;

// 4 line, 20 character/line LCD Display
LiquidCrystal_I2C lcd(I2C_Address,I2C_NumChar,I2C_NumLines); 
// The TinyGPS++ object
TinyGPSPlus gps;

// MicroSD Card
//Create the variables to be used by SdFat Library
char fileName[] = "GPS01.txt";//Create an array that contains the name of our file.   
SdFat sd;
SdFile myFile;
const uint8_t chipSelect = 8; // CS = 8 on Sparkfun Card

// Global variables
bool haveData=true;
bool firstPass = true;
float homeLat, homeLong, currentLat, currentLong;
unsigned long last = 0UL;
char Latitude[20],Longitude[20],CurrentTime[20],Status[22],CurrentShortTime[20],LatShort[20],LongShort[20];
int lastDistance=0,numSaved=0;
unsigned long dist;  
static int lastChecksum=0,currentChecksum=0;

void setup()
{
  bool result;
  int err;
  
  Serial.begin(9600);

//  Serial.println(F("SD Test.ino"));
//  Serial.println(F("Testing saving data to SD Card"));
//  Serial.println(F("by Evan Westermann"));
//  Serial.println();
  
  pinMode(LED,OUTPUT);
  pinMode(BUTTON,INPUT);
  pinMode(SDCARD, OUTPUT);
  pinMode(chipSelect, OUTPUT);
//  Serial.println("Pin Modes have been set");
  
//  Serial.println("Attempting to initialize SD Card");
  err = sd.begin(chipSelect, SPI_HALF_SPEED);
  if (!err) 
    sd.errorHalt("Unable to initialize SD Card.");
  else
//    Serial.println("SD Card Initialized successfully");
  
  digitalWrite(chipSelect,HIGH);
  
//  Serial.println("Attempting to Open file on SD Card");
  err = myFile.open(fileName, O_RDWR | O_CREAT | O_APPEND);
  if (!err) 
    sd.errorHalt("Opening file for write failed");
  else
    {
//    Serial.println("Open successful");
    myFile.close();
    }

  lcd.init(); 
  lcd.backlight();
  sprintf(LatShort,"%4d.%5d",0,0);
  sprintf(LongShort,"%4d.%5d",0,0);
  sprintf(CurrentShortTime, "%02d:%02d:%02d  ", 0, 0,0);
  sprintf(Status, "Initializing");
  LCD_Print();
}

// This custom version of delay() ensures that the gps object
// is being "fed".
static void smartDelay(unsigned long ms)
{
  bool homeSet=false;
 
  unsigned long start = millis();
  do 
  {
    while (Serial.available())
      gps.encode(Serial.read());
//   if(digitalRead(BUTTON) == HIGH && !homeSet)
//      {
//      digitalWrite(LED,HIGH);
//      homeLat = currentLat;
//      homeLong = currentLong;
//      Serial.println("Home Position set");
//      homeSet = true;
//      }
  } while (millis() - start < ms);
//  digitalWrite(LED,LOW);
}

void SPrintLatitude()
{
  currentLat = gps.location.lat();
  // Location parameters have 5 decimal places
  int dec = (float)(currentLat - (int)currentLat)*100000;
  int num = currentLat;

  sprintf(LatShort,"%4d.%5d",num,dec);
  sprintf(Latitude,"Lat: %s St%02d",LatShort,gps.GetSatellites());
//  smartDelay(0);
}

void SPrintLongitude()
{
  currentLong = gps.location.lng();
  // Location parameters have 5 decimal places
  int dec = abs((float)(currentLong - (int)currentLong)*100000);
  int num = currentLong;

  sprintf(LongShort,"%4d.%05d",num,dec);
  sprintf(Longitude,"Long: %s",LongShort);
//  smartDelay(0);
}

void SPrintTime(TinyGPSTime &t)
{
  int hours;
  
  if (!t.isValid())
    sprintf(CurrentTime,"Time: %02d:%02d:%02d  ",0,0,0);
  else
   {
    // -4 hours to get to EST
    hours = t.hour() - 4;
    if(hours < 0)
       hours += 24;
    sprintf(CurrentTime, "Time: %02d:%02d:%02d  ", hours, t.minute(), t.second());
    sprintf(CurrentShortTime, "%02d:%02d:%02d  ", hours, t.minute(), t.second());
   }
//  smartDelay(0);
}

void LCD_Print()
{
  if(haveData)
    {
    lcd.setCursor(0, 0);
    lcd.print(Latitude);
    lcd.setCursor(0, 1);
    lcd.print(Longitude);
    lcd.setCursor(0, 2);
    lcd.print(CurrentTime);
    lcd.setCursor(0, 3);
    lcd.print(Status);
    }
  smartDelay(0);
}

void loop()
{
  char outData[30];
  int err,numSat=0;
  float theLatitude,theLongitude;
    
  lastChecksum = currentChecksum;
  currentChecksum = gps.failedChecksum();
  numSat = abs(gps.GetSatellites());
  theLatitude = gps.location.lat();
  theLongitude = gps.location.lng();
  
  SPrintLatitude();
  SPrintLongitude();
  SPrintTime(gps.time);
     
  sprintf(outData,"%0d5,%05d",numSat,currentChecksum-lastChecksum);
  
  err = myFile.open(fileName, O_RDWR | O_CREAT | O_APPEND);
  if (!err) 
    {
    sprintf(Status,"Opening file for write failed");
    LCD_Print();
    sd.errorHalt("Opening file for write failed");
    }
    
  myFile.print(CurrentShortTime);
  myFile.print(",");
  myFile.print(LatShort);
  myFile.print(",");
  myFile.print(LongShort);
  myFile.print(",");
  myFile.println(outData);

  // close the file:
  myFile.close();
  
  ++numSaved;
  sprintf(Status,"Number Saved = %d",numSaved);

  LCD_Print();
  
  smartDelay(1000);
}