SD card variable file name

I am creating the charges logger for a vending machine rebuilt to use RFID cards. I would like so after every time the card is removed and replaced a new file is created with the current date. I am using the SD library so the statement in question is file = SD.open("filename.txt", FILE_WRITE); I have an RTC which puts each element of time/date into an int so I have an int minute, int hour, and so on. I am just starting to try to put minute as the file name but I will need to put pieces together to achieve a full date. Also, I would need to be able to append a number if there is a duplicate file name as I probably will not use seconds in the file name (or maybe I could). And finally, I don't how to get the .txt in as I don't want my client to have to modify the file name. (The txt file gets pasted into excel for processing.)

I know I need some sort of character array but I can't get anything to work. I am thinking "String - object" may be the right path but that's not working for me either.

Help is always greatly appreciated,

GTech

You said character array, so what went wrong when you tried that?

you might need to understand <string.h> a bit more, read this all the way through avr-libc: <string.h>: Strings

I know I need some sort of character array but I can't get anything to work.

Posting what you have tried is always better than hand-waving.

I am thinking "String - object" may be the right path but that's not working for me either.

Don't think that String, which is just a wrapper around char arrays, is going to solve all of your problems.

There are a number of ways to convert int values to strings (lower case s - NULL terminated char array). The itoa() function can do it, with no formatting, one at a time. The sprintf() function can do it, with formatting, and converting multiple values at one time.

There is a tradeoff with using sprintf() which is that your code size goes up.

Don't forget the restrictions on the length of the name (8 characters). Don't forget the extension for the name.

Um, WHILE we're on the subject of filenames on SD cards... :\

My intent is to log sporadic events to an SD card, tagging them with the time. I'd like to save a file-a-day and create the new file at midnight. Embedding the ten bold lines below gives me a workable filename which changes correctly at midnight and the code recognizes it and everything's fine.

BUT, when I try to make a function out of it (I'll need to check for the date rollover several times in my code) I can't get the function to return anything when I do a "if(! SD.exists(filename)) {...}" later on. (the error says "invalid conversion from 'char*' to 'char'" and highlights "return filename;" in the function)

Up until now I've pretty much banged everything out inside the regular setup and loop functions and this is my first real experience with making my own function and returning variables... Everybody's comments & criticisms are appreciated.

char getFilename() {
** DateTime now = RTC.now(); int year = now.year(); int month = now.month(); int day = now.day();**
** char filename[] = "00000000.CSV";**
** filename[0] = '2';**
** filename[1] = '0';**
** filename[2] = year%10 + '0';**
** filename[3] = year%10 + '0';**
** filename[4] = month/10 + '0';**
** filename[5] = month%10 + '0';**
** filename[6] = day/10 + '0';**
** filename[7] = day%10 + '0';**
return filename;
}

  char getFilename() {

This says that the function is to return a character, which is not the same as an array of characters.

So, hey, look at that. The compiler was right. Well, we knew it was.

Now, before you think that you can actually return filename from that function, forget it. filename is a local variable which goes out of scope when the function ends. If you return a pointer to that memory, it will be garbage as soon as the memory is reused.

What you need to do is pass to the function the array that you want modified. Since arrays act a lot like pointers, define the function as taking a pointer to a char.

void getFilename(char *fileName)
{
}

and call it like so:

char myFileName[16];
getFilename(myFileName);

Let's see if you can figure out how to write to the argument in the function then. If not, come on back. We'll be here all week.

GTech13,

here is a snippet that might help:

      char file_name[] = "xxxxxxxx.CSV";    // prototype file name
      char extension[] = "CSV";  // sometimes the extension gets modified

      sprintf(file_name,"%c%c%cCALIB.%s",EEPROM.read(fpf0),EEPROM.read(fpf1),
      EEPROM.read(fpf2),extension);

      /*  open the CAL file on the SD.  New file each time
       The serial number, customer number, file number are on the processor EEPROM
       */

      if (file.open(&root, file_name, O_CREAT | O_APPEND | O_WRITE))
      { 
        PgmPrintln("calibration file opened"); 
        Serial.println(file_name);
      }

Use your date strings instead of the EE reads in sprintf(). I increment the EE bytes and save them again so each file name is a sequential number. Should work with date strings. Are you setting the file creation dates and the file modification dates? They are useful and facilitate sorting file names when looking at the directory.

JC

I got it working - and can see where I was off and that I need to understand pointers better. I know I probably shouldn't use "filename" inside-of and outside-of the function, but it compiles and the SD card functions accept it with no problems, so: .

Thank you Paul.
Ex uno disce omnes


in setup:
char filename[] = "00000000.CSV";

function call:
getFilename(filename);

function:
__ void getFilename(char filename) {__
** DateTime now = RTC.now(); int year = now.year(); int month = now.month(); int day = now.day();
*
** filename[0] = '2';**
** filename[1] = '0';**
** filename[2] = year%10 + '0';**
** filename[3] = year%10 + '0';**
** filename[4] = month/10 + '0';**
** filename[5] = month%10 + '0';**
** filename[6] = day/10 + '0';**
** filename[7] = day%10 + '0';**
** filename[8] = '.';**
** filename[9] = 'C';**
** filename[10] = 'S';**
** filename[11] = 'V';**
** return;**
** }**

s3jn:
I got it working - and can see where I was off and that I need to understand pointers better. I know I probably shouldn't use "filename" inside-of and outside-of the function, but it compiles and the SD card functions accept it with no problems, so: .

Thank you Paul.
Ex uno disce omnes


in setup:
char filename[] = "00000000.CSV";

function call:
getFilename(filename);

function:
__ void getFilename(char filename) {__
** DateTime now = RTC.now(); int year = now.year(); int month = now.month(); int day = now.day();
*
** filename[0] = '2';**
** filename[1] = '0';**
** filename[2] = year%10 + '0';**
** filename[3] = year%10 + '0';**
** filename[4] = month/10 + '0';**
** filename[5] = month%10 + '0';**
** filename[6] = day/10 + '0';**
** filename[7] = day%10 + '0';**
** filename[8] = '.';**
** filename[9] = 'C';**
** filename[10] = 'S';**
** filename[11] = 'V';**
** return;**
** }**

suggest
filename[2] = year/10 + '0';

very usefull code thanks a lot !!

as for the year, if i use the /10 suggested i get this.

    filename[0] = year/10 + '0';
    filename[1] = year%10 + '0';
ù1

I am using this as I assume no registries before year 2000 and this way if arduino reboots cause of a file corruption, it will create a new file with a different HOUR.

void getFilename(char *filename) {
    DateTime now = RTC.now(); int year = now.year(); int month = now.month(); int day = now.day(); int hour = now.hour();

    filename[0] = '1';  //year/10 + '0';        // im getting error here so for now force it to 1.
    filename[1] = year%10 + '0';
    filename[2] = month/10 + '0'; 
    filename[3] = month%10 + '0';
    filename[4] = day/10 + '0';
    filename[5] = day%10 + '0';
    filename[6] = hour/10 + '0';
    filename[7] = hour%10 + '0';
    filename[8] = '.';
    filename[9] = 'C';
    filename[10] = 'S';
    filename[11] = 'V';
    return;
  }

ive printed year and contains "2011", so i thing thats the problem.

I have done this and works

    filename[0] = (year-2000)/10 + '0';
    filename[1] = year%10 + '0';

I guess its good enough for me that it works till 2099 hehe

yup
(year-2000)/10 is even better
i suspect the original worked as it's 2011!!!

You'd think the IDE would allow using String variables to use for the FileName.
Hopefully they can implement this.

Using Char Arrays is not dynamic enough because when we use char array for the filename, some of the chars might be null or invalid data.

It would make sense to use String variables for a String based filename, but they haven't implemented this yet.

My code for creating a filename is here, though it is not the method I wanted to use because of waisted memory space so I'll be entering the filename into an array coming over Serial.:

#include <SD.h>

char Data = '0';

byte FileNumber = 0;

char CommandChar = '0';

char FileName[] = "xxxxxxxx.xxx";

byte CharPosition = 0;
byte Counter = 0;

File MyFile;

// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(10, OUTPUT); //cs

Serial.begin(9600);
SD.begin(10);
}

// the loop routine runs over and over again forever:
void loop() {

while (1){
while (Serial.available() == 0){
}
if (Serial.available() > 0){
CommandChar = Serial.read();
}

switch(CommandChar){

//1 open file for read
//2 open file for write
//3 close file
//4 set filename
//5 delete FileName
//6 write char to file
//7 read char from file
//8 send char to PC
//A Get Char from Serial

case '0':
//No Operation
CommandChar= '0';
break;

case '1':
//Open File For Read Access
MyFile = SD.open(FileName, FILE_READ);

CommandChar= '0';

break;

case '2':
//Open FIle for Write Access
MyFile = SD.open(FileName, FILE_WRITE);

CommandChar= '0';

break;

case '3':
//Close FileName
MyFile.close();

break;

case '4':
//Set FileName 8 chars + "." + 3 more extension chars

for (Counter = 0; Counter < 13; Counter++){
while (Serial.available() == 0){
}
if (Serial.available() > 0){
Data = Serial.read();
FileName[CharPosition] += Data;
CharPosition++;
}
}
CharPosition = 0;
Counter = 0;
CommandChar = '0';

break;

case '5':
//Remove Filename from sd card
SD.remove(FileName);

CommandChar = '0';

break;

case '6':
//Write Char to File

MyFile.write(Data);

CommandChar = '0';
break;

case '7':
//Read Char from File

Data = MyFile.read();

CommandChar= '0';
break;

case '8':
//Send Char to PC

Serial.println(Data);

CommandChar= '0';
break;

case '9':
// Load Char to Data
while (Serial.available() == 0){
}
if (Serial.available() > 0){
Data = Serial.read();
}
CommandChar = '0';
break;

}

}

}

Using Char Arrays is not dynamic enough because when we use char array for the filename, some of the chars might be null or invalid data.

This is not true. The data in String is just a char array.

char *buffer;        // the actual char array

I wanted to use because of waisted memory space so I'll be entering the filename into an array coming over Serial.:

This is also not true. String uses realloc so when you add a character at a time to a string it can use several times as much as needed, depending on the state of the heap.

You can easily use String to open a file.

void setup() {
  String filename = "test.txt";
  file = SD.open(filename.c_str(), FILE_WRITE);
}

I was building filenames dynamically with:

//Set FileName 8 chars + "." + 3 more extension chars
while (Serial.available() == 0){
}
if (Serial.available() > 0){
Char Data = Serial.read();
temp += Data; String temp = temp+Data

}

//Then after building the filename, I did this:

temp.toCharArray(FileName, temp.length());
MyFile = SD.open(FileName, FILE_WRITE);

Then writing data, I did this:

MyFile.print(Data);

but I think this would work as well:

MyFile.print(Serial.read());

The strange thing is, the filesize changes as shown on the PC and through getting SD FileInfo with the Arduino. Maybe Windows puts some filesystem info in the files on the Windows side.

I was trying to find a way to do file transfer but had to do this in binary.

I suppose doing this with Byte would work the same as with Char.
Trying to play wav files written with Chars I'm having trouble with.

On the PC side, I send Byte or chr(byte) to send an ASCII char
On the Arduino Side, I was testing between reading Byte and Char from serial.read.

Which one would I use for binary data transfer, and should I be using 512 byte array instead of writing each individual bytes/chars to the new files

Thanks?

Your program will fail if the file name is too long in this statement since memory will be overwritten.

  temp.toCharArray(FileName, temp.length());

If Data is type char, this will write the byte to the file.

MyFile.print(Data);

This will format the int value returned by read() as a decimal string and write the characters to the file.

MyFile.print(Serial.read());

What OS are you using on the PC? Are you sure the file is opened in binary mode on the PC? If the transfer is binary, the files will be the same size.

Hello,
thanks all for the fruitful discussion and the code. Right was I was looking for. I came close to the solution myself but couldn't get it quite right.
It would be nice if someone could explain the term + '0' in e.g.

filename[4] = month/10 + '0';

since that is exactly what I didn't do.
Thanks in advance
godo

It would be nice if someone could explain the term + '0'

The values 4 and '4' are not the same. However, the difference between the values from 0 to 9 and the characters '0' to '9' is consistent, for each value of n. So,
'n' - n = '0'.

Therefore, what the term + '0' is doing is converting the value n to the character 'n'. (0 -> '0', 3 -> '3', etc.)

godo:
It would be nice if someone could explain the term + '0'

Characters are stored as bytes in memory.
When told to add a character to a number, the C compiler actually adds the corresponding byte.
The code for translating between bytes and characters is called the ASCII code.
Now look at an ASCII table. Here's one:

With this in mind, you can try an example. Look up the decimal value of the symbol '0', add 6 to it, and look up the resulting symbol in the ASCII table.

Does this help explain what's going on?

Note that this trick only works for positive single-digit numbers! Try adding 10 to '0' and see what you get.

More info:

https://www.arduino.cc/en/Reference/Char

Thanks PaulS and Gdunge,
I suspected it was some casting procedure, but I didn't quite get it. Now it is clearer.
I tried it differently first, like
filename[4] = char (day/10);
but that did not work. Why is that not the same?
Cheers

Why is that not the same?

Suppose day is 17. Dividing 17 by 10 results in 1. Casting 1 to a character is valid, but the result is not the character '1'. It is a non-printing character.

Your question makes it look like you don't quite get the idea of the ASCII code.

Here is some history that might help:

And here's my quick explanation.

The American Standard Code for Information Interchange (ASCII) is an ordered list of symbols (letters, numbers, punctuation, and "control characters"). Each symbol is given a number, starting at 0 and going up to 127.

To print letters on screen or paper, computers use the ASCII code to tell the device what symbol to print. If your Arduino program wants to print an "A" on the screen, it needs to send the appropriate number to the Serial device - which, for the letter "A", is the number 65.

So if your program does this:

Serial.write(65);

then this comes out:

A

Just for fun, here is a program that plays around with various ways to use the ASCII code to print things to the screen:

// Some exercises with the ASCII code
// You can find an ASCII code table in lots of places. Here's one:
// https://www.arduino.cc/en/Reference/ASCIIchart
// Doug Weathers, 27 Jan 2016

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

  // print the capital letters ("A" = 65, "Z" = 90)
  // Just send the numbers corresponding to the letters we want to print on the screen
  for (int i = 65; i <= 90; i++)
  {
    Serial.write(i);
  }

  // Send a line feed code (LF) to skip down to the next line for our next piece of output
  Serial.write(10);

  // Send "Hello World!", followed by a LF
  // I looked up each letter in the ASCII table and typed the corresponding numbers into the array
  byte bytes[13] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10};
  for (int i = 0; i < 13; i++)
  {
    Serial.write(bytes[i]);
  }

  // Do it a bit easier, where you don't need to keep track of how many characters are in your array
  // The C compiler counts the numbers you typed in and correctly defines the array for you
  char chars[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 0};
  // println checks for the end of the line, marked by a zero, and that's how it knows to stop printing
  // Also it automatically puts on the LF codes for you.
  Serial.println(chars);

  // Do it even easier
  // The C compiler looks up each letter in the ASCII table and puts the correct byte into the array,
  // then puts a zero at the end to mark the end of the string.
  char cstring[] = "Hello World!";
  Serial.println(cstring);

  // Do it the easiest way
  // This is called a string constant or string literal
  Serial.println("Goodbye, cruel world!");
}

void loop() {
  // put your main code here, to run repeatedly:

}

The output looks like this:

ABCDEFGHIJKLMNOPQRSTUVWXYZ
Hello World!
Hello World!
Hello World!
Goodbye, cruel world!

For more clues, check out these sections of the Arduino reference:

https://www.arduino.cc/en/Reference/Char