Board displays nothing when more variables are instantiated

The code as-is works. It cycles through letters displaying each one on the board's (UNO R4 Wifi) led matrix. As soon as I uncomment any additional letter the board displays nothing and nothing is printed to Serial. I can comment out any of the active letters and uncomment any of the currently unused letters and it still works.

I'm new to Arduino and c++. Is there are limit to the of variables I can instantiate in the constructor or am I hitting a memory issue or something else?

What are some good options for debugging this and similar issues?

test.ino

#include "Arduino_LED_Matrix.h"
#include <String.h>
#include <cstddef>
#include <Arduino.h>
#include "MatrixAlphabet.h"

ArduinoLEDMatrix matrix;
MatrixAlphabet alphabet;

byte frame[8][12] = {
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

void setup() {
  Serial.begin(9600);
  matrix.begin();
}

int index = 0;

void loop() {
  // index = index % 4;
  Letter letterA = alphabet.getLetterTest(index);
  Serial.print("Rows:");
  Serial.print(letterA.getRows());
  Serial.print("\tCols:");
  Serial.print(letterA.getCols());
  Serial.println();
  for(int row = 0; row < 5; row++) {
    for(int col = 0; col < 3; col++) {
      frame[row][col] = letterA.getByteArray()[row][col];
      Serial.print(letterA.getByteArray()[row][col]);
      Serial.print(",");
    }
    Serial.println();
  }
  
  Serial.println("----------------");
  matrix.renderBitmap(frame, 8, 12);
  delay(100);
  index++;
}

MatrixAlphabet.h

#ifndef MATRIX_ALPHABET_H
#define MATRIX_ALPHABET_H

#include <iostream>
#include <vector>
#include <stdexcept>
#include <array> 
#include "Letter.h"

class MatrixAlphabet {
public:
    MatrixAlphabet();
    Letter getLetterTest(int index);

private:
    Letter A;
    Letter B;
    Letter C;
    Letter D;
    Letter E;
    Letter F;
    Letter G;
    Letter H;
    Letter I;
    Letter J;
    // Letter K;
    // Letter L;
    Letter M;
    // Letter N;
    // Letter O;
    Letter P;
    // Letter Q;
    // Letter R;
    // Letter S;
    // Letter T;
    // Letter U;
    // Letter V; 
    // Letter W;
    // Letter X;
    // Letter Y;
    // Letter Z;
    std::vector<Letter> letters;
};

#endif

MatrixAlphabet.cpp

#include "MatrixAlphabet.h"

// Letter letters[] = {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z};
MatrixAlphabet::MatrixAlphabet() : 
  A(5, 3),
  B(5, 3),
  C(5, 3),
  D(5, 3),
  E(5, 3),
  F(5, 3),
  G(5, 3),
  H(5, 3),
  I(5, 3),
  J(5, 3),
  // K(5, 3),
  // L(5, 3)
  M(5, 3),
  // N(5, 3)
  // O(5, 3)
  P(5, 3)
  // Q(5, 3),
  // R(5, 3),
  // S(5, 3),
  // T(5, 3),
  // U(5, 3),
  // V(5, 3),
  // W(5, 3),
  // X(5, 3),
  // Y(5, 3),
  // Z(5, 3)
 {
  
  A.fillFromByteArray({
    { 0, 1, 0 },
    { 1, 0, 1 },
    { 1, 1, 1 },
    { 1, 0, 1 },
    { 1, 0, 1 }
  });

  B.fillFromByteArray({
    { 1, 1, 0},
    { 1, 0, 1},
    { 1, 1, 0},
    { 1, 0, 1},
    { 1, 1, 0}
  });

  C.fillFromByteArray({
    { 0, 1, 0},
    { 1, 0, 1},
    { 1, 0, 0},
    { 1, 0, 1},
    { 0, 1, 0}
  });

  D.fillFromByteArray({
    { 1, 1, 0},
    { 1, 0, 1},
    { 1, 0, 1},
    { 1, 0, 1},
    { 1, 1, 0}
  });

  E.fillFromByteArray({
    { 1, 1, 1},
    { 1, 0, 0},
    { 1, 1, 1},
    { 1, 0, 0},
    { 1, 1, 1}
  });

  F.fillFromByteArray({
    { 1, 1, 1},
    { 1, 0, 0},
    { 1, 1, 1},
    { 1, 0, 0},
    { 1, 0, 0}
  });

  G.fillFromByteArray({
    { 1, 1, 1},
    { 1, 0, 0},
    { 1, 0, 1},
    { 1, 0, 1},
    { 1, 1, 1}
  });

  H.fillFromByteArray({
    { 1, 0, 1},
    { 1, 0, 1},
    { 1, 1, 1},
    { 1, 0, 1},
    { 1, 0, 1}
  });
  
  I.fillFromByteArray({
    { 1, 1, 1},
    { 0, 1, 0},
    { 0, 1, 0},
    { 0, 1, 0},
    { 1, 1, 1}
  });
  
  J.fillFromByteArray({
    { 1, 1, 1},
    { 0, 1, 0},
    { 0, 0, 1},
    { 1, 0, 1},
    { 0, 1, 0}
  });
  
  // K.fillFromByteArray({
  //   { 1, 0, 0},
  //   { 1, 0, 1},
  //   { 1, 0, 1},
  //   { 1, 1, 0},
  //   { 1, 0, 1}
  // });
  
  // L.fillFromByteArray({
  //   { 1, 0, 0},
  //   { 1, 0, 0},
  //   { 1, 0, 0},
  //   { 1, 0, 0},
  //   { 1, 1, 1}
  // });
  
  M.fillFromByteArray({
    { 1, 0, 1},
    { 1, 1, 1},
    { 1, 0, 1},
    { 1, 0, 1},
    { 1, 0, 1}
  });
  
  // N.fillFromByteArray({
  //   { 1, 0, 1},
  //   { 1, 0, 1},
  //   { 1, 1, 1},
  //   { 1, 0, 1},
  //   { 1, 0, 1}
  // });
  
  // O.fillFromByteArray({
  //   { 1, 1, 1},
  //   { 1, 0, 1},
  //   { 1, 0, 1},
  //   { 1, 0, 1},
  //   { 1, 1, 1}
  // });
  
  P.fillFromByteArray({
    { 1, 1, 0},
    { 1, 0, 1},
    { 1, 1, 0},
    { 1, 0, 0},
    { 1, 0, 0}
  });
  
//   Q.fillFromByteArray({
//     { 1, 1, 1},
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 1, 1, 1},
//     { 1, 1, 1}
//   });
  
//   R.fillFromByteArray({
//     { 1, 1, 0},
//     { 1, 0, 1},
//     { 1, 1, 0},
//     { 1, 0, 1},
//     { 1, 0, 1}
//   });
  
//   S.fillFromByteArray({
//     { 0, 1, 1},
//     { 1, 0, 0},
//     { 0, 1, },
//     { 0, 0, 1},
//     { 1, 1, 0}
//   });
  
//   T.fillFromByteArray({
//     { 1, 1, 1},
//     { 0, 1, 0},
//     { 0, 1, 0},
//     { 0, 1, 0},
//     { 0, 1, 0}
//   });
  
//   U.fillFromByteArray({
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 1, 1, 1}
//   });
  
//   V.fillFromByteArray({
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 0, 1, 0}
//   });
  
//   W.fillFromByteArray({
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 1, 1, 1},
//     { 1, 0, 1}
//   });
  
//   X.fillFromByteArray({
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 0, 1, 0},
//     { 1, 0, 1},
//     { 1, 0, 1}
//   });
  
//   Y.fillFromByteArray({
//     { 1, 0, 1},
//     { 1, 0, 1},
//     { 0, 1, 0},
//     { 0, 1, 0},
//     { 0, 1, 0}
//   });
  
  // Z.fillFromByteArray({
  //   { 1, 1, 1},
  //   { 0, 0, 1},
  //   { 0, 1, 0},
  //   { 1, 0, 0},
  //   { 1, 1, 1}
  // });
  letters = {A,B,C,D,E,F,G,H,I,J,M};
}

Letter MatrixAlphabet::getLetterTest(int index) {
  return letters[index % size(letters)];
}

Letter.h

#ifndef LETTER_H
#define LETTER_H

#include <iostream>
#include <vector>
#include <stdexcept>
#include <array> 

class Letter {
    public:
      Letter(int rows, int cols);
      void fillFromByteArray(const std::vector<std::vector<uint8_t>> byteArray);
      std::vector<std::vector<uint8_t>> getByteArray();
      int getRows();
      int getCols();

    private:
      int rows;
      int cols;
      std::vector<std::vector<uint8_t>> letterArray;
};

#endif

Letter.cpp

#include "Letter.h"

Letter::Letter(int rows_, int columns_) {
  rows = rows_;
  cols = columns_; 
  letterArray = std::vector<std::vector<uint8_t>>(rows, std::vector<uint8_t>(cols, 0));
}

void Letter::fillFromByteArray(const std::vector<std::vector<uint8_t>> byteArray) {
  for (size_t i = 0; i < rows; ++i) {
      for (size_t j = 0; j < cols; ++j) {
          letterArray[i][j] = byteArray[i][j];
      }
  }
}

std::vector<std::vector<uint8_t>> Letter::getByteArray() {
  return letterArray;
}

int Letter::getRows() {
  return rows;
}

int Letter::getCols() {
  return cols;
}

What are the additional letters?

Letter just being the byte arrays for displaying A-Z.
For instance, in the provided code T is commented out.
If I uncomment it something breaks but it's unclear where/how.

From MatrixAlphabet.h

...
 // Letter T;
...

From MatrixAlphabet.cpp

...
// T(5, 3),
...
//   T.fillFromByteArray({
//     { 1, 1, 1},
//     { 0, 1, 0},
//     { 0, 1, 0},
//     { 0, 1, 0},
//     { 0, 1, 0}
//   });
...

Remember to change the indexing values in the code, as well!

I refactored the letter instantiation out of the constructor and made them static variables instead. I can still only create 12 'Letters' before the board stops working.

I also more than doubled the size of the 2d arrays for most letters (with filler) and it still worked with the first 12 letters. So it doesn't seem like a memory issue.

#ifndef MATRIX_ALPHABET_H
#define MATRIX_ALPHABET_H

#include <iostream>
#include <vector>
#include <stdexcept>
#include <array> 
#include "Letter.h"

class MatrixAlphabet {
public:
    Letter getLetterTest(int index);

private:
    static Letter A;
    static Letter B;
    static Letter C;
    static Letter D;
    static Letter E;
    static Letter F;
    static Letter G;
    static Letter H;
    static Letter I;
    static Letter J;
    static Letter K;
    static Letter L;
    static Letter M;
    // static Letter N;
    // static Letter O;
    // static Letter P;
    // static Letter Q;
    // static Letter R;
    // static Letter S;
    // static Letter T;
    // static Letter U;
    // static Letter V; 
    // static Letter W;
    // static Letter X;
    // static Letter Y;
    // static Letter Z;
    static std::vector<Letter> letters;
};

#endif
#include "MatrixAlphabet.h"

Letter MatrixAlphabet::A(5,3, {
  { 0, 1, 0 },
  { 1, 0, 1 },
  { 1, 1, 1 },
  { 1, 0, 1 },
  { 1, 0, 1 }
});

Letter MatrixAlphabet::B(5,7, {
  { 1, 1, 0, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1},
  { 1, 1, 0, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1},
  { 1, 1, 0, 1, 1, 1, 1}
});

Letter MatrixAlphabet::C(5,3, {
  { 0, 1, 0},
  { 1, 0, 1},
  { 1, 0, 0},
  { 1, 0, 1},
  { 0, 1, 0}
});

Letter MatrixAlphabet::D(5,7, {
  { 1, 1, 0, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1},
  { 1, 1, 0, 1, 1, 1, 1}
});

Letter MatrixAlphabet::E(5,7, {
  { 1, 1, 1, 0, 0, 0, 0},
  { 1, 0, 0, 0, 0, 0, 0},
  { 1, 1, 1, 0, 0, 0, 0},
  { 1, 0, 0, 0, 0, 0, 0},
  { 1, 1, 1, 0, 0, 0, 0}
});

Letter MatrixAlphabet::F(5,7, {
  { 1, 1, 1, 1, 1, 1, 1},
  { 1, 0, 0, 1, 1, 1, 1},
  { 1, 1, 1, 1, 1, 1, 1},
  { 1, 0, 0, 1, 1, 1, 1},
  { 1, 0, 0, 1, 1, 1, 1}
});

Letter MatrixAlphabet::G(5,7, {
  { 1, 1, 1, 0, 0, 0, 0},
  { 1, 0, 0, 0, 0, 0, 0},
  { 1, 0, 1, 0, 0, 0, 0},
  { 1, 0, 1, 0, 0, 0, 0},
  { 1, 1, 1, 0, 0, 0, 0}
});

Letter MatrixAlphabet::H(5,7, {
  { 1, 0, 1, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1},
  { 1, 1, 1, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1}
});

Letter MatrixAlphabet::I(5,7, {
  { 1, 1, 1, 0, 0, 0, 0},
  { 0, 1, 0, 0, 0, 0, 0},
  { 0, 1, 0, 0, 0, 0, 0},
  { 0, 1, 0, 0, 0, 0, 0},
  { 1, 1, 1, 0, 0, 0, 0}
});

Letter MatrixAlphabet::J(5,7, {
  { 1, 1, 1, 1, 1, 1, 1},
  { 0, 1, 0, 1, 1, 1, 1},
  { 0, 0, 1, 1, 1, 1, 1},
  { 1, 0, 1, 1, 1, 1, 1},
  { 0, 1, 0, 1, 1, 1, 1}
});

Letter MatrixAlphabet::K(5,3, {
  { 1, 0, 0},
  { 1, 0, 1},
  { 1, 0, 1},
  { 1, 1, 0},
  { 1, 0, 1}
});

Letter MatrixAlphabet::L(5,7, {
  { 1, 0, 0, 1, 1, 1, 1},
  { 1, 0, 0, 1, 1, 1, 1},
  { 1, 0, 0, 1, 1, 1, 1},
  { 1, 0, 0, 1, 1, 1, 1},
  { 1, 1, 1, 1, 1, 1, 1}
});

Letter MatrixAlphabet::M(5,3, {
  { 1, 0, 1},
  { 1, 1, 1},
  { 1, 0, 1},
  { 1, 0, 1},
  { 1, 0, 1}
});

// Letter MatrixAlphabet::N(5,3, {
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 1, 1},
//   { 1, 0, 1},
//   { 1, 0, 1}
// });
  

// Letter MatrixAlphabet::O(5, 3, {
//   { 1, 1, 1},
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 1, 1}
// });

// Letter MatrixAlphabet::P(5,3, {
//   { 1, 1, 0},
//   { 1, 0, 1},
//   { 1, 1, 0},
//   { 1, 0, 0},
//   { 1, 0, 0}
// });

// Letter MatrixAlphabet::Q(5,3, {
//   { 1, 1, 1},
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 1, 1},
//   { 1, 1, 1}
// });

// Letter MatrixAlphabet::R(5,3, {
//   { 1, 1, 0},
//   { 1, 0, 1},
//   { 1, 1, 0},
//   { 1, 0, 1},
//   { 1, 0, 1}
// });

// Letter MatrixAlphabet::S(5,3, {
//   { 0, 1, 1},
//   { 1, 0, 0},
//   { 0, 1, },
//   { 0, 0, 1},
//   { 1, 1, 0}
// });

// Letter MatrixAlphabet::T(5,3, {
//   { 1, 1, 1},
//   { 0, 1, 0},
//   { 0, 1, 0},
//   { 0, 1, 0},
//   { 0, 1, 0}
// });

// Letter MatrixAlphabet::U(5,3, {
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 1, 1}
// });

// Letter MatrixAlphabet::V(5,3, {
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 0, 1, 0}
// });

// Letter MatrixAlphabet::W(5,3, {
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 1, 1, 1},
//   { 1, 0, 1}
// });

// Letter MatrixAlphabet::X(5,3, {
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 0, 1, 0},
//   { 1, 0, 1},
//   { 1, 0, 1}
// });

// Letter MatrixAlphabet::Y(5,3, {
//   { 1, 0, 1},
//   { 1, 0, 1},
//   { 0, 1, 0},
//   { 0, 1, 0},
//   { 0, 1, 0}
// });

// Letter MatrixAlphabet::Z(5,3, {
//   { 1, 1, 1},
//   { 0, 0, 1},
//   { 0, 1, 0},
//   { 1, 0, 0},
//   { 1, 1, 1}
// });

std::vector<Letter> MatrixAlphabet::letters = {A,B,C,D,E,F,G,H,I,J,K,L};//,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z};


// Letter letters[] = {A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z};


Letter MatrixAlphabet::getLetterTest(int index) {
  return letters[index % size(letters)];
}
Sketch uses 198600 bytes (75%) of program storage space. Maximum is 262144 bytes.
Global variables use 13240 bytes (40%) of dynamic memory, leaving 19528 bytes for local variables. Maximum is 32768 bytes.

Trying your original, got all the way to Z if the instance is dynamically allocated, instead of as a global variable. There, you just have a pointer

MatrixAlphabet *alphabet;

that is assigned at the start

void setup() {
  Serial.begin(115200);
  matrix.begin();
  alphabet = new MatrixAlphabet;
}

Then just need to adjust the member function call, which now uses a pointer -- only one

  Letter letterA = alphabet->getLetterTest(index);

Related: I couldn't find a "how much free heap" function for R4 after ten seconds of searching, so I spent a minute writing one (and five more refining it)

void freemem() {
  static size_t z = 1 << 10;
  static size_t d = 1 << 10;
  void *x = malloc(z);
  if (x) {
    free(x);
    Serial.print("alloc: ");
    Serial.print(z, HEX);
    Serial.print(" (");
    Serial.print(z);
    Serial.print(") at: ");
    Serial.println(reinterpret_cast<uintptr_t>(x), HEX);
    z += d;
  } else {
    Serial.print("failed alloc:\t\t\t ");
    Serial.println(z, HEX);
    if (d > 16) {
      d >>= 1;
    }
    z -= d;
  }
}

Added a call to it in loop. With your original ten-ish letters, was able to allocate 11KB. After instantiating to Z, down to just 4KB. Seems like a lot of memory. The first suspect might be the vectors-of-vectors. Don't see much use of the STL on Arduino.

@chiseledrump There is little point using those dynamic data structure libraries on Arduino, or any other microcontroller.

The advantage of these libraries, when used on PCs, servers, cloud compute resources etc is that only the memory required, and no more, is used to hold the data. The amount of memory needed is often known only at run-time. Using less memory means more is available for other processes to use, and, in the case of cloud compute resources, is less expensive.

But on a microcontroller, there is usually only one process. So there is no advantage to keeping memory usage down to a minimum, because no other processes will benefit from that. Providing the required memory does not exceed the total available, the single process might as well use all of it.

For this reason, static, rather than dynamic data structures are more often used when programming microcontrollers.

i know you probably want to figure out what the problems are in the code you wrote, but sometimes it's easier to start with something less complicated.

i wrote a script to translate your byte matrices into more compatct byte arrays. The display routine shows how bits can be extracted to create a bit matrix for your renderBitmap().

look this over
output

A
     * 
    * *
    ***
    * *
    * *
B
    ** 
    * *
    ** 
    * *
    ** 
C
     * 
    * *
    *  
    * *
     * 
#include <stdio.h>

typedef unsigned char byte;

const int Ncol = 3;
const int Nrow = 5;

struct Letter {
    byte        row [Nrow];
    const char *lbl;
} letter [] = {
    {{ 2, 5, 7, 5, 5, }, "A" },
    {{ 6, 5, 6, 5, 6, }, "B" },
    {{ 2, 5, 4, 5, 2, }, "C" },
    {{ 6, 5, 5, 5, 6, }, "D" },
    {{ 7, 4, 7, 4, 7, }, "E" },
    {{ 7, 4, 7, 4, 4, }, "F" },
    {{ 7, 4, 5, 5, 7, }, "G" },
    {{ 5, 5, 7, 5, 5, }, "H" },
    {{ 7, 2, 2, 2, 7, }, "I" },
    {{ 7, 2, 1, 5, 2, }, "J" },
    {{ 4, 5, 5, 6, 5, }, "K" },
    {{ 4, 4, 4, 4, 7, }, "L" },
    {{ 5, 7, 5, 5, 5, }, "M" },
    {{ 5, 5, 7, 5, 5, }, "N" },
    {{ 7, 5, 5, 5, 7, }, "O" },
    {{ 6, 5, 6, 4, 4, }, "P" },
    {{ 7, 5, 5, 7, 7, }, "Q" },
    {{ 6, 5, 6, 5, 5, }, "R" },
    {{ 3, 4, 1, 1, 6, }, "S" },
    {{ 7, 2, 2, 2, 2, }, "T" },
    {{ 5, 5, 5, 5, 7, }, "U" },
    {{ 5, 5, 5, 5, 2, }, "V" },
    {{ 5, 5, 5, 7, 5, }, "W" },
    {{ 5, 5, 2, 5, 5, }, "X" },
    {{ 5, 5, 2, 2, 2, }, "Y" },
    {{ 7, 1, 2, 4, 7, }, "Z" },
};
const int Nletter = sizeof(letter)/sizeof(Letter);

// -----------------------------------------------------------------------------
char s [90];

void
dispLetter (
    int idx )
{
    Serial.println (letter [idx].lbl);

    for (int r = 0; r < Nrow; r++)  {
        Serial.print ("    ");
        byte bit = 1 << (Ncol-1);
        for (int c = 0; c < Ncol; c++, bit >>= 1)
            Serial.print ( (letter [idx].row [r] & bit) ? '*' : ' ');
        Serial.println ();
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    for (int n = 0; n < Nletter; n++)
        dispLetter (n);
}

void loop () { }