-1

Is there a way to make the Arduino wait until all the serial data has been received? Say I send a 32 byte long string over serial (the length is actually random), how would I make my Arduino wait till all 32 bytes have been received?

If I execute Serial.readBytes straight away, it only reads one char. At the moment I'm using delay, but I'm worried that it is wasting time on the Arduino, as I would like it to be processed straight away. What I want to happen is I plug in my Arduino, type in the name of an input and output file, and it will start copying the input file into the output file. I don't want to re-upload every time I want to change files, so I would like to select over serial which ones to use. However, sometimes there will be no \r or \n sent at the end, which cuts off some of the file name.

Here is my code:

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

SdFat SD;
File sourceFile;
File outputFile;

int bufSize;
uint32_t lastPos = 0, timeLast, timeNow;
//fix this (error: deprecated conversion from string constant to 'char*')
//char *outputFileName = "output.wav";
#define outputFileName "output.wav"
byte avail;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  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.");
enterInputFileName:
  Serial.println(F("Enter input file name:"));
  while (!Serial.available()); // wait till serial available
  delay(100);
  avail = Serial.available() - 2; // extra '\n' & '\r' that we dont want
  char inbuf[avail];
  Serial.readBytes(inbuf, avail);
  Serial.read(); // remove '\n' from buffer
  Serial.read(); //remove '\r' from buffer
  inbuf[avail] = '\0';

  char inputFileName[avail];
  strcpy(inputFileName, inbuf);
confirmInputFileName:
  Serial.print(F("Select file '"));
  Serial.print(inputFileName);
  Serial.println(F("'? Y/N"));

  while (!Serial.available()); // wait till serial available
  delay(100); //wait for all chars to be recieved
  avail = Serial.available() - 2;
  if (avail > 1) {
    Serial.println(F("Enter 1 character"));
    while (Serial.available()) { //clear serial buffer
      Serial.read();
    }
    goto confirmInputFileName;
  }
  char confirm = Serial.read();
  Serial.read();
  Serial.read();
  switch (confirm) {
    case 'Y': break;
    case 'y': break;
    case 'N': goto enterInputFileName; break;
    case 'n': goto enterInputFileName; break;
    default: Serial.println(F("Enter Y/N")); goto confirmInputFileName; break;
  }
  //up to here works
  Serial.println(SD.exists(inputFileName));

  SD.remove(outputFileName);
  outputFile = SD.open(outputFileName, FILE_WRITE);
  outputFile.close();
  //read source and write to output
  sourceFile = SD.open(inputFileName);
  timeLast = micros();
  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
      bufSize = min((freeRam() * 0.5 ), sourceFile.available());
      byte data[bufSize];
      sourceFile.readBytes(data, bufSize);
      lastPos = sourceFile.position();
      sourceFile.close();
      outputFile = SD.open(outputFileName, O_APPEND | O_WRITE);
      outputFile.write(data, bufSize);
      printStats();
      outputFile.close();
      sourceFile = SD.open(inputFileName);
      sourceFile.seek(lastPos);
    }
    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.print("error opening files");
    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);
  //todo fix this line
  //Serial.print(F("\tExists(I,O): ")); Serial.print(SD.exists(inputFileName)); Serial.print(SD.exists(outputFileName));
  Serial.print('\n');
  timeLast = micros();
}
5
  • 1
    ... Count them. Jul 11 '18 at 2:39
  • I did say that the amount would be random
    – Eternal
    Jul 11 '18 at 2:46
  • 1
    Then you need to be sending the length too, or terminating the data somehow. Jul 11 '18 at 2:51
  • 2
  • 1
    Please show the code you tried for reading the incoming data, and describe the data, especially how the Arduino can be able to tell how much data to read / when to stop reading & start processing.
    – JRobert
    Jul 12 '18 at 12:55
0

You need to format the message so that the receiver knows where the message ends and a new one begins.

Then on he receiving end you fill a buffer until a full message is received and then handle the message.

A more advanced tactic is to process the bytes as you receive them. This requires that you build a statemachine but means you don't have to store a copy of the entire message.

1
  • I decided to use a loop to read into a buffer until it got to a newline or carriage return char, or it has waited longer than it takes to receive a char.
    – Eternal
    Jul 19 '18 at 2:46
1

after source code was add to the question:

you use the readBytes function wrong. if you set the size parameter to count of available bytes it will read the count of available bytes.

allocate a buffer and set the second parameter as the buffer size. the readBytes will try to fill the buffer, waiting max one second for the next character, which is enough to read the complete line sent from Serial Monitor.

if (Serial.available()) {    
  char buff[50];
  int length = Serial.readBytes(buff, sizeof(buff) - 1);
  buff[length] = 0; // terminating zero of c-string
  Serial.println(buff);
}

note: readBytes will wait one second after the last character received. better is to use readBytesUntil with expected terminator. or you can set smaller timeout on Serial with Serial.setTimeout(200)

-1

I was concerned about the same thing recently when I worked on a project that would read serial data (received on a Bluetooth HC-05).

My loop() does a lot of work for my project (Never Type A Password Again (Arduino-driven Touchscreen Device @ CodeProject.com ) but reading the serial data is a big part of the work to do also.

I implemented the following code and it has been literally running for weeks now with no problems.

if (Serial1.available() > 0) {
    incomingByte = Serial1.read();
    while (incomingByte > 0){
      if (incomingByte == 0x01){
        Keyboard.press(KEY_LEFT_CTRL);
        Keyboard.press(KEY_LEFT_ALT);
        Keyboard.write(KEY_DELETE);
        Keyboard.release(KEY_LEFT_ALT);
        Keyboard.release(KEY_LEFT_CTRL);
      }
      else{          
         Keyboard.write(incomingByte);
      }
      incomingByte = Serial1.read();
    }
  }

You can see that it reads bytes while they are available which might lead you to think it might lock up, but it handles it very well.

Quite A Bit Of Work

It does quite a bit of work because it is capturing any bytes that come across Bluetooth (and then it types them on the connected computer -- that's a long story, check out the article for more).

Quite A Few Bytes

It also reads at least 64 bytes (password length + special Control char bytes) but it handles it quite well.

2
  • while (Serial1.available > 0), without the while (incomingByte > 0) and second read would do the same. but the concept read byte handle byte is good, but not for this question
    – Juraj
    Jul 11 '18 at 17:39
  • @Juraj I checked and that is a good point. I will look more closely at that. thanks. I think the answer still helps the OP get closer to an answer since it was somewhat of a open-ended question.
    – raddevus
    Jul 11 '18 at 17:41

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.