SD Card Turns Off After a Couple Hours

Hi! I have an issue with my SD Card that I just can't figure out. I have tried this code with an arduino mega and leonardo and they both have stopped uploading data to the SD card after an hour or two when the computer is unplugged from the device. The board is connected to a 5V ready mico-sd breakout board, a sparkfun rtc module, an sdi12 sensor, and a digital temperature sensor. I have tried using the SD card library and have tried to change everything I can from strings to char but the sdi12 sensor requires some things in String format. Let me know if you have any idea what could be wrong.
Here is my code:

#include <Arduino.h>
#include <EEPROM.h>
#include <Wire.h>
#include <SDI12.h>
#include <SPI.h>
#include <OneWire.h> 
#include <DallasTemperature.h>
#include <SdFat.h>
#include <SparkFunDS1307RTC.h> 

#define ONE_WIRE_BUS 9 
#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */
#define DATA_PIN 11         /*!< The pin of the SDI-12 data bus */
#define POWER_PIN -1        /*!< The sensor power pin (or -1 if not switching power) */

extern unsigned int __bss_end;
extern unsigned int __heap_start;
extern void *__brkval;

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
SDI12 mySDI12(DATA_PIN);
char c[] = "";
const int chipSelect = 53;
int k = 0;
const char space[] = " "; //used fof all the spaces
static int8_t lastSecond = -1;
//char fileName[9] = "data.txt";
String temp = "";
float result = 0;
SdFat sd;
SdFile file; 
uint8_t numSensors = 0;
// keeps track of active SDI12 addresses
bool isActive[64] = {
  0,
};
//------------free memory function-------------
int freeMemory() {
  int free_memory;
  
  if((int)__brkval == 0)
    free_memory = ((int)&free_memory) - ((int)&__bss_end);
  else
    free_memory = ((int)&free_memory) - ((int)__brkval);
  
  return free_memory;
}
//--------------------------decimal to char--------------------------
char decToChar(byte i) {
  if (i < 10) return i + '0';
  if ((i >= 10) && (i < 36)) return i + 'a' - 10;
  if ((i >= 36) && (i <= 62))
    return i + 'A' - 36;
  else
    return i;
}
//---------------------checks active sdi12 sensors------------------------
boolean checkActive(char i) {
  String myCommand = "";
  myCommand        = "";
  myCommand += (char)i;  // sends basic 'acknowledge' command [address][!]
  myCommand += "!";

  for (int j = 0; j < 3; j++) {  // goes through three rapid contact attempts
    mySDI12.sendCommand(myCommand);
    delay(100);
    if (mySDI12.available()) {  // If we here anything, assume we have an active sensor
      mySDI12.clearBuffer();
      return true;
    }
  }
  mySDI12.clearBuffer();
  return false;
}
//---------------char to string-----------
String convertToString(char* a)
{
    int i;
    String s = "";
    int size = strlen(a);
    for (i = 0; i < size-2; i++) {
        s = s + a[i];
    }
    return s;
}
//----------sdi12 repeated function for 1M1!--------------
String getsdi(char i, String meas_type, char sdiResponse, uint8_t cmd_number){
    
    int numResults = 0;
    
    mySDI12.clearBuffer();
    String command = "";
    command += i;
    command += "M";
    command += meas_type;
    command += "!";  
    //Serial.println(command);
    mySDI12.sendCommand(command);
    delay(100);
      
    String sdi= mySDI12.readStringUntil('\n');
    Serial.print(sdi);
    sdi.trim();

    String sdiResponse2 = sdi.substring(0,1);
    Serial.print(sdiResponse2); //address
    Serial.print(", ");
    
    uint8_t wait = sdi.substring(1, 4).toInt();
    Serial.print(wait);
    Serial.print(", ");
    sdiResponse2 = sdiResponse2 + ", " + sdi.substring(1, 4); //address, wait_time
    
    numResults = sdi.substring(4).toInt();
    Serial.print(numResults);
    Serial.print(", ");
    sdiResponse2 = sdiResponse2 + ", " + sdi.substring(4); //address, wait_time, number of results

    unsigned long timerStart = millis();
     while ((millis() - timerStart) < (1000UL * (wait + 1))) {
    if (mySDI12.available())  // sensor can interrupt us to let us know it is done early
    {
      Serial.print(millis() - timerStart);
      Serial.print(", ");
      mySDI12.clearBuffer();
      break;
    }
  }
      delay(30);
      mySDI12.clearBuffer();
      if(numResults >0) {
        return getResults(i, numResults, sdiResponse2, cmd_number);
      }
      String sdiString = convertToString(sdiResponse);
     
      return sdiString;
}
///---------1D0! part of sdi12 response--------------------------------------------------------
String getResults(char i, int resultsExpected, String sdiResponse, uint8_t cmd_number) {
      result = 0;
      int pointer = 0;
      int a = 0;
      String sdiString;
      String sdiResponse2;
      uint8_t resultsReceived = 0; 
      while(resultsReceived < resultsExpected && cmd_number<=9){
           String command = "";
           command = "";
           command += i;
           command += "D";
           command += cmd_number;
           command += "!";  
           mySDI12.sendCommand(command);

          uint32_t start = millis();
          while(mySDI12.available() < 3 && (millis() - start) <1500){}
          mySDI12.read();
          char c = mySDI12.peek();
          if(c == '+') {mySDI12.read(); }
          
          while(mySDI12.available()) {
            char c = mySDI12.peek();
            if(c == '-' || (c >= '0' && c <= '9') || c == '.'){
              float result = mySDI12.parseFloat(SKIP_NONE);
              Serial.print(String(result, 10));
              if(pointer == 0)
              { 
                sdiString = sdiResponse;
                sdiResponse2 = sdiString + ", " + String(result, 5);
              }else{
                sdiResponse2 = (sdiResponse2) + ", " + String(result, 5);
              }
              if(result != -9999) {resultsReceived++; }
            }else if(c== '+'){
              mySDI12.read();
              Serial.print(", ");
              pointer = pointer + 1;
            }else{
              mySDI12.read(); 
            }
            delay(10);
          }
          if(resultsReceived < resultsExpected) {Serial.print(", ");}
          cmd_number=cmd_number + 1;
        }
    mySDI12.clearBuffer();
    return sdiResponse2;
}
//-----------------------------------------------------------------
String printTime(){
  String timee = String(rtc.hour()) + ":";
  //rtc.autoTime();
  if (rtc.minute() < 10){
    timee.concat("0"); 
  }
    timee.concat(String(rtc.minute()));
    timee.concat(":"); 
    
  if (rtc.second() < 10){
    timee.concat("0");
  }
  
  timee.concat(String(rtc.second()));
  timee.concat(" , ");
  timee.concat(String(rtc.month()));
  timee.concat("/");
  timee.concat(String(rtc.date()));
  timee.concat("/");
  timee.concat(String(rtc.year()));
 
  return(timee);
}  
//----------------------------------------------
void printInfo(char i) {
  String command = "";
  command += (char)i;
  command += "I!";
  mySDI12.sendCommand(command);
  delay(100);

  String sdiResponse = mySDI12.readStringUntil('\n');
  sdiResponse.trim();
  // allccccccccmmmmmmvvvxxx...xx<CR><LF>
  
  Serial.print(sdiResponse.substring(0, 1));  // address
  Serial.print(", ");
  Serial.print(sdiResponse.substring(1, 3).toFloat() / 10);  // SDI-12 version number
  Serial.print(", ");
  Serial.print(sdiResponse.substring(3, 11));  // vendor id
  Serial.print(", ");
  Serial.print(sdiResponse.substring(11, 17));  // sensor model
  Serial.print(", ");
  Serial.print(sdiResponse.substring(17, 20));  // sensor version
  Serial.print(", ");
  Serial.print(sdiResponse.substring(20));  // sensor id
  Serial.print(", ");
  
}
//-----------------------------------------------------
void setup(void) {
  char on[23] = "Opening SDI-12 bus...";
  char powerup[23] = "Powering up sensors...";
  char initialize[25] = "Initializing SD card...";
  char fail[27] = "Card failed or not present";
  char success[] = "Card initiliazed.";
  char num_sense[] = "Total number of sensors: ";
  int k =0;
  Serial.begin(SERIAL_BAUD);
  Wire.begin();
  delay(300);
  sensors.begin();
  mySDI12.begin();
  Serial.println(on);


   Serial.print(initialize);
  //see if card is present
  if(sd.begin(53, SPI_HALF_SPEED)){
    Serial.println(success);
    }else{
    Serial.println(fail);
    }
  delay(2000);
  
  rtc.begin();
  rtc.autoTime();
  //rtc.setTime(0, 10, 35, 04, 06, 07, 2022);
  delay(2000);
  
  //SPI.begin();
  
  while(!Serial)
    ;
 
  delay(500);

   if (POWER_PIN > 0) { //if power pin is on (>0) then it should be put as that
    Serial.println(powerup);
    pinMode(POWER_PIN, OUTPUT);
    digitalWrite(POWER_PIN, HIGH); //writing a high or low value to the power pin
    delay(200);
  }
  
  for (byte i = 0; i < 62; i++) {
    char addr = decToChar(i);
    if (checkActive(addr)) {
      numSensors++;
      isActive[i] = 1;
      printInfo(addr);
      Serial.println();
    }
  }
  Serial.print(num_sense);
  Serial.println(numSensors);

}

void loop(void ) {
    char wiper[] = "";
    const char rtcFail[22] = "RTC get datetime fail";
    int hour, minute, second, day, month, year = 0;
    String datatime = "";
    
    char myCommand[4] = "1M!";
    char sdiResponse[100];
    if(file.open("data.csv", O_CREAT | O_WRITE | O_APPEND)){
      if(k==0){
        file.println("Time (hour:min:sec, month/day/year, Temperature, sensor address, est. measuring time, number of measurements, measurement 1, measurement 2, SRAM available");
        }
      rtc.update();
      datatime = printTime();
      
      file.print(datatime);
      Serial.print(datatime);

      file.print(", ");
      Serial.print(", ");

      sensors.requestTemperatures();
      delay(1000);
      float temp = sensors.getTempCByIndex(0);
      file.print(temp);
      Serial.print(temp);

      file.print(", ");
      Serial.println(", ");
      digitalWrite(LED_BUILTIN, HIGH);

      String commands[] = {"", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
      for (uint8_t a = 0; a < 1; a++) {
       // measure one at a time
      for (byte i = 0; i < 62; i++) {
        char addr = decToChar(i);
        if (isActive[i]) {
           Serial.print(millis() / 1000);
          Serial.print(millis());
          Serial.print(", ");
          String temp0 = getsdi(addr, commands[a], sdiResponse, 0);
          int str_len = temp0.length() + 1;
          temp0.toCharArray(sdiResponse, str_len);
      }
    }
  }
      Serial.print(sdiResponse);
      file.print(sdiResponse);
      
      Serial.print(", ");
      file.print(", ");

      int memory_available = freeMemory(); 
      Serial.print(memory_available);
      file.print(memory_available);

      file.println();
      if(k==100){
        k = 0;
        file.sync();
      }
      file.close();
      Serial.println();
      k= k+1;
      }else{
        Serial.println("Failed to open the file");
                
        while(!sd.begin()){
          delay(1000);
          Serial.println("Starting SD");
          }
       }
       
    delay(30000);
   }
  

Monitor the time-to-sleep for a pattern (or none).

Does it always crash at the same point - the same number of records saved?

If you mean that this does not happen when your circuit is powered from the computer via USB, then I would suspect it is a power problem. Can you post a diagram showing how everything is powered? If you are powering the Arduinos with a higher voltage at Vin, then powering the peripherals from the 5V pin, then the onboard 5V regulators may be overheating. If you power from USB, those regulators aren't used.

It crashes at different points each time and from what I can tell there isn't a pattern. The SRAM availability stays constant for the whole program and it doesn't stop outputting sensor values it just stops. It also seems like the arduino continues running the code since I connected it to an LED that lights up when it is gathering measurements and that continues running over night even if the SD logging stops short. Also, the last couple tests have crashed after 166 measurements, 150 measurements, 130 measurements, and 123 measurements. I am powering the arduino with a 12V battery and am currently running a test with it connected to the usb to see if it logs more data to the SD card so will let you know how that goes. I have attached a circuit diagram but let me know if there is anything unclear.

So I did the test connected to the USB and it stopped after 151 entries again. Since I had the serial monitor up, I checked after and there were still values being written to the serial monitor but not the SD card once it stopped.

Ok, I think that settles the power issue. Sorry, but I have nothing else to suggest.

The String class might be causing memory fragmentation and eventual heap overflow = crash.

1 Like

If it is causing this wouldn't the SRAM available be decreasing? I currently have a function running that tells me the amount of SRAM available which I thought would help give me a heads-up if the String class is causing overflow. Let me know your thoughts.

Is that SDI-12 - Arduino Reference (and GitHub - EnviroDIY/Arduino-SDI-12: An Arduino library for SDI-12 communication with a wide variety of environmental sensors. This library provides a general software solution, without requiring any additional hardware.) ?

Which things?

sendCommand has a cString alternative void sendCommand(const char* cmd, int8_t extraWakeTime = SDI12_WAKE_DELAY).

You have at least one bug in your code; if you set the warning level to all, you will be able to find it.
When you call String getsdi(char i, String meas_type, char sdiResponse, uint8_t cmd_number) { you pass it a pointer to an array instead of a character in this line (String temp0 = getsdi(addr, commands[a], sdiResponse, 0);).

The assocciated warning is

C:\Users\sterretje\AppData\Local\Temp\arduino_modified_sketch_182995\sketch_jul25a.ino: In function 'void loop()':

C:\Users\sterretje\AppData\Local\Temp\arduino_modified_sketch_182995\sketch_jul25a.ino:345:66: warning: invalid conversion from 'char*' to 'char' [-fpermissive]

           String temp0 = getsdi(addr, commands[a], sdiResponse, 0);

You will also get an other related warning

C:\Users\sterretje\AppData\Local\Temp\arduino_modified_sketch_182995\sketch_jul25a.ino: In function 'String getsdi(char, String, char, uint8_t)':

C:\Users\sterretje\AppData\Local\Temp\arduino_modified_sketch_182995\sketch_jul25a.ino:135:49: warning: invalid conversion from 'char' to 'char*' [-fpermissive]

   String sdiString = convertToString(sdiResponse);

Things like char wiper[] = ""; are, although not used, worrying; if you use it, be aware that it's an character array with only one element. Any attempt to do modify it will very likely result in undefined behaviour.

Regarding the use of String (capital S). When you pass a String object to a function, the complete object is placed on the stack. So that's e.g. 81 bytes for a message plus an additional 6 bytes. If you instead use a reference, you will only place the reference on the stack which is significantly smaller (I think 2 bytes on AVR).

Some advice:

  1. Get rid of all unused variables. It will clean out the list of warnings,
  2. Never use single character variable names in the global space; it's a mission to search for them.

Hi! I am trying to follow your suggestions but am having a slight issue. For the check active function, I have a char as the input. It sends the correct values (as checked by serial.print) but when I concatenate it with another char then it just outputs gibberish. I have tried looking online but honestly am pretty lost. I have pasted the code below. Let me know if you have a suggestion to fix this. Thank you so much!

boolean checkActive(char i) {
  char Command2[3];
  strcat(Command2, i);
  strcat(Command2, "!");
  for (int j = 0; j < 3; j++) {  // goes through three rapid contact attempts
    mySDI12.sendCommand(Command2);
    delay(100);
    if (mySDI12.available()) {  // If we here anything, assume we have an active sensor
      mySDI12.clearBuffer();
      return true;
    }
  }
  mySDI12.clearBuffer();
  return false;
}
  1. Command2 is not initialised so it's not a cString but an array of characters.
  2. You can not strcat a character to a cString like you do with String objects.
  3. You should not strcat a cString to an uninitialised cString.
  char Command2[3];
  memset(Command2, '\0', sizeof(Command2));
  Command2[0] = i;
  strcat(Command2, "!");

The memset line initialises the array with terminating NUL characters.
The 3rd line copies the variable i to the first position of the array.

Thank you! That helped clear up a lot. I have everything working but the getresults function. The if else statements are currently just sending it to the area marked "3" with serial print statements which I can't seem to figure out why. I have attached my code to see if there is something you see that I missed. I have tried a bunch but it just keeps declaring the command about 10 times then just going to the third branch of the if else statement so it doesn't actually get the data being outputted by the sdi12 sensor. Let me know your thoughts. Thank you for all of the guidance.

void getResults(char *i, int resultsExpected, char *sdiResponse, uint8_t cmd_number, int count) {
      int pointer = 0;
      uint8_t resultsReceived = 0; 
      cmd_number = 0;
      while(resultsReceived < resultsExpected && cmd_number<=9){
           
           char Command2[4];
           memset(Command2, '\0', sizeof(Command2));
           Command2[0]= i;
           strcat(Command2, "D");
           strcat(Command2, "0"); //can be made to cmd_number
           strcat(Command2, "!");
           //Serial.print(Command2);
            mySDI12.sendCommand(Command2, WAKE_DELAY);
            

            uint32_t start = millis();
            while (mySDI12.available() < 3 && (millis() - start) < 1500) {}
            mySDI12.read(); 
            char c = mySDI12.peek(); 
            Serial.print(c);
            //Serial.print(String(c));// check if there's a '+' and toss if so
            if (c == '+') { mySDI12.read(); }

            /*
            int index = 0;
            char sdi2[15];
            memset(sdi2, '\0', sizeof(sdi2));
            sdi2[index] = mySDI12.peek();
            
            if(sdi2[index] == '+') {
              Serial.print("plus read ");
              mySDI12.read();
              index++;
            }
          */
          while(mySDI12.available()) {
            //sdi2[index] = mySDI12.peek();
            //Serial.print(sdi2[index]);
            //Serial.print(c);
            char c = mySDI12.peek();
            Serial.print(c);
            //if(sdi2[index] == '-' || (sdi2[index] >= '0' && sdi2[index] <= '9') || sdi2[index] == '.'){
             if (c == '-' || (c >= '0' && c <= '9') || c == '.') { 
              float result = mySDI12.parseFloat(SKIP_NONE);
              //Serial.print(String(result));
              Serial.print(" 1 ");
              if(pointer == 0)
              { 
                char temp[10] = {' '};
                int result_int = (int)result;
                float result_float = (abs(result) - abs(result_int)) * 10000;
                int result_fra = (int)result_float;
                sprintf(temp, "%d.%d", result_int, result_fra);
                //sdiResponse[count] = String(result, 3);
                strcat(sdiResponse, temp);
                Serial.print(sdiResponse[count]);
                count++;
              }else{
                sdiResponse[count] =',';
                count++;
                sdiResponse[count] = ' ';
                count++;
                sdiResponse[count] = result;
                Serial.print(sdiResponse[count]);
                count++;
              }
              if(result != -9999) {resultsReceived++; }
            }else if(c== '+'){
              mySDI12.read();
              Serial.print(", ");
              pointer = pointer + 1;
              Serial.print("2");
            }else{
              mySDI12.read();
              Serial.print("3"); 
            }
            //index++;
            delay(10);
          }
          if(resultsReceived < resultsExpected) {Serial.print(", ");}
          cmd_number=cmd_number + 1;
        }
    mySDI12.clearBuffer();
}

Can you give one or more examples of typical expected output from the sensor that you're reading.

//Edit

  1. Is there a line ending in the message (e.g. '\n')?
  2. I notoce that you do a number of reads andthrow the result away; is that intentionally?

Yes that is intentional. So usually the response should be (+ value + value) so the program should through out the first plus before the while(sdi.available) loop. Then peeking into the value should give the first digit and that should cause the program to go into the first if loop and save the full value as a float then add it to the cstring created earlier. Then it should go to the second loop since it is a plus but that doesn't need to be added to the cstring. Then it will go into the first if loop again to write the second value. I attached the example code that I originally was looking at if that is helpful.

bool getResults(char i, int resultsExpected) {
  uint8_t resultsReceived = 0;
  uint8_t cmd_number      = 0;
  while (resultsReceived < resultsExpected && cmd_number <= 9) {
    String command = "";
    // in this example we will only take the 'DO' measurement
    command = "";
    command += i;
    command += "D";
    command += cmd_number;
    command += "!";  // SDI-12 command to get data [address][D][dataOption][!]
    Serial.print(command);
    mySDI12.sendCommand(command, WAKE_DELAY);

    uint32_t start = millis();
    while (mySDI12.available() < 3 && (millis() - start) < 1500) {}
    mySDI12.read();           // ignore the repeated SDI12 address
    char c = mySDI12.peek(); 
    Serial.print(c);
    //Serial.print(String(c));// check if there's a '+' and toss if so
    if (c == '+') { mySDI12.read(); }

    while (mySDI12.available()) {
      char c = mySDI12.peek();
      Serial.print(c);
      if (c == '-' || (c >= '0' && c <= '9') || c == '.') {
        Serial.print(" 1 ");
        float result = mySDI12.parseFloat(SKIP_NONE);
        Serial.print(String(result, 10));
        if (result != -9999) { resultsReceived++; }
      } else if (c == '+') {
        Serial.print(" 2 ");
        mySDI12.read();
        Serial.print(", ");
      } else {
        Serial.print(" 3 ");
        mySDI12.read();
      }
      delay(10);  // 1 character ~ 7.5ms
    }
    if (resultsReceived < resultsExpected) { Serial.print(", "); }
    cmd_number++;
  }
  mySDI12.clearBuffer();

  return resultsReceived == resultsExpected;
}

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