Problems with PROGMEM, and Serial.readString()

Hi, the problems I'm getting with this project are getting weirder and weirder.

The program takes an input from serial (A component name) and searches through a 3D array of strings to find a match. The coordinates from the array represents the position of the draw. I.e. coords (0, 0) represent the top left drawer and a laser is pointed at it. The last dimension is used for when there are multiple things in the draw but both will output the same position (variable name : pos)

My problem is that the arduino restarts (I'm assuming this because it prints a line from "void setup()") when I input a phrase (i.e. screws). The output to serial moniter is:
|sāø®
Hello

I changed my code to use PROGMEM because before when stored in SRAM the component_array was filled to its appropriate size (i.e. all the components were in the array) it would stop working (The global variables still only used 60% of SRAM). It stopped working because Serial.readString() stopped working but it would not reset.
However when only one row was in the array (while keeping the initialised size to component_array[5][6][2]) The code would find the position fine.

I printed "|" on either side of input so I could see if there were any spaces or \n.


String input = "unchanged";
byte pos[2] = {0, 0};
bool part_found = false;

void setup() {
  Serial.begin(9600);
  Serial.println("\nHello\n");
  yServo.attach(9);
  xServo.attach(10);
  pinMode(6, OUTPUT);
} // setup()


void loop() {
  
  while (Serial.available() == 0) {
  }  

    input = Serial.readString();
    input = formatInput(input);

    Serial.print("|"); 
    Serial.print(input); 
    Serial.println("|"); 
    findPos(input);

    if (part_found) {
      Serial.print("Row: ");
      Serial.print(pos[0]);
      Serial.print("\tColumn: ");
      Serial.println(pos[1]);
      
      pointLaser();
    } else {
      Serial.println("Part not found.");
    }
    input = "";
  
} // loop()



void findPos(String input) {
  part_found = false;
  for (int i = 0; i < NUM_ROWS; i++) {
    for (int j = 0; j < NUM_COLS; j++) {
      strcpy_P(buffer1, (char *)pgm_read_word(&(component_array[i][j][0])));  // Necessary casts and dereferencing, just copy.
      // Serial.println(buffer1);
      strcpy_P(buffer2, (char *)pgm_read_word(&(component_array[i][j][1])));  // Necessary casts and dereferencing, just copy.
      // Serial.println(buffer2);      // Serial.print("Input: |");
        // Serial.print(input);
        // Serial.print("|\tComponent 1: |");
        // Serial.print(component_array[i][j][0]);
        // Serial.print("|\tComponent 2: ");
        // Serial.println(component_array[i][j][1]);
      if (input == buffer1 || input == buffer2) {

        pos[0] = i + 1;
        pos[1] = j + 1;

        part_found = true;

        // Break
        i = NUM_ROWS;
        j = NUM_COLS;
      }
    }
  } 
} // findPos()


void pointLaser(){
  byte angle_y = map(pos[0], 1, NUM_ROWS, Y_TL, Y_BL);
  byte angle_x = map(pos[1], 1, NUM_COLS, X_TL, X_TR);
  yServo.write(angle_y);
  xServo.write(angle_x);
  delay(100);
  analogWrite(6, 8);
  delay(5000);
  analogWrite(6, 0);
  
}

String formatInput(String input) {
  input.toLowerCase();
  byte input_last_char_index = input.length() - 1;
  if (input.charAt(input_last_char_index) == '\n' || input.charAt(input_last_char_index) == '\r' )  // Remove new line character at end
    input.remove(input_last_char_index);  
  --input_last_char_index;
  if (input.charAt(input_last_char_index) == '\n' || input.charAt(input_last_char_index) == '\r' )  // Remove new line character at end
    input.remove(input_last_char_index); 
  
  return input;
}





// const String component_array[NUM_ROWS][NUM_COLS][2] = {
//   {{"resistors", "resistors"}, {"lm6uu", "hinges"}, {"capacitors", "inductors"}, {"ldrs", empty_spot}, {"power resistors","power resisters"}, {"leds", empty_spot}},
    
//   {{"desoldered parts", "fuses"}, {"barrel connectors", "vgas"}, {"capacitors", "salvaged parts"}, {"rotary encoders", "crocodile clips"}, 
//   {"screw terminals", "female headers"}, {"male headers", "xt60 connectors"}},
  
//   {{"female barrel connectors", "earphone cables"}, {"photointerrupters", "optical encoders"}, {"gold", "cable organizers"}, 
//   {"rubber feet", "fishing set"}, {"shelf holders", "motion resistors"}, {"power transistors", empty_spot}},
  
//   {{"wifi stuff", empty_spot}, {"headphone jack", empty_spot}, {"bridge rectifier", "battery protectors"}, {"joysticks", "watch parts"}, 
//   {"diodes", "diodes"}, {"mosfets", "mosfets"}},
  
//   {{"optics", "dvd parts"}, {"magnets", "magnets"}, {"springs", "springs"}, {"aaa batteries", "small bolts"}, {"screws", "large bolts"},
//   {"transistors", "tiny bolts"}}
// };

The commented array at the bottom of the code was before I adjusted the code to load the array to PROGMEM

Which arduino are you using?

Please post code that can actually be compiled, there is too much left out of the code.

strcpy_P does not need the pgm_read_word() and casting to char*, the 2nd argument is already a pointer into PROGMEM.

< edit >
Storing an array of strings (null-terminated char arrays) into PROGMEM can be a bit tricky. If not done correctly, you will end up with an array of char* in PROGMEM, but the strings themselves stored in ram.

1 Like

Turns out it was a dumb mistake because I forgot to add the size for the 3rd Dimension.
I'm not sure why it compiled the first time without any errors but thank you for responding.

You don't need to use Strings all over and if you do, you could replace part of your formatInput() function (after transforming into small caps) with the trim() method

but for data entry, I would suggest to study Serial Input Basics.

Here is a small example that might give you some ideas if you go for fixed width for the keywords

You can test it here:

const size_t nbCols = 5;
const size_t maxWordLength = 10;

struct t_line {
  char wordsInLine[nbCols][maxWordLength + 1];
};

static const t_line matrix[] PROGMEM = {
  {"word 00", "word 01", "word 02", "word 03", "word 04"},
  {"word 10", "word 11", "word 12", "word 13", "word 14"},
  {"word 20", "word 21", "word 22", "word 23", "word 24"},
  {"word 30", "word 31", "word 32", "word 33", "word 34"},
  {"word 40", "word 41", "word 42", "word 43", "word 44"},
  {"word 50", "word 51", "word 52", "word 53", "word 54"},
  {"word 60", "word 61", "word 62", "word 63", "word 64"},
  {"word 70", "word 71", "word 72", "word 73", "word 74"},
  {"word 80", "word 81", "word 82", "word 83", "word 84"},
};

const size_t nbLines = sizeof matrix / sizeof * matrix;

bool findKeyword(const char * keyword, size_t & col, size_t & line) {
  bool found = false;
  for (size_t i = 0; i < nbLines; i++) {
    for (size_t j = 0; j < nbCols; j++) {
      if (strcmp_P(keyword, matrix[i].wordsInLine[j]) == 0) {
        found = true;
        col = j;
        line = i;
        break;
      }
    }
    if (found) break;
  }
  return found;
}

void test(const char* keyword) {
  size_t l, c;
  char wordToFind[maxWordLength + 1];

  strlcpy(wordToFind, keyword, maxWordLength + 1); // this is to simulate the fact that we would have a buffer to serial input
  Serial.print(F("Searching coordinates for \"")); Serial.print(wordToFind); Serial.print(F("\" ==> "));
  if (findKeyword(wordToFind, c, l)) {
    Serial.print(F("found at line ")); Serial.print(l); Serial.print(F(" and column ")); Serial.print(c);Serial.println(F(" (counting from index 0)."));
  } else {
    Serial.println(F("not found."));
  }
}

void setup() {
  Serial.begin(115200); Serial.println();
  Serial.println(F("This is the matrix of keywords"));
  Serial.println(F("------------------------------\n"));

  for (size_t i = 0; i < nbLines; i++) {
    for (size_t j = 0; j < nbCols; j++) {
      Serial.print((__FlashStringHelper*) matrix[i].wordsInLine[j]);
      Serial.print(F("\t\t"));
    }
    Serial.println();
  }

  Serial.println(F("------------------------------\n"));

  // -------------
  test("word 00");
  test("word 11");
  test("word 22");
  test("word 42");
  test("word 84");
  test("will fail");
}

void loop() {}
1 Like

The trim() and Input basics was useful, thank you.
Ill keep the fixed keyword code ready for another project

glad it helped

side note: the keywords can all have different length ā€” I chose the same representation just because it was easier to see what's going on ā€” they are just stored using the same number of bytes so you need to define that maximum length which was my maxWordLength constant

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.