Cannot make Serial.readString() work on MEGA 2650

I have a RobotDyn MEGA2650 R3 board. Lots of memory and I/O. I am working on a project and want to read in an ID and PW and then store it in EEPROM. I have searched and searched trying to understand my code error. Perhaps this is something unique or a problem with using the MEGA2650. Here is a short snippet that does not show ALL the things I have tried but does show the Serial.readString() problem and noise problem.

  • I cannot read a String from my Arduino monitor, unless I wait for the timeout after typing something. You can’t see it, but in the case below I typed random characters that met the syntax criteria. Hence the “Good ID and PW!” I would like to get the string back immediately when I hit the return key. I have tried various IDE monitor line ending options. I would really like to learn how to use String and not char reads, if possible.

  • I get a lot of noise every time at the beginning I would like to eliminate, see output at the bottom.

In case you were wondering, I want a timeout ( Serial.setTimeout(10000); // 10 Seconds ) so that if nothing is typed or if no serial device is attached, the code will fall though the prompt for setting an ID and PW.

bool IDF = false;
bool PWF = false;
#define STARTINGID "012345678"
#define STARTINGPW "01234567890123"
String ID = STARTINGID; // 9 chars + null = 10
String PW = STARTINGPW; // 13 chars + null = 14
#define BAUD_RATE 19200

void setup() {
  // put your setup code here, to run once:

}

void loop() {
// This needs to run on a MEGA2650
// I read that 
// Serial uses interrupts so interrupts must be kept enabled.

// Setup Serial port
Serial.begin(BAUD_RATE); 
// I don't know how long before I/O can commence
// assuming a USB keyboard or terminal is connected at Pwr On

// Set serial timeout
Serial.setTimeout(10000); // 10 Seconds
// Must type ID (and later PW)
// within 10 seconds of prompt or you will miss your chance until
// next Pwr On or Reset

// Prompt for ID and PW
//
// This is a debug line
  Serial.println("\n"+ID+" "+PW+" "+ID.length() );
// Ask the local user to set his ID and PW if he hasn't or if he wants to change it
  Serial.println("\n\nMust set your ID and PW if you have not done so");
  Serial.println("Wait for timeout to keep existing id pw");

  Serial.println("ID Length must be 1 to 9 characters"); 

  Serial.print("ID: "); // prompt for the ID
  ID = Serial.readString();
  Serial.println(ID);
  IDF = ( (ID.length() < 1) || (ID.length() > 9) );
  
  Serial.println("PW length must be 8 to 13 characters");
  Serial.print("PW: "); // prompt for the ID
  PW = Serial.readString();
  Serial.println(PW);
  PWF = ( (PW.length() < 8) || (PW.length() > 13) );
     
  if ( (PWF == true) && (IDF == true) ) {
    Serial.println("Good ID and PW!");
// We have a good ID and PW from the user
//
  }
  else
   Serial.println("Not Good ID and PW");

}

Here is output I get on /dev/cu.SLAB_USBtoUART monitor at 19,200 Baud

H⸮!J JJ⸮J⸮⸮⸮⸮⸮H⸮
⸮C⸮
߈⸮C⸮⸮⸮⸮Ψ
⸮⸮⸮⸮o⸮⸮⸮⸮⸮
⸮ �⸮⸮⸮!
X⸮X⸮⸮⸮!⸮ ⸮⸮
012345678 01234567890123 9

Must set your ID and PW if you have not done so
Wait for timeout to keep existing id pw
ID Length must be 1 to 9 characters
ID:
PW length must be 8 to 13 characters
PW:
Good ID and PW!

0

Must set your ID and PW if you have not done so
Wait for timeout to keep existing id pw
ID Length must be 1 to 9 characters
ID:
PW length must be 8 to 13 characters
PW:
Good ID and PW!

0

As you discovered, Serial.readString() will wait the full timeout duration before terminating. If you want the function to terminate immediately on reading a line ending then you can use Serial.readStringUntil(). For example, if you have Serial Monitor set to use newline (\n) for the line ending, then you can use this code:

Serial.readStringUntil('\n')

I think your idea of using the timeout to set the time window for the user to update their credentials is an unnecessary hack. Much better is to use millis(). What you’re wanting to do is wait until there is serial input or 10 seconds has passed, whichever comes first. Then if there is serial input available, parse it. In code, that translates to:

unsigned long timestamp = millis();
while(Serial.available() == 0 && millis() - timestamp < 10000) {}  // wait for serial input or timeout
if(Serial.available > 0) {  // serial input is available

Once the user has entered ID, you probably don’t want another timeout so you just want to wait for the serial input before parsing PW:

while(Serial.available() == 0) {}  // wait for serial input

Now that you’re not abusing setTimeout, you can use the function to set a sensible timeout to make your code responsive in case the user didn’t set the correct line ending in Serial Monitor.

Note that the while loops above are blocking code, in that they prevent any other code from running while waiting on the serial input. Blocking code is usually something to avoid, but in the particular case of wanting to wait on startup for user input before running any other code, it makes sense.

The setup() function runs once on start up, whereas the loop() function runs over and over perpetually. So the setup() function is the logical place to put code that only needs to run once on start up.

The use of String (capital S) is not advisable due to possible memory fragmentation that can result in unpredictable run-time problems.

Further Serial.begin() should be placed in setup(); if you place it in loop(), I think it needs to be accompanied by Serial.end(), e.g. at the end of loop(); this is however a strange requirement and to my knowledge only used when you have to change baudrates on the fly.

Pert:
Thank you so much for the extensive consultation! I did not understand the function of Serial.setTimeout(). I reread the reference and now it makes sense. I plan to use millis() to effect a timeout for both the ID and PW prompts, as as I want to handle all possible “abnormal” conditions such as when the user accidentally enters an ID but then decides not to change the previously saved ID and PW.

You understood correctly that my intent was to give the user one opportunity to enter an ID and PW at power on and so I put it in setup();

sterreti:

I read that String (capital S) had issues with memory corruption. I chose Strings to learn more about them and because I will likely use very little of my MEGA 2650’s RAM. However, I do want my software to run for months or years without the need to restart, so I think I will heed your advice and remove use of Strings. Also, I’ve not read anything about garbage collection on an Arduino.

Regarding the Serial.end(): I planned to leave the serial port open to write out error codes in loop(); The reference says Serial.end() “disables serial communication, allowing the RX and TX pins to be used for general input and output.” However, I don’t plan to use these pins.

Again, thank you both!

For switching your code to use use strings instead of String, the Serial.readBytesUntil() function is the string equivalent of Serial.readStringUntil():
https://www.arduino.cc/reference/en/language/functions/communication/serial/readbytesuntil

Just move Serial.begin() to setup(). Serial.begin() might have side effects resulting in data corruption.