Dynamic Memory full and I don't know why

Hey guys, I have a problem and after hours of searching I gave up and decided to ask you.

I have a following function:

void getCharacter(byte byteArray[], char character) {

    switch (character) {
      case ' ':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = B00000000;
        }
        break;
      case '0':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = IMAGES[0][i];
        }
        break;
      [...MORE NUMEBRS...]
      case '9':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = IMAGES[9][i];
        }
        break;
      case 'a':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = IMAGES[36][i];
        }
        break;
      [...MORE CHARACTERS...]
      case 'z':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = IMAGES[61][i];
        }
        break;
      default:
        break;
   }
}

I invoke it like this:

void printWord (byte line, String wordToPrint) {

  for (int i = 0; i < wordToPrint.length(); i++)
  {
    byte myChar[8];
    getCharacter(myChar, wordToPrint.charAt(i));
    printDisplay(line, i, myChar);
  }

}

And everything works fine. My whole project consumes about 70% of dynamic memory.
Now, I added this function, which in my opinion should work same as printWorld(), but apperantly it doesn't:

void printClockDesc(byte line, byte pos, char* hour) {

    byte myChar[8];
    getCharacter(myChar, 'm');
    printDisplay(line, pos+2, myChar);

    if(atoi(hour) >= 12)
    {
      getCharacter(myChar, 'p');
      printDisplay(line, pos+1, myChar);
    } else {
      getCharacter(myChar, 'a');
      printDisplay(line, pos+1, myChar);
    }

}

And suddenly dynamic memory is 130% full and the project doesn't compile. Am I missing something about how characters work? I found out getCharacter() function is the cause of the problem but I don't understand why and how to fix it.

It's my first Arduino project and I have only Java background, which doesn't help at all :frowning:

Hope you guys have a clue what am I doing wrong.

Sherrie

what am I doing wrong.

Not posting your code.

Sherrie:
what am I doing wrong.

You didn't give the byte counts, just percentages. Knowing how many bytes the memory used changed by might provide a clue.
Of course if you had included all of your code we could have reproduced the problem ourselves. Or not... it could be a problem in your configuration.

@johnwasser 2048 Bytes are 100%, before invoking printClockDesc() I have 1442 Bytes occupied (70%), and adding this function results in 2660 occupied Bytes (130%).
But I just realized invoking printWord() one more time also causes problems, although both functions execute one after another and are not nested. It this case I don't understand why used memory changes.

Sorry for providing too little code! At this moment my project is pretty big, I will try to provide smallest not working example, but it can take some time.

But I just realized invoking printWord() one more time also causes problems, although both functions execute one after another and are not nested. It this case I don't understand why used memory changes.

Writing code to call a function under some circumstances that the compiler will see can never happen will trigger the compiler to dump that code.

Whether that is what happened, or not, is a mystery since you haven't posted all of your code.

If you'd like just snippets of answers, carry on. If you'd like a complete answer, POST YOUR CODE.

So I reduced the size of my project. Short description: I have two rows of LED displays and control them with the LedControl library.
When commenting out the "printClockDesc(1, 0, hours);" line in the loop() function I have 1006 bytes (49%) of dynamic memory used. If I leave the comment out, 2224 bytes (108%) are used.
printClockDesc() can be found in OUTPUT tab.

MAIN TAB

#include <LedControl.h>

// Emulate Serial1 on pins 5/6 if not present
#ifndef HAVE_HWSERIAL1
#include "SoftwareSerial.h"
SoftwareSerial Serial1(5, 6); // RX, TX
#endif

// 1. DISPLAY GLOBES
int DIN1 = 10;
int CS1 =  9;
int CLK1 = 8;
LedControl lc1 = LedControl(DIN1, CLK1, CS1, 8);

// 2. DISPLAY GLOBES
int DIN2 = 13;
int CS2 =  12;
int CLK2 = 11;
int numberOfDisplays = 8;
LedControl lc2 = LedControl(DIN2, CLK2, CS2, 8);

// WEATHER GLOBES
char temperature = "27";
char weatherConditionGerman = "klarer Himmel";
char hours = "12";


void setup() {
  // initialize serial for debugging
  Serial.begin(115200);

  setupWholeDisplay();
}

unsigned int startTimeCheck;
unsigned int myCheckDelay = 500;

void loop() {

  if (millis() - startTimeCheck > myCheckDelay) {
    printClock(1, 0, hours);
    //printClockDesc(1, 0, hours);
    printWord(2, "Temp: " + String(temperature) + " Himmel: " + weatherConditionGerman);
    startTimeCheck = millis();
  }
}

ALPHABET TAB

const int IMAGES[76][8] = {
  {
    //  0
    B01111100,
    B11000110,
    B11001110,
    B11011110,
    B11110110,
    B11100110,
    B01111100,
    B00000000
  }, {
    //  9
    B01111000,
    B11001100,
    B11001100,
    B01111100,
    B00001100,
    B00011000,
    B01110000,
    B00000000
  }, {
    //  a
    B00000000,
    B00000000,
    B01111000,
    B00001100,
    B01111100,
    B11001100,
    B01110110,
    B00000000
  }, {
    //  z
    B00000000,
    B00000000,
    B11111100,
    B10011000,
    B00110000,
    B01100100,
    B11111100,
    B00000000
  }, {
    //  clock1
    B00111100,
    B01001110,
    B10001111,
    B10001111,
    B10000001,
    B10000001,
    B01000010,
    B00111100
  }, {
    //  clock2
    B00111100,
    B01000010,
    B10000001,
    B10000001,
    B10001111,
    B10001111,
    B01001110,
    B00111100
  }, {
    //  clock3
    B00111100,
    B01000010,
    B10000001,
    B10000001,
    B11110001,
    B11110001,
    B01110010,
    B00111100
  }, {
    //  clock4
    B00111100,
    B01110010,
    B11110001,
    B11110001,
    B10000001,
    B10000001,
    B01000010,
    B00111100
  }
};
const int IMAGES_LEN = sizeof(IMAGES) / 8;


void getCharacter(byte byteArray[], char character) {

    switch (character) {
      case ' ':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = B00000000;
        }
        break;
      case '0':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = IMAGES[0][i];
        }
        break;
      [...MORE NUMEBRS...]
      case '9':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = IMAGES[9][i];
        }
        break;
      case 'a':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = IMAGES[36][i];
        }
        break;
      [...MORE CHARACTERS...]
      case 'z':
        for (int i = 0; i < 8; i++) {
          byteArray[i] = IMAGES[61][i];
        }
        break;
      default:
        break;
   }
}

void getClock(byte byteArray[], int hour) {
  int clockQuarter = 0;
  if (hour == 12 || hour == 0) {
    for (int i = 0; i < 8; i++) {
      byteArray[i] = IMAGES[72][i];
    }
  } else if (hour == 15 || hour == 3) {
    for (int i = 0; i < 8; i++) {
      byteArray[i] = IMAGES[73][i];
    }
  } else if (hour == 18 || hour == 6) {
    for (int i = 0; i < 8; i++) {
      byteArray[i] = IMAGES[74][i];
    }
  } else if (hours == 21 || hour == 9) {
    for (int i = 0; i < 8; i++) {
      byteArray[i] = IMAGES[75][i];
    }
  }
}

OUTPUT TAB

byte matrixMemory1[8][8];
byte matrixMemory2[50][8];

void printClock(byte line, byte pos, char* hour) {
    byte myChar[8];
    getClock(myChar, atoi(hour));
    printDisplay(line, pos, myChar);
}

void printClockDesc(byte line, byte pos, char* hour) {
    byte myChar[8];
    String m = "m";
    getCharacter(myChar, m.charAt(0));
    printDisplay(line, pos+2, myChar);
    if(atoi(hour) >= 12) {
      getCharacter(myChar, 'p');
      printDisplay(line, pos+1, myChar);
    } else {
      getCharacter(myChar, 'a');
      printDisplay(line, pos+1, myChar);
    }
}

void printWord (byte line, String wordToPrint) {

  for (int i = 0; i < wordToPrint.length(); i++) {
    byte myChar[8];
    getCharacter(myChar, wordToPrint.charAt(i));
    printDisplay(line, i, myChar);
  }
}

void printDisplay(byte line, int disNr, char* content) {
  for (int j = 0; j < 8; j++) {
    if (line == 1) {
      matrixMemory1[disNr][j] = content[j];
    }
    else if (line == 2) {
      matrixMemory2[disNr][j] = content[j];
    }
  }

  if (line == 1) {
    if (disNr < lc1.getDeviceCount()) {
      printByte(1, matrixMemory1[disNr], disNr);
    }
  }
  else if (line == 2) {
    if (disNr < lc2.getDeviceCount()) {
      printByte(2, matrixMemory2[disNr], disNr);
    }
  }
}

DISPLAY TAB

void setupWholeDisplay () {
  int i;
  // 1.
  for (i = 0; i < lc1.getDeviceCount(); i++) {
    lc1.shutdown(i, false);      //The MAX72XX is in power-saving mode on startup
    lc1.setIntensity(i, 15);     // Set the brightness to maximum value
    lc1.clearDisplay(i);         // and clear the display
  }
  // 2.
  for (i = 0; i < lc2.getDeviceCount(); i++) {
    lc2.shutdown(i, false);      //The MAX72XX is in power-saving mode on startup
    lc2.setIntensity(i, 15);     // Set the brightness to maximum value
    lc2.clearDisplay(i);         // and clear the display
  }
}

void printByte(byte line, byte character [], int disAddr)
{
  int i = 0;
  for (i = 0; i < 8; i++)
  {
    if(line == 1) {
      lc1.setRow(disAddr, i, reverse(character[7 - i]));
    } else if(line == 2) {
      lc2.setRow(disAddr, i, reverse(character[7 - i]));
    }
  }
}

byte reverse(byte in) {
  byte out = 0;
  for (int i = 0; i < 8; i++)
    out |= ((in >> i) & 1) << (7 - i);
  return out;
}

I hope it's not too much code now :slight_smile:

const int IMAGES[76][8] = {why isn't this in flash memory?

AWOL:
const int IMAGES[76][8] = {why isn't this in flash memory?

It is. The real question is why it isn't constrained to stay there.

PaulS:
It is. The real question is why it isn't constrained to stay there.

This is also true.

maybe the compiler 'optimizes' the call with constant char as parameter.

Thank you guys for you input.
I once moved the IMAGES array to program memory but it didn't seem to save any space, so I decided not to use it, as I'm a newbie and try to keep things simple (for me). But now I tried it again and although I did not "gain" any addidional memory space, the strange memory "spike" while using more functions IS gone.
I couldn't say I understand this behaviour, so if any of you want to explain, feel free.
But for now I'm happy that my program works again :wink:

I once moved the IMAGES array to program memory but it didn't seem to save any space

It wouldn't save any flash memory, because it was already there, but it would save a lot of RAM.

AWOL:

const int IMAGES[76][8] = {

why isn't this in flash memory?

And why is it int and not byte?

char temperature = "27";
char weatherConditionGerman = "klarer Himmel";
char hours = "12";

Check your types. Did you not get a compiler warning?

printWord(2, "Temp: " + String(temperature) + " Himmel: " + weatherConditionGerman);

Somebody is about to jump out and tell you not to use the String class...oops it was me.
Better just to allocate a static buffer and use snprintf() or similar.

unsigned int startTimeCheck;
unsigned int myCheckDelay = 500;
...
startTimeCheck = millis();

millis() returns an unsigned long

void getCharacter(byte byteArray[], char character) {
............OMG

You are clearly a bit of a masochist. Is that switch statement really ~76 elements long?
Instead of repeating the inner for loops, you could have created a function and just passed the image index as a parameter.
Better would be, if you arrange your character array in ASCII order, you should be able to just calculate the indexes by taking offsets. Or you could use a lookup table (an array of indices to your character table, indexed by the ASCII character number).

I got your snippets to compile for the UNO. I was unable to reproduce the problem because you left out most of the ALPHABET TAB. Perhaps you should attach the full file so your problem can be reproduced.

You should turn up your compiler warning level (in Preferences) and fix all the places where you use a 'char' where you need a 'char *' (character pointer).

/Users/john/Documents/Arduino/sketch_aug15a/sketch_aug15a.ino:23:20: warning: invalid conversion from 'const char*' to 'char' [-fpermissive]
 char temperature = "27";
                    ^
/Users/john/Documents/Arduino/sketch_aug15a/sketch_aug15a.ino:24:31: warning: invalid conversion from 'const char*' to 'char' [-fpermissive]
 char weatherConditionGerman = "klarer Himmel";
                               ^
/Users/john/Documents/Arduino/sketch_aug15a/sketch_aug15a.ino:25:14: warning: invalid conversion from 'const char*' to 'char' [-fpermissive]
 char hours = "12";
              ^
/Users/john/Documents/Arduino/sketch_aug15a/sketch_aug15a.ino: In function 'void loop()':
/Users/john/Documents/Arduino/sketch_aug15a/sketch_aug15a.ino:41:27: warning: invalid conversion from 'char' to 'char*' [-fpermissive]
     printClock(1, 0, hours);
                           ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:4:6: note: initializing argument 3 of 'void printClock(byte, byte, char*)'
 void printClock(byte line, byte pos, char* hour) {
      ^
/Users/john/Documents/Arduino/sketch_aug15a/alphabet.ino: In function 'void getClock(byte*, int)':
/Users/john/Documents/Arduino/sketch_aug15a/alphabet.ino:122:7: warning: unused variable 'clockQuarter' [-Wunused-variable]
   int clockQuarter = 0;
       ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino: In function 'void printClock(byte, byte, char*)':
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:7:35: warning: invalid conversion from 'byte* {aka unsigned char*}' to 'char*' [-fpermissive]
     printDisplay(line, pos, myChar);
                                   ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:33:6: note: initializing argument 3 of 'void printDisplay(byte, int, char*)'
 void printDisplay(byte line, int disNr, char* content) {
      ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino: In function 'void printClockDesc(byte, byte, char*)':
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:14:37: warning: invalid conversion from 'byte* {aka unsigned char*}' to 'char*' [-fpermissive]
     printDisplay(line, pos+2, myChar);
                                     ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:33:6: note: initializing argument 3 of 'void printDisplay(byte, int, char*)'
 void printDisplay(byte line, int disNr, char* content) {
      ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:17:39: warning: invalid conversion from 'byte* {aka unsigned char*}' to 'char*' [-fpermissive]
       printDisplay(line, pos+1, myChar);
                                       ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:33:6: note: initializing argument 3 of 'void printDisplay(byte, int, char*)'
 void printDisplay(byte line, int disNr, char* content) {
      ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:20:39: warning: invalid conversion from 'byte* {aka unsigned char*}' to 'char*' [-fpermissive]
       printDisplay(line, pos+1, myChar);
                                       ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:33:6: note: initializing argument 3 of 'void printDisplay(byte, int, char*)'
 void printDisplay(byte line, int disNr, char* content) {
      ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino: In function 'void printWord(byte, String)':
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:26:21: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
   for (int i = 0; i < wordToPrint.length(); i++) {
                     ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:29:33: warning: invalid conversion from 'byte* {aka unsigned char*}' to 'char*' [-fpermissive]
     printDisplay(line, i, myChar);
                                 ^
/Users/john/Documents/Arduino/sketch_aug15a/output.ino:33:6: note: initializing argument 3 of 'void printDisplay(byte, int, char*)'
 void printDisplay(byte line, int disNr, char* content) {
      ^
In file included from /Users/john/Library/Arduino15/packages/arduino/hardware/avr/1.6.19/cores/arduino/Arduino.h:257:0,
                 from /Users/john/Library/Arduino15/packages/arduino/hardware/avr/1.6.19/libraries/SoftwareSerial/src/SoftwareSerial.cpp:43:
/Users/john/Library/Arduino15/packages/arduino/hardware/avr/1.6.19/libraries/SoftwareSerial/src/SoftwareSerial.cpp: In member function 'void SoftwareSerial::begin(long int)':
/Users/john/Library/Arduino15/packages/arduino/hardware/avr/1.6.19/variants/standard/pins_arduino.h:74:39: warning: comparison is always true due to limited range of data type [-Wtype-limits]
 #define digitalPinToPCICR(p)    (((p) >= 0 && (p) <= 21) ? (&PCICR) : ((uint8_t *)0))
                                       ^
/Users/john/Library/Arduino15/packages/arduino/hardware/avr/1.6.19/libraries/SoftwareSerial/src/SoftwareSerial.cpp:319:7: note: in expansion of macro 'digitalPinToPCICR'
   if (digitalPinToPCICR(_receivePin)) {

       ^
/Users/john/Library/Arduino15/packages/arduino/hardware/avr/1.6.19/variants/standard/pins_arduino.h:74:39: warning: comparison is always true due to limited range of data type [-Wtype-limits]
 #define digitalPinToPCICR(p)    (((p) >= 0 && (p) <= 21) ? (&PCICR) : ((uint8_t *)0))
                                       ^
/Users/john/Library/Arduino15/packages/arduino/hardware/avr/1.6.19/libraries/SoftwareSerial/src/SoftwareSerial.cpp:360:6: note: in expansion of macro 'digitalPinToPCICR'
     *digitalPinToPCICR(_receivePin) |= _BV(digitalPinToPCICRbit(_receivePin));

      ^
Sketch uses 6110 bytes (18%) of program storage space. Maximum is 32256 bytes.
Global variables use 994 bytes (48%) of dynamic memory, leaving 1054 bytes for local variables. Maximum is 2048 bytes.

Note: Switching from an UNO to Leonardo saves the RAM taken up by SoftwareSerial:

Sketch uses 7346 bytes (25%) of program storage space. Maximum is 28672 bytes.
Global variables use 840 bytes (32%) of dynamic memory, leaving 1720 bytes for local variables. Maximum is 2560 bytes.

Note: The compiler allows ranges in switch/case statements:

    // DIGITS
    case '0'...'9':
      for (int i = 0; i < 8; i++) {
        byteArray[i] = IMAGES[character - '0'][i];
      }
      break;

    // LOWERCASE
    case 'a' ...'z':
      for (int i = 0; i < 8; i++) {
        byteArray[i] = IMAGES[36 + character - 'a'][i];
      }
      break;

Do you actually use all 76 characters? If not, you can save space by leaving the unused characters out of your font.