Serial monitor freezing when sending Strings more than once

Here is my code:

#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel pixel(1,11);

const String a[119] = {"-","h","he","li","be","b","c","n","o",
              "f","ne","na","mg","al","si","p","s","cl",
              "ar","k","ca","sc","ti","v","cr","mn","fe",
              "co","ni","cu","zn","ga","ge","as","se","br",
              "kr","rb","sr","y","zr","nb","mo","tc","ru",
              "rh","pd","ag","cd","in","sn","sb","te","i",
              "xe","cs","ba","la","ce","pr","nd","pm","sm",
              "eu","gd","tb","dy","ho","er","tm","yb","lu",
              "hf","ta","w","re","os","ir","pt","au","hg",
              "tl","pb","bi","po","at","rn","fr","ra","ac",
              "th","pa","u","np","pu","am","cm","bk","cf",
              "es","fm","md","no","lr","rf","db","sg","bh",
              "hs","mt","ds","rg","cn","nh","fl","mc","lv",
              "ts","og"};
const String b[119] = {"-","-","h","l","b","-","-","-","-",
              "-","n","n","m","a","s","-","-","c",
              "a","-","c","s","t","-","c","m","f",
              "c","n","c","z","g","g","a","s","b",
              "k","r","s","-","z","n","m","t","r",
              "r","p","a","c","i","s","s","t","-",
              "x","c","b","l","c","p","n","p","s",
              "e","g","t","d","h","e","t","y","l",
              "h","t","-","r","o","i","p","a","h",
              "t","p","b","p","a","r","f","r","a",
              "t","p","-","n","p","a","c","b","c",
              "e","f","m","n","l","r","d","s","b",
              "h","m","d","r","c","n","f","m","l",
              "t","o"};
String x;
int y;
String tmp1;
String tmp2;
int z;

void setup(){
  pixel.begin();
  pixel.setPixelColor(0, 0, 0, 20);
  pixel.show();
}

void loop(){
  Serial.begin(9600);
  while(!Serial){}
  x = Serial.readString();
  back:
  if(x.length() > 0){
    if(x.startsWith(" ")){ //Space
      x.remove(0,1);
      Serial.print(" ");
    }else if(x.startsWith("n")){ //Number
      x.remove(0,1);
      y = x.toInt();
      Serial.print(y);
      tmp2 = y;
      z = tmp2.length();
      x.remove(0,z);
    }else if(x.startsWith("a")){ //Letter "a"
      x.remove(0,1);
      y = x.toInt();
      Serial.print(a[y]);
      removeInt();
    }else if(x.startsWith("A")){ //Capital "A"
      x.remove(0,1);
      y = x.toInt();
      tmp1 = a[y];
      tmp1.toUpperCase();
      Serial.print(tmp1);
      removeInt();
    }else if(x.startsWith("b")){ //Letter "b"
      x.remove(0,1);
      y = x.toInt();
      Serial.print(b[y]);
      removeInt();
    }else if(x.startsWith("B")){ //Capital "B"
      x.remove(0,1);
      y = x.toInt();
      tmp1 = b[y];
      tmp1.toUpperCase();
      Serial.print(tmp1);
      removeInt();
    }
    goto back;
  }
}

void removeInt(){
  if(y < 10){  
    x.remove(0,1);
  }else if(y < 100){
    x.remove(0,2);
  }else{
    x.remove(0,3);
  }
}

I am using the Adafruit QT Py (ATSAMD21E18)

To clarify:
const String a[119] and const String b[119] are lookup tables containing all of the strings that are needed.

String x is the variable that holds string(s) sent through the Serial monitor.

int y is variable that holds the index used for the lookup tables.

String tmp1 and String tmp2 aka "Temporary 1" and "Temporary 2" respectively, are variables used for working with strings as the lookup tables cannot/should not be modified.

int z is simply a temporary variable used to store the length of some strings.

This code is meant to take a string sent through the Serial monitor and then use that string to print back a readable string.

What this code is supposed to do:
Read a string (with a particular format) sent through the Serial monitor.
Print a string based on the first character(s):
Lowercase "a" > Using the number following the "a" as the index, print a string from lookup table a[119], and remove the letter and number.
Uppercase "A" > Same as Lowercase, but change the string to Uppercase before printing.
Uppercase and Lowercase "B" > same as "A".
Space > Print a space.
The letter "n" > Print the number following the "n", remove the letter and number.
Repeat this process until the entire string is gone (or rather, until the length of the String x is equal to 0).

If I send "A92a7a19a102a74a7 A23a13a92b63 n123456" with the Serial monitor, it prints back the string "Unknown Value 123456", which is exactly as intended.

However, if I send the string (or any string) again, it prints back nothing. Trying to send something once or twice more causes everything to freeze up and I am unable to interact with the IDE, and it must be forcibly closed using the Task Manager.

Although it works as intended the first time, I would like to be able to repeat the process by sending multiple strings.

Can anyone help me understand why it only functions once and what is causing this freezing?
I am somewhat new to programming, so perhaps my methodology isn't ideal.

start by putting

  Serial.begin(9600);
  while (!Serial) {}

in the setup, this does not belong to the loop.

the loop - as its name indicates - will loop for you, so no need for a goto... (use a while() to "empty" the user String)

Serial.readString(); wil fail on timeout.... not a great way to input data

I would suggest to study Serial Input Basics to handle this (and not to use the String class)

Oops, I didn't realize that that was in the loop rather than setup.
Kind of forgot how while() works.

Thanks!

you could use while() in something like this (without using the String class and reading the input in a cString buffer)

const char endMarker = '\n';          // Endmarker for ending the Input
const size_t bufferSize = 128;

char currentInputArray[bufferSize + 1]; // an array to store the received data, +1 to store a trailing null char
size_t currentBufferIndex = 0;

const char* a[] = {"-", "h", "he", "li", "be", "b", "c", "n", "o",
                   "f", "ne", "na", "mg", "al", "si", "p", "s", "cl",
                   "ar", "k", "ca", "sc", "ti", "v", "cr", "mn", "fe",
                   "co", "ni", "cu", "zn", "ga", "ge", "as", "se", "br",
                   "kr", "rb", "sr", "y", "zr", "nb", "mo", "tc", "ru",
                   "rh", "pd", "ag", "cd", "in", "sn", "sb", "te", "i",
                   "xe", "cs", "ba", "la", "ce", "pr", "nd", "pm", "sm",
                   "eu", "gd", "tb", "dy", "ho", "er", "tm", "yb", "lu",
                   "hf", "ta", "w", "re", "os", "ir", "pt", "au", "hg",
                   "tl", "pb", "bi", "po", "at", "rn", "fr", "ra", "ac",
                   "th", "pa", "u", "np", "pu", "am", "cm", "bk", "cf",
                   "es", "fm", "md", "no", "lr", "rf", "db", "sg", "bh",
                   "hs", "mt", "ds", "rg", "cn", "nh", "fl", "mc", "lv",
                   "ts", "og"
                  };
const size_t aCount = sizeof a / sizeof a[0];

const char* b[] = {"-", "-", "h", "l", "b", "-", "-", "-", "-",
                   "-", "n", "n", "m", "a", "s", "-", "-", "c",
                   "a", "-", "c", "s", "t", "-", "c", "m", "f",
                   "c", "n", "c", "z", "g", "g", "a", "s", "b",
                   "k", "r", "s", "-", "z", "n", "m", "t", "r",
                   "r", "p", "a", "c", "i", "s", "s", "t", "-",
                   "x", "c", "b", "l", "c", "p", "n", "p", "s",
                   "e", "g", "t", "d", "h", "e", "t", "y", "l",
                   "h", "t", "-", "r", "o", "i", "p", "a", "h",
                   "t", "p", "b", "p", "a", "r", "f", "r", "a",
                   "t", "p", "-", "n", "p", "a", "c", "b", "c",
                   "e", "f", "m", "n", "l", "r", "d", "s", "b",
                   "h", "m", "d", "r", "c", "n", "f", "m", "l",
                   "t", "o"
                  };
const size_t bCount = sizeof b / sizeof b[0];

inline void printUpper(const char* str) {
  while (*str) Serial.write(toupper(*str++));
}

long readLong(char* &position) {
  char* endPtr;
  long v = strtol(position, &endPtr, 10);
  if (endPtr == position) {
    Serial.print(F("\nerror parsing number at ")); Serial.println(position);
    v = -1;
  } else {
    position = endPtr;
  }
  return v;
}

void setup() {
  Serial.begin(115200); Serial.println();
  Serial.println(F("\n\nenter text terminated by NL"));
}

void loop() {
  int currentInput = Serial.read();                                               // try to read. May be nothing is available
  if (currentInput != -1) {                                                       // if we received something
    if (currentInput != endMarker) {                                              // and it's not the endMarker
      if (currentInput != '\r') {                                                 // just ignore carriage return '\r'
        if (currentBufferIndex < bufferSize) {                                    // test if we have room left
          currentInputArray[currentBufferIndex++] = (char) currentInput;          // keep the new byte into the array & count up
          currentInputArray[currentBufferIndex] = '\0';                           // Keep things tiddy as a cString
        } else {
          Serial.println(F("\nerror input too long. Increase bufferSize."));
        }
      }
    } else {                                                                      // we have received the endMarker, parse the line
      char* parsePosition = currentInputArray;
      Serial.print(F("DECRYPTED: "));
      while (*parsePosition) {
        switch (*parsePosition) {
          case ' ': {
              parsePosition++;                                                    // skip that command
              Serial.write(' ');
              break;
            }

          case 'n': {
              parsePosition++;                                                    // skip that command
              Serial.print(readLong(parsePosition));
              break;
            }

          case 'a': {
              parsePosition++;                                                    // skip that command
              long n = readLong(parsePosition);
              if ((n >= 0) && (n < aCount)) Serial.print(a[n]);
              else Serial.println(F("Error: a index out of bounds"));
              break;
            }

          case 'A': {
              parsePosition++;                                                    // skip that command
              long n = readLong(parsePosition);
              if ((n >= 0) && (n < aCount)) printUpper(a[n]);
              else Serial.println(F("Error: A index out of bounds"));
              break;
            }

          case 'b': {
              parsePosition++;                                                    // skip that command
              long n = readLong(parsePosition);
              if ((n >= 0) && (n < bCount)) Serial.print(b[n]);
              else Serial.println(F("Error: b index out of bounds"));
              break;
            }
          case 'B': {
              parsePosition++;                                                    // skip that command
              long n = readLong(parsePosition);
              if ((n >= 0) && (n < bCount)) printUpper(b[n]);
              else Serial.println(F("Error: B index out of bounds"));
              break;
            }

          default: break;

        }
      }
      currentBufferIndex = 0; // get ready for a new attempt, reset index
      currentInputArray[0] = '\0'; // start with an empty cString
      Serial.println(F("\n\nenter text terminated by NL"));
    }
  }
}

typed here from your work for the decryption business logic, fully untested. ➜ to be tried out with Serial Monitor at 115200 bauds and end of line set to NL or CR/NL

PS/ the b array seems to hold only 1 char strings so could be optimized to be a char array instead of a cString pointer array.

While this approach take less RAM than the String stuff, it's still 1056 bytes of RAM wasted. :unamused:

indeed. but it's a pain to type the code to get an array of PROGMEM strings... been lazy :slight_smile:
could save a bit on the second array

A small hint about the devastating blow to the RAM would have been nice. :grinning:

you've done it :slight_smile: and it's a fair point (and if it's the only thing the code does, then it's OK, there is enough memory even on a UNO).

EDIT:

you are right, this is bothering me and probably not too much of an hassle to type if we consider that most cStrings in the a array require 3 bytes, so you don't loose much making a 2D array

this code should then work and not require as much work

const char a[][3] PROGMEM = {"-", "h", "he", "li", "be", "b", "c", "n", "o",
                             "f", "ne", "na", "mg", "al", "si", "p", "s", "cl",
                             "ar", "k", "ca", "sc", "ti", "v", "cr", "mn", "fe",
                             "co", "ni", "cu", "zn", "ga", "ge", "as", "se", "br",
                             "kr", "rb", "sr", "y", "zr", "nb", "mo", "tc", "ru",
                             "rh", "pd", "ag", "cd", "in", "sn", "sb", "te", "i",
                             "xe", "cs", "ba", "la", "ce", "pr", "nd", "pm", "sm",
                             "eu", "gd", "tb", "dy", "ho", "er", "tm", "yb", "lu",
                             "hf", "ta", "w", "re", "os", "ir", "pt", "au", "hg",
                             "tl", "pb", "bi", "po", "at", "rn", "fr", "ra", "ac",
                             "th", "pa", "u", "np", "pu", "am", "cm", "bk", "cf",
                             "es", "fm", "md", "no", "lr", "rf", "db", "sg", "bh",
                             "hs", "mt", "ds", "rg", "cn", "nh", "fl", "mc", "lv",
                             "ts", "og",
                            };

const size_t aCount = sizeof a / sizeof a[0];

const char b[] PROGMEM = {'-', '-', 'h', 'l', 'b', '-', '-', '-', '-',
                          '-', 'n', 'n', 'm', 'a', 's', '-', '-', 'c',
                          'a', '-', 'c', 's', 't', '-', 'c', 'm', 'f',
                          'c', 'n', 'c', 'z', 'g', 'g', 'a', 's', 'b',
                          'k', 'r', 's', '-', 'z', 'n', 'm', 't', 'r',
                          'r', 'p', 'a', 'c', 'i', 's', 's', 't', '-',
                          'x', 'c', 'b', 'l', 'c', 'p', 'n', 'p', 's',
                          'e', 'g', 't', 'd', 'h', 'e', 't', 'y', 'l',
                          'h', 't', '-', 'r', 'o', 'i', 'p', 'a', 'h',
                          't', 'p', 'b', 'p', 'a', 'r', 'f', 'r', 'a',
                          't', 'p', '-', 'n', 'p', 'a', 'c', 'b', 'c',
                          'e', 'f', 'm', 'n', 'l', 'r', 'd', 's', 'b',
                          'h', 'm', 'd', 'r', 'c', 'n', 'f', 'm', 'l',
                          't', 'o',
                         };

const size_t bCount = sizeof b / sizeof b[0];

const char endMarker = '\n';          // Endmarker for ending the Input
const size_t bufferSize = 128;

char currentInputArray[bufferSize + 1]; // an array to store the received data, +1 to store a trailing null char
size_t currentBufferIndex = 0;

inline void printFromFlash(const char* str) {
  size_t index = 0;
  while (pgm_read_byte_near(str + index) != '\0')
    Serial.write(pgm_read_byte_near(str + index++));
}

inline void printUpper(const char* str) {
  size_t index = 0;
  while (pgm_read_byte_near(str + index) != '\0')
    Serial.write(toupper(pgm_read_byte_near(str + index++)));
}

long readLong(char* &position) {
  char* endPtr;
  long v = strtol(position, &endPtr, 10);
  if (endPtr == position) {
    Serial.print(F("\nerror parsing number at ")); Serial.println(position);
    v = -1;
  } else {
    position = endPtr;
  }
  return v;
}

void setup() {
  Serial.begin(115200); Serial.println();
  Serial.println(F("\n\nenter text terminated by NL"));
}

void loop() {
  int currentInput = Serial.read();                                               // try to read. May be nothing is available
  if (currentInput != -1) {                                                       // if we received something
    if (currentInput != endMarker) {                                              // and it's not the endMarker
      if (currentInput != '\r') {                                                 // just ignore carriage return '\r'
        if (currentBufferIndex < bufferSize) {                                    // test if we have room left
          currentInputArray[currentBufferIndex++] = (char) currentInput;          // keep the new byte into the array & count up
          currentInputArray[currentBufferIndex] = '\0';                           // Keep things tiddy as a cString
        } else {
          Serial.println(F("\nerror input too long. Increase bufferSize."));
        }
      }
    } else {                                                                      // we have received the endMarker, parse the line
      char* parsePosition = currentInputArray;
      Serial.print(F("DECRYPTED: "));
      while (*parsePosition) {
        switch (*parsePosition) {
          case ' ': {
              parsePosition++;                                                    // skip that command
              Serial.write(' ');
              break;
            }

          case 'n': {
              parsePosition++;                                                    // skip that command
              Serial.print(readLong(parsePosition));
              break;
            }

          case 'a': {
              parsePosition++;                                                    // skip that command
              long n = readLong(parsePosition);
              if ((n >= 0) && (n < aCount)) printFromFlash(a[n]);
              else Serial.println(F("Error: a index out of bounds"));
              break;
            }

          case 'A': {
              parsePosition++;                                                    // skip that command
              long n = readLong(parsePosition);
              if ((n >= 0) && (n < aCount)) printUpper(a[n]);
              else Serial.println(F("Error: A index out of bounds"));
              break;
            }

          case 'b': {
              parsePosition++;                                                    // skip that command
              long n = readLong(parsePosition);
              if ((n >= 0) && (n < bCount)) Serial.write(pgm_read_byte_near(b + n));
              else Serial.println(F("Error: b index out of bounds"));
              break;
            }
          case 'B': {
              parsePosition++;                                                    // skip that command
              long n = readLong(parsePosition);
              if ((n >= 0) && (n < bCount)) Serial.write(toupper(pgm_read_byte_near(b + n)));
              else Serial.println(F("Error: B index out of bounds"));
              break;
            }

          default: break;

        }
      }
      currentBufferIndex = 0; // get ready for a new attempt, reset index
      currentInputArray[0] = '\0'; // start with an empty cString
      Serial.println(F("\n\nenter text terminated by NL"));
    }
  }
}

Does PROGMEM work on a ATSAMD21E18 as the OP is using?

If PROGMEM does not actually exist, the compiler will ignore the PROGMEM, but should automatically put const data in the flash memory (or whatever the equivalent is on the board being used). Knowing there is no PROGMEM as such would eliminate the need for the special techniques to read the data from PROGMEM.

1 Like

oops.. forgot about that.
Hey 2 versions for the price of one :slight_smile:

yeah, my pgm_read_byte_near() would be an issue