0

I have some quite long code to copy a file from an SD to the same card, but under a different name. I have one version where the file names are built-in to the sketch, but to make it more portable, i have implemented a serial file selection system. There are 3 phases; choosing the input file, choosing the output file, and the copying. I have included checks to see if the files exist, confirmation for the names and so on. However, in the middle of my code, just before the copying phase, the input file disappears, and the arduino does not find it. It finds it in the first phase, finds the output file, but can't find the input file in the copying phase. SD.exists() and SD.open() both confirm this, but if i remove the sd card and put it into a computer, the file is there.

My code:

#include <SoftReset.h>
#include <SPI.h>
#include <SdFat.h>
#define csPin 4
#define roundTo 64

SdFat SD;
File sourceFile;
File outputFile;

int bufSize;
uint32_t lastPos = 0, timeLast, timeNow;
#define BAUD 115200
//make timeBetweenChars the time it takes (in milliseconds) between each char being transferred over serial, and make it at least one
unsigned long timeBetweenChars = max(1000 / (BAUD / 8), 1);

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(BAUD);
  while (!Serial) {
  }
  Serial.print(F("Initializing SD card..."));
  /******************************************************************************/
  // see if the card is present and can be initialized:
  if (!SD.begin(csPin, SPI_FULL_SPEED)) {
    Serial.println(F("Card failed, or not present"));
    soft_restart();
  }
  Serial.println("Card initialized.");

  Serial.println(F("Files on card:"));
  SD.ls();
  //******************************************************************************
  //START OF INPUT FILE NAME
  //******************************************************************************
enterInputFileName:
  Serial.println(F("Enter input file name:"));
  while (!Serial.available()); // wait till serial available
  char inbuf[64];
  /******************************************************************************/
  //create variable to store how many valid (not newline or carriage return) chars have been read
  byte pos = 0;
  //read into array as long as not getting newline char
  while (Serial.available() && Serial.peek() != '\n' && Serial.peek() != '\r') {
    inbuf[pos] = Serial.read();
    pos++;
    //wait so there is time for the next char to arrive in the buffer
    delay(timeBetweenChars);
  }
  while (Serial.available() && (Serial.peek() == '\n' || Serial.peek() == '\r')) {
    //remove next char from buffer
    Serial.read();
    //wait in case there is another char to be cleared
    delay(timeBetweenChars);
  }
  //end array at pos
  inbuf[pos] = '\0';
  /******************************************************************************/
  //create new array with correct size for input file name
  char inputFileName[pos];
  //copy data from inbuf to inputFileName
  strcpy(inputFileName, inbuf);
  /******************************************************************************/
  //confirm if you entered the right file name
confirmInputFileName:
  Serial.print(F("Select file '"));
  Serial.print(inputFileName);
  Serial.println(F("'? Y/N"));

  while (!Serial.available()); // wait till serial available
  char confirm = Serial.read();
  bool tooManyChars = 0;
  // wait for the next chars
  delay(timeBetweenChars);
  while (Serial.available()) {
    //read next char
    char extra = Serial.read();
    //if it isn't a newline or carriage return
    if (extra != '\n' && extra != '\r') {
      //too many characters entered
      tooManyChars = 1;
    }
    //wait for next char
    delay(timeBetweenChars);
  }
  /******************************************************************************/
  if (tooManyChars) {
    Serial.println(F("Enter 1 char"));
    goto confirmInputFileName;
  }

  //if only 1 char entered
  switch (confirm) {
    case 'Y': break;
    case 'y': break;
    case 'N': goto enterInputFileName; break;
    case 'n': goto enterInputFileName; break;
    //invalid char entered
    default: Serial.println(F("Enter Y/N")); goto confirmInputFileName; break;
  }
  /******************************************************************************/
  Serial.print(F("Using file '"));
  Serial.print(inputFileName);
  Serial.println("' as input file");
  if (SD.exists(inputFileName)) {
    Serial.println(F("File exists"));
  }
  else {
    Serial.println(F("Error, file does not exist. Please choose another file"));
    goto enterInputFileName;
  }
  //******************************************************************************
  //END OF INPUT FILE NAME
  //******************************************************************************


  //******************************************************************************
  //START OF OUTPUT FILE NAME
  //******************************************************************************
enterOutputFileName:
  //clear input buffer
  for (byte w = 0; w < (sizeof(inbuf) / sizeof(inbuf[0])); w++) {
    inbuf[w] = '\0';
  }
  Serial.println(F("Enter output file name:"));
  //wait for some chars to arrive
  while (!Serial.available());
  /******************************************************************************/
  //create variable to store how many valid (not newline or carriage return) chars have been read
  pos = 0;
  //found here Serial.print(SD.exists(inputFileName));
  //read into array as long as not getting newline char
  while (Serial.available() && Serial.peek() != '\n' && Serial.peek() != '\r') {
    //file found on first loop, but not after that
    // found here Serial.println(SD.exists(inputFileName));
    inbuf[pos] = Serial.read();
    //not found here Serial.println(SD.exists(inputFileName));
    pos++;
    //wait so there is time for the next char to arrive in the buffer
    delay(timeBetweenChars);
  }
  //input file not found here
  while (Serial.available() && (Serial.peek() == '\n' || Serial.peek() == '\r')) {
    //remove next char from buffer
    Serial.read();
    //wait in case there is another char to be cleared
    delay(timeBetweenChars);
  }
  //end array at pos
  inbuf[pos] = '\0';
  /******************************************************************************/
  //create new array with correct size for output file name
  char outputFileName[pos];
  //copy data from inbuf to outputFileName
  strcpy(outputFileName, inbuf);
  /******************************************************************************/
  //confirm if you entered the right file name
confirmOutputFileName:
  Serial.print(F("Select file '"));
  Serial.print(outputFileName);
  Serial.println(F("'? Y/N"));

  while (!Serial.available()); // wait till serial available
  confirm = Serial.read();
  tooManyChars = 0;
  // wait for the next chars
  delay(timeBetweenChars);
  while (Serial.available()) {
    //read next char
    char extra = Serial.read();
    //if it isn't a newline or carriage return
    if (extra != '\n' && extra != '\r') {
      //too many characters entered
      tooManyChars = 1;
    }
    //wait for next char
    delay(timeBetweenChars);
  }
  /******************************************************************************/
  if (tooManyChars) {
    Serial.println(F("Enter 1 char"));
    goto confirmOutputFileName;
  }

  //if only 1 char entered
  switch (confirm) {
    case 'Y': break;
    case 'y': break;
    case 'N': goto enterOutputFileName; break;
    case 'n': goto enterOutputFileName; break;
    //invalid char entered
    default: Serial.println(F("Enter Y/N")); goto confirmOutputFileName; break;
  }
  /******************************************************************************/
  Serial.print(F("Using file '"));
  Serial.print(outputFileName);
  Serial.println("' as output file");

  if (SD.exists(outputFileName)) {
    Serial.println(F("File already exists. Overwrite? Y/N"));
confirmOverwrite:
    while (!Serial.available()); // wait till serial available
    confirm = Serial.read();
    tooManyChars = 0;
    // wait for the next chars
    delay(timeBetweenChars);
    while (Serial.available()) {
      //read next char
      char extra = Serial.read();
      //if it isn't a newline or carriage return
      if (extra != '\n' && extra != '\r') {
        //too many characters entered
        tooManyChars = 1;
      }
      //wait for next char
      delay(timeBetweenChars);
    }
    /******************************************************************************/
    if (tooManyChars) {
      Serial.println(F("Enter 1 char"));
      goto confirmOverwrite;
    }

    //if only 1 char entered
    bool overwrite = 0;
    switch (confirm) {
      case 'Y': overwrite = 1; break;
      case 'y': overwrite = 1; break;
      case 'N': goto enterOutputFileName; break;
      case 'n': goto enterOutputFileName; break;
      //invalid char entered
      default: Serial.println(F("Enter Y/N")); goto confirmOverwrite; break;
    }
    if (overwrite) {
      Serial.println(F("Removing output file"));
      SD.remove(outputFileName);
    }
  }
  Serial.println(F("Creating output file"));
  outputFile = SD.open(outputFileName, FILE_WRITE);
  outputFile.close();
  //******************************************************************************
  //END OF OUTPUT FILE NAME
  //******************************************************************************


  //******************************************************************************
  //START OF COPYING
  //******************************************************************************
  //read source and write to output
  sourceFile = SD.open(inputFileName);
  timeLast = micros();
  //debug line
  Serial.print(F("Exists(I,O): ")); Serial.print(SD.exists(inputFileName)); Serial.println(SD.exists(outputFileName));
  if (sourceFile) {
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);
    Serial.println(F("Copying"));
    while (sourceFile.available()) {
      //make buffer size 50% of free ram, or amount of bytes left if smaller
      int ram = round((freeRam() * 0.5) / roundTo) * roundTo;
      bufSize = min(ram, sourceFile.available());
      //round to nearest value of roundTo
      bufSize = round(bufSize / roundTo) * roundTo;
      byte data[bufSize];
      sourceFile.readBytes(data, bufSize);
      lastPos = sourceFile.position();
      sourceFile.close();
      outputFile = SD.open(outputFileName, O_APPEND | O_WRITE);
      outputFile.write(data, bufSize);
      outputFile.close();
      sourceFile = SD.open(inputFileName);
      sourceFile.seek(lastPos);
      //Serial.print(F("\tExists(I,O): ")); Serial.print(SD.exists(inputFileName)); Serial.print(SD.exists(outputFileName));
      printStats();
    }
    sourceFile.close();
    outputFile.close();
    Serial.println(F("Finished"));
    unsigned int sec = millis() / 1000;
    unsigned int mins = sec / 60;
    sec -= mins * 60;
    Serial.print(F("Time taken: "));
    Serial.print(mins);
    Serial.print(':');
    Serial.print(sec);
    Serial.print(F("Speed: "));
    Serial.print((sourceFile.size() / 1000) / (millis() / 1000));
    Serial.print(F(" KB/s"));
  }
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening files");
    Serial.print(F("Exists(I,O): ")); Serial.print(SD.exists(inputFileName)); Serial.println(SD.exists(outputFileName));
    SD.initErrorHalt();
  }
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(200);
  digitalWrite(LED_BUILTIN, LOW);
  delay(200);
}

int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

void printStats() {
  timeNow = micros();
  uint32_t timeTaken = timeNow - timeLast;
  float sent = sourceFile.position() / 1000.0;
  float fileSize = sourceFile.size() / 1000.0;
  float left = fileSize - sent;
  float speed;
  speed = 1000000UL / timeTaken * bufSize;
  speed /= 1000;
  unsigned int sec = millis() / 1000;
  unsigned int mins = sec / 60;
  sec -= mins * 60;
  int estSec = (left / speed);
  int estMin = estSec / 60;
  estSec -= estMin * 60;
  Serial.print(F("\tSent (KB): ")); Serial.print(sent);
  Serial.print(F("\tLeft (KB): ")); Serial.print(left);
  Serial.print(F("\tSize (KB): ")); Serial.print(fileSize);
  Serial.print(F("\tTime: "));      Serial.print(mins); Serial.print(':'); Serial.print(sec);
  Serial.print(F("\tSpeed (KB/sec): "));     Serial.print(speed);
  Serial.print(F("\tFree: "));      Serial.print(freeRam() - bufSize);
  Serial.print(F("\tBuf: "));       Serial.print(bufSize);
  Serial.print(F("\tEST: "));       Serial.print(estMin); Serial.print(':'); Serial.print(estSec);
  //Serial.print(F("\tExists(I,O): ")); Serial.print(SD.exists(inputFileName)); Serial.print(SD.exists(outputFileName));
  Serial.print('\n');
  timeLast = micros();
}

My serial output(I chose Nova.mp3 as input file and output.mp3 as output file):

Initializing SD card...Card initialized.
Files on card:
output.mp3
Nova.mp3
Eclipse.wav
Enter input file name:
Select file 'Nova.mp3'? Y/N
Using file 'Nova.mp3' as input file
File exists
Enter output file name:
Select file 'output.mp3'? Y/N
Using file 'output.mp3' as output file
File already exists. Overwrite? Y/N
Removing output file
Creating output file
Exists(I,O): 01
error opening files
Exists(I,O): 01
No error found.

I know i am not running out of memory, and have no compiler errors, but i have no clue what is going wrong. Is it possible I have put in some unnecessary code? Also, if it was possible to have multiple files open at a time, would i spped up my read/write speed?

  • you char array for inputFileName is one char shorter then needed. if 0 is at pos you need to allocate and copy pos + 1 chars because the indexing starts at 0. (btw: your code is very ineffective) – Juraj Jul 19 '18 at 10:56
  • I know indexing starts at 0, and if you didn't notice, i actually add 1 to pos at the end of the loop, so the array should be long enough. And how is it ineffective? do you mean inefficient? Edit: this actually made my code start copying, will update if it finishes successfully. – Rowan Radosav - McRae Jul 20 '18 at 7:32
  • too much code for the task – Juraj Jul 20 '18 at 9:57
  • How should i compress it? i can't do the copy loop as simply as i'd like, because only 1 file can be opened at a time, and i put the file selection confirms in because sometimes people make typos. what can i make smaller – Rowan Radosav - McRae Jul 20 '18 at 23:29
  • "As of version 1.0, the library supports opening multiple files." arduino.cc/en/Reference/SD – Juraj Jul 21 '18 at 4:47
1

The problem is that you have the char arrays for zero terminated c-string one char shorter then needed. I see you assign the terminating zero to inbuf[pos] and then you allocate pos chars for the filename and copy pos count of characters. That leaves out the terminating zero and the c-string of the filename is not terminated by 0 and continues in memory with random content.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.