Nano GPS data randomly corrupt when writing to SDcard

Hardware: Arduino Nano, MicroSD card module (SPI), Neo 7m GPS module

When I run the Example "SD - ReadWrite", everything works fine. I have tried multiple SD cards and reformatted them several times. I have tested the setup over USB of course, but also standalone with battery power (both 3.3v and 5v) and still don't get a pattern. The Neo NMEA data from the GPS has always been consistent. So I am confident in the circuit setup.

When I run my sketch, the card consistently initializes fine. However, randomly the Nano writes the GPS data to the SD card as expected, and sometimes it writes garbage characters. Everything in the Serial Monitor displays correctly every time.

I believe it is some kind of memory allocation or memory read/write speed issue but I can't pin the problem down. I've spent about four days reading forum posts here, in Stack Overflow, and web search engines with no concrete answers. Please help if you are familiar with this - it seems to be a very common setup.

Full sketch code and output file attached below.

Thank you.


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

// set up variables and constants
Sd2Card card;
SdVolume volume;
File myFile;
String day, month, year, minute, second, Date, Time, speed, Data;
int hour = 0;
double Latitude, Longitude, Altitude, Dire;
static const int RXPin = 3, TXPin = 4;
static const int chipSelect = 10;

#define MOSI 11
#define MISO 12
#define SCLK 13

// Initialize objects
TinyGPSPlus gps;
SoftwareSerial SerialGPS(RXPin, TXPin);


void setup()
{
  Serial.begin(9600);

  Serial.print("Initializing SD card...");

  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

  Serial.print("Creating GPS_data.txt...");
  myFile = SD.open("GPS_data.txt", FILE_WRITE);
  if (myFile) {
    myFile.println(F("Latitude, Longitude, Altitude, Date, Time, Speed, Course"));
    myFile.close();
    Serial.println("done.");
  } 
  else {
    Serial.println("error opening GPS_data.txt");
  }

SerialGPS.begin(9600);

}

void loop()
{
  // This section displays information every time a new NMEA sentence is correctly encoded.
  while (SerialGPS.available() > 0)
    if (gps.encode(SerialGPS.read()))
      displayInfo();

  if (millis() > 5000 && gps.charsProcessed() < 10)
  {
    Serial.println("No GPS detected: check wiring.");
    while(true);
  }
}


void displayInfo()
{
  if (gps.location.isValid()) {
    Latitude = gps.location.lat();
    Longitude = gps.location.lng();
  }
  else
  {
    Serial.println("INVALID GPS Location");
  }

  if (gps.date.isValid()) {
    month = gps.date.month();
    day = gps.date.day();
    year = gps.date.year();
    Date = month + "/" + day + "/" + year;
  }
  else
  {
    Serial.println("INVALID GPS Date");
  }

  if (gps.time.isValid()) {
    hour = (gps.time.hour() - 4);
    minute = gps.time.minute();
    second = gps.time.second();
    if ((gps.time.minute() < 10) && (gps.time.second() < 10)) {
      Time = ":0" + minute + ":0" + second; }
    else if (gps.time.second() < 10) {
      Time = ":" + minute + ":0" + second; }
    else if (gps.time.minute() < 10) {
      Time = ":0" + minute + ":" + second; }
    else {
      Time = ":" + minute + ":" + second; }
  }
  else
  {
    Serial.println("INVALID GPS Time");
  }

  if (gps.altitude.isValid()) {
    Altitude = gps.altitude.feet();
  }
  else
  {  
    Serial.println("INVALID GPS Altitude");
  }

 if (gps.speed.isValid()) {
    speed = gps.speed.mph();
  }
  else
  {
    Serial.println("INVALID GPS Speed");
  }

 if (gps.course.isValid()) {
    Dire = gps.course.deg();
  }
  else
  {
    Serial.println("INVALID GPS Course");
  }

  Data = Date + ", " + hour + Time + ", " + speed + "mph, ";
  Serial.print(Latitude, 4);
  Serial.print(",");
  Serial.print(Longitude, 4);
  Serial.print(", ");
  Serial.print(Altitude, 0);
  Serial.print("ft, ");
  Serial.print(Data);
  Serial.print(Dire, 0);
  Serial.println("deg");

  myFile = SD.open("GPS_Data.txt", FILE_WRITE); 
  if (myFile) {
    Serial.print(F("Logging information to GPS_data.txt...")); 
    myFile.print(Latitude, 4);
    myFile.print(",");
    myFile.print(Longitude, 4);
    myFile.print(", ");
    myFile.print(Altitude, 0);
    myFile.print("ft, ");
    myFile.print(Data);
    myFile.print(Dire, 0);
    myFile.println("deg");
    Serial.println(F("done."));
    myFile.close();
  } 
  else {
    Serial.println("error writing information to GPS_data.txt");
  }

smartDelay(10000);  
}


static void smartDelay(unsigned long ms)
{
  unsigned long start = millis();
  do 
  {
    while (SerialGPS.available())
      gps.encode(SerialGPS.read());
  } while (millis() - start < ms);
}

GPS_DATA.TXT (681 Bytes)

Use of the String class corrupts memory and leads to program crashes, at least on AVR based Arduinos. There is no need to use Strings, so the best approach is to get rid of them.

The Arduino SD library is inherently unreliable. If you are serious about long term data logging, switch to RPi modules. The difference in reliability is like night and day, even with the small, very cheap Pi Zero (or Zero W series).

Finally, opening a file, writing a few lines, then closing it again is a typical beginner mistake that vastly increases the power draw, the SD card error rate and the time spent writing to the SD card.

Open the file once in setup() and close it again when you are finished collecting data.

It is a good idea to issue myFile.flush() every hour or day to update file pointers, which will reduce data loss upon premature power loss or program malfunction.

2 Likes

try this:

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

#define RXPin  3
#define TXPin  4
#define chipSelect 10
#define MOSI 11
#define MISO 12
#define SCLK 13
const char FileName[] = "GPS_data.txt";

// Initialize objects
TinyGPSPlus gps;
SoftwareSerial SerialGPS(RXPin, TXPin);

void setup()
{
  Serial.begin(9600);

  Serial.print("Initializing SD card...");

  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");
  if (!SD.exists(FileName)) {
    Serial.print("Creating GPS_data.txt...");
    File myFile = SD.open(FileName, FILE_WRITE);
    if (myFile) {
      myFile.println(F("Latitude, Longitude, Altitude, Date, Time, Speed, Course"));
      myFile.close();
      Serial.println("done.");
    }
    else Serial.println("error writing file");
  }
  SerialGPS.begin(9600);
  Serial.end();
}

void loop()
{
  // This section displays information every time a new NMEA sentence is correctly encoded.
  while (SerialGPS.available() > 0)    if (gps.encode(SerialGPS.read()))      displayInfo();
}

void displayInfo()
{
  static byte month = 0;
  static byte day = 0;
  static uint16_t year = 0;
  static byte hour = 0;
  static byte minute = 0;
  static byte second = 0;
  char Latitude[10], Longitude[10], Altitude[10], Dire[10],speed[10];

  if (gps.location.isValid()) {
    dtostrf(gps.location.lat(), 4, 4, Latitude);
    dtostrf(gps.location.lng(), 4, 4, Longitude);
  }

  if (gps.date.isValid()) {
    month = gps.date.month();
    day = gps.date.day();
    year = gps.date.year();
  }

  if (gps.time.isValid()) {
    hour = (gps.time.hour() - 4);
    minute = gps.time.minute();
    second = gps.time.second();
  }

  if (gps.altitude.isValid())dtostrf(gps.altitude.feet(), 4, 0, Altitude);
  if (gps.speed.isValid())dtostrf(gps.speed.mph(), 4, 2, speed);
  if (gps.course.isValid())dtostrf( gps.course.deg(), 4, 0, Dire);

  char Data[200];
  sprintf(Data, "%s,%s, %sft, %02u/%02u/%u, %02u:%02u:%02u, %smph, %sdeg",
          Latitude,
          Longitude,
          Altitude,
          day,
          month,
          year,
          hour ,
          minute,
          second,
          speed,
          Dire
         );

  File myFile = SD.open(FileName, FILE_WRITE);
  if (myFile) {
    myFile.println(Data);
    myFile.close();
  }
}
1 Like

I have seen similar things when the power supply impedance is to hi or the voltage is collapsing. If you can put a scope on the power lines and see what they yield.

kolaha, thank you for the interesting re-write - impressive!

However, the SD card initializes but the sketch never proceeds to open the file. The GPS LED is flashing indicating a fix. No other information is available.

Serial Monitor output:
"Initializing SD card...initialization done."

That can mean the GPS has just got time sync, which is not the same as a location fix.

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

#define RXPin  3
#define TXPin  4
#define chipSelect 10
#define MOSI 11
#define MISO 12
#define SCLK 13
#define FileName  F("GPS_data.txt")

// Initialize objects
TinyGPSPlus gps;
SoftwareSerial SerialGPS(RXPin, TXPin);

void setup()
{
  Serial.begin(115200);

  Serial.print(F("Initializing SD card..."));

  if (!SD.begin(chipSelect)) {
    Serial.println(F("initialization failed!"));
    while (1);
  }
  Serial.println(F("initialization done."));
  if (!SD.exists(FileName)) {
    Serial.print(F("Creating GPS_data.txt..."));
    File myFile = SD.open(FileName, FILE_WRITE);
    if (myFile) {
      myFile.println(F("Latitude, Longitude, Altitude, Date, Time, Speed, Course"));
      myFile.close();
      Serial.println(F("done."));
    }
    else Serial.println(F("error writing file"));
  }
  SerialGPS.begin(9600);
 }

void loop()
{
  // This section displays information every time a new NMEA sentence is correctly encoded.
  while (SerialGPS.available() > 0)    if (gps.encode(SerialGPS.read()))      displayInfo();
}

void displayInfo()
{
  static byte month = 0;
  static byte day = 0;
  static uint16_t year = 0;
  static byte hour = 0;
  static byte minute = 0;
  static byte second = 0;
  char Latitude[10], Longitude[10], Altitude[10], Dire[10],speed[10];

  if (gps.location.isValid()) {
    dtostrf(gps.location.lat(), 4, 4, Latitude);
    dtostrf(gps.location.lng(), 4, 4, Longitude);
  }

  if (gps.date.isValid()) {
    month = gps.date.month();
    day = gps.date.day();
    year = gps.date.year();
  }

  if (gps.time.isValid()) {
    hour = (gps.time.hour() - 4);
    minute = gps.time.minute();
    second = gps.time.second();
  }

  if (gps.altitude.isValid())dtostrf(gps.altitude.feet(), 4, 0, Altitude);
  if (gps.speed.isValid())dtostrf(gps.speed.mph(), 4, 2, speed);
  if (gps.course.isValid())dtostrf( gps.course.deg(), 4, 0, Dire);

  char Data[200];
  sprintf(Data, "%s,%s, %sft, %02u/%02u/%u, %02u:%02u:%02u, %smph, %sdeg",
          Latitude,
          Longitude,
          Altitude,
          day,
          month,
          year,
          hour ,
          minute,
          second,
          speed,
          Dire
         );

  File myFile = SD.open(FileName, FILE_WRITE);
  if (myFile) {
    myFile.println(Data);
    myFile.close();
  }
  Serial.println(Data);
}

kohola, thank you again for your working with me on this. It's really close to working now!

I figured out the card write problem:

if (!SD.exists(FileName)) {
    Serial.print("Creating GPS_data.txt...");
    File myFile = SD.open(FileName, FILE_WRITE);

needed to be:
if (SD.exists(FileName)) {      // **!**SD replaced with SD

Aside from some other minor tweaks to the sketch add some Serial Monitor troubleshooting feedback, it's all working except the Altitude is now consistently not read/writing to the file. The data structure seems no different from Lat, Lon, Speed, and Course so I'm not sure why just the Altitude is writing a weird value each time.

Could it be something in this line?
if (gps.altitude.isValid())dtostrf(gps.altitude.feet(), 4, 0, Altitude);

Please see attached text file for the current output.

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

#define RXPin  3
#define TXPin  4
#define chipSelect 10
#define MOSI 11
#define MISO 12
#define SCLK 13
const char FileName[] = "GPS_data.txt";

// Initialize objects
TinyGPSPlus gps;
SoftwareSerial SerialGPS(RXPin, TXPin);

void setup()
{
  Serial.begin(115200);

  Serial.print("Initializing SD card...");

  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

  if (SD.exists(FileName)) {
    Serial.print("Creating GPS_data.txt...");
    File myFile = SD.open(FileName, FILE_WRITE);
    if (myFile) {
      myFile.println(F("Latitude, Longitude, Altitude, Date, Time, Speed, Course"));
      myFile.close();
      Serial.println("done.");
    }
    else Serial.println("error writing file");
  }

  SerialGPS.begin(9600);

}

void loop()
{
  // This section displays information every time a new NMEA sentence is correctly encoded.
  while (SerialGPS.available() > 0)
     if (gps.encode(SerialGPS.read()))
        displayInfo();
  
  if (millis() > 5000 && gps.charsProcessed() < 10)
  {
    Serial.println("No GPS detected: check wiring.");
    while(true);
  }
}

void displayInfo()
{
  static byte month = 0;
  static byte day = 0;
  static uint16_t year = 0;
  static byte hour = 0;
  static byte minute = 0;
  static byte second = 0;
  char Latitude[10], Longitude[10], Altitude[10], Dire[10], Speed[10];

  if (gps.location.isValid()) {
    dtostrf(gps.location.lat(), 4, 4, Latitude);
    dtostrf(gps.location.lng(), 4, 4, Longitude);
  }
  else {Serial.println("INVALID GPS Location"); }

  if (gps.date.isValid()) {
    month = gps.date.month();
    day = gps.date.day();
    year = gps.date.year();
  }
  else {Serial.println("INVALID GPS Date"); }

  if (gps.time.isValid()) {
    hour = (gps.time.hour() - 4);
    minute = gps.time.minute();
    second = gps.time.second();
  }
  else {Serial.println("INVALID GPS Time"); }

  if (gps.altitude.isValid())dtostrf(gps.altitude.feet(), 4, 0, Altitude);
  else  {Serial.println("INVALID GPS Altitude");  }
  if (gps.speed.isValid())dtostrf(gps.speed.mph(), 4, 2, Speed);
  else  {Serial.println("INVALID GPS Speed");  }
  if (gps.course.isValid())dtostrf(gps.course.deg(), 4, 0, Dire);
  else  {Serial.println("INVALID GPS Course");  }

  char Data[200];
  sprintf(Data, "%s,%s, %sft, %02u/%02u/%u, %02u:%02u:%02u, %smph, %sdeg",
    Latitude, Longitude, Altitude, day, month, year, hour, minute, second, Speed, Dire);

  if (SD.exists(FileName)) {
    File myFile = SD.open(FileName, FILE_WRITE);
    if (myFile) {
      myFile.println(Data);
      Serial.println(F("File written."));
      myFile.close();
    }
    else {Serial.println("error writing information to GPS_data.txt"); }
  }

  smartDelay(5000);

}


static void smartDelay(unsigned long ms)
{
  unsigned long start = millis();
  do 
  {
    while (SerialGPS.available())
      gps.encode(SerialGPS.read());
  } while (millis() - start < ms);
}

GPS_Data2.txt (378 Bytes)

this is not a problem, this should be so and create new file only if it not exist

kolaha, that makes sense now.

What do you think of "Altitude" and "GPS_Data2.txt"? I swapped out a new Nano and a new SD card module as a troubleshooting step but that particular problem still exists.

It's almost working!

if (gps.altitude.isValid()){
  float Alt = float(gps.altitude.feet());
  dtostrf(Alt , 4, 0, Altitude);
  Serial.println(Alt );
  Serial.println(Altitude);
}else Serial.println("Altitude not valid);

After lots of experimentation, I got it working. The solution was to reduce the total size of the "Data" string thusly:

  1. Change the char allocation sizes of Altitude, Dire, and Speed from [10] to [6].
  2. Adjust the "Data" total down by combining the newly reduced values ((10 - 4) x 3), multiplied by a factor of 2, then subtracted from the Data total. 200 - ((6 x 3) x 2) = 164.

I don't know if the root cause was a bus speed or memory issue, but this change consistently eliminated the corruption when writing the "Data" string to the file.

Why this specific formula? I don't know - other numerical variations threw errors when compiled. I suspect 'sprintf' had something to do with it once the Date and Time were thrown in, but I'm not familiar with C++ so I'd have to study that specific command syntax in much more detail.

Thank you so much for working with me on this!

char Latitude[10], Longitude[10], Altitude[6], Dire[6], Speed[6];

char Data[164];

For Nano - most probably the memory

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