Arduino SD Card write issue - intermittent logging/creating file

Hey everyone,

Wondering if you could give me a hand. I've built a testing rig that records the time it takes to extend and retract a pneumatic cylinder. I'm running into issues logging the data. When I tested the system out for 4 hours, it logged the data no problem. After an 8 hour run I had an SD with that appeared to stop logging after 3-4 minutes. I couldn't get the system to log any data to the SD card. I troubleshooted, reformatted the SD card, and reset and reloaded the sketch, it appeared to fix the issue. Let it run over night and no data, not even a CSV file this time. I'm a bit at a loss. I checked the HiLetgo spec, its 5V in on VCC, the specs say it had a converter for 3.3 for the SD card itself. CS is wired to P10. I have a wiring diagram but I'm not allowed to upload yet.

System is an Arduino UNO R3
Using a HiLetgo Micro SD card adapter http://www.hiletgo.com/ProductDetail/2158021.html
Samsung 256GB SD card formatted FAT32

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

const int relayPin = 8; //Relay for Control Solenoid
const int ExtPin = A3; //Extension Switch Signal
const int RetPin = A1; //Retraction Switch Signal (home)
const int StSpPin = 7; //Start Stop Button
const int chipSelect = 10; //SD Module CS line

bool isRunning = false; //Flag system running
bool stopRequested = false; //Flag stop request
unsigned long startTime; //Start time
unsigned long extensionTime; //Cyl ext. time
unsigned long retractionTime; //Cyl ret. time

enum CylinderState { IDLE, EXTENDING, RETRACTING, RETURN_TO_HOME }; 
CylinderState state = IDLE; //Initial state to idle

//Initial Pins and SD card
void setup() {
  pinMode(relayPin, OUTPUT); 
  pinMode(ExtPin, INPUT); //4-5V input on trigger
  pinMode(RetPin, INPUT); //4-5V input on trigger
  pinMode(StSpPin, INPUT_PULLUP); //Switch to ground
  Serial.begin(9600);

  if (!SD.begin(chipSelect)) {
    Serial.println("SD Card Missing or Error");
    isRunning = false;
    while (1); //Stop if SD card missing or issue
  }
  Serial.println("System Ready");
}

void loop() {
  // Start top button handling
  if (digitalRead(StSpPin) == LOW) {
    delay(500); //Deadbounce delay
    if (isRunning) {
      stopRequested = true; //Set stop flag if system is running
      Serial.println("Stop Requested");
    } else {
      isRunning = true; //Start system if not running
      Serial.println("Starting System");
      state = digitalRead(RetPin) == HIGH ? IDLE : RETURN_TO_HOME; //Check if cylinder is at home position. Pin 4 high
      delay(2000); // Delay before starting cycle
    }
    while(digitalRead(StSpPin) == LOW);
  }

//States of cylinder control
  if (isRunning) {
    switch (state) {
      case RETURN_TO_HOME:
        Serial.println("Returning to Home");
        // Checking if cylinder is at home position
        if (digitalRead(RetPin) == HIGH) {
          state = IDLE; //Switch to idle if cylinder is at home position
          Serial.println("Reached Home: IDLE State");
        }
        break;

      case IDLE:
      //Check if stop is requested
        if (stopRequested) {
          isRunning = false; //Stop
          stopRequested = false;
          Serial.println("System Stopped");
        } else {
          state = EXTENDING; //Switch to Ext state
          digitalWrite(relayPin, HIGH); //Trigger relay on for pneumatic solenoid
          Serial.println("Extending Cylinder");
          startTime = millis(); //Record start time of extensions
        }
        break;

      case EXTENDING:
        if (digitalRead(ExtPin) == HIGH) {
          extensionTime = millis() - startTime; //Calc extension time
          Serial.print("Extension Time: ");
          Serial.println(extensionTime);
          delay(2000); //Pause between cycle
          state = RETRACTING; //Switch to retracting state
          digitalWrite(relayPin, LOW); //Trigger relay off for pneumatin sold
          Serial.println("Retracting Cylinder");
          startTime = millis(); //Record time of retraction
        }
        break;

      case RETRACTING:
        if (digitalRead(RetPin) == HIGH) {
          retractionTime = millis() - startTime; //Calculte traction time
          Serial.print("Retraction Time: ");
          Serial.println(retractionTime);
          logData(extensionTime, retractionTime); //Record data to SD
          delay(2000); //Pause Between cycle
          state = stopRequested ? RETURN_TO_HOME : IDLE;
          if (stopRequested) {
            Serial.println("Returning to Home after Stop Request");
          }
        }
        break;
    }
  } else {
    digitalWrite(relayPin, LOW); //Relay off when not running
    if(state != IDLE) {
      Serial.println("System Idle - Cylinder Stopped");
    }
  }
}

//Log data to SD
void logData(unsigned long extTime, unsigned long retrTime) {
  File dataFile = SD.open("datalog.csv", FILE_WRITE);
  if (dataFile) {
    dataFile.print(millis());
    dataFile.print(",");
    dataFile.print(extTime);
    dataFile.print(",");
    dataFile.println(retrTime);
    dataFile.close();
    Serial.println("Data Logged to SD Card");
  } else {
    Serial.println("Error with SD Card");
  }
}

have you ruled out any issue with the relays / power that would create noisy signal lines for your arduino ?

The relay and SD VCC are sharing the same 5V. I wouldn't suspected a voltage drop issue on the VCC since the test it run of 4 hours had no problem. I can try and split them apart.

no high voltage line crossing the SPI signal ?

Strange! I see code that opens the file each time you want to log. Always the same file name. Always open the write. BUT NEVER CLOSE the file. So that would seem to indicate only the last block of data is what is on the file.

In your analysis, did you find the beginning data of your test or did you find the last data of the test? Why do you NEVER close the file?

I'm assuming you mean the CS, SCK, MOSI, and MISO on the SD card. No they are each individually wires to the the Pin 10, 11, 12, 13 respectively. I also have that group of wires separate for the 5V VCC.

void logData(unsigned long extTime, unsigned long retrTime) {
  File dataFile = SD.open("datalog.csv", FILE_WRITE);
  if (dataFile) {
    dataFile.print(millis());
    dataFile.print(",");
    dataFile.print(extTime);
    dataFile.print(",");
    dataFile.println(retrTime);
    dataFile.close(); // <========= LOOK HERE 😇😃🥶
    Serial.println("Data Logged to SD Card");
  } else {
    Serial.println("Error with SD Card");
  }
}

I do have a dataFile.close() right above the SD.println. Heres how I tested it. My bench testing I ran one cycle, extending and retracting then checked the card. It logged both times. Leaving the old file, i ran 3 more cycles, and it logged the additional cycles after the first cycles. Deleted the file, and let it run for about a minutes and counted the cycles. It had the correct amount of cycles in the file. Then cleared the data and let it run for 4 hours. Logged 1149 cycles.

Then deleted the file and did my first 8 hour run. When I went to collect the data that's when I first encountered the missing data. It only logged 87 cycles. I connected to the UNO and using the serial monitor I was getting Error with SD Card messages. Checked the wiring for loose connections, ended up reformatting the card and wiping and re-uploading the same sketch. Did some of the same test I did above running a few times and checking, had data again.

Let the machine run over night about 15 hours. No datafile this time. Just a blank folder.

Reading through some other threads they mention using no larger than a 32GB SD card. Since I'm running a 256GB could this be the issue? It seems FAT32 when larger formats as a SDXC, and this can cause an issue. I didnt see any mention of this in any of the SD library documents.

Yes, I did miss the closing of the file. However when you open and write and close the file, you NEVER get more that 512 bytes of data. You need to open ONE TIME in setup() and design your code so you CLOSE the file at the end of your recording period. Perhaps a push button.

Really, why is that. I thought it would be best to open and close the file after every write to keep. That way if there is an issue power loss or something else the file wouldn't be lost. What cause the maximum byte limit, UNO memory size?

it's OK to open - print - close but it's not optimal in terms of timing.

the SD interface writes in 512 bytes chunks. so if you open, write, write, write, .... every time you reach 512 bytes in the buffer, it issues a write to the SD. and then you close and it issues a fine write for whatever is in the buffer and closes the file.

if you just open, write and close, then you dump only a few maeaninful bytes to the SD card but you still send 512 bytes. so it's take longer than needed.

the good thing about opening, writing, closing is that if you get a crash for whatever reason, you don't loose many samples that would have been in the 512 bytes buffer and not written.

I would suggest to use the SDFat library it's more robust (the SD library is a quite old extract from the SDFat library that got stuck in time. SDFat has been evolving)

Only because I went just now to check: the Datalogger example for the SD library does seem to suggest that this is at least acceptable.

I did notice the flush() method in the API just now. Would that be relevant here, or any more efficient?

I think you should find an SDHC card, and see if that makes any difference. My memory is that SD.h doesn't do SDXC. However, I think SdFat.h does.

Ok I think I get what Paul and you are saying now. Each write send 512 bytes regardless. For optimization you can store than write to reduce instances. But you risk loss from a crash.

Method I'm doing is less optimal, but can be safer with data.

For this things case I'm not worried about speed with the system, latency isn't a concern, the testing rig is already cycling more frequently than the real world application. But if it was a concern it would be better to buffer then write.

I actually just bought one. 32GB and formatted Fat 32. Tossed it in. Cant hurt anything just yet.

flushing the data is what close() does before releasing the handle. so with flushing you would save the time it takes to open and close the file every time but you would still send 512 bytes with every flush

1 Like

Have you tried running the Read/Write example sketch in the SD library? Or even the Datalogging example with your setup?

I'm not sure what those are. Can you explain what the examples are?

BTW so far the 32GB SDHC is logging fine. I have my laptop hooked up and using sketch monitor its writing the data. And the card has a CSV file with data in it.