Create incremental file names each time datalogging is started.

Hello all, I'm having a bit of trouble working out how I can get this to work the way I need, since most questions I can find that were asked previously don't quite work the way I have my device set up.

Basically, it monitors and prints out temperature to an 8x2 character LCD, however, at any point you can flick a switch, and every 5 seconds, it logs temperature data to a text file on the SD card, simple enough, then you just stick it into excel, and graph it out. (It doesn't have an RTC, so no timestamp, but I am not sure how you'd implement that, aside from using the millis counter and incrementing some other counter, not a priority right now).

Anyhow, since this can run continuously and you can turn on and off logging at will, it just sticks everything into one file, which is fine for single use, but I'd like to add a bit more functionality.

Basically, each time you start logging, it would check for the files on the card, and if it saw files up to "Log 0033", it would then change over the putting the current info into "log 0034", and so forth. It might also work with another text file that keeps the log entries, if you can read the last line in a file.

Anyhow the main thing that has me a little stumped, is that since I have this as a function in the main loop, I will need a way for it to check the files and increment the file name each time I start logging. I figure a variable that will change from true or false will let me bypass the file checking and creation once it's underway, but incrementing the file is the difficult bit.

Here's the section of code which takes care of the datalogging.

logstate = digitalRead(2);   // read the input pin
  if (logstate == LOW) {
    if (currentMillis - sdpreviousMillis >= sdinterval) {  //Write to SD card every 5 seconds.
    sdpreviousMillis = currentMillis;
    digitalWrite(A1, HIGH);
    myFile = SD.open("Log.txt", FILE_WRITE);
    myFile.print(celsius);
    myFile.println(" C ");
    myFile.close();
    delay(5);
    digitalWrite(A1, LOW);
   }
  }

and here is the code in it's entirety (still needs to be cleaned up a bit, and some of the annotations don't match the values)

#include <OneWire.h>
#include <LiquidCrystal.h>
#include <SPI.h>
#include <SD.h>

unsigned long owpreviousMillis = 0;
const long owinterval = 750;
//---------------------------------------------------------------------------------- One wire temperature sensor stuff
OneWire  ds(A3);                 //Setting one wire bus pin
byte i;                  
byte present = 0;
byte type_s;
byte data[12];
byte addr[8];
float celsius, fahrenheit;      //Variables for celsius and farenheit conversions
unsigned long previousMillis = 0;
float lasttemp;                 //Variable for last temperature
float delta;                    //Variable for temperaturechange
const long interval = 750;     //Check once every 1 second

//---------------------------------------------------------------------------------- End of one wire temperature sensor stuff.
                                
LiquidCrystal lcd(8, 7, 5, 6, 3, 4); //Defining pins for the LCD

//---------------------------------------------------------------------------------- SD card stuff
const int chipSelect = 10;              //Setting SD chip select pin (obviously)
File myFile;
unsigned long sdpreviousMillis = 0;
const long sdinterval = 4245;           //SD card write interval

int logstate = 0;

//-----------------------------------------------------------------------------------End of pre-setup


void setup(void) {
  pinMode(9, OUTPUT);     //Backlight Pin
  digitalWrite(9, LOW);
  pinMode(2, INPUT);      //Momentary button to toggle logging
  digitalWrite(2, HIGH);  //Set internal pull-up to stop floating noise.
  pinMode(A2, OUTPUT);    //Green LED for temperature stability indication
  pinMode(A1, OUTPUT);    //Red LED for sd card activity indication
  Serial.begin(115200);
//------------------------- Testing LCD and LED's, displaying version number and checking for SD card.
  lcd.begin(8, 2);
  lcd.setCursor(0,0);
  lcd.print("STARTING");
  lcd.setCursor(0, 1);
  lcd.print("VER. 1.6");
  digitalWrite(A1, HIGH);
  digitalWrite(A2, HIGH);
  delay(1000);
  digitalWrite(A1, LOW);
  digitalWrite(A2, LOW);
  lcd.clear();

  if (!SD.begin(10)) {                    // Check for SD card
    digitalWrite(A1, HIGH);
    lcd.setCursor(0,0);
    lcd.print("SD CHECK");
    lcd.setCursor(0,1);
    lcd.print("  FAIL  ");
    delay(1000);
    lcd.clear();
    digitalWrite(A1, LOW);
  } else {
    delay(1000);
    digitalWrite(A1, HIGH);
    lcd.setCursor(0,0);
    lcd.print("SD CHECK");
    lcd.setCursor(0,1);
    lcd.print("  PASS  ");
    delay(1000);
    lcd.clear();
    digitalWrite(A1, LOW);
  }  
//----------------------------------- Setting up one wire temperature sensor.
  if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }
  Serial.print("ROM =");
  for( i = 0; i < 8; i++) {
    Serial.write(' ');
    Serial.print(addr[i], HEX);
  }
  if (OneWire::crc8(addr, 7) != addr[7]) {
    Serial.println("CRC is not valid!");
    return;
  }
  Serial.println();

  // the first ROM byte indicates which chip
  switch (addr[0]) {
  case 0x10:
    Serial.println("  Chip = DS18S20");  // or old DS1820
    type_s = 1;
    break;
  case 0x28:
    Serial.println("  Chip = DS18B20");
    type_s = 0;
    break;
  case 0x22:
    Serial.println("  Chip = DS1822");
    type_s = 0;
    break;
  default:
    Serial.println("Device is not a DS18x20 family device.");
    return;
  } 

}
//----------------------------------------------------------------------------- End of setup. 

void loop(void) {
  
  unsigned long currentMillis = millis();

  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // start conversion, with parasite power on at the end
  delay(750);


  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad
    
  Serial.print("  Data = ");
  Serial.print(present, HEX);
  Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }
  Serial.print(" CRC=");
  Serial.print(OneWire::crc8(data, 8), HEX);
  Serial.println();

  // Convert the data to actual temperature
  // because the result is a 16 bit signed integer, it should
  // be stored to an "int16_t" type, which is always 16 bits
  // even when compiled on a 32 bit processor.
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw << 3; // 9 bit resolution default
    if (data[7] == 0x10) {
      // "count remain" gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } 
  else {
    byte cfg = (data[4] & 0x60);
    // at lower res, the low bits are undefined, so let's zero them
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    //// default is 12 bit resolution, 750 ms conversion time
  }

  celsius = (float)raw / 16.0;
  fahrenheit = celsius * 1.8 + 32.0;
  Serial.print("  Temperature = ");
  Serial.print(celsius);
  Serial.print(" Celsius, ");
  Serial.print(fahrenheit);
  Serial.println(" Fahrenheit");

  delta = celsius - lasttemp;                        //Create value to check how much temperature has changed
  
  if (currentMillis - previousMillis >= interval) {  //If it has been 20 seconds, check to turn LED on
    previousMillis = currentMillis;
    if ((delta < 00.03) && (delta > -00.03)) {       //If temperature hasn't changed by +/- .05 turn led on.
      digitalWrite(A2, HIGH);
    } else if ((delta >= 00.06) || (delta <= -00.06)) {
        digitalWrite(A2, LOW);
      }
    }

  lcd.setCursor(0, 0);
  lcd.print(celsius);
  lcd.print(" C ");
  lcd.setCursor(0, 1);
  lcd.print(fahrenheit);
  lcd.print(" F ");
  lasttemp = celsius;

  logstate = digitalRead(2);   // read the input pin
  if (logstate == LOW) {
    if (currentMillis - sdpreviousMillis >= sdinterval) {  //Write to SD card every 5 seconds.
    sdpreviousMillis = currentMillis;
    digitalWrite(A1, HIGH);
    myFile = SD.open("Log.txt", FILE_WRITE);
    myFile.print(celsius);
    myFile.println(" C ");
    myFile.close();
    delay(5);
    digitalWrite(A1, LOW);
   }
  }
  if (logstate == LOW) {
    digitalWrite(9, HIGH);
  } else {
    digitalWrite(9, LOW);
  }
  
 }

I'll keep to the grindstone once I wake up a bit more, but I figure maybe someone may have encountered this exact same thing and already knows of a solution. May as well tackle it on two fronts

Have a separate file that has solely as its content the count of how many log files there have been.
Read it.
Increment it.
Create new log file.

ieee488:
Have a separate file that has solely as its content the count of how many log files there have been.
Read it.
Increment it.
Create new log file.

Yeah, it will just have to be one more thing I delete when clearing out the sd card, not a big deal, I can search for it in the setup. Main thing is getting it to make a new file every time logging is started, but not a new file each time it logs to the card, which is the main thing I was hoping someone may have already done.

I don't know if you've already solved your issue, but here are my thoughts a month late.

You need to keep track of whether you are about to write to a new file or will be adding to an existing file. A single variable can do this for you. Since it is a flag, I'd initialize it as a boolean. Lets call it "newfile". See how I add it to your section of code for datalogging.

  logstate = digitalRead(2);   // read the input pin
  if (logstate == LOW) {
    if (currentMillis - sdpreviousMillis >= sdinterval) {  //Write to SD card every 5 seconds.
      sdpreviousMillis = currentMillis;

      if (newfile == true) {
        initializeLogFile();
        newfile = false;  // Turn off flag so a new log file isn't initialized until the input pin switch is turned off and back on again.
      }

      digitalWrite(A1, HIGH);
      myFile = SD.open(logFileName, FILE_WRITE); // logFileName is set in the initializeLogFile() function.
      myFile.print(celsius);
      myFile.println(" C ");
      myFile.close();
      delay(5);
      digitalWrite(A1, LOW);
    }
  }

  else {
  newfile = true;  // prepare to create a new log file when the input pin switch is turned off.
  }

I've introduced a new function here for you. Writing it as a function should make it easier to use in other sketches. This function generates a file name with what ever method you use:

  • My method that you found in this thread.
  • ieee488's suggestion of having a separate file to keep the count of log files.
  • Similar to ieee488's suggestion but using the builtin EEPROM to store the count.
  • Use an RTC and generate the filename from the current date and/or time.
  • Some other method I haven't thought of here.

This is the perfect place to add one or more header lines to the file. Either for documentation or for formatting for csv import. If you want csv import, get rid of writing "C" on each line and write "Celsius" as the first line. Having the habit of writing a header row will come in handy when you do multiple column data files in the future.

I left the function empty for you to fill with your favorite code.

initializeLogFile() {
  // Find the next file name and save it in the variable logFileName
  // Optionally add a header row for csv import.
}

Don't forget to initialize newfile, and logFileName before setup() since they are globals (near where you initialize logstate would probably make the most sense). I would probably change the pin number your read to a global constant so you don't have to fish all the way through the source to change it if you change the hardware pin. But that is a style issue, and you may have already planned to do that during a clean up pass.

Something else I noticed. In your setup() I see you are setting the pinmode of pin 2 to "INPUT" and then issuing a "digitalWrite(2,HIGH);". Ever since Arduino 1.x the preferred method for this is to use the pinmode "INPUT_PULLUP". There is no guarantee that the digitalWrite(pin,HIGH) to an input pin method is compatible across chip types should you want to port code (or have good habits) for any of the other chips that the Arduino IDE supports. Kudos on knowing the internal pull up technique. But, a momentary switch won't work with this code unless you want to hold the switch down while logging is happening. A toggle switch, slide switch, or jumper pins with a shorting block would probably work better here. The code as you wrote it will only log to a file while the contacts in the switch to ground on pin 2 are closed. (But this may be some of the annotations that you said aren't accurate...)

I hope this helps point you in the right direction.